Compare commits

...
2 Commits
6 changed files with 123 additions and 54 deletions
+14 -7
View File
@@ -6,15 +6,22 @@ from src.dc.commands import setup_commands
bot = discord.Bot(intents=discord.Intents.all()) bot = discord.Bot(intents=discord.Intents.all())
setup_commands(bot=bot) setup_commands(bot=bot)
_bot_initialized = False
@bot.event @bot.event
async def on_ready(): async def on_ready():
server_id = config["server"] global _bot_initialized
server_vc_id = config["vc"]
channel = validate_channel(bot=bot, server_id=server_id, channel_id=server_vc_id)
logger(f"{bot.user} ist online") logger(f"{bot.user} ist online")
await rename_vc(bot=bot, voice_channel=channel)
if not _bot_initialized:
_bot_initialized = True
server_id = config["server"]
server_vc_id = config["vc"]
channel = validate_channel(bot=bot, server_id=server_id, channel_id=server_vc_id)
await rename_vc(bot, voice_channel=channel)
else:
logger("Reconnected to discord gateway, this wont disturb your current ride")
try: try:
bot.run(config["token"]) bot.run(config["token"])
@@ -23,12 +30,12 @@ except:
''' '''
TODO TODO
- Only choose connections in the future - 1024 embed limit
- discord reconnection handling - automatic reload of operators
- Automatic transfer
- discord status - discord status
- text announcements - text announcements
- voice announcements - voice announcements
- improved error handling (retry connection) - improved error handling (retry connection)
- multi language support - multi language support
- random = False
''' '''
+41 -30
View File
@@ -1,6 +1,7 @@
import requests import requests
import random import random
import json import json
from datetime import datetime, timezone
from src.utils import logger, config, get_train_name, convert_iso_string from src.utils import logger, config, get_train_name, convert_iso_string
@@ -16,10 +17,10 @@ endpoint = "https://api.transitous.org"
def get_random_stop_id() -> str: def get_random_stop_id() -> str:
assigned_station = random.choice(stations) assigned_station = random.choice(stations)
req = f"{endpoint}/api/v1/geocode?text={assigned_station}" req = f"{endpoint}/api/v1/geocode"
try: try:
response = requests.get(req, headers=headers) response = requests.get(req, params={"text": assigned_station}, headers=headers)
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
except requests.RequestException as e: except requests.RequestException as e:
@@ -35,61 +36,71 @@ def get_random_stop_id() -> str:
return entry["id"] return entry["id"]
def get_random_connection(stop_id: str) -> str: def get_random_connection(stop_id: str) -> str:
max_pages = 5 now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
print(f"Aktuelle Zeit für Query: {now}")
cursor = None cursor = None
count = 20 max_pages = 5
min_results = 5
trip_ids = []
all_stop_times = []
for _ in range(max_pages): for _ in range(max_pages):
params = f"stopId={stop_id}&n={count}" params = {
"stopId": stop_id,
"n": 20,
"time": now,
}
if cursor: if cursor:
params += f"&pageCursor={cursor}" params["pageCursor"] = cursor
req = f"{endpoint}/api/v1/stoptimes?{params}" try:
response = requests.get(f"{endpoint}/api/v5/stoptimes", params=params, headers=headers)
response.raise_for_status()
data = response.json()
except requests.RequestException as e:
print(e)
break
try: stop_times = data.get("stopTimes", [])
response = requests.get(req, headers=headers) all_stop_times.extend(stop_times)
response.raise_for_status()
data = response.json()
except requests.RequestException as e:
print(e)
return None
trip_ids = [] for entry in stop_times:
stop_times = data.get("stopTimes", []) dep = entry.get("place", {}).get("departure") or entry.get("place", {}).get("arrival")
for entry in stop_times: print(f"{entry.get('mode')}: {convert_iso_string(dep)}")
trip_id = entry["tripId"] trip_id = entry["tripId"]
if entry["mode"] in blacklist: if entry["mode"] in blacklist:
continue continue
trip_ids.append(trip_id) trip_ids.append(entry["tripId"])
if len(trip_ids) >= 5: if len(trip_ids) >= min_results:
break break
cursor = data.get("nextPageCursor") cursor = data.get("nextPageCursor")
if not cursor: if not cursor:
break break
if not trip_ids: if not trip_ids:
logger("Couldn't find any connection", "fatal") logger("Couldn't find any connection", "fatal")
return None return None
trip_id = random.choice(trip_ids) trip_id = random.choice(trip_ids)
for trip in stop_times:
if trip.get("tripId") == trip_id: from_station = None
from_station = trip.get("place").get("name") for entry in all_stop_times:
if entry.get("tripId") == trip_id:
from_station = entry.get("place", {}).get("name")
break break
print(from_station)
return { return {
"trip_id": trip_id, "trip_id": trip_id,
"from_station": from_station "from_station": from_station
} }
def get_trip_details(trip_id: str, from_station: str) -> dict: def get_trip_details(trip_id: str, from_station: str) -> dict:
req = f"{endpoint}/api/v2/trip?tripId={trip_id}" req = f"{endpoint}/api/v2/trip"
try: try:
response = requests.get(req, headers=headers) response = requests.get(req, params={"tripId": trip_id}, headers=headers)
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
except requests.RequestException as e: except requests.RequestException as e:
+1
View File
@@ -1,6 +1,7 @@
emoji_list = { emoji_list = {
"Fallback": "💺", "Fallback": "💺",
"BUS": "🚎", "BUS": "🚎",
"COACH": "🚎",
"TRAM": "🚈", "TRAM": "🚈",
"REGIONAL_RAIL": "🚊", "REGIONAL_RAIL": "🚊",
"HIGHSPEED_RAIL": "🚅", "HIGHSPEED_RAIL": "🚅",
+4 -2
View File
@@ -38,7 +38,7 @@ OPERATORS = {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/ODEG-Logo_Neu.svg/960px-ODEG-Logo_Neu.svg.png", "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/ODEG-Logo_Neu.svg/960px-ODEG-Logo_Neu.svg.png",
"color": 0x00745C "color": 0x00745C
}, },
"Nederlandse Spoorwegen": { "NS": {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/Logo_NS.svg/960px-Logo_NS.svg.png", "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/Logo_NS.svg/960px-Logo_NS.svg.png",
"color": 0X00337F, "color": 0X00337F,
"slogan": ["Goed op weg", "Welkom in de trein van morgen", "Veilig, Vlug, Voordelig", "we haben een serious probleem", "Neuken in de keuken"] "slogan": ["Goed op weg", "Welkom in de trein van morgen", "Veilig, Vlug, Voordelig", "we haben een serious probleem", "Neuken in de keuken"]
@@ -109,10 +109,12 @@ OPERATOR_ALIASES = {
"DB Regio AG Bayern": OPERATORS["db_bayern"], "DB Regio AG Bayern": OPERATORS["db_bayern"],
"DB Fernverkehr AG": OPERATORS["db_allgemein"], "DB Fernverkehr AG": OPERATORS["db_allgemein"],
"DB Regio AG NRW": OPERATORS["db_allgemein"], "DB Regio AG NRW": OPERATORS["db_allgemein"],
"DB Regio AG Nord": OPERATORS["db_allgemein"],
"DB Regio AG Südost": OPERATORS["db_allgemein"], "DB Regio AG Südost": OPERATORS["db_allgemein"],
"DB Regio AG Nordost": OPERATORS["db_allgemein"], "DB Regio AG Nordost": OPERATORS["db_allgemein"],
"DB Regio AG Mitte": OPERATORS["db_allgemein"], "DB Regio AG Mitte": OPERATORS["db_allgemein"],
"SBB GmbH": OPERATORS["SBB"], "SBB GmbH": OPERATORS["SBB"],
"Schweizerische Bundesbahnen SBB": OPERATORS["SBB"], "Schweizerische Bundesbahnen SBB": OPERATORS["SBB"],
} }
+25 -9
View File
@@ -1,26 +1,22 @@
import discord import discord
import asyncio import asyncio
from datetime import datetime from datetime import datetime, timedelta, date
from src.api import transitous from src.utils import logger, channel_formatting, choose_connection
from src.utils import logger, channel_formatting
_scheduled_task: asyncio.Task | None = None _scheduled_task: asyncio.Task | None = None
async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = False): async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = False):
global trip global trip, _scheduled_task
if not from_scheduler and _scheduled_task and not _scheduled_task.done(): if not from_scheduler and _scheduled_task and not _scheduled_task.done():
_scheduled_task.cancel() _scheduled_task.cancel()
station_id = transitous.get_random_stop_id() trip = choose_connection()
connection = transitous.get_random_connection(station_id)
trip = transitous.get_trip_details(connection["trip_id"], connection["from_station"])
arrival = trip["arrival"] arrival = trip["arrival"]
long_name = trip["long_name"] long_name = trip["long_name"]
print("-----------------") print("-----------------")
logger(f"Umstieg: {long_name}, Ankunft: {arrival}") logger(f"Umstieg: {long_name}, Ankunft: {arrival} Uhr")
logger(f"Betreiber: {trip["agency"]}, Typ: {trip["mode"]}") logger(f"Betreiber: {trip["agency"]}, Typ: {trip["mode"]}")
logger(f"Versuche Namen zu ändern, wenn nichts passiert bin ich im cooldown... (warte bis zu 10min!)") logger(f"Versuche Namen zu ändern, wenn nichts passiert bin ich im cooldown... (warte bis zu 10min!)")
@@ -30,3 +26,23 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals
logger(f"Name geändert!") logger(f"Name geändert!")
_scheduled_task = asyncio.create_task(_schedule_next_transfer(bot, arrival))
async def _schedule_next_transfer(bot, arrival):
now = datetime.now()
parsed_time = datetime.strptime(arrival, "%H:%M").time()
arrival_dt = datetime.combine(date.today(), parsed_time)
if arrival_dt < now:
arrival_dt += timedelta(days=1)
wait_seconds = (arrival_dt - now).total_seconds()
if wait_seconds > 0:
remaining = str(timedelta(seconds=wait_seconds))
logger(f"Nächster Umstieg in {remaining.split('.')[0]} ({arrival} Uhr)")
await asyncio.sleep(wait_seconds)
logger("Zug angekommen, wähle neue Verbindung")
await rename_vc(bot, from_scheduler=True)
+32
View File
@@ -1,10 +1,13 @@
import json import json
import os import os
import importlib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import src.data.operators as operators import src.data.operators as operators
from src.data.emojis import emoji_list from src.data.emojis import emoji_list
_operator_mtime = None
with open("config.json", "r") as file: with open("config.json", "r") as file:
config = json.load(file) config = json.load(file)
@@ -16,8 +19,19 @@ def logger(msg, log_type="info") -> str:
if status == "FATAL": if status == "FATAL":
os._exit(1) os._exit(1)
def choose_connection() -> dict:
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"])
return trip
def convert_iso_string(isostring) -> str: def convert_iso_string(isostring) -> str:
timezone = config.get("timezone", "Europe/Berlin")
dt = datetime.fromisoformat(isostring.replace('Z', '+00:00')) dt = datetime.fromisoformat(isostring.replace('Z', '+00:00'))
dt = dt.astimezone(ZoneInfo(timezone))
if dt.second >= 30: if dt.second >= 30:
dt += timedelta(minutes=1) dt += timedelta(minutes=1)
@@ -43,7 +57,25 @@ def get_train_name(train_name: str, mode: str) -> str:
return train return train
def _reload_operators_if_changed():
global _operator_mtime
path = operators.__file__
current_mtime = os.path.getmtime(path)
if _operator_mtime is None:
_operator_mtime = current_mtime
return
if current_mtime != _operator_mtime:
importlib.reload(operators)
_operator_mtime = current_mtime
logger("operators.py wurde automatisch neu geladen (Änderungen erkannt)")
def get_operator_metadata(agency: str, route_color: str) -> dict: 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"] 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"]) logo = op_data.get("logo", operators.OPERATORS["fallback"]["logo"])