feat: add multi-chat routing and bill-specific dashboard countdown
This commit is contained in:
96
main.sh
96
main.sh
@@ -2,6 +2,31 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# macOS ships Bash 3.2 and BSD date. Prefer Homebrew's compatible runtime when
|
||||
# it is installed, even when this script was started explicitly with /bin/bash.
|
||||
for coreutils_path in \
|
||||
/opt/homebrew/opt/coreutils/libexec/gnubin \
|
||||
/usr/local/opt/coreutils/libexec/gnubin; do
|
||||
if [[ -d "$coreutils_path" ]]; then
|
||||
PATH="$coreutils_path:$PATH"
|
||||
export PATH
|
||||
break
|
||||
fi
|
||||
done
|
||||
unset coreutils_path
|
||||
|
||||
if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 3))); then
|
||||
for modern_bash in /opt/homebrew/bin/bash /usr/local/bin/bash; do
|
||||
if [[ -x "$modern_bash" ]]; then
|
||||
exec "$modern_bash" "$0" "$@"
|
||||
fi
|
||||
done
|
||||
printf 'Error: Bash 4.3 or newer is required (found %s).\n' "$BASH_VERSION" >&2
|
||||
printf 'On macOS, install it with: brew install bash coreutils\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
unset modern_bash
|
||||
|
||||
readonly STATE_URL="https://mahgit.ir/MeghdadFadaee/daily-blackout-check/raw/branch/main/state/outages.json"
|
||||
readonly TEHRAN_TZ="Asia/Tehran"
|
||||
|
||||
@@ -36,20 +61,21 @@ trap cleanup EXIT
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./main.sh [-i MINUTES]
|
||||
Usage: ./main.sh [-i MINUTES] [-b BILL_ID]
|
||||
|
||||
Continuously display current and upcoming electricity outages from the public
|
||||
repository state. The countdown updates every second.
|
||||
|
||||
Options:
|
||||
-i, --interval MINUTES Refetch state at this interval (default: 10)
|
||||
-b, --bill-id BILL_ID Use only this bill for the large countdown
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
NO_COLOR Disable ANSI colors when set
|
||||
COLUMNS Override the detected output width
|
||||
|
||||
Required commands: bash, curl, jq, and GNU date
|
||||
Required commands: Bash 4.3+, curl, jq, and GNU date
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -299,6 +325,7 @@ record_fields() {
|
||||
build_schedule() {
|
||||
local path="$1"
|
||||
local now_epoch="$2"
|
||||
local countdown_bill_id="${3:-}"
|
||||
local record start_epoch stop_epoch phase phase_order sort_key line hero_key=""
|
||||
local -a fields sortable=()
|
||||
|
||||
@@ -324,7 +351,8 @@ build_schedule() {
|
||||
phase="ONGOING"
|
||||
phase_order=0
|
||||
sort_key="$(printf '0:%020d:%s:%s' "$start_epoch" "${fields[0]}" "${fields[1]}")"
|
||||
if [[ -z "$hero_key" || "$stop_epoch" -lt "$hero_key" ]]; then
|
||||
if [[ (-z "$countdown_bill_id" || "${fields[0]}" == "$countdown_bill_id") \
|
||||
&& (-z "$hero_key" || "$stop_epoch" -lt "$hero_key") ]]; then
|
||||
hero_key="$stop_epoch"
|
||||
HERO="$phase"$'\t'"$start_epoch"$'\t'"$stop_epoch"$'\t'"$record"
|
||||
fi
|
||||
@@ -332,9 +360,11 @@ build_schedule() {
|
||||
phase="UPCOMING"
|
||||
phase_order=1
|
||||
sort_key="$(printf '1:%020d:%s:%s' "$start_epoch" "${fields[0]}" "${fields[1]}")"
|
||||
if [[ -z "$hero_key" && -z "$HERO" ]]; then
|
||||
if [[ (-z "$countdown_bill_id" || "${fields[0]}" == "$countdown_bill_id") \
|
||||
&& -z "$hero_key" && -z "$HERO" ]]; then
|
||||
HERO="$phase"$'\t'"$start_epoch"$'\t'"$stop_epoch"$'\t'"$record"
|
||||
elif [[ "${HERO%%$'\t'*}" == "UPCOMING" ]]; then
|
||||
elif [[ (-z "$countdown_bill_id" || "${fields[0]}" == "$countdown_bill_id") \
|
||||
&& "${HERO%%$'\t'*}" == "UPCOMING" ]]; then
|
||||
local current_hero_start
|
||||
current_hero_start="${HERO#*$'\t'}"
|
||||
current_hero_start="${current_hero_start%%$'\t'*}"
|
||||
@@ -772,6 +802,7 @@ schedule_next_transition() {
|
||||
render_dashboard() {
|
||||
local now_epoch="$1"
|
||||
local columns="${2:-}"
|
||||
local countdown_bill_id="${3:-}"
|
||||
local rule phase start_epoch stop_epoch record remaining banner
|
||||
local schedule_line ignored
|
||||
|
||||
@@ -785,20 +816,30 @@ render_dashboard() {
|
||||
return
|
||||
fi
|
||||
|
||||
IFS=$'\t' read -r phase start_epoch stop_epoch record <<<"$HERO"
|
||||
if [[ "$phase" == "ONGOING" ]]; then
|
||||
remaining=$((stop_epoch - now_epoch))
|
||||
else
|
||||
remaining=$((start_epoch - now_epoch))
|
||||
fi
|
||||
banner="$(banner_duration "$remaining")"
|
||||
rule="$(repeat_character '=' "$columns")"
|
||||
|
||||
printf '%s%s%s\n' "$CYAN" "$rule" "$RESET"
|
||||
printf '\n'
|
||||
printf '%s%s' "$BOLD" "$YELLOW"
|
||||
print_large_countdown "$banner" "$columns"
|
||||
printf '%s' "$RESET"
|
||||
|
||||
if [[ -z "$HERO" ]]; then
|
||||
center_line "NO CURRENT OR UPCOMING OUTAGE" "$columns"
|
||||
center_line "Countdown bill $countdown_bill_id" "$columns"
|
||||
else
|
||||
IFS=$'\t' read -r phase start_epoch stop_epoch record <<<"$HERO"
|
||||
if [[ "$phase" == "ONGOING" ]]; then
|
||||
remaining=$((stop_epoch - now_epoch))
|
||||
else
|
||||
remaining=$((start_epoch - now_epoch))
|
||||
fi
|
||||
banner="$(banner_duration "$remaining")"
|
||||
if [[ -n "$countdown_bill_id" ]]; then
|
||||
center_line "COUNTDOWN FOR BILL $countdown_bill_id" "$columns"
|
||||
printf '\n'
|
||||
fi
|
||||
printf '%s%s' "$BOLD" "$YELLOW"
|
||||
print_large_countdown "$banner" "$columns"
|
||||
printf '%s' "$RESET"
|
||||
fi
|
||||
|
||||
printf '\n%s%s%s\n' "$CYAN" "$rule" "$RESET"
|
||||
printf '\n%sOUTAGE SCHEDULE (%d)%s\n\n' "$BOLD" "${#SCHEDULE[@]}" "$RESET"
|
||||
|
||||
@@ -812,7 +853,7 @@ render_dashboard() {
|
||||
main() {
|
||||
local now_epoch next_refresh=0 refresh_minutes=10 refresh_seconds state_ready=0
|
||||
local last_rendered=-1 next_transition=0 rebuild_schedule=0 frame key input_fd=0
|
||||
local display_columns
|
||||
local display_columns countdown_bill_id=""
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
@@ -833,6 +874,19 @@ main() {
|
||||
refresh_minutes="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
-b|--bill-id)
|
||||
if (($# < 2)); then
|
||||
printf 'Error: %s requires a value\n' "$1" >&2
|
||||
usage >&2
|
||||
return 2
|
||||
fi
|
||||
countdown_bill_id="$2"
|
||||
shift 2
|
||||
;;
|
||||
--bill-id=*)
|
||||
countdown_bill_id="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
printf 'Error: unknown option: %s\n' "$1" >&2
|
||||
usage >&2
|
||||
@@ -845,6 +899,10 @@ main() {
|
||||
printf 'Error: interval must be a whole number from 1 to 1440 minutes\n' >&2
|
||||
return 2
|
||||
fi
|
||||
if [[ -n "$countdown_bill_id" && ! "$countdown_bill_id" =~ ^[0-9]+$ ]]; then
|
||||
printf 'Error: bill ID must contain only decimal digits\n' >&2
|
||||
return 2
|
||||
fi
|
||||
refresh_seconds=$((10#$refresh_minutes * 60))
|
||||
|
||||
require_command curl
|
||||
@@ -890,7 +948,7 @@ main() {
|
||||
|
||||
if ((state_ready)); then
|
||||
if ((rebuild_schedule || (next_transition > 0 && now_epoch >= next_transition))); then
|
||||
if build_schedule "$STATE_PATH" "$now_epoch"; then
|
||||
if build_schedule "$STATE_PATH" "$now_epoch" "$countdown_bill_id"; then
|
||||
next_transition="$(schedule_next_transition "$now_epoch")"
|
||||
rebuild_schedule=0
|
||||
else
|
||||
@@ -903,7 +961,7 @@ main() {
|
||||
display_columns="$(terminal_columns)"
|
||||
frame="$(
|
||||
if ((state_ready)); then
|
||||
render_dashboard "$now_epoch" "$display_columns"
|
||||
render_dashboard "$now_epoch" "$display_columns" "$countdown_bill_id"
|
||||
else
|
||||
render_unavailable "$display_columns"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user