#!/bin/bash # client version, calls server for actual API info usage() { cat <][-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 EXPIRATION=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