Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(weather): Fix timeout, reduce calls, apply shellcheck #327

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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 "$@"