diff --git a/main.py b/main.py index 1f006b6..82acaf6 100644 --- a/main.py +++ b/main.py @@ -2,8 +2,10 @@ import discord from src.utils import config, logger from src.dc.handlers import rename_vc from src.dc.helpers import validate_channel +from src.dc.commands import setup_commands bot = discord.Bot(intents=discord.Intents.all()) +setup_commands(bot=bot) @bot.event async def on_ready(): @@ -17,4 +19,16 @@ async def on_ready(): try: bot.run(config["token"]) except: - logger("Feher peim parsen des tokens", "fatal") \ No newline at end of file + logger("Feher peim parsen des tokens", "fatal") + +''' +TODO +- Only choose connections in the future +- discord reconnection handling +- Automatic transfer +- discord status +- text announcements +- voice announcements +- improved error handling (retry connection) +- Footer Notice Slogangs +''' \ No newline at end of file diff --git a/src/api/transitous.py b/src/api/transitous.py index 888e702..813b791 100644 --- a/src/api/transitous.py +++ b/src/api/transitous.py @@ -1,7 +1,8 @@ import requests import random +from datetime import datetime -from src.utils import logger, config, get_train_name +from src.utils import logger, config, get_train_name, convert_iso_string stations = config["stations"] blacklist = config["blacklist"] @@ -14,8 +15,8 @@ headers = { endpoint = "https://api.transitous.org" def get_random_stop_id() -> str: - stop = random.choice(stations) - req = f"{endpoint}/api/v1/geocode?text={stop}" + assigned_station = random.choice(stations) + req = f"{endpoint}/api/v1/geocode?text={assigned_station}" try: response = requests.get(req, headers=headers) @@ -26,7 +27,7 @@ def get_random_stop_id() -> str: return None if response.status_code == 404: - logger(f"Error finding station '{stop}'") + logger(f"Error finding station '{assigned_station}'") for entry in data: if entry.get("type") != "STOP": @@ -54,7 +55,8 @@ def get_random_connection(stop_id: str) -> str: return None trip_ids = [] - for entry in data.get("stopTimes", []): + stop_times = data.get("stopTimes", []) + for entry in stop_times: trip_id = entry["tripId"] if entry["mode"] in blacklist: continue @@ -71,9 +73,14 @@ def get_random_connection(stop_id: str) -> str: logger("Couldn't find any connection", "fatal") return None - return random.choice(trip_ids) + trip_id = random.choice(trip_id) + from_station = stop_times[0]["place"]["name"] + return { + "trip_id": random.choice(trip_ids), + "from_station": from_station + } -def get_trip_details(trip_id: str) -> dict: +def get_trip_details(trip_id: str, from_station: str) -> dict: req = f"{endpoint}/api/v2/trip?tripId={trip_id}" try: @@ -88,31 +95,37 @@ def get_trip_details(trip_id: str) -> dict: display_name = legs["displayName"] trip_from = legs["tripFrom"]["name"] - trip_to = legs["tripTo"]["name"] + goes_to = legs["tripTo"]["name"] start_time = legs["startTime"] end_time = legs["endTime"] mode = legs["mode"] + + departure = convert_iso_string(start_time) + arrival = convert_iso_string(end_time) train_name = get_train_name(display_name, mode) - trip_details = { - "long_name": f"{train_name} nach {trip_to} von {trip_from}", + "long_name": f"{train_name} nach {goes_to} von {from_station}", "short_name": display_name, - "from": trip_from, - "to": trip_to, + "from": from_station, + "to": goes_to, "agency": legs["agencyName"], "route_color": legs.get("routeColor"), "duration": legs["duration"], - "start_time": start_time, - "end_time": end_time, + "departure": departure, + "arrival": arrival, "mode": mode, "stops": {} } - trip_details["stops"][trip_from] = start_time + trip_details["stops"][trip_from] = departure for stop in legs["intermediateStops"]: - trip_details["stops"][stop["name"]] = stop["arrival"] - trip_details["stops"][trip_to] = end_time + arrival = convert_iso_string(stop["arrival"]) + trip_details["stops"][stop["name"]] = arrival + if stop.get("name") == from_station: + departure_time = stop["departure"] + trip_details["departure"] = convert_iso_string(departure_time) + trip_details["stops"][goes_to] = arrival return trip_details diff --git a/src/dc/commands.py b/src/dc/commands.py new file mode 100644 index 0000000..339e466 --- /dev/null +++ b/src/dc/commands.py @@ -0,0 +1,7 @@ +import discord +from src.dc.embeds import build_info_embed + +def setup_commands(bot: discord.Bot): + @bot.slash_command(description="Informationen über die aktuelle Fahrt") + async def info(ctx): + await ctx.respond(embed=build_info_embed()) \ No newline at end of file diff --git a/src/dc/embeds.py b/src/dc/embeds.py new file mode 100644 index 0000000..8b1973f --- /dev/null +++ b/src/dc/embeds.py @@ -0,0 +1,48 @@ +import discord +import random + +from src.utils import convert_iso_string, get_operator_metadata +from src.dc.helpers import format_timestamp_to_dc + +def build_embed_footer(mode: str, slogans): + footer_notice = f"Data provided by https://transitous.org • Typ: {mode}" + icon = "https://avatars.githubusercontent.com/u/24960008?s=60&v=4" + + if slogans is not None: + footer_text = f"{random.choice(slogans)} • {footer_notice}" + else: + footer_text = footer_notice + + return { + "text": footer_text, + "icon": icon + } + +def build_info_embed() -> discord.Embed: + from src.dc.handlers import trip + + agency = trip["agency"] + metadata = get_operator_metadata(agency, trip["route_color"]) + departure = format_timestamp_to_dc(trip["departure"]) + arrival = format_timestamp_to_dc(trip["arrival"]) + embed = discord.Embed( + title = trip["long_name"], + description=f"Abfahrt von {trip["from"]} um {departure}. Ankunft um {arrival}", + color = metadata["color"] + ) + + route_lines = [] + for stop_name, stop_arrival in trip["stops"].items(): + if stop_name == trip["from"]: + route_lines.append(f"**• {stop_name} ({stop_arrival} Uhr)**") + else: + route_lines.append(f"• {stop_name} ({stop_arrival} Uhr)") + embed.add_field(name="Route", value="\n".join(route_lines)) + + footer = build_embed_footer(trip["mode"], metadata["slogans"]) + embed.set_footer(text=footer["text"], icon_url=footer["icon"]) + + embed.set_author(name=agency) + embed.set_thumbnail(url=metadata["logo"]) + + return embed \ No newline at end of file diff --git a/src/dc/handlers.py b/src/dc/handlers.py index b22e348..6599691 100644 --- a/src/dc/handlers.py +++ b/src/dc/handlers.py @@ -8,14 +8,15 @@ from src.utils import logger, channel_formatting _scheduled_task: asyncio.Task | None = None async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = False): + global trip if not from_scheduler and _scheduled_task and not _scheduled_task.done(): _scheduled_task.cancel() station_id = transitous.get_random_stop_id() - trip_id = transitous.get_random_connection(station_id) - trip = transitous.get_trip_details(trip_id) + connection = transitous.get_random_connection(station_id) + trip = transitous.get_trip_details(connection["trip_id"], connection["from_station"]) - arrival = datetime.fromisoformat(trip["end_time"]) + arrival = trip["arrival"] long_name = trip["long_name"] print("-----------------") @@ -25,7 +26,7 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals formatting = channel_formatting(trip["mode"]) await voice_channel.edit(name=f"{formatting}{long_name}") - await voice_channel.set_status(f"Ankunft um {arrival.strftime('%H:%M')}") + await voice_channel.set_status(f"Ankunft um {arrival}") logger(f"Name geändert!") diff --git a/src/dc/helpers.py b/src/dc/helpers.py index c364db3..cf1c7a8 100644 --- a/src/dc/helpers.py +++ b/src/dc/helpers.py @@ -1,5 +1,6 @@ import discord -from src.utils import logger +from datetime import datetime, timedelta +from src.utils import logger, convert_iso_string def validate_channel(bot: discord.bot, server_id: int, channel_id: int): guild = bot.get_guild(server_id) @@ -12,4 +13,19 @@ def validate_channel(bot: discord.bot, server_id: int, channel_id: int): logger(f"Es konnte kein VC mit der id {channel_id} gefunden werden", "fatal") return False - return channel \ No newline at end of file + return channel + +def format_timestamp_to_dc(timestr): + parsed_time = datetime.strptime(timestr, "%H:%M") + now = datetime.now() + final_datetime = datetime.now().replace( + hour=parsed_time.hour, + minute=parsed_time.minute, + second=0, + microsecond=0 + ) + + if final_datetime <= now: + final_datetime += timedelta(days=1) + + return discord.utils.format_dt(final_datetime, style="t") \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index a714a64..13f2832 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,8 +2,10 @@ import json import os from datetime import datetime +import src.data.operators as operators from src.data.emojis import emoji_list + with open("config.json", "r") as file: config = json.load(file) @@ -14,6 +16,10 @@ def logger(msg, log_type="info") -> str: if status == "FATAL": os._exit(1) +def convert_iso_string(isostring) -> str: + datetime_isostring = datetime.fromisoformat(isostring) + return datetime_isostring.strftime('%H:%M') + def channel_formatting(mode: str) -> str: formatting = config.get("formatting", "") @@ -32,4 +38,26 @@ def get_train_name(train_name: str, mode: str) -> str: else: train = train_name - return train \ No newline at end of file + return train + +def get_operator_metadata(agency: str, route_color: str) -> dict: + 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"]) + slogans = op_data.get("slogans") + + color = op_data.get("color") + if color is None: + if route_color is not None: + try: + color = int(f"0x{route_color.upper()}") + except ValueError: + color = operators.OPERATORS["fallback"]["color"] + else: + color = operators.OPERATORS["fallback"]["colors"] + + return { + "logo": logo, + "color": color, + "slogans": slogans + } \ No newline at end of file