Files

191 lines
5.6 KiB
Bash
Executable File

#!/bin/sh
# weather - query current weather and forecasts for any location via wttr.in.
#
# wttr.in (https://github.com/chubin/wttr.in) is a console-oriented weather
# service backed by World Weather Online data. It supports several output
# formats: a graphical ANSI view for terminals (the default), one-line text
# formats (built-in presets 1-4 or a custom %-notation string), a rich JSON
# document (?format=j1 / j2) for scripts and APIs, plus PNG / HTML /
# Prometheus metrics.
#
# This script is a thin, friendly wrapper around the wttr.in HTTP API. It
# builds the request URL from the supplied location and options, fetches the
# data with curl and (when JSON is requested) pretty-prints it with jq.
#
# Usage:
# weather [options] [location]
#
# Options:
# -l, --location LOC Location: city, airport code, domain, IP,
# "lat,lon" coordinates, or "~Name" for a custom label.
# Default: auto-detect from the request IP.
# -u, --units U Unit system: m (metric, default), u (USCS/imperial),
# M (metric with wind speed in m/s).
# -L, --lang LANG Output language code, e.g. de, fr, ru, zh-cn.
# -f, --format FMT Output format:
# j1 -> rich JSON document (default for --json)
# j2 -> JSON document, imperial units
# 1-4 -> built-in one-line presets
# "%..." -> custom one-line %-notation format
# Omit to get the default graphical terminal view.
# -j, --json Alias for --format j1 (emit JSON).
# -0 Current weather only.
# -1 Current weather + today's forecast.
# -2 Current weather + today's + tomorrow's forecast.
# -q, --quiet Quiet: no "Weather report" header / city name.
# -A Force ANSI output (ignore User-Agent detection).
# -h, --help Show this help text.
#
# Environment:
# WTTRAPI Base endpoint. Default: https://wttr.in
# WEATHER_TIMEOUT curl connect/read timeout in seconds. Default: 20
#
# Dependencies: curl, jq (jq only needed for the JSON format).
#
# Examples:
# weather
# weather London
# weather -u u -L de Berlin
# weather -f 3 "New York"
# weather -f "%l: %c %t (feels %f), wind %w %m" Paris
# weather -j Tokyo
set -eu
WTTRAPI="${WTTRAPI:-https://wttr.in}"
TIMEOUT="${WEATHER_TIMEOUT:-20}"
# Single-letter options (view/quiet/units) concatenated into one run.
SHORT=""
LANG_CODE=""
FORMAT=""
LOCATION=""
usage() {
sed -n '/^# Usage:/,/^# Examples:/p' "$0" | sed 's/^# \{0,1\}//'
}
# --- Parse arguments. ---
while [ "$#" -gt 0 ]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-l|--location)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
LOCATION="$2"
shift 2
;;
-u|--units)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
case "$2" in
m|u|M) SHORT="${SHORT}$2" ;;
*) echo "weather: unknown unit '$2' (use m, u or M)" >&2; exit 1 ;;
esac
shift 2
;;
-L|--lang)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
LANG_CODE="$2"
shift 2
;;
-f|--format)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
FORMAT="$2"
shift 2
;;
-j|--json)
FORMAT="j1"
shift
;;
-0|-1|-2|-q|-A)
SHORT="${SHORT}${1#-}"
shift
;;
--)
shift
LOCATION="${LOCATION:+$LOCATION }$(printf '%s' "$*" | sed 's/^ //')"
break
;;
-*)
echo "weather: unknown option '$1' (try --help)" >&2
exit 1
;;
*)
# Positional: append to location (preserves multi-word locations).
LOCATION="${LOCATION:+$LOCATION }$1"
shift
;;
esac
done
# --- Assemble the query string. ---
# wttr.in accepts a leading run of single-letter options, then '&'-joined
# long options (key=value). We build both parts and join them.
QUERY="$SHORT"
# Long options: format and lang.
LONG=""
if [ -n "$FORMAT" ]; then
case "$FORMAT" in
j1|j2)
LONG="${LONG:+$LONG&}format=${FORMAT}"
;;
[1-4])
LONG="${LONG:+$LONG&}format=${FORMAT}"
;;
*)
# Custom %-notation one-line format -> URL-encode it.
ENC=$(printf '%s' "$FORMAT" | jq -sRr @uri)
LONG="${LONG:+$LONG&}format=${ENC}"
;;
esac
fi
if [ -n "$LANG_CODE" ]; then
LONG="${LONG:+$LONG&}lang=$(printf '%s' "$LANG_CODE" | jq -sRr @uri)"
fi
# Combine short + long parts into a single query string.
if [ -n "$QUERY" ] && [ -n "$LONG" ]; then
QUERY="${QUERY}&${LONG}"
elif [ -z "$QUERY" ] && [ -n "$LONG" ]; then
QUERY="$LONG"
fi
# URL-encode the location (spaces -> %20, etc.). Empty means auto-detect.
if [ -n "$LOCATION" ]; then
LOC_ENC=$(printf '%s' "$LOCATION" | jq -sRr @uri)
else
LOC_ENC=""
fi
URL="${WTTRAPI}/${LOC_ENC}"
if [ -n "$QUERY" ]; then
URL="${URL}?${QUERY}"
fi
# --- Fetch. ---
# wttr.in returns the graphical ANSI art whenever the request looks like it
# comes from a console client (curl's default User-Agent), and HTML for
# browsers. So we keep curl's default identity. The "-A" flag above is a
# wttr.in URL option ("force ANSI") and is already part of the query string.
RESP=$(curl -s -L --max-time "$TIMEOUT" "$URL") || {
echo "weather: failed to reach $WTTRAPI" >&2
exit 1
}
# --- Emit. ---
if [ "$FORMAT" = "j1" ] || [ "$FORMAT" = "j2" ]; then
if command -v jq >/dev/null 2>&1; then
printf '%s\n' "$RESP" | jq . 2>/dev/null || printf '%s\n' "$RESP"
else
printf '%s\n' "$RESP"
fi
else
printf '%s\n' "$RESP"
fi
exit 0