weather/weather
2023-02-27 10:05:13 -05:00

463 lines
13 KiB
Bash
Executable File

#!/bin/bash
# client version, calls server for actual API info
usage() {
cat <<EOF
usage: $0 [-a KEY][-s QUERY | -z ZIPCODE][-u <c|f>][-p][-i][-l][-h]
Queries OpenWeatherMapAPI for weather or lat/long
options:
-a, --apikey set the api key to use to KEY
-l, --location print the location being used
-p, --pretty enables nerdfont symbols
-d, --desc prints the conditions text as well
-z, --zipcode returns the weather for ZIPCODE
-s, --search returns the weather for QUERY
-u, --units sets the temperature units
-i, --icon_test tests the icons used
-r, --radar displays the radar for a given location
-f, --forecast displays the forecast a given location
-h, --help show this message
ZIPCODE sets the location to the given zipcode
to narrow results, include the ISO country code
ex) -z "02139,US"
QUERY sets the location to the search
QUERY format is "CITY,REGION,COUNTRY" where
EOF
exit $1
}
WORKING_DIR="$HOME/.local/share/weather"
WEATHER_FILE=".env"
TIMEOUT=60 # timeout for info
TIME=$(date +%s)
if [[ ! -d "$WORKING_DIR" ]] ; then
mkdir -p "$WORKING_DIR"
fi
# going to weather directory
cd "$WORKING_DIR"
print_weather() {
# prints the weather
# check to see if it is expired
check_expiration
# checking if description enabled
if [[ "$DESCRIPTION" = true ]] ; then
DESC=$(echo "$CONDITIONS" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1')
DESC=$(printf '%s ' "$DESC")
fi
if [[ "$PRETTY" = true ]] ; then
# print with NF icons
icon=$(get_icon "$WEATHER_ICON")
humidity_icon=$(echo -e '\ue373')
# checking units
if [[ "$UNIT" == "C" ]] ; then
temp_icon=$(echo -e '\ufa03')
else
temp_icon=$(echo -e '\ufa04')
fi
else
# default printing
humidity_icon="% RH"
if [[ "$UNIT" == "C" ]] ; then
temp_icon="*C"
else
temp_icon="*F"
fi
fi
# printing
printf '%s%s %.1f%s %d%s in %s\n' "$DESC" "$icon" $TEMPERATURE "$temp_icon" $HUMIDITY "$humidity_icon" "$CITY"
# save before exiting
save_info
}
print_location() {
# prints the location being used for the weather
# uses reverse geocoding
LOCATION_URL="https://api.openweathermap.org/geo/1.0/reverse?lat=$LAT&lon=$LON&appid=$API_KEY"
LOCATION=$(curl --silent -fL "$LOCATION_URL" | gojq '.[0] | del(.local_names)')
name=$(echo "$LOCATION" | gojq '.name' | tr -d '"')
state=$(echo "$LOCATION" | gojq '.state' | tr -d '"')
country=$(echo "$LOCATION" | gojq '.country' | tr -d '"')
printf 'Location is set to %s in %s, %s\n' "$name" "$state" "$country"
}
check_expiration() {
# checks if the info is expired
if [[ $TIME -gt $EXPIRATION ]] ; then
# expired
get_weather
fi
}
get_location() {
# searching for user location
if [[ -n "$SEARCH" ]] ; then
# search
LOCATION_URL="https://api.openweathermap.org/geo/1.0/$SEARCH&appid=$API_KEY&limit=5"
RESULTS=$(curl --silent -fL "$LOCATION_URL")
# check if its an array
is_array=$(echo "$RESULTS" | gojq 'if type=="array" then 1 else 0 end')
if [[ $is_array -eq 1 ]] ; then
# returned result is an array
NUM_RESULTS=$(echo "$RESULTS" | gojq '. | length')
if [[ $NUM_RESULTS -gt 1 ]] ; then
# provide menu for multiple results
for ((i=0; i < $NUM_RESULTS; i++)); do
# adding options
res=$(echo "$RESULTS" | gojq ".[$i]")
name=$(echo "$res" | gojq '.name' | tr -d '"')
state=$(echo "$res" | gojq '.state' | tr -d '"')
country=$(echo "$res" | gojq '.country' | tr -d '"')
opt=$(printf '%s %s, %s' "$name" "$state" "$country")
OPTIONS+=($(($i+1)) "$opt")
done
# executing dialog menu
exec 3>&1
SELECTION=$(dialog \
--title 'Multiple Locations Found!' \
--clear \
--cancel-label 'Exit' \
--ok-label 'Select' \
--menu 'Please select the desired location. If none of the options look correct, try making refining your search by including a country code and/or region' 0 0 4 \
"${OPTIONS[@]}" \
2>&1 1>&3)
exit_status=$?
exec 3>&-
clear
if [[ $exit_status -eq 1 || $exit_satus -eq 255 ]] ; then
# canceled or escaped
echo "Location not changed"
exit 0
fi
# fix index
SELECTION=$(($SELECTION - 1))
fi
# updating result
RESULTS=$(echo "$RESULTS" | gojq ".[$SELECTION]")
fi
# Update info
LAT=$(echo "$RESULTS" | gojq ".lat")
LON=$(echo "$RESULTS" | gojq ".lon")
CITY=$(echo "$RESULTS" | gojq ".name" | tr -d '"')
else
# no search, default to user IP
url="http://ip-api.com/csv/?fields=252"
res=$(curl --silent -fL "$url")
LAT=$(awk -F , '{print $5}' <<<"$res")
LON=$(awk -F , '{print $6}' <<<"$res")
CITY=$(awk -F , '{print $3}' <<<"$res")
fi
# check
if [[ -z "$LAT" || -z "$LON" ]] ; then
echo "No location found!" >&2
exit 1
fi
# force refresh
EXPIRED=0
}
get_weather() {
# calls api for weather based on $LAT, $LONG
if [[ -z "$LAT" || -z "$LON" ]] ; then
# no lat or lon
get_location
fi
if [[ "$UNIT" =~ ^[Cc]$ ]] ; then
# units set to metric
UNIT="C"
UNITS="metric"
elif [[ "$UNIT" =~ ^[Ff]$ || -z "$UNIT" ]] ; then
# imperial (default)
UNIT="F"
UNITS="imperial"
else
printf 'Unit %s unrecognized\n' "$UNIT" >&2
exit 1
fi
WEATHER_URL="https://api.openweathermap.org/data/2.5/weather?lat=$LAT&lon=$LON&appid=$API_KEY&units=$UNITS"
WEATHER=$(curl --silent -fL "$WEATHER_URL")
CONDITIONS=$(echo $WEATHER | gojq -r '.weather[0].description')
TEMPERATURE=$(echo $WEATHER | gojq -r '.main.temp')
HUMIDITY=$(echo $WEATHER | gojq -r '.main.humidity')
WEATHER_ICON=$(echo $WEATHER | gojq -r '.weather[0].icon')
EXPIRATION=$(($TIME+$TIMEOUT))
}
update_key() {
# updates the API key used
printf 'Testing API key %s... ' "$API_KEY"
WEATHER_URL="https://api.openweathermap.org/data/2.5/weather?lat=42.3736&lon=71.1097&appid=$API_KEY"
CODE=$(curl --silent "$WEATHER_URL" | gojq '.cod' | tr -d '"')
if [[ $CODE -eq 401 ]] ; then
# API_KEY error
printf 'Error: Invalid API Key "%s"!\n' "$API_KEY" >&2
exit 1
fi
printf 'Success: Key Updated!\n'
# saving new key
save_info
}
save_info() {
# saves set env vars to file
printf '%s="%s"\n' \
"API_KEY" "$API_KEY" \
"PARSER" "$PARSER" \
"LAT" "$LAT" \
"LON" "$LON" \
"CITY" "$CITY" \
"TEMPERATURE" "$TEMPERATURE" \
"HUMIDITY" "$HUMIDITY" \
"UNIT" "$UNIT" \
"WEATHER_ICON" "$WEATHER_ICON" \
"CONDITIONS" "$CONDITIONS" \
"EXPIRATION" "$EXPIRATION" > "$WEATHER_FILE"
}
load_info() {
# loads env vars
if [[ ! -e "$WEATHER_FILE" ]] ; then
# generates blank info on fresh installs
# test for gojq
PARSER=gojq
if ! command -v $PARSER ; then
# test for jq
PARSER=jq
if ! command -v $PARSER ; then
echo "$1 depends on jq or gojq"
exit 1
fi
fi
save_info
fi
source $WEATHER_FILE
}
display_forecast() {
# two step process, gets weather station from NWS, then mpv to play radar
if [[ -z "$LAT" || -z "$LON" ]] ; then
echo "Location not found!"
exit 1
fi
echo "Loading weather..."
if [[ -n "$PRETTY" ]] ; then
# nerdfont
curl -fsSL "https://v2d.wttr.in/${LAT},${LON}?F" | less -R
else
curl -fsSL "https://wttr.in/${LAT},${LON}?F" | less -R
fi
exit 0
}
display_radar() {
# two step process, gets weather station from NWS, then mpv to play radar
if [[ -z "$LAT" || -z "$LON" ]] ; then
echo "Location not found!"
exit 1
fi
# grabbing nws station
NWS=$(curl -fsSL "https://api.weather.gov/points/$LAT,$LON")
STATION=$(echo "$NWS" | gojq '.properties.radarStation' | tr -d '"')
# validation
if [[ -z "$STATION" ]] ; then
echo "NWS Station error!"
exit 1
fi
# mpv to play weather in fullscreen
echo "Fetching weather for NWS Station $STATION..."
mpv --fs --loop-file "https://radar.weather.gov/ridge/standard/${STATION}_loop.gif" >/dev/null 2>&1
exit 0
}
get_icon() {
# sets NF symbols
tod=$(echo "$1" | sed --expression='s/[0-9]//g')
conditions=$(echo "$1" | sed --expression='s/[^0-9]//g')
# getting icon
if [[ "$tod" == "d" ]] ; then
# day icons
case "$conditions" in
"01") echo -e '\ue30d' ;; # clear
"02") echo -e '\ue30c' ;; # scattered clouds
"03") echo -e '\ue302' ;; # broken clouds
"04") echo -e '\ue312' ;; # cloudy
"09") echo -e '\ue309' ;; # showers
"10") echo -e '\ue308' ;; # rain
"11") echo -e '\ue30f' ;; # thunderstorm
"13") echo -e '\uf2dc' ;; # snow
"50") echo -e '\ue303' ;; # mist
* ) echo -e '\ue374' ;; # unknown
esac
elif [[ "$tod" == "n" ]] ; then
# night icons
case "$conditions" in
"01") echo -e '\ue32b' ;; # clear
"02") echo -e '\ue379' ;; # scattered clouds
"03") echo -e '\ue37e' ;; # broken clouds
"04") echo -e '\ue312' ;; # cloudy
"09") echo -e '\ue326' ;; # showers
"10") echo -e '\ue325' ;; # rain
"11") echo -e '\ue32a' ;; # thunderstorm
"13") echo -e '\uf2dc' ;; # snow
"50") echo -e '\ue346' ;; # mist
* ) echo -e '\ue374' ;; # unknown
esac
else
echo "TOD not recognized"
exit 1
fi
}
icon_test() {
#tests icons
printf 'Testing weather icons\nIf any look broken, check that NerdFont is installed\n'
printf '\nDay:\n'
printf 'Clear: \ue30d\n'
printf 'Partly Cloudy: \ue30c\n'
printf 'Cloudy: \ue302\n'
printf 'Very Cloudy: \ue312\n'
printf 'Showers: \ue309\n'
printf 'Rain: \ue308\n'
printf 'Thunderstorm: \ue30f\n'
printf 'Snow: \uf2dc\n'
printf 'Fog: \ue303\n'
# night icons
printf '\nNight:\n'
printf 'Clear: \ue32b\n'
printf 'Partly Cloudy: \ue379\n'
printf 'Cloudy: \ue37e\n'
printf 'Very Cloudy: \ue312\n'
printf 'Showers: \uf2dc\n'
printf 'Rain: \ue325\n'
printf 'Thunderstorm: \ue32a\n'
printf 'Snow: \uf2dc\n'
printf 'Fog: \ue346\n'
printf '\nAssorted:\n'
printf 'Degrees (F) \ufa04\n'
printf 'Degrees (C) \ufa03\n'
printf '%% Humidity \ue373\n'
exit 0
}
# shifting longform
for arg in "$@"; do
shift
case "$arg" in
'--zipcode') set -- "$@" "-z" ;;
'--location') set -- "$@" "-l" ;;
'--apikey') set -- "$@" "-a" ;;
'--coords') set -- "$@" "-c" ;;
'--search') set -- "$@" "-s" ;;
'--units') set -- "$@" "-u" ;;
'--pretty') set -- "$@" "-p" ;;
'--pretty') set -- "$@" "-p" ;;
'--icon_test') set -- "$@" "-i" ;;
'--radar') set -- "$@" "-r" ;;
'--forecast') set -- "$@" "-f" ;;
'--help') set -- "$@" "-h" ;;
*) set -- "$@" "$arg" ;;
esac
done
# loading the info
load_info
while getopts "chpldrfiz:s:u:a:" opt; do
case "$opt" in
'a' )
API_KEY="$OPTARG"
update_key
;;
'u' )
UNIT="$OPTARG"
;;
'l' )
print_location
exit 0
;;
'z' )
[ -n "$SEARCH" ] && usage 1 || SEARCH="/zip?zip=$OPTARG"
;;
's' )
[ -n "$SEARCH" ] && usage 1 || SEARCH="/direct?q=$OPTARG"
;;
'p' )
PRETTY=true
;;
'd' )
DESCRIPTION=true
;;
'i' )
icon_test
;;
'r' )
display_radar
;;
'f' )
display_forecast
;;
'h' )
usage 0
;;
'?' )
usage 1
;;
esac
done
# test for APIKEY
if [[ -z "$API_KEY" ]] ; then
echo "No API Key found!"
exit 1
fi
if [[ -n "$SEARCH" ]] ; then
# perform search
get_location
fi
if [[ -z "$QUIET" ]] ; then
print_weather
fi