#!/bin/bash # Route 5 departure watcher — runs in background, posts to Discord when bus departs # Usage: bash watch-departure.sh [channel_id] # Example: bash watch-departure.sh "2026-02-12T13:30:00Z" 1467247377743347953 set -eo pipefail SCHED_DEPART="$1" CHANNEL="${2:-1467247377743347953}" # Default: DM channel VP_URL="https://data.texas.gov/download/cuc7-ywmd/application%2Fjson" FIRST_STOP="5854" USER_STOP="964" TOKEN=$(printenv DISCORD_BOT_TOKEN) MAX_POLLS=40 # ~13 minutes max watch time POLL_INTERVAL=20 # seconds between polls # Find the trip matching this scheduled departure find_trip() { local TU=$(curl -sL --max-time 10 "https://data.texas.gov/download/mqtr-wwpy/application%2Fjson" 2>/dev/null) echo "$TU" | jq -r --arg sched "$SCHED_DEPART" '.entity[] | select(.tripUpdate.trip.routeId == "5" and .tripUpdate.trip.directionId == 0) | select([.tripUpdate.stopTimeUpdate[] | select(.stopId == "5854") | ((.departure.time // .arrival.time) | tonumber)] | .[0] == ($sched | sub("Z$";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime)) | .tripUpdate.trip.tripId' 2>/dev/null | head -1 } # Convert ISO to epoch sched_epoch() { date -d "$SCHED_DEPART" +%s 2>/dev/null || date -u -d "${SCHED_DEPART%Z}" +%s 2>/dev/null } SCHED_TS=$(sched_epoch) SCHED_CST=$(TZ=America/Chicago date -d "@$SCHED_TS" +"%I:%M %p" 2>/dev/null) # Find the trip ID TRIP_ID=$(find_trip) if [ -z "$TRIP_ID" ]; then # Fallback: find closest eastbound trip TU=$(curl -sL --max-time 10 "https://data.texas.gov/download/mqtr-wwpy/application%2Fjson" 2>/dev/null) TRIP_ID=$(echo "$TU" | jq -r --arg ts "$SCHED_TS" '[.entity[] | select(.tripUpdate.trip.routeId == "5" and .tripUpdate.trip.directionId == 0) | {tripId: .tripUpdate.trip.tripId, depart: ([.tripUpdate.stopTimeUpdate[] | select(.stopId == "5854") | (.departure.time // .arrival.time)] | .[0] | tonumber)}] | sort_by((. .depart - ($ts | tonumber)) | fabs) | .[0].tripId' 2>/dev/null) fi if [ -z "$TRIP_ID" ]; then # Post error curl -s -X POST -H "Authorization: Bot $TOKEN" -H "Content-Type: application/json" \ -d "{\"content\":\"āš ļø Could not find Route 5 trip for $SCHED_CST departure.\"}" \ "https://discord.com/api/v10/channels/$CHANNEL/messages" > /dev/null exit 1 fi # Poll until departure for i in $(seq 1 $MAX_POLLS); do VP=$(curl -sL --max-time 8 "https://data.texas.gov/download/cuc7-ywmd/application%2Fjson" 2>/dev/null) VEHICLE=$(echo "$VP" | jq -c ".entity[] | select(.vehicle.trip.tripId == \"$TRIP_ID\")" 2>/dev/null) if [ -z "$VEHICLE" ]; then sleep $POLL_INTERVAL continue fi STOP_ID=$(echo "$VEHICLE" | jq -r '.vehicle.stopId') STATUS=$(echo "$VEHICLE" | jq -r '.vehicle.currentStatus') SPEED=$(echo "$VEHICLE" | jq -r '.vehicle.position.speed') VEH_ID=$(echo "$VEHICLE" | jq -r '.vehicle.vehicle.label') # Bus has left the first stop if [ "$STOP_ID" != "$FIRST_STOP" ] || ([ "$STATUS" = "IN_TRANSIT_TO" ] && [ "$STOP_ID" != "$FIRST_STOP" ]); then DEPART_TS=$(date +%s) DEPART_CST=$(TZ=America/Chicago date +"%I:%M:%S %p") DELAY=$((DEPART_TS - SCHED_TS)) DELAY_MIN=$((DELAY / 60)) # Calculate ETA at user stop (7m26s from first stop) ETA_TS=$((DEPART_TS + 446)) ETA_CST=$(TZ=America/Chicago date -d "@$ETA_TS" +"%I:%M %p" 2>/dev/null) if [ "$DELAY_MIN" -le 0 ]; then STATUS_MSG="🟢 On time" elif [ "$DELAY_MIN" -le 2 ]; then STATUS_MSG="🟔 ~${DELAY_MIN}min late" else STATUS_MSG="šŸ”“ ${DELAY_MIN}min late" fi MSG="🚌 **Route 5 Departed!**\nBus ${VEH_ID} left Anderson/Northcross at ${DEPART_CST}\nScheduled: ${SCHED_CST} | ${STATUS_MSG}\nšŸ“ ETA at Woodrow/Choquette: **${ETA_CST}**" CONTENT=$(printf "$MSG") curl -s -X POST -H "Authorization: Bot $TOKEN" -H "Content-Type: application/json" \ -d "$(jq -n --arg c "$CONTENT" '{content: $c}')" \ "https://discord.com/api/v10/channels/$CHANNEL/messages" > /dev/null exit 0 fi sleep $POLL_INTERVAL done # Timeout — bus never departed (or we missed it) curl -s -X POST -H "Authorization: Bot $TOKEN" -H "Content-Type: application/json" \ -d "{\"content\":\"āš ļø Route 5 watcher timed out — could not confirm $SCHED_CST departure from Anderson/Northcross.\"}" \ "https://discord.com/api/v10/channels/$CHANNEL/messages" > /dev/null