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 0000000..0a351c5 Binary files /dev/null and b/src/data/assets/transitous-logo.png differ 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"])