diff --git a/.gitignore b/.gitignore index c61264a..1a291fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ __pycache__/ src/data/announcements/* !src/data/announcements/.gitkeep config.json -src/legacy/ -main_legacy.py -data.json \ No newline at end of file +stations.json \ No newline at end of file diff --git a/data.json b/data.json new file mode 100644 index 0000000..a68ed54 --- /dev/null +++ b/data.json @@ -0,0 +1,698 @@ +{ + "duration": 1440, + "startTime": "2026-08-16T21:25:00Z", + "endTime": "2026-08-16T21:49:00Z", + "transfers": 0, + "id": "CogBCgI1NBIiMjAyNjA4MTZfMjM6MjVfbmwtT3Blbk9WXzM3Njc3ODE5OBoRbmwtT3Blbk9WXzM5ODA3NDch/ilVouwlSkAp147iHHX0E0AyEW5sLU9wZW5PVl8zOTc5OTIwOZRIopdRMEpAQcGxZ89lmhNASKzViNQGUMzgiNQGWgZTVUJXQVlgAQ==", + "legs": [ + { + "mode": "SUBWAY", + "from": { + "name": "Amsterdam, Gein", + "stopId": "nl-OpenOV_3980747", + "parentId": "nl-OpenOV_NL:S:30009530", + "importance": 0.018040798604488373, + "lat": 52.296284, + "lon": 4.988728000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "departure": "2026-08-16T21:25:00Z", + "scheduledDeparture": "2026-08-16T21:25:00Z", + "scheduledTrack": "1", + "track": "1", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NOT_ALLOWED", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS train", + "descriptionText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS trains to Zuid or at Spaklerweg for Overamstel. Details: gvb.nl" + } + ], + "modes": [ + "SUBWAY", + "BUS" + ] + }, + "to": { + "name": "Amsterdam, Centraal Station", + "stopId": "nl-OpenOV_3979920", + "parentId": "nl-OpenOV_NL:S:30009500", + "importance": 0.17953550815582275, + "lat": 52.377489999999995, + "lon": 4.900779000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:49:00Z", + "scheduledArrival": "2026-08-16T21:49:00Z", + "vertexType": "TRANSIT", + "pickupType": "NOT_ALLOWED", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Neem M52 naar Zuid voor RAI en de richting Isolatorweg. Details: gvb.nl/zomermetro|-- Take M52 to Zuid for RAI and the direction Isolatorweg", + "descriptionText": "Neem M52 naar Zuid voor RAI en de richting Isolatorweg. Details: gvb.nl/zomermetro|-- Take M52 to Zuid for RAI and the direction Isolatorweg. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "HIGHSPEED_RAIL", + "LONG_DISTANCE", + "NIGHT_RAIL", + "REGIONAL_RAIL", + "SUBWAY", + "TRAM", + "BUS", + "FERRY" + ] + }, + "duration": 1440, + "startTime": "2026-08-16T21:25:00Z", + "endTime": "2026-08-16T21:49:00Z", + "scheduledStartTime": "2026-08-16T21:25:00Z", + "scheduledEndTime": "2026-08-16T21:49:00Z", + "realTime": true, + "scheduled": true, + "interlineWithPreviousLeg": false, + "headsign": "Centraal Station", + "tripFrom": { + "name": "Amsterdam, Gein", + "stopId": "nl-OpenOV_3980747", + "parentId": "nl-OpenOV_NL:S:30009530", + "importance": 0.018040798604488373, + "lat": 52.296284, + "lon": 4.988728000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "departure": "2026-08-16T21:25:00Z", + "scheduledDeparture": "2026-08-16T21:25:00Z", + "scheduledTrack": "1", + "track": "1", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NOT_ALLOWED", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS train", + "descriptionText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS trains to Zuid or at Spaklerweg for Overamstel. Details: gvb.nl" + } + ], + "modes": [ + "SUBWAY", + "BUS" + ] + }, + "tripTo": { + "name": "Amsterdam, Centraal Station", + "stopId": "nl-OpenOV_3979920", + "parentId": "nl-OpenOV_NL:S:30009500", + "importance": 0.17953550815582275, + "lat": 52.377489999999995, + "lon": 4.900779000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:49:00Z", + "scheduledArrival": "2026-08-16T21:49:00Z", + "vertexType": "TRANSIT", + "pickupType": "NOT_ALLOWED", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Neem M52 naar Zuid voor RAI en de richting Isolatorweg. Details: gvb.nl/zomermetro|-- Take M52 to Zuid for RAI and the direction Isolatorweg", + "descriptionText": "Neem M52 naar Zuid voor RAI en de richting Isolatorweg. Details: gvb.nl/zomermetro|-- Take M52 to Zuid for RAI and the direction Isolatorweg. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "HIGHSPEED_RAIL", + "LONG_DISTANCE", + "NIGHT_RAIL", + "REGIONAL_RAIL", + "SUBWAY", + "TRAM", + "BUS", + "FERRY" + ] + }, + "routeId": "nl-OpenOV_152364", + "routeUrl": "", + "directionId": "0", + "routeColor": "fff200", + "routeTextColor": "000000", + "routeType": 1, + "agencyName": "GVB", + "agencyUrl": "http://www.gvb.nl/klantenservice", + "agencyFareUrl": "", + "agencyId": "GVB", + "tripId": "20260816_23:25_nl-OpenOV_376778198", + "routeShortName": "54", + "routeLongName": "Lijn 54", + "tripShortName": "", + "displayName": "54", + "cancelled": false, + "source": "nl_OpenOV.gtfs.zip/stop_times.txt:1582071:1582085", + "intermediateStops": [ + { + "name": "Amsterdam, Reigersbos", + "stopId": "nl-OpenOV_3979741", + "parentId": "nl-OpenOV_NL:S:30009528", + "importance": 0.018029676750302315, + "lat": 52.29557400000001, + "lon": 4.973636000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:26:00Z", + "departure": "2026-08-16T21:27:00Z", + "scheduledArrival": "2026-08-16T21:26:00Z", + "scheduledDeparture": "2026-08-16T21:27:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS train", + "descriptionText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS trains to Zuid or at Spaklerweg for Overamstel. Details: gvb.nl" + } + ], + "modes": [ + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Station Holendrecht", + "stopId": "nl-OpenOV_3981303", + "parentId": "nl-OpenOV_NL:S:30009526", + "importance": 0.026576673611998558, + "lat": 52.29839300000001, + "lon": 4.959859000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:28:00Z", + "departure": "2026-08-16T21:29:00Z", + "scheduledArrival": "2026-08-16T21:28:00Z", + "scheduledDeparture": "2026-08-16T21:29:00Z", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS train", + "descriptionText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS trains to Zuid or at Spaklerweg for Overamstel. Details: gvb.nl" + } + ], + "modes": [ + "REGIONAL_RAIL", + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Bullewijk", + "stopId": "nl-OpenOV_3981144", + "parentId": "nl-OpenOV_NL:S:30009524", + "importance": 0.017878592014312744, + "lat": 52.30693, + "lon": 4.952079, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:30:00Z", + "departure": "2026-08-16T21:31:00Z", + "scheduledArrival": "2026-08-16T21:30:00Z", + "scheduledDeparture": "2026-08-16T21:31:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS train", + "descriptionText": "Stap op Bijlmer ArenA over op NS-treinen naar Zuid of op Spaklerweg voor Overamstel. Details: gvb.nl --|Change at Bijlmer ArenA for NS trains to Zuid or at Spaklerweg for Overamstel. Details: gvb.nl" + } + ], + "modes": [ + "SUBWAY" + ] + }, + { + "name": "Amsterdam, Station Bijlmer ArenA", + "stopId": "nl-OpenOV_3980962", + "parentId": "nl-OpenOV_NL:S:30009522", + "importance": 0.04671318084001541, + "lat": 52.31261, + "lon": 4.946935, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:32:00Z", + "departure": "2026-08-16T21:32:00Z", + "scheduledArrival": "2026-08-16T21:32:00Z", + "scheduledDeparture": "2026-08-16T21:32:00Z", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Neem NS-treinen naar Zuid of stap op Spaklerweg over voor Overamstel. Details: gvb.nl/zomermetro --|Take NS trains to Zuid or change at Spak", + "descriptionText": "Neem NS-treinen naar Zuid of stap op Spaklerweg over voor Overamstel. Details: gvb.nl/zomermetro --|Take NS trains to Zuid or change at Spaklerweg for Overamstel. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "LONG_DISTANCE", + "COACH", + "NIGHT_RAIL", + "REGIONAL_RAIL", + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Strandvliet", + "stopId": "nl-OpenOV_3981329", + "parentId": "nl-OpenOV_NL:S:30009520", + "importance": 0.017878592014312744, + "lat": 52.31868399999999, + "lon": 4.941375000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:33:00Z", + "departure": "2026-08-16T21:34:00Z", + "scheduledArrival": "2026-08-16T21:33:00Z", + "scheduledDeparture": "2026-08-16T21:34:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Spaklerweg over voor Overamstel of op Amstel voor RAI. Details: gvb.nl/zomermetro –|Change at Spaklerweg for Overamstel or at Amstel", + "descriptionText": "Stap op Spaklerweg over voor Overamstel of op Amstel voor RAI. Details: gvb.nl/zomermetro –|Change at Spaklerweg for Overamstel or at Amstel for RAI. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "SUBWAY" + ] + }, + { + "name": "Duivendrecht, Station Duivendrecht", + "stopId": "nl-OpenOV_3979812", + "parentId": "nl-OpenOV_NL:S:30009551", + "importance": 0.018022045493125916, + "lat": 52.323895, + "lon": 4.936262000000002, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:35:00Z", + "departure": "2026-08-16T21:35:00Z", + "scheduledArrival": "2026-08-16T21:35:00Z", + "scheduledDeparture": "2026-08-16T21:35:00Z", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-09T15:42:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-09T15:42:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Neem NS-treinen naar Zuid of stap op Spaklerweg over voor Overamstel. Details: gvb.nl/zomermetro --|Take NS trains to Zuid or change at Spak", + "descriptionText": "Neem NS-treinen naar Zuid of stap op Spaklerweg over voor Overamstel. Details: gvb.nl/zomermetro --|Take NS trains to Zuid or change at Spaklerweg for Overamstel. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Van der Madeweg", + "stopId": "nl-OpenOV_3981387", + "parentId": "nl-OpenOV_NL:S:30009516", + "importance": 0.0271453857421875, + "lat": 52.329903, + "lon": 4.930333000000001, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:36:00Z", + "departure": "2026-08-16T21:37:00Z", + "scheduledArrival": "2026-08-16T21:36:00Z", + "scheduledDeparture": "2026-08-16T21:37:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Stap op Spaklerweg over voor Overamstel of op Amstel voor RAI. Details: gvb.nl/zomermetro –|Change at Spaklerweg for Overamstel or at Amstel", + "descriptionText": "Stap op Spaklerweg over voor Overamstel of op Amstel voor RAI. Details: gvb.nl/zomermetro –|Change at Spaklerweg for Overamstel or at Amstel for RAI. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Spaklerweg", + "stopId": "nl-OpenOV_3980158", + "parentId": "nl-OpenOV_NL:S:30009518", + "importance": 0.02681240625679493, + "lat": 52.340239999999994, + "lon": 4.92107, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:39:00Z", + "departure": "2026-08-16T21:39:00Z", + "scheduledArrival": "2026-08-16T21:39:00Z", + "scheduledDeparture": "2026-08-16T21:39:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "SUBWAY", + "BUS" + ] + }, + { + "name": "Amsterdam, Amstelstation", + "stopId": "nl-OpenOV_3980520", + "parentId": "nl-OpenOV_NL:S:30009514", + "importance": 0.052102621644735336, + "lat": 52.346489999999996, + "lon": 4.9175699999999996, + "level": 0.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:40:00Z", + "departure": "2026-08-16T21:41:00Z", + "scheduledArrival": "2026-08-16T21:40:00Z", + "scheduledDeparture": "2026-08-16T21:41:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "alerts": [ + { + "communicationPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "impactPeriod": [ + { + "start": "2026-07-04T04:00:00Z", + "end": "2026-08-16T23:00:00Z" + } + ], + "cause": "UNKNOWN_CAUSE", + "effect": "UNKNOWN_EFFECT", + "headerText": "Neem tram 23 voor RAI of bus 78 voor Overamstel. Details: gvb.nl/zomermetro. --|Take tram 23 for RAI or bus 78 for Overamstel. Details: gvb.nl/summermetro", + "descriptionText": "Neem tram 23 voor RAI of bus 78 voor Overamstel. Details: gvb.nl/zomermetro. --|Take tram 23 for RAI or bus 78 for Overamstel. Details: gvb.nl/summermetro" + } + ], + "modes": [ + "REGIONAL_RAIL", + "SUBWAY", + "TRAM", + "BUS" + ] + }, + { + "name": "Amsterdam, Wibautstraat", + "stopId": "nl-OpenOV_3980265", + "parentId": "nl-OpenOV_NL:S:30009512", + "importance": 0.027183683589100838, + "lat": 52.354332, + "lon": 4.9121879999999996, + "level": -2.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:42:00Z", + "departure": "2026-08-16T21:43:00Z", + "scheduledArrival": "2026-08-16T21:42:00Z", + "scheduledDeparture": "2026-08-16T21:43:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "SUBWAY", + "TRAM", + "BUS" + ] + }, + { + "name": "Amsterdam, Weesperplein", + "stopId": "nl-OpenOV_3981336", + "parentId": "nl-OpenOV_NL:S:30009510", + "importance": 0.02798701450228691, + "lat": 52.361236999999996, + "lon": 4.907945, + "level": -2.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:44:00Z", + "departure": "2026-08-16T21:45:00Z", + "scheduledArrival": "2026-08-16T21:44:00Z", + "scheduledDeparture": "2026-08-16T21:45:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "SUBWAY", + "TRAM", + "BUS" + ] + }, + { + "name": "Amsterdam, Waterlooplein", + "stopId": "nl-OpenOV_3980640", + "parentId": "nl-OpenOV_NL:S:30009508", + "importance": 0.027206193655729294, + "lat": 52.366985, + "lon": 4.9031400000000005, + "level": -2.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:46:00Z", + "departure": "2026-08-16T21:46:00Z", + "scheduledArrival": "2026-08-16T21:46:00Z", + "scheduledDeparture": "2026-08-16T21:46:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "SUBWAY", + "TRAM", + "BUS" + ] + }, + { + "name": "Amsterdam, Nieuwmarkt", + "stopId": "nl-OpenOV_3981245", + "parentId": "nl-OpenOV_NL:S:30009502", + "importance": 0.02679653838276863, + "lat": 52.371914, + "lon": 4.901427000000001, + "level": -2.0, + "tz": "Europe/Brussels", + "arrival": "2026-08-16T21:47:00Z", + "departure": "2026-08-16T21:47:00Z", + "scheduledArrival": "2026-08-16T21:47:00Z", + "scheduledDeparture": "2026-08-16T21:47:00Z", + "scheduledTrack": "2", + "track": "2", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "SUBWAY" + ] + } + ], + "legGeometry": { + "points": "wd|vbBornoHN??fGFxa@?hQOzU?jFwBhF}ApDVhFLhg@^bi@LnDM|UBno@?bIKj[?vATxl@NpNFz`@D~j@?z`@?f]Fpd@Vl[?dGLjQVdGL|Jb@hQHlE\\rMnAxVb@|Id@jFzAlZhAtLv@vLjAvL?ZbAvLf@fGfBbSjAtL~Ib~@rClZvAjPd@lEdH|t@tCf\\`C`Tr@zJd@hFdA`IpBhQxBhQjEpX|AvLpCpXjCtWbGpn@zHnz@fBdR|A`TjAbSd@tWVvLWtM[`T_@|JmAhQaBhRcClP_DjQsEbT_DzKoD|JkD~J{E|J}BnE_DfG_DjFkBtBkFfHeFhGiGhGqHjG{OzLee@t\\{PvN_SnQW?eAz@cj@xf@g@\\aAz@a@ZgAz@e@z@u@\\qHfHap@nj@u@z@kAx@O\\e@?qCrCse@bc@_SrP_IdIiXbVeJdI}OxMsAxAaRpPmBxAq@x@q@z@yIdIkGjFeOxMuJbJ}Az@kCrCg@\\sAvA{HjGgGhGO?wXbV{FlFoWfU_d@h`@s`@p]oUjSsOzMoCrCkAx@_DrCeEpDmGfHmh@|e@sAx@kAz@sAvAyAxA{p@lk@u@z@q@z@gEpDsDrCkCrCw`@r\\{JbJqk@vg@qBxAil@pi@yBvBce@db@wAz@mBvA]z@u@\\e@x@u@\\cAz@}OvNwDpDaf@db@sAxAaCtBcTnQS\\}XfUqHjG{BtBaAz@u@x@oCvBkFnEwGfHw`@p]gb@l_@wX`Wu@\\iAvAkBxAgAz@wAvAqRlRgYdVyNvN_DrC?\\K?{FnEgSrPO?cj@|e@oDnDsBxAgm@ri@yAvAuJfI{f@bc@iBtBiAz@u@\\iBtBccA~}@wL`Kcj@th@wVbW_UfT_VdVwAvAgAz@eXxYoCtCyBrCW?k@x@u@z@{FhGkBrCeSjSmGhGuNtO{L~KqLbJoCtBeKbJkK`K_}@lcAW\\cAz@cAvA}@z@sAvAsj@hm@}h@th@wIbJwFjFuWt[}O~KcP`L_DrC{@x@w@z@_DrCcAz@cAz@kAx@gAxAsEpDU\\{AvAgc@l_@iUlSce@ha@_IdImBtByFnEea@r]e@z@wAx@kM|LaCtByBtBsCvBkCtByNzMa@Z}BvBuJdIs`@r]sj@|e@kiC`~Bos@hn@sfAxaA_\\~XoUlR{PzMcVnRyLfIqBz@iCxAqGpDeU|M}bAxj@eA\\{Az@ac@pSwBvAsEvBwDrCcIrDwBxAiA\\_DxAyIrDgOjG_MvCuNpEyUvDgJvCkGxAkNhHcj@fXk_Adf@gJnFiZvOkKnFyBz@ya@pSWZ}^rRyQ|LyIjG_M~Kkl@nk@sb@fb@wVfUaP~L}P~KwMlGuOdJqVhIwSpFcWfJgLpEwLnFgOjHga@nSsFtCs{@lc@eS~LaR~KkdA`i@ia@pSihA~i@c`@nScJpEgGrDcV`Li}@df@uOhIwf@dXsWzNwDxA_MnFmQjHufBxz@oLnFea@fWuJjGiGlFmFlF_DpDmDpDqCnDeEjGeEhG_ItNsY|o@sMvYgZzp@yF|KuE|KkF|KwG|KyDhGyBrC{FfHaHfHmLbKkHnE}FtCoDxAeExAeE|@iBz@mFz@eJ|@_Nb@oG@{JsA_DYeIuAiLgE_TyI_o@aY_NcGkCuAoAy@sEoC_HiEsDqCsIaHoJyJyBoCeEiFcMgPu^kc@qL{IqM_H}S_HkKqAqMsAaNDgSxCoHxAcMtDsR~LkHfHsCpDiBtBaAvBY?sAtBqEfHcCrCuE~Ja@z@oGvM_DdI_IdTkVnt@wBqC", + "precision": 6, + "length": 430 + }, + "bikesAllowed": true, + "wheelchairAccessible": "NOT_ACCESSIBLE", + "reservation": "NONE", + "alternatives": [] + } + ] +} \ No newline at end of file diff --git a/main.py b/main.py index 53b8c97..c03a2e9 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,22 @@ import discord +import sys + from src.utils import logger from src.config import config from src.dc.handlers import rename_vc from src.dc.helpers import validate_channel from src.dc.commands import setup_commands +from src.api.transitous import check_stations bot = discord.Bot(intents=discord.Intents.all()) setup_commands(bot=bot) _bot_initialized = False +if len(sys.argv) > 1: + if sys.argv[1] == "stations": + check_stations() + sys.exit(0) + @bot.event async def on_ready(): global _bot_initialized diff --git a/src/api/transitous.py b/src/api/transitous.py index 0f26fe0..5a26c09 100644 --- a/src/api/transitous.py +++ b/src/api/transitous.py @@ -16,6 +16,77 @@ headers = { endpoint = "https://api.transitous.org" +def check_stations(): + all_stations_output = {} + minimal_overview = { + "stations": {} + } + + for station in stations: + req = f"{endpoint}/api/v1/geocode" + + try: + response = requests.get(req, params={"text": station}, headers=headers) + response.raise_for_status() + data = response.json() + except requests.RequestException as e: + logger(f"An error occured while checking for station {station}: {e}") + continue + + stops_dict = {} + aliases_list = [] + exact_match = False + + for entry in data: + if entry.get("type") != "STOP": + continue + + station_id = entry.get("id") + stop_name = entry.get("name") + coords = f"{entry.get("lat")}, {entry.get("lon")}" + modes = entry.get("modes") + + stop_details = { + "tz": entry.get("tz"), + "country": entry.get("country"), + "coords": coords, + "modes": modes, + "id": station_id, + } + + if stop_name: + stops_dict[stop_name] = stop_details + aliases_list.append(stop_name) + + if stop_name == station: + if not exact_match: + logger(f"Exact match found! {station} is an assigned station! Bot would use that station directly") + exact_match = True + + if not stops_dict: + logger("Failed to grab ID from 'search_name'", "error") + continue + + all_stations_output[station] = { + "associated": stops_dict + } + + minimal_overview["stations"][station] = aliases_list + + logger(json.dumps(minimal_overview, indent=4, ensure_ascii=False)) + + + print(f"\nIf you want, I can save a more detailed version directly as a json file to disk.") + print("The json would provide informations like coords, country and transport modes that are from every specific associated station.") + prompt = input("This would help you to identify the associated stations more accurately (y/n): ") + if prompt == "y" or prompt == "yes": + with open('stations.json', 'w') as f: + json.dump(all_stations_output, f, indent=4, ensure_ascii=False) + logger("stations.json generated") + else: + logger("okay :(") + + def get_random_stop_id() -> str | None: assigned_station = random.choice(stations) req = f"{endpoint}/api/v1/geocode" @@ -42,14 +113,15 @@ def get_random_stop_id() -> str | None: stop_ids[stop_id] = stop_name break - if stop_ids is None: logger(f"Failed to grab ID from '{assigned_station}'", "error") return None stop_ids_list = list(stop_ids.keys()) stops_string = ", ".join(stop_ids.values()) - logger(f"Availabe stations: {stops_string}") + if len(stop_ids_list) > 1: + logger(f"Station '{assigned_station}' not found, choosing random from available: {stops_string}") + logger(f"No station associated as '{assigned_station}', choosing random from available") chosen_stop_id = random.choice(stop_ids_list) return chosen_stop_id @@ -176,9 +248,6 @@ def get_trip_details(random_connection: dict | None) -> dict | None: if not valid: return None - with open('data.json', 'w') as f: - json.dump(data, f, indent=4, ensure_ascii=False) - logger(json.dumps(trip_details, indent=4, ensure_ascii=False)) return trip_details diff --git a/src/data/operators.py b/src/data/operators.py index 5a09541..4b9aaf7 100644 --- a/src/data/operators.py +++ b/src/data/operators.py @@ -102,6 +102,10 @@ OPERATORS = { "DSB": { "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/DSB_company_logo.svg/960px-DSB_company_logo.svg.png", "color": 0xB22B32 + }, + "Vr": { + "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Vr_Logo.png/330px-Vr_Logo.png", + "color": 0x00B451 } } diff --git a/src/dc/embeds.py b/src/dc/embeds.py index 495d78b..2a4860e 100644 --- a/src/dc/embeds.py +++ b/src/dc/embeds.py @@ -35,16 +35,22 @@ def build_info_embed() -> discord.Embed: stops = trip["stops"] next_stop = get_next_station(trip["stops"]) - next_stop_station = next_stop["name"] + next_stop_text = "" + next_stop_station = None + if next_stop: + print("next_stop") + next_stop_station = next_stop.get("name") + next_stop_text = f". Nächster Halt: **{next_stop_station}**" via = format_via_list(stops) if via: - via += f". Nächster Halt: **{next_stop_station}**" + via += next_stop_text embed.add_field(name="Über", value=via, inline=False) else: - embed.add_field(name="Nächster Halt", value=next_stop_station, inline=False) - + if next_stop: + embed.add_field(name="Nächster Halt", value=next_stop_station, inline=False) + route_fields = format_stop_list(stops, next_stop_station) for field_name, field_value in route_fields: embed.add_field(name=field_name, value=field_value, inline=False) diff --git a/src/utils.py b/src/utils.py index 2b48988..3614210 100644 --- a/src/utils.py +++ b/src/utils.py @@ -182,7 +182,7 @@ def format_via_list(stops: dict) -> str: return via return None -def format_stop_list(stops: dict, next_stop: str) -> list[tuple[str, str]]: +def format_stop_list(stops: dict, next_stop: str | None) -> list[tuple[str, str]]: fields = [] field_lines, field_length, part = [], 0, 1