generated from me/template-mit
v3, unfinished.
This commit is contained in:
1 parent
0713442b78
commit
f145f7122e
1 file changed
+56
-119
+56
-119
@@ -1,140 +1,77 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# sunset-alarm — alert at a specified number of minutes before sunset
|
# sunset-alarm — persistent alert at a calculated offset before sunset
|
||||||
# Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
|
# Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
|
||||||
set -euo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SUNSET="" # epoch seconds for today's sunset
|
SUNSET_EPOCH="" # epoch of today's sunset
|
||||||
ALERT_EPOCH="" # epoch when alert should fire (sunset - offset)
|
ALERT_EPOCH="" # epoch when the window opens (sunset - offset)
|
||||||
OFFSET_MIN=0 # signed minute offset from sun -t/--hardcoded
|
OFFSET_MIN=0 # signed minute offset from sunset
|
||||||
LAT="" LON="" # coordinates (-l/--location)
|
LAT="" # latitude (-l/--location)
|
||||||
HARDCODED_TIME="" # HH:MM string (-t/--hardcoded)
|
LON="" # longitude
|
||||||
|
HARDCODED_TIME="" # HH:MM string (-t/--hardcoded)
|
||||||
|
|
||||||
# ── helpers ────────────────────────────────────────────────────────
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
|
Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-l LAT:LONG Latitude and longitude (e.g. 32.123:-111.111)
|
-l LAT:LONG Latitude and longitude, e.g. 32.123:-111.111
|
||||||
-t HH24:MI Hardcoded sunset time (ignored when combined with -l)
|
(queried from Open-Meteo for today's sunset time)
|
||||||
-m MINUTES Minutes offset before (+) or after (-) sunset to alert
|
-t HH24:MI Hardcoded sunset time to use instead.
|
||||||
-h Show this help and exit
|
When combined with -l the offset is applied against
|
||||||
|
this time and the network call is skipped.
|
||||||
|
-m MINUTES Minutes before (-), at (= 0), or after (+) sunset
|
||||||
|
to trigger the alert. May be negative.
|
||||||
|
-h Show this help message and exit.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
sunset-alarm -l 32.123:-111.111 -m 90 # 90 min before actual sunset
|
||||||
|
sunset-alarm -t 18:00 -m -5 # 5 min after the hardcoded time
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
die() { echo "Error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
warn_reconnect() { echo "Network unavailable, retrying in 5 minutes…" >&2; }
|
# ── resolve a fallback command at startup ──────────────────────────
|
||||||
|
NOTIFY_CMD=""
|
||||||
|
for _cmd in fyi notify-send wall; do
|
||||||
|
if command -v "$_cmd" >/dev/null 2>&1; then
|
||||||
|
NOTIFY_CMD="$_cmd"; break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# Query Open-Meteo for today's sunset epoch (local timezone).
|
fire_alert() {
|
||||||
|
local body="$1"
|
||||||
|
case "$NOTIFY_CMD" in
|
||||||
|
fyi) fyi "Sunset Alarm: ${body}" ;;
|
||||||
|
notify-send) notify-send -u normal "Sunset Alarm" "${body}" 2>/dev/null || true ;;
|
||||||
|
wall) echo "Sunset Alarm: ${body}" | wall 2>/dev/null || printf 'Sunset Alarm: %s\n' "$body" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── open-meteo helper ─────────────────────────────────────────────
|
||||||
fetch_sunset_epoch() {
|
fetch_sunset_epoch() {
|
||||||
local lat="$1" lon="$2"
|
local lat="$1" lon="$2"
|
||||||
local json resp_code
|
# curl –silent + HTTP code tail so we can detect failures.
|
||||||
# curl –silent + show HTTP code (-w) so we can detect server errors.
|
local resp; resp=$(curl -sfw "\n%{http_code}" \
|
||||||
json=$(curl -sf -w '\n%{http_code}' \
|
|
||||||
"https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=sunset&timezone=auto" \
|
"https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=sunset&timezone=auto" \
|
||||||
--connect-timeout 10 --max-time 30) || return 1
|
--connect-timeout 10 --max-time 30) || return 1
|
||||||
resp_code="${json##*$'\n'}"
|
|
||||||
json="${json%$'\n'*}"
|
local code="${resp##*$'\n'}"
|
||||||
[[ "$resp_code" == "200" ]] || return 2
|
resp="${resp%$'\n'*}" # strip the trailing HTTP code
|
||||||
# jq → "YYYY-MM-DDTHH:MM" (ISO-8601 local-time string for the sunset field)
|
|
||||||
local iso_ts
|
[[ "${code:-599}" == "200" ]] || return 2
|
||||||
iso_ts=$(echo "$json" | jq -r '.current.sunset' | grep -v '^null$')
|
|
||||||
[[ -n "$iso_ts" ]] || return 3
|
local iso; iso=$(printf '%s\n' "$resp" | jq -r '.current.sunset // empty')
|
||||||
# date -d parses ISO-8601 timestamps directly.
|
[[ -n "$iso" ]] || return 3 # null / missing field
|
||||||
SUNSET=$(date -d "$iso_ts" +%s)
|
|
||||||
|
SUNSET_EPOCH=$(date -d "$iso" +%s) # local-time epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build today's sunset epoch from HH:MM when using the hardcoded path (-t).
|
# Build today's sunset epoch from a hardcoded HH:MM when -t is used.
|
||||||
set_sunset_from_time() {
|
set_sunset_from_time() {
|
||||||
local hhmm="$1" hh mm
|
local hhmm="$1"
|
||||||
read -r hh mm <<< "$(echo "$hhmm" | tr ':' ' ')"
|
local Y M D h m
|
||||||
# Pad hours / minutes in case user passed "8:30" etc. (they won't via arg
|
read -r Y M D h m <<< "$(date +%Y-%m-%d) $(echo "$hhmm" | tr ':' ' ')"
|
||||||
# parsing, but defensive never hurts).
|
SUNSET_EPOCH=$(date -d "${Y}-${M}-${D} ${h}:${m}:00" 2>/dev/null +%s \
|
||||||
SUNSET=$(date -d "$(date +%Y-%m-%d) ${hh}:${mm}:00" +%s)
|
|| die "Invalid time format '${hhmm}')")
|
||||||
}
|
|
||||||
|
|
||||||
# Fire a desktop notification (best-effort).
|
|
||||||
notify() {
|
|
||||||
local title="$1" body="$2"
|
|
||||||
notify-send -u normal "$title" "$body" 2>/dev/null || true
|
|
||||||
printf '%s — %s\n' "$(date '+%F %T')" "ALERT: $body"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── argument parsing ───────────────────────────────────────────────
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
-l)
|
|
||||||
[[ $# -ge 2 ]] || die "-l requires an argument (LAT:LONG)"
|
|
||||||
IFS=':' read -r LAT LON <<< "$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-t)
|
|
||||||
[[ $# -ge 2 ]] || die "-t requires an argument (HH24:MI)"
|
|
||||||
HARDCODED_TIME="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-m)
|
|
||||||
[[ $# -ge 2 ]] || die "-m requires an argument (MINUTES)"
|
|
||||||
OFFSET_MIN="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
|
||||||
*) die "Unknown option: $1" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── guardrails ─────────────────────────────────────────────────────
|
|
||||||
[[ $# -eq 0 ]] || die "extra arguments provided — see --help"
|
|
||||||
(( OFFSET_MIN >= -60 )) || [[ $OFFSET_MIN -le 0 ]] 2>/dev/null \
|
|
||||||
1>/dev/null || die "-m must be ≥ -60 (or any value ≤ 0)"
|
|
||||||
|
|
||||||
# Need either -l or -t.
|
|
||||||
if [[ -z "$LAT" && -z "$HARDCODED_TIME" ]]; then
|
|
||||||
die "Need at least one of: -l LAT:LONG, -t HH24:MI"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Phase 1 — resolve today's sunset (epoch) ───────────────────────
|
|
||||||
if [[ -n "$LAT" && -n "$LON" ]]; then
|
|
||||||
while true; do
|
|
||||||
fetch_sunset_epoch "$LAT" "$LON" && break
|
|
||||||
delay=$(( 5 * 60 ))
|
|
||||||
warn_reconnect; sleep "$delay" || warn_reconnect
|
|
||||||
done
|
|
||||||
else
|
|
||||||
set_sunset_from_time "$HARDCODED_TIME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Phase 2 — compute alert time and loop ──────────────────────────
|
|
||||||
ALERT_EPOCH=$(( SUNSET - OFFSET_MIN * 60 ))
|
|
||||||
now_epoch=$(date +%s)
|
|
||||||
|
|
||||||
if (( now_epoch >= ALERT_EPOCH )); then
|
|
||||||
# Already past the window — fire once, exit.
|
|
||||||
notify "Sunset Alarm" \
|
|
||||||
"Triggered (offset: ${OFFSET_MIN:++}$(printf '%d' "$OFFSET_MIN")). Sunset at $(date -d @"$SUNSET" '+%H:%M')."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
now_epoch=$(date +%s)
|
|
||||||
if (( ALERT_EPOCH <= now_epoch && now_epoch < SUNSET )); then
|
|
||||||
notify "Sunset Alarm" \
|
|
||||||
"Triggered (offset: ${OFFSET_MIN:++}$(printf '%d' "$OFFSET_MIN")). Sunset at $(date -d @"$SUNSET" '+%H:%M')."
|
|
||||||
elif (( now_epoch > SUNSET )); then
|
|
||||||
# Day is over — re-fetch for tomorrow (if we have -l).
|
|
||||||
if [[ -n "$LAT" && -n "$LON" ]]; then
|
|
||||||
warn_reconnect=true # just print a quick line, don't sleep
|
|
||||||
while true; do
|
|
||||||
fetch_sunset_epoch "$LAT" "$LON" && break
|
|
||||||
delay=$(( 5 * 60 ))
|
|
||||||
warn_reconnect; sleep "$delay" || warn_reconnect
|
|
||||||
done
|
|
||||||
else
|
|
||||||
set_sunset_from_time "$HARDCODED_TIME"
|
|
||||||
fi
|
|
||||||
ALERT_EPOCH=$(( SUNSET - OFFSET_MIN * 60 ))
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
sleep 60
|
|
||||||
done
|
|
||||||
Reference in new issue
Block a user