From 41644a597bb7b5d60a0d01a329cb5bd018cbc5f1 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Sat, 6 Jun 2026 10:42:28 -0400 Subject: [PATCH 1/4] [NJ Transit] Note scheduled departures --- apps/njtransit/nj_transit.star | 45 ++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/njtransit/nj_transit.star b/apps/njtransit/nj_transit.star index 72687d30d..67af47635 100644 --- a/apps/njtransit/nj_transit.star +++ b/apps/njtransit/nj_transit.star @@ -45,6 +45,7 @@ load("http.star", "http") load("re.star", "re") load("render.star", "render") load("schema.star", "schema") +load("time.star", "time") #URL TO NJ TRANSIT DEPARTURE VISION WEBSITE NJ_TRANSIT_DV_URL = "https://www.njtransit.com/dv-to" @@ -452,23 +453,63 @@ def get_departures_for_station(station): return [] departures_data = departure_screens.get("items", []) + now = time.now().in_location(TIMEZONE) # Convert GraphQL response to our departure struct format result = [] for item in departures_data: + departure_date = item.get("departureDate", "") + status = item.get("status", "") departure_struct = struct( - departing_at = item.get("departureDate", ""), + departing_at = departure_date, destination = str(item.get("destination", "")).upper(), service_line = item.get("lineAbbreviation", "AMTK"), train_number = str(item.get("trainID", "")), track_number = item.get("track"), - departing_in = item.get("status", ""), + departing_in = get_departing_in(departure_date, now, status), ) result.append(departure_struct) print("Found '%s' departures from GraphQL API" % len(result)) return result +def get_departing_in(departure_date, now, status): + """ + Returns NJ Transit's status when present, otherwise a compact countdown. + NJ Transit provides times without a date, so use the current NY date and + roll forward when the next departure is after midnight. + """ + if status != "": + return status + + if departure_date == "": + return "" + + departure_time = time.parse_time( + "{} {}".format(now.format("2006-01-02"), departure_date), + format = "2006-01-02 3:04 PM", + location = TIMEZONE, + ) + + if departure_time < now: + departure_time = departure_time + time.parse_duration("24h") + + minutes_until_departure = (departure_time - now).minutes + + if minutes_until_departure <= 1: + return "Sched now" + + if minutes_until_departure < 60: + return "Sched in {}m".format(int(minutes_until_departure)) + + hours = int(minutes_until_departure / 60) + minutes = int(minutes_until_departure % 60) + + if minutes == 0: + return "Sched in {}h".format(hours) + + return "Sched in {}h {}m".format(hours, minutes) + def fetch_stations_from_website(): """ Function fetches trains station list from NJ Transit website From 793fca8526ed5a696fe902b5b86b16e4750ea65a Mon Sep 17 00:00:00 2001 From: David Goldman Date: Thu, 8 Oct 2026 20:29:48 -0400 Subject: [PATCH 2/4] Update NJ Transit for Nuxt data format change --- apps/njtransit/nj_transit.star | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/njtransit/nj_transit.star b/apps/njtransit/nj_transit.star index 67af47635..a67321a36 100644 --- a/apps/njtransit/nj_transit.star +++ b/apps/njtransit/nj_transit.star @@ -349,6 +349,10 @@ def render_departure_row(departure): def get_schema(): options = getStationListOptions() + # Keep the schema valid if NJ Transit is unavailable or changes its page data. + if len(options) == 0: + options = [create_option("Station list unavailable", DEFAULT_STATION)] + fields = [ schema.Dropdown( id = "station", @@ -541,26 +545,28 @@ def fetch_stations_from_website(): # The format uses indexed references where objects contain indices pointing to values data = json.decode(json_content) - # Iterate through the data array to find station objects - # Station objects have the pattern: {"__typename": idx1, "title": idx2, "path": idx3} - # where data[idx1] == "TrainScheduleStation" and data[idx2] is the station name + # Iterate through the data array to find station objects. Nuxt stores the + # typename and title as indices into this same array. Do not depend on other + # fields such as path or stationCode, which have changed over time. stations_found = 0 + stations_seen = {} for i in range(len(data)): item = data[i] # Check if this is a dict with the expected station structure - if type(item) == "dict" and "__typename" in item and "title" in item and "path" in item: + if type(item) == "dict" and "__typename" in item and "title" in item: typename_idx = item["__typename"] title_idx = item["title"] # Safely get values by index - if typename_idx < len(data) and title_idx < len(data): + if type(typename_idx) == "int" and type(title_idx) == "int" and typename_idx >= 0 and title_idx >= 0 and typename_idx < len(data) and title_idx < len(data): typename = data[typename_idx] title = data[title_idx] # Check if this is a TrainScheduleStation with a valid title - if typename == "TrainScheduleStation" and type(title) == "string" and len(title) > 0: + if typename == "TrainScheduleStation" and type(title) == "string" and len(title) > 0 and title not in stations_seen: result.append(title) + stations_seen[title] = True stations_found = stations_found + 1 print("Got response of '%s' stations" % stations_found) From 6957171be2860b8eed2b429e70d839b73c18d501 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Thu, 8 Oct 2026 20:41:38 -0400 Subject: [PATCH 3/4] NJ Transit: Only check departure vision data The regular train data notably has a unified Secaucus Junction Station while the departure vision which we're using has the separated Secaucus Junction Lower Level and Secaucus Junction Upper Level --- apps/njtransit/nj_transit.star | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/njtransit/nj_transit.star b/apps/njtransit/nj_transit.star index a67321a36..a2a3400b4 100644 --- a/apps/njtransit/nj_transit.star +++ b/apps/njtransit/nj_transit.star @@ -545,13 +545,27 @@ def fetch_stations_from_website(): # The format uses indexed references where objects contain indices pointing to values data = json.decode(json_content) - # Iterate through the data array to find station objects. Nuxt stores the - # typename and title as indices into this same array. Do not depend on other - # fields such as path or stationCode, which have changed over time. + # Find the explicit Departure Vision station collection. Nuxt stores the + # collection, its objects, and their fields as indices into this same array. + dv_stations_idx = None + for item in data: + if type(item) == "dict" and "dvStations" in item: + candidate_idx = item["dvStations"] + if type(candidate_idx) == "int" and candidate_idx >= 0 and candidate_idx < len(data) and type(data[candidate_idx]) == "list": + dv_stations_idx = candidate_idx + break + + if dv_stations_idx == None: + print("Could not find Departure Vision stations in Nuxt data") + return result + stations_found = 0 stations_seen = {} - for i in range(len(data)): - item = data[i] + for station_idx in data[dv_stations_idx]: + if type(station_idx) != "int" or station_idx < 0 or station_idx >= len(data): + continue + + item = data[station_idx] # Check if this is a dict with the expected station structure if type(item) == "dict" and "__typename" in item and "title" in item: From 14719470a844edc306918f85a6b51163aeb7c238 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Oct 2026 00:49:03 +0000 Subject: [PATCH 4/4] chore: auto-update manifest metadata --- apps/njtransit/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/njtransit/manifest.yaml b/apps/njtransit/manifest.yaml index 921b070c5..2dd833745 100644 --- a/apps/njtransit/manifest.yaml +++ b/apps/njtransit/manifest.yaml @@ -10,4 +10,4 @@ recommendedInterval: 5 category: transit tags: [] published: '2022-12-06T04:28:51Z' -updated: '2026-06-08T04:46:54Z' +updated: '2026-10-09T00:41:38Z'