Skip to content

Commit

Permalink
refactor(weather): Fix timeout, reduce calls, apply shellcheck (#327)
Browse files Browse the repository at this point in the history
* refactor(weather): Fix timeout, reduce calls, apply shellcheck

Fix Darwin timeout function duration argument. Remove unnecessary API
calls to “ipinfo.io”. Confirm to Google Shell style guide and appease
shellcheck.

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

remove forgotten test string

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

add return when unknown location

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

update weather_wrapped local vars

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

add curl --fail flag

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

update fetch_weather_information argument comments, missing API_URLwq

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

comment example raw response body

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

change locale

* fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck

use tr for lowercase due to OSX bash3.2
  • Loading branch information
bmodotdev authored Feb 16, 2025
1 parent dd1a7ab commit a97cf51
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 89 deletions.
174 changes: 103 additions & 71 deletions scripts/weather.sh
Original file line number Diff line number Diff line change
@@ -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/tcp/ipinfo.io/443" && timeout 1 bash -c "</dev/tcp/wttr.in/443" && [[ "$(display_weather)" != "error" ]]; then
echo "$(display_weather)$(display_location)"
else
echo "Weather Unavailable"
if ! timeout 1 bash -c "</dev/tcp/wttr.in/443"; then
printf 'Weather Unavailable\n'
return
fi

# BashFAQ/002: assignment of substitution does not effect status code.
local _resp
if ! _resp=$(fetch_weather_information "$_show_fahrenheit" "$_location"); then

# e.g. "curl: (22) The requested URL returned error: 404"
case "${_resp##* }" in
404)
printf 'Unknown Location\n'
;;
*)
printf 'Weather Unavailable\n'
;;
esac

return
fi

format_weather_info "$_resp" "$_show_location"
}

#run main driver program
main
main "$@"
41 changes: 23 additions & 18 deletions scripts/weather_wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,35 @@
# setting the locale, some users have issues with different locales, this forces the correct one
export LC_ALL=en_US.UTF-8

#wrapper script for running weather on interval

fahrenheit=$1
location=$2
fixedlocation=$3

DATAFILE=/tmp/.dracula-tmux-data
DATAFILE="/tmp/.dracula-tmux-data"
LAST_EXEC_FILE="/tmp/.dracula-tmux-weather-last-exec"
RUN_EACH=1200
TIME_NOW=$(date +%s)
TIME_LAST=$(cat "${LAST_EXEC_FILE}" 2>/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 "$@"

0 comments on commit a97cf51

Please sign in to comment.