From 1b0b10dc4587ccf696bbaefd64e77ec27a5cd95c Mon Sep 17 00:00:00 2001 From: Kaaninchen <124433727+kaaninchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:11:05 +0200 Subject: [PATCH] transitous rewrite: validate connections --- main.py | 3 +- src/api/transitous.py | 54 ++++++++++++++++-------- src/data/assets/transitous-logo.license | 2 + src/data/assets/transitous-logo.png | Bin 0 -> 1351 bytes src/dc/handlers.py | 22 +++++++--- src/utils.py | 25 ++++++++--- 6 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 src/data/assets/transitous-logo.license create mode 100644 src/data/assets/transitous-logo.png diff --git a/main.py b/main.py index e76d5c8..812affc 100644 --- a/main.py +++ b/main.py @@ -31,11 +31,10 @@ except: ''' TODO - 1024 embed limit -- automatic reload of operators - discord status - text announcements - voice announcements - improved error handling (retry connection) - multi language support -- random = False +- README ''' \ No newline at end of file diff --git a/src/api/transitous.py b/src/api/transitous.py index 68ef964..fcf67af 100644 --- a/src/api/transitous.py +++ b/src/api/transitous.py @@ -1,9 +1,9 @@ import requests import random import json -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta -from src.utils import logger, config, get_train_name, convert_iso_string +from src.utils import logger, config, get_train_name, convert_iso_string, validate_connection stations = config["stations"] blacklist = config["blacklist"] @@ -15,8 +15,9 @@ headers = { endpoint = "https://api.transitous.org" -def get_random_stop_id() -> str: +def get_random_stop_id() -> str | None: assigned_station = random.choice(stations) + logger(f"Station: {assigned_station}") req = f"{endpoint}/api/v1/geocode" try: @@ -24,18 +25,30 @@ def get_random_stop_id() -> str: response.raise_for_status() data = response.json() except requests.RequestException as e: - logger(f"An error occured while searching for a connection: {e}", "fatal") + logger(f"An error occured while searching for a connection: {e}", "error") return None if response.status_code == 404: - logger(f"Error finding station '{assigned_station}'") + logger(f"Error finding station '{assigned_station}'", "error") + return None + id = [] for entry in data: if entry.get("type") != "STOP": continue - return entry["id"] + entry_id = entry.get("id", None) + id.append(entry_id) -def get_random_connection(stop_id: str) -> str: + if id is None: + logger(f"Failed to grab ID from '{assigned_station}'", "error") + return None + + return random.choice(id) + +def get_random_connection(stop_id: str) -> str | None: + if stop_id is None: + return None + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") print(f"Aktuelle Zeit für Query: {now}") cursor = None @@ -58,15 +71,13 @@ def get_random_connection(stop_id: str) -> str: response.raise_for_status() data = response.json() except requests.RequestException as e: - print(e) + logger(e, "Error") break stop_times = data.get("stopTimes", []) all_stop_times.extend(stop_times) for entry in stop_times: - dep = entry.get("place", {}).get("departure") or entry.get("place", {}).get("arrival") - print(f"{entry.get('mode')}: {convert_iso_string(dep)}") trip_id = entry["tripId"] if entry["mode"] in blacklist: continue @@ -80,7 +91,7 @@ def get_random_connection(stop_id: str) -> str: break if not trip_ids: - logger("Couldn't find any connection", "fatal") + logger("Couldn't find any connection", "error") return None trip_id = random.choice(trip_ids) @@ -96,28 +107,36 @@ def get_random_connection(stop_id: str) -> str: "from_station": from_station } -def get_trip_details(trip_id: str, from_station: str) -> dict: +def get_trip_details(random_connection: dict | None) -> dict | None: + if random_connection is None: + return None + req = f"{endpoint}/api/v2/trip" try: - response = requests.get(req, params={"tripId": trip_id}, headers=headers) + response = requests.get(req, params={"tripId": random_connection["trip_id"]}, headers=headers) response.raise_for_status() data = response.json() except requests.RequestException as e: - logger(f"An error occured while trying to get the route details: {e}", "fatal") + logger(f"An error occured while trying to get the route details: {e}", "error") return None legs = data["legs"][0] - + end_time = legs["endTime"] + from_station = random_connection["from_station"] display_name = legs["displayName"] trip_from = legs["tripFrom"]["name"] goes_to = legs["tripTo"]["name"] start_time = legs["startTime"] - end_time = legs["endTime"] mode = legs["mode"] - departure = convert_iso_string(start_time) + is_valid = validate_connection(start_time, end_time) + if not is_valid: + return None + arrival = convert_iso_string(end_time) + departure = convert_iso_string(start_time) + train_name = get_train_name(display_name, mode) trip_details = { @@ -143,5 +162,6 @@ def get_trip_details(trip_id: str, from_station: str) -> dict: trip_details["departure"] = convert_iso_string(departure_time) trip_details["stops"][goes_to] = arrival + logger(json.dumps(trip_details, indent=4, ensure_ascii=False)) return trip_details diff --git a/src/data/assets/transitous-logo.license b/src/data/assets/transitous-logo.license new file mode 100644 index 0000000..4b5c5bb --- /dev/null +++ b/src/data/assets/transitous-logo.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2024 Mathis Brüchert +SPDX-License-Identifier: CC-BY-SA-4.0 \ No newline at end of file diff --git a/src/data/assets/transitous-logo.png b/src/data/assets/transitous-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0a351c549c68f4c1ccaee8c320b7c3eca1e2077a GIT binary patch literal 1351 zcmV-N1-SZ&P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$@u}MThR9Fe^SA9$zMHGLtdv^=h z`@ZrSL_F#zglI0LX&XcJG!hlHL~KH8YOJ;zP1Rsx8vc+*qp4C2QAx2)Q%N?-) zg&n(s-tIUUH>T3?KZ<#IIxjMV?GHWC!*Ps`BmKx|?F%8cRs+b~S!c8u z?7fsp8a(5qoqGk16j-v00Esc36Y6wP?Q;kgVmMr+I#z; zbubAGj?8}`{7T4p5T@?F`qlP;wh|!NaI~a+|FAjr86YHp5Ujm-`0{>&VIQ~2s0dh{ z^JPn}5h7eZR|1v=Gq%UP1spU-aqxOY6ErHdc$ZWv{}jthsr-W8^6ALrxI8Eave zLC~ve*Th&-cPXu9GSt((Q9LAD;X=9uC$BxI>djKfSy}w&fx=v7I%fDkYKC>F1j5=% z5OeP>Mn^DW&Z(rBNf9jpJvG=e(U{BpiUP>lIA)`sROdv2-EEy&MMqAiztYmy74i>{ zLe1e;__8JSIZz-sW`-b_Jw1zZt^|ofUP^DxnzC?tRDoZ8P`_6Ghvui27NxQBe{Vx)15OK)oA~-|uH70Efe&^!N8G4Gj(d zNdSm86A-C$DZ5MRCUK^|AvYVHPxRLcLy3oDS=G+HUCw3SKmkAV6SD zc3UhK#pQBoa?XO+l!26#6e7zqM#{}st7{Hfc-Z#mtmJ0alE zcAG65v)xZOAWzP+R8s)lf-Bt;T0apgNgI?q2;KUb1>+xhp8u;!Kp1)-Hdnk{JSA1{>5L64DGtHvg^DzX9-OxF=8G-+=%C002ov JPDHLkV1j5QUF84( literal 0 HcmV?d00001 diff --git a/src/dc/handlers.py b/src/dc/handlers.py index c7f0b53..4ce76c8 100644 --- a/src/dc/handlers.py +++ b/src/dc/handlers.py @@ -11,24 +11,36 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals if not from_scheduler and _scheduled_task and not _scheduled_task.done(): _scheduled_task.cancel() + attempt = 0 + max_attempt = 10 trip = choose_connection() + while trip is None and attempt < max_attempt: + attempt += 1 + logger(f"Attempt {attempt}: Failed to select route, retrying...", "error") + trip = choose_connection() + + if trip is None: + logger(f"Failed to select route after {max_attempt} attempts", "fatal") + return False + arrival = trip["arrival"] long_name = trip["long_name"] + mode = trip["mode"] print("-----------------") logger(f"Umstieg: {long_name}, Ankunft: {arrival} Uhr") - logger(f"Betreiber: {trip["agency"]}, Typ: {trip["mode"]}") + logger(f"Betreiber: {trip["agency"]}, Typ: {mode}") logger(f"Versuche Namen zu ändern, wenn nichts passiert bin ich im cooldown... (warte bis zu 10min!)") - formatting = channel_formatting(trip["mode"]) + formatting = channel_formatting(mode) await voice_channel.edit(name=f"{formatting}{long_name}") await voice_channel.set_status(f"Ankunft um {arrival}") logger(f"Name geändert!") - _scheduled_task = asyncio.create_task(_schedule_next_transfer(bot, arrival)) + _scheduled_task = asyncio.create_task(_schedule_next_transfer(bot, arrival, voice_channel)) -async def _schedule_next_transfer(bot, arrival): +async def _schedule_next_transfer(bot, arrival, voice_channel): now = datetime.now() parsed_time = datetime.strptime(arrival, "%H:%M").time() arrival_dt = datetime.combine(date.today(), parsed_time) @@ -44,5 +56,5 @@ async def _schedule_next_transfer(bot, arrival): await asyncio.sleep(wait_seconds) logger("Zug angekommen, wähle neue Verbindung") - await rename_vc(bot, from_scheduler=True) + await rename_vc(bot, voice_channel, from_scheduler=True) diff --git a/src/utils.py b/src/utils.py index 3ea006f..34f6265 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,7 +1,7 @@ import json import os import importlib -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from zoneinfo import ZoneInfo import src.data.operators as operators @@ -19,14 +19,30 @@ def logger(msg, log_type="info") -> str: if status == "FATAL": os._exit(1) -def choose_connection() -> dict: +def choose_connection() -> dict | None: from src.api import transitous station_id = transitous.get_random_stop_id() connection = transitous.get_random_connection(station_id) - trip = transitous.get_trip_details(connection["trip_id"], connection["from_station"]) + trip = transitous.get_trip_details(connection) return trip +def validate_connection(start_time: str, end_time: str) -> bool: + now = datetime.now(timezone.utc) + start_dt = datetime.fromisoformat(start_time.replace("Z", "+00:00")) + end_dt = datetime.fromisoformat(end_time.replace("Z", "+00:00")) + max_wait_time = config.get("max_wait_time", 6) + + if end_dt < now: + logger(f"Verbindung liegt bereits in der Vergangenheit: {start_dt}", "error") + return False + + if start_dt > now + timedelta(hours=max_wait_time): + logger(f"Verbindung liegt zu weit in der Zukunft: {start_dt}", "error") + return False + + return True + def convert_iso_string(isostring) -> str: timezone = config.get("timezone", "Europe/Berlin") dt = datetime.fromisoformat(isostring.replace('Z', '+00:00')) @@ -34,7 +50,6 @@ def convert_iso_string(isostring) -> str: if dt.second >= 30: dt += timedelta(minutes=1) - return dt.strftime('%H:%M') def channel_formatting(mode: str) -> str: @@ -75,7 +90,7 @@ def _reload_operators_if_changed(): def get_operator_metadata(agency: str, route_color: str) -> dict: _reload_operators_if_changed() - + op_data = operators.OPERATOR_ALIASES.get(agency) or operators.OPERATORS.get(agency) or operators.OPERATORS["fallback"] logo = op_data.get("logo", operators.OPERATORS["fallback"]["logo"])