Compare commits

..
10 Commits
+125 -44
View File
@@ -1,24 +1,41 @@
#!/bin/sh #!/bin/sh
# sunset-alarm -- Alert before or after sunset # sunset-alarm -- Alert before or after sunset.
# Displays a persistent alert when current time is within # Displays an alert when current time is within (sunset - specified minutes).
# (sunset minus specified minutes) of midnight.
############################################################################### ###############################################################################
# Defaults # Defaults
############################################################################### ###############################################################################
LATLONG="" declare LATLONG="" SUNSET_TIME="" MINUTES="" LAST_DATE="" PERSIST="" NOTIFIED=""
SUNSET_TIME="" typeset -i SUNSET_EPOCH=0 CURRENT_TIME=0
MINUTES=""
SUNSET_EPOCH=0
LAST_DATE=""
PROMPT="sunset-alarm"
############################################################################### ###############################################################################
# Usage # Usage
############################################################################### ###############################################################################
usage() { usage() {
echo "Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]" >&2 cat <<EOF
Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-p] [-h]
Display a notification when a certain time related to sunset has been reached.
Parameters:
-l) Takes a LAT:LONG parameter to check Open Meteo for the sunset time if
there is an Internet connection. Otherwise may fail to notify.
-t) Takes a hard-coded 24-hour formatted time for guessing when sunset is.
-m) Takes the number of minutes that the alert should appear before sunset.
Using a negative time is allowed if you prefer having the alert after.
-p) Persist the notification, showing back up every 5 minutes to midnight.
-h) Display this usage.
Example: Show the alert 90 minutes before sunset in Colorado City AZ.
sunset-alarm -l 37:-113 -m 90
Example: Show the alert at exactly 8:30 PM.
sunset-alarm -t 20:30 -m 0
Example: Show the alert 30 minutes after sunset in Flagstaff AZ.
sunset-alarm -l 35.1987671:-111.651813 -m -30
EOF
exit 1 exit 1
} }
@@ -30,20 +47,31 @@ while [ $# -gt 0 ]; do
-l) LATLONG="$2"; shift 2 ;; -l) LATLONG="$2"; shift 2 ;;
-t) SUNSET_TIME="$2"; shift 2 ;; -t) SUNSET_TIME="$2"; shift 2 ;;
-m) MINUTES="$2"; shift 2 ;; -m) MINUTES="$2"; shift 2 ;;
-p) PERSIST="true"; shift 1 ;;
-h) usage ;; -h) usage ;;
--) shift; break ;; --) shift; break ;;
*) echo "Error: unknown argument '$1'" >&2; usage ;; *) echo "ERROR: unknown argument '$1'" >&2; usage ;;
esac esac
done done
# Validate inputs # Validate inputs.
if [ -z "$MINUTES" ]; then if [ -z "$MINUTES" ]; then
echo "Error: -m MINUTES is required." >&2 echo "ERROR: -m MINUTES is required." >&2
usage usage
fi fi
if [ -z "$LATLONG" ] && [ -z "$SUNSET_TIME" ]; then if [ -z "$LATLONG" ] && [ -z "$SUNSET_TIME" ]; then
echo "Error: must specify either -l LAT:LONG or -t HH:MI." >&2 echo "ERROR: must specify either -l LAT:LONG or -t HH:MI." >&2
usage
fi
LATLONG=${LATLONG//\,/:}
LATLONG=${LATLONG//\ /:}
LATLONG=${LATLONG//\_/:}
LATLONG=${LATLONG//\&/:}
LATLONG=${LATLONG//\?/:}
if [[ -n "$LATLONG" && "$LATLONG" != *":"* ]]; then
echo "ERROR: LAT:LONG is missing the colon." >&2
usage usage
fi fi
@@ -51,12 +79,15 @@ fi
# Internet connectivity check # Internet connectivity check
############################################################################### ###############################################################################
check_internet() { check_internet() {
if curl -s --max-time 5 -o /dev/null https://hyperling,com; then if curl -s --max-time 5 -o /dev/null https://hyperling.com; then
echo "Confirmed connectivity with Hyperling."
return 0 return 0
fi fi
if curl -s --max-time 5 -o /dev/null https://mullvad.net; then if curl -s --max-time 5 -o /dev/null https://mullvad.net; then
echo "Confirmed connectivity with Mullvad."
return 0 return 0
fi fi
echo "ERROR: Connectivity checks failed." >&2
return 1 return 1
} }
@@ -64,29 +95,40 @@ check_internet() {
# Get sunset from Open Meteo API # Get sunset from Open Meteo API
############################################################################### ###############################################################################
fetch_sunset_time() { fetch_sunset_time() {
echo "Checking for sunset time of lat:long ${LATLONG}."
LAT=$(echo "$LATLONG" | cut -d: -f1) LAT=$(echo "$LATLONG" | cut -d: -f1)
LON=$(echo "$LATLONG" | cut -d: -f2) LON=$(echo "$LATLONG" | cut -d: -f2)
TODAY="`date +'%Y-%m-%d'`"
RESPONSE=$(curl -s "https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&date=today") #REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&date=today"
#REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&forecast_days=1"
REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&start_date=${TODAY}&end_date=${TODAY}"
RESPONSE=$(curl -s "$REQUEST")
status="$?"
echo "Curl command completed with a status of $status."
if [ -z "$RESPONSE" ]; then if [[ -z "$RESPONSE" ]]; then
echo "ERROR: Did not receive response. $REQUEST" >&2
return 1 return 1
fi fi
# Extract sunset timestamp like "2025-06-15T19:32:00" # Extract sunset timestamp like "2025-06-15T19:32:00".
SUNSET_RAW=$(echo "$RESPONSE" | grep -o '"sunset":"[^"]*"' | head -1 | sed 's/.*://"//;s/"$//') SUNSET_RAW=$(echo "$RESPONSE" | jq -r '.daily.sunset[0]')
if [ -z "$SUNSET_RAW" ]; then if [[ -z "$SUNSET_RAW" ]]; then
echo "ERROR: Could not parse time. $RESPONSE" >&2
return 1 return 1
fi fi
# Convert to epoch # Convert to epoch.
SUNSET_EPOCH=$(date -d "$(echo "$SUNSET_RAW" | cut -dT -f1) $(echo "$SUNSET_RAW" | cut -dT -f2)" +%s 2>/dev/null) SUNSET_EPOCH=$(date -d "$(echo "$SUNSET_RAW" | cut -dT -f1) $(echo "$SUNSET_RAW" | cut -dT -f2)" +%s 2>/dev/null)
if [ -z "$SUNSET_EPOCH" ]; then if [[ -z "$SUNSET_EPOCH" ]]; then
echo "ERROR: Could not calculate epoch. $SUNSET_RAW" >&2
return 1 return 1
fi fi
echo "Got ${SUNSET_RAW} and converted it to epoch ${SUNSET_EPOCH}."
return 0 return 0
} }
@@ -94,16 +136,20 @@ fetch_sunset_time() {
# Show alert via fallback chain: fyi -> notify-send -> wall # Show alert via fallback chain: fyi -> notify-send -> wall
############################################################################### ###############################################################################
show_alert() { show_alert() {
TITLE="Sunset Alarm" ALERT=$(date -d @${ALERT_WINDOW_START} +"%H:%M")
MESSAGE="Sunset is at $(date -d @${SUNSET_EPOCH} +"%H:%M")." SUNSET=$(date -d @${SUNSET_EPOCH} +"%H:%M")
PLURAL=$(if (( $MINUTES != 1 )); then echo "s"; fi)
persistent="-t 0 --wait" TITLE="Sunset Alarm ($ALERT)"
MESSAGE="It's ${MINUTES} minute${PLURAL} to $SUNSET."
keep_alive="-t 0 --wait"
icon="-i weather-clear -u critical" icon="-i weather-clear -u critical"
if command -v fyi >/dev/null 2>&1; then if command -v fyi >/dev/null 2>&1; then
fyi $icon $persistent "$TITLE" "$MESSAGE" fyi $icon $keep_alive "$TITLE" "$MESSAGE"
elif command -v notify-send >/dev/null 2>&1; then elif command -v notify-send >/dev/null 2>&1; then
notify-send $icon $persistent "$TITLE" "$MESSAGE" notify-send $icon $keep_alive "$TITLE" "$MESSAGE"
elif command -v wall >/dev/null 2>&1; then elif command -v wall >/dev/null 2>&1; then
echo "${TITLE}: ${MESSAGE}" | wall echo "${TITLE}: ${MESSAGE}" | wall
else else
@@ -113,6 +159,18 @@ show_alert() {
fi fi
} }
###############################################################################
# Sleep until a certain amount of time
###############################################################################
rest() {
if [[ "$CURRENT_TIME" -ge 0 ]]; then
TARGET="$1"
sleep_seconds=$(( TARGET - CURRENT_TIME ))
echo "Sleeping ${sleep_seconds}s until $(date -d @$TARGET +%H:%M:%S)."
sleep "$sleep_seconds"
fi
}
############################################################################### ###############################################################################
# Main loop # Main loop
############################################################################### ###############################################################################
@@ -120,52 +178,75 @@ while true; do
TODAY=$(date +%Y-%m-%d) TODAY=$(date +%Y-%m-%d)
CURRENT_TIME=$(date +%s) CURRENT_TIME=$(date +%s)
# Check if we need to fetch new sunset time for a new day # Check if we need to fetch new sunset time for a new day.
if [ "$LAST_DATE" != "$TODAY" ]; then if [[ "$LAST_DATE" != "$TODAY" || "$SUNSET_EPOCH" == 0 ]]
then
LAST_DATE="$TODAY" LAST_DATE="$TODAY"
if [ -n "$LATLONG" ]; then if [[ -n "$LATLONG" ]]; then
# Need internet to get sunset time # Need internet to get sunset time.
if ! check_internet; then if ! check_internet; then
echo "No internet connection. Retrying in 5 minutes..." >&2 echo "ERROR: No internet connection. Retrying in 5 minutes." >&2
sleep 300 sleep 300
continue continue
fi fi
fetch_sunset_time || { fetch_sunset_time || {
echo "Error fetching sunset time. Retrying in 5 minutes..." >&2 echo "ERROR: Failed fetching sunset time. Retrying in 5 minutes." >&2
sleep 300 sleep 300
continue continue
} }
if [[ "$SUNSET_EPOCH" == 0 ]]; then
echo "ERROR: Sunset time is still null. Retrying in 5 minutes." >&2
sleep 300
continue
fi
else else
# User provided sunset time directly, convert to epoch # User provided sunset time directly, convert to epoch.
SUNSET_EPOCH=$(date -d "${TODAY} ${SUNSET_TIME}" +%s 2>/dev/null) SUNSET_EPOCH=$(date -d "${TODAY} ${SUNSET_TIME}" +%s 2>/dev/null)
if [ -z "$SUNSET_EPOCH" ]; then if [[ -z "$SUNSET_EPOCH" ]]; then
echo "Error: could not parse time '${SUNSET_TIME}'." >&2 echo "ERROR: Could not parse time '${SUNSET_TIME}'." >&2
sleep 60 sleep 60
continue continue
fi fi
fi fi
fi fi
# Calculate alert window start (sunset minus specified minutes) # Calculate alert window start (sunset minus specified minutes).
ALERT_WINDOW_START=$(( SUNSET_EPOCH - (MINUTES * 60) )) ALERT_WINDOW_START=$(( SUNSET_EPOCH - (MINUTES * 60) ))
# Calculate midnight of the current day # Calculate midnight of the current day
MIDNIGHT=$(date -d "${TODAY} +1 day" +%s) MIDNIGHT=$(date -d "${TODAY} +1 day" +%s)
# Alert while current time is within the window (from window start until midnight) # Alert while current time is within the window (from window start until midnight).
if [ "$CURRENT_TIME" -ge "$ALERT_WINDOW_START" ] && \ if [[ "$SUNSET_EPOCH" -ge 0 \
[ "$CURRENT_TIME" -lt "$MIDNIGHT" ]; then && ( -z "$NOTIFIED" || -n "$PERSIST" ) \
&& "$CURRENT_TIME" -ge "$ALERT_WINDOW_START" \
&& "$CURRENT_TIME" -lt "$MIDNIGHT" ]]
then
echo "Alert time has been reached, show message."
show_alert show_alert
sleep 5 # Re-alert every 5 minutes while in alert window NOTIFIED="true"
echo "Alert has been dismissed."
if [[ -n "$PERSIST" ]]; then
echo "Sleeping for 5 minutes before next notification."
sleep 300
fi
elif [[ "$SUNSET_EPOCH" == 0 ]]; then
echo "ERROR: Sunset time has not been calculated yet, cannot alert." >&2
elif [[ -n "$NOTIFIED" && -z "$PERSIST" ]]; then
echo "Already notified for today ($TODAY)."
rest $MIDNIGHT
else else
sleep 60 echo "Not time to notify yet."
rest $ALERT_WINDOW_START
fi fi
# Reset when we've passed midnight so we fetch new time tomorrow # Reset when we've passed midnight so we fetch the new time for the day.
if [ "$CURRENT_TIME" -ge "$MIDNIGHT" ]; then if [[ "$CURRENT_TIME" -ge "$MIDNIGHT" ]]; then
LAST_DATE="" LAST_DATE=""
NOTIFIED=""
fi fi
done done