diff --git a/scripts/weather.sh b/scripts/weather.sh index 4c3e7dc..89e8055 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -1,97 +1,129 @@ #!/usr/bin/env bash # setting the locale, some users have issues with different locales, this forces the correct one -export LC_ALL=C.utf8 +export LC_ALL=en_US.UTF-8 -fahrenheit=$1 -location=$2 -fixedlocation=$3 +API_URL="https://wttr.in" +DELIM=":" # emulate timeout command from bash - timeout is not available by default on OSX if [ "$(uname)" == "Darwin" ]; then - timeout() { - perl -e 'alarm shift; exec @ARGV' "$duration" "$@" + function timeout() { + local _duration + _duration="${1:-1}" + command -p perl -e 'alarm shift; exec @ARGV' "$_duration" "$@" } fi -display_location() -{ - if $location && [[ ! -z "$fixedlocation" ]]; then - echo " $fixedlocation" - elif $location; then - city=$(curl -s https://ipinfo.io/city 2> /dev/null) - region=$(curl -s https://ipinfo.io/region 2> /dev/null) - echo " $city, $region" - else - echo '' - fi -} +# Fetch weather information from remote API +# Globals: +# API_URL +# DELIM +# Arguments: +# show fahrenheit, either "true" or "false" +# optional fixed location to query weather data about +function fetch_weather_information() { + local _show_fahrenheit _location _unit + _show_fahrenheit="$1" + _location="$2" -fetch_weather_information() -{ - display_weather=$1 - # it gets the weather condition textual name (%C), and the temperature (%t) - api_response=$(curl -sL wttr.in/${fixedlocation// /%20}\?format="%C+%t$display_weather") - - if [[ $api_response = "Unknown location;"* ]]; then - echo "Unknown location error" + if "$_show_fahrenheit"; then + _unit="u" else - echo $api_response + _unit="m" fi + + # If the user provies a "fixed location", `@dracula-fixed-location`, that the + # API does not recognize, the API may suggest a users actual geoip GPS + # location in the response body. This can lead to user PI leak. + # Drop response body when status code >= 400 and return nonzero by passing the + # `--fail` flag. Execute curl last to allow the consumer to leverage the + # return code. Pass `--show-error` and redirect stderr for the consumer. + command -p curl -L --silent --fail --show-error \ + "${API_URL}/${_location// /%20}?format=%C${DELIM}%t${DELIM}%l&${_unit}" 2>&1 } -#get weather display -display_weather() -{ - if $fahrenheit; then - display_weather='&u' # for USA system - else - display_weather='&m' # for metric system - fi - weather_information=$(fetch_weather_information $display_weather) +# Format raw weather information from API +# Globals: +# DELIM +# Arguments: +# The raw weather data as returned by "fetch_weather_information()" +# show location, either "true" or "false" +function format_weather_info() { + local _raw _show_location + _raw="$1" # e.g. "Rain:+63°F:Houston, Texas, United States" + _show_location="$2" - weather_condition=$(echo "$weather_information" | awk -F' -?[0-9]' '{print $1}' | xargs) # Extract condition before temperature, e.g. Sunny, Snow, etc - temperature=$(echo "$weather_information" | grep -oE '[-+]?[0-9]+°[CF]') # Extract temperature, e.g. +31°C, -3°F, etc - unicode=$(forecast_unicode $weather_condition) + local _weather _temp _location + _weather="${_raw%%"${DELIM}"*}" # slice temp and location to get weather + _weather=$(printf '%s' "$_weather" | tr '[:upper:]' '[:lower:]') # lowercase weather, OSX’s bash3.2 does not support ${v,,} + _temp="${_raw#*"${DELIM}"}" # slice weather to get temp and location + _temp="${_temp%%"${DELIM}"*}" # slice location to get temp + _temp="${_temp/+/}" # slice "+" from "+74°F" + _location="${_raw##*"${DELIM}"}" # slice weather and temp to get location + [ "${_location//[^,]/}" == ",," ] && _location="${_location%,*}" # slice country if it exists - # Mac Only variant should be transparent on Linux - if [[ "${temperature/+/}" == *"===="* ]]; then - temperature="error" - fi + case "$_weather" in + 'snow') + _weather='❄' + ;; + 'rain' | 'shower') + _weather='☂' + ;; + 'overcast' | 'cloud') + _weather='☁' + ;; + 'na') + _weather='' + ;; + *) + _weather='☀' + ;; + esac - if [[ "${temperature/+/}" == "error" ]]; then - # Propagate Error - echo "error" + if "$_show_location"; then + printf '%s %s %s' "$_weather" "$_temp" "$_location" else - echo "$unicode ${temperature/+/}" # remove the plus sign to the temperature + printf '%s %s' "$_weather" "$_temp" fi } -forecast_unicode() -{ - weather_condition=$(echo $weather_condition | awk '{print tolower($0)}') - - if [[ $weather_condition =~ 'snow' ]]; then - echo '❄ ' - elif [[ (($weather_condition =~ 'rain') || ($weather_condition =~ 'shower')) ]]; then - echo '☂ ' - elif [[ (($weather_condition =~ 'overcast') || ($weather_condition =~ 'cloud')) ]]; then - echo '☁ ' - elif [[ $weather_condition = 'NA' ]]; then - echo '' - else - echo '☀ ' - fi -} +# Display weather, temperature, and location +# Globals +# none +# Arguments +# show fahrenheit, either "true" (default) or "false" +# show location, either "true" (default) or "false" +# optional fixed location to query data about, e.g. "Houston, Texas" +function main() { + local _show_fahrenheit _show_location _location + _show_fahrenheit="${1:-true}" + _show_location="${2:-true}" + _location="$3" -main() -{ # process should be cancelled when session is killed - if timeout 1 bash -c "/dev/null || echo "0") +INTERVAL=1200 -main() -{ - current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Call weather script on interval to prevent exhausting remote API +# Globals: +# DATAFILE +# LAST_EXEC_FILE +# INTERVAL +# Arguments: +# show fahrenheit, either "true" (default) or "false" +# show location, either "true" (default) or "false" +# optional fixed location to query data about, e.g. "Houston, Texas" +function main() { + local _show_fahrenheit _show_location _location _current_dir _last _now + _show_fahrenheit="$1" + _show_location="$2" + _location="$3" + _current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + _last=$(cat "$LAST_EXEC_FILE" 2>/dev/null || echo 0) + _now=$(date +%s) - if [ "$(expr ${TIME_LAST} + ${RUN_EACH})" -lt "${TIME_NOW}" ]; then + if (((_now - _last) > INTERVAL)); then # Run weather script here - $current_dir/weather.sh $fahrenheit $location "$fixedlocation" > "${DATAFILE}" - echo "${TIME_NOW}" > "${LAST_EXEC_FILE}" + "${_current_dir}/weather.sh" "$_show_fahrenheit" "$_show_location" "$_location" >"${DATAFILE}" + printf '%s' "$_now" >"${LAST_EXEC_FILE}" fi cat "${DATAFILE}" } -#run main driver function -main +main "$@"