From 3ed14d1c1295627d4e4d6347ab7a3a6fd4f39a9b Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 17:59:16 -0600 Subject: [PATCH 1/9] refactor(weather): Fix timeout, reduce calls, apply shellcheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Darwin timeout function duration argument. Remove unnecessary API calls to “ipinfo.io”. Confirm to Google Shell style guide and appease shellcheck. --- scripts/weather.sh | 152 ++++++++++++++++++++----------------- scripts/weather_wrapper.sh | 42 +++++----- 2 files changed, 107 insertions(+), 87 deletions(-) diff --git a/scripts/weather.sh b/scripts/weather.sh index 4c3e7dc..dba357f 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -2,96 +2,110 @@ # setting the locale, some users have issues with different locales, this forces the correct one export LC_ALL=C.utf8 -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() -{ - 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") +# Fetch weather information from remote API +# Globals: +# 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" - if [[ $api_response = "Unknown location;"* ]]; then - echo "Unknown location error" + if "$_show_fahrenheit"; then + _unit="u" else - echo $api_response + _unit="m" fi + + command -p curl -sL "${API_URL}/${_location// /%20}?format=%C${DELIM}%t${DELIM}%l&${_unit}" } -#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" + _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 + _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)}') +# 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" - 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 '☀ ' + # 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 _time_last + _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 + echo updating cache # 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 "$@" From f003b5960c00e11a825909b4c94238745cd1efb6 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:58:07 -0600 Subject: [PATCH 2/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck remove forgotten test string --- scripts/weather_wrapper.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/weather_wrapper.sh b/scripts/weather_wrapper.sh index 403cf40..48d221b 100755 --- a/scripts/weather_wrapper.sh +++ b/scripts/weather_wrapper.sh @@ -25,7 +25,6 @@ function main() { _now=$(date +%s) if (((_now - _last) > INTERVAL)); then - echo updating cache # Run weather script here "${_current_dir}/weather.sh" "$_show_fahrenheit" "$_show_location" "$_location" >"${DATAFILE}" printf '%s' "$_now" >"${LAST_EXEC_FILE}" From a349591bec2aab15ae5d307b2da00cf8e1ad9b73 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:35:43 -0600 Subject: [PATCH 3/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck add return when unknown location --- scripts/weather.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/weather.sh b/scripts/weather.sh index dba357f..74a5291 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -103,6 +103,7 @@ function main() { if [[ "$_resp" = "Unknown location"* ]]; then printf 'Unknown location error\n' + return fi format_weather_info "$_resp" "$_show_location" From 18f52884cfcbaa55fc39501264e5b1ffee7777b8 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:38:05 -0600 Subject: [PATCH 4/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck update weather_wrapped local vars --- scripts/weather_wrapper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/weather_wrapper.sh b/scripts/weather_wrapper.sh index 48d221b..ba74c22 100755 --- a/scripts/weather_wrapper.sh +++ b/scripts/weather_wrapper.sh @@ -16,7 +16,7 @@ INTERVAL=1200 # 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 _time_last + local _show_fahrenheit _show_location _location _current_dir _last _now _show_fahrenheit="$1" _show_location="$2" _location="$3" From 3217c5a07caf46b723015424680650c502ffd1c7 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:08:44 -0600 Subject: [PATCH 5/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck add curl --fail flag --- scripts/weather.sh | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/scripts/weather.sh b/scripts/weather.sh index 74a5291..595befa 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -31,7 +31,14 @@ function fetch_weather_information() { _unit="m" fi - command -p curl -sL "${API_URL}/${_location// /%20}?format=%C${DELIM}%t${DELIM}%l&${_unit}" + # 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 } # Format raw weather information from API @@ -93,16 +100,25 @@ function main() { _location="$3" # process should be cancelled when session is killed - if ! timeout 1 bash -c " Date: Sun, 2 Feb 2025 23:46:36 -0600 Subject: [PATCH 6/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck update fetch_weather_information argument comments, missing API_URLwq --- scripts/weather.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/weather.sh b/scripts/weather.sh index 595befa..61cefe8 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -16,6 +16,7 @@ fi # Fetch weather information from remote API # Globals: +# API_URL # DELIM # Arguments: # show fahrenheit, either "true" or "false" From 1991d1daa326dbb04e2bcb63a02d4be2ca85bfc5 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:58:45 -0600 Subject: [PATCH 7/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck comment example raw response body --- scripts/weather.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/weather.sh b/scripts/weather.sh index 61cefe8..5149ead 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -50,7 +50,7 @@ function fetch_weather_information() { # show location, either "true" or "false" function format_weather_info() { local _raw _show_location - _raw="$1" + _raw="$1" # e.g. "Rain:+63°F:Houston, Texas, United States" _show_location="$2" local _weather _temp _location From fdbd77b798b6cc566aa06e25dc1229d5e563e5cd Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:07:47 -0600 Subject: [PATCH 8/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck change locale --- scripts/weather.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/weather.sh b/scripts/weather.sh index 5149ead..34a9357 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -1,6 +1,6 @@ #!/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 API_URL="https://wttr.in" DELIM=":" From 0318481e7de02b910182ebb99053ed1d2c8497c3 Mon Sep 17 00:00:00 2001 From: William E Little Jr <90572149+bmodotdev@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:13:44 -0600 Subject: [PATCH 9/9] fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck use tr for lowercase due to OSX bash3.2 --- scripts/weather.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/weather.sh b/scripts/weather.sh index 34a9357..89e8055 100755 --- a/scripts/weather.sh +++ b/scripts/weather.sh @@ -54,15 +54,15 @@ function format_weather_info() { _show_location="$2" local _weather _temp _location - _weather="${_raw%%"${DELIM}"*}" # slice temp and location to get weather - _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 - - case "${_weather,,}" in + _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 + + case "$_weather" in 'snow') _weather='❄' ;;