mirror of
https://github.com/kaaninchen/Gleiswechsel.git
synced 2026-09-17 16:52:47 +00:00
Compare commits
9
Commits
dbf
...
53358c08b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53358c08b1 | ||
|
|
46fe9eb61e | ||
|
|
697a0f6218 | ||
|
|
cf696df5f5 | ||
|
|
f2a686edca | ||
|
|
8770d03bb6 | ||
|
|
826b543347 | ||
|
|
adfa0a0440 | ||
|
|
5f6c3a223b |
@@ -3,3 +3,5 @@ __pycache__/
|
|||||||
src/data/announcements/*
|
src/data/announcements/*
|
||||||
!src/data/announcements/.gitkeep
|
!src/data/announcements/.gitkeep
|
||||||
config.json
|
config.json
|
||||||
|
src/legacy/
|
||||||
|
main_legacy.py
|
||||||
@@ -1,40 +1,34 @@
|
|||||||
import discord
|
import discord
|
||||||
import random
|
from src.utils import config, logger
|
||||||
from discord.ext import tasks
|
from src.dc.handlers import rename_vc
|
||||||
from src.config import config
|
from src.dc.helpers import validate_channel
|
||||||
from src.handlers import rename_vc
|
from src.dc.commands import setup_commands
|
||||||
from src.commands import setup_commands
|
|
||||||
from src.embeds import build_error_embed
|
|
||||||
from src.utils import logger
|
|
||||||
from src.data.status import discord_status
|
|
||||||
|
|
||||||
bot = discord.Bot(intents=discord.Intents.all())
|
bot = discord.Bot(intents=discord.Intents.all())
|
||||||
setup_commands(bot)
|
setup_commands(bot=bot)
|
||||||
|
|
||||||
@tasks.loop(minutes=5)
|
|
||||||
async def change_status():
|
|
||||||
status = random.choice(discord_status)
|
|
||||||
await bot.change_presence(activity=discord.Game(name=f"{status} • /info"))
|
|
||||||
|
|
||||||
_bot_initialized = False
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
global _bot_initialized
|
server_id = config["server"]
|
||||||
print(f"{bot.user} ist online")
|
server_vc_id = config["vc"]
|
||||||
if not change_status.is_running():
|
channel = validate_channel(bot=bot, server_id=server_id, channel_id=server_vc_id)
|
||||||
change_status.start()
|
|
||||||
if not _bot_initialized:
|
|
||||||
_bot_initialized = True
|
|
||||||
await rename_vc(bot)
|
|
||||||
else:
|
|
||||||
logger("Discord Reconnect, laufende Fahrt bleibt unangetastet")
|
|
||||||
|
|
||||||
@bot.event
|
logger(f"{bot.user} ist online")
|
||||||
async def on_application_command_error(ctx, error):
|
await rename_vc(bot=bot, voice_channel=channel)
|
||||||
embed = build_error_embed(f"Ein Fehler ist aufgetreten: {error}")
|
|
||||||
await ctx.respond(embed=embed)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bot.run(config['token'])
|
bot.run(config["token"])
|
||||||
except:
|
except:
|
||||||
logger("Fehler beim parsen des token", "fatal")
|
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)
|
||||||
|
- multi language support
|
||||||
|
'''
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import requests
|
||||||
|
import random
|
||||||
|
import json
|
||||||
|
|
||||||
|
from src.utils import logger, config, get_train_name, convert_iso_string
|
||||||
|
|
||||||
|
stations = config["stations"]
|
||||||
|
blacklist = config["blacklist"]
|
||||||
|
user_agent = config["http"]["user_agent"]
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"User-Agent": f"{user_agent}"
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = "https://api.transitous.org"
|
||||||
|
|
||||||
|
def get_random_stop_id() -> str:
|
||||||
|
assigned_station = random.choice(stations)
|
||||||
|
req = f"{endpoint}/api/v1/geocode?text={assigned_station}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(req, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger(f"An error occured while searching for a connection: {e}", "fatal")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if response.status_code == 404:
|
||||||
|
logger(f"Error finding station '{assigned_station}'")
|
||||||
|
|
||||||
|
for entry in data:
|
||||||
|
if entry.get("type") != "STOP":
|
||||||
|
continue
|
||||||
|
return entry["id"]
|
||||||
|
|
||||||
|
def get_random_connection(stop_id: str) -> str:
|
||||||
|
max_pages = 5
|
||||||
|
cursor = None
|
||||||
|
count = 20
|
||||||
|
|
||||||
|
for _ in range(max_pages):
|
||||||
|
params = f"stopId={stop_id}&n={count}"
|
||||||
|
if cursor:
|
||||||
|
params += f"&pageCursor={cursor}"
|
||||||
|
|
||||||
|
req = f"{endpoint}/api/v1/stoptimes?{params}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(req, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
trip_ids = []
|
||||||
|
stop_times = data.get("stopTimes", [])
|
||||||
|
for entry in stop_times:
|
||||||
|
trip_id = entry["tripId"]
|
||||||
|
if entry["mode"] in blacklist:
|
||||||
|
continue
|
||||||
|
trip_ids.append(trip_id)
|
||||||
|
|
||||||
|
if len(trip_ids) >= 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
cursor = data.get("nextPageCursor")
|
||||||
|
if not cursor:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not trip_ids:
|
||||||
|
logger("Couldn't find any connection", "fatal")
|
||||||
|
return None
|
||||||
|
|
||||||
|
trip_id = random.choice(trip_ids)
|
||||||
|
for trip in stop_times:
|
||||||
|
if trip.get("tripId") == trip_id:
|
||||||
|
from_station = trip.get("place").get("name")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(from_station)
|
||||||
|
return {
|
||||||
|
"trip_id": trip_id,
|
||||||
|
"from_station": from_station
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_trip_details(trip_id: str, from_station: str) -> dict:
|
||||||
|
req = f"{endpoint}/api/v2/trip?tripId={trip_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(req, 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")
|
||||||
|
return None
|
||||||
|
|
||||||
|
legs = data["legs"][0]
|
||||||
|
|
||||||
|
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)
|
||||||
|
arrival = convert_iso_string(end_time)
|
||||||
|
train_name = get_train_name(display_name, mode)
|
||||||
|
|
||||||
|
trip_details = {
|
||||||
|
"long_name": f"{train_name} nach {goes_to} von {from_station}",
|
||||||
|
"short_name": display_name,
|
||||||
|
"from": from_station,
|
||||||
|
"to": goes_to,
|
||||||
|
"agency": legs["agencyName"],
|
||||||
|
"route_color": legs.get("routeColor"),
|
||||||
|
"duration": legs["duration"],
|
||||||
|
"departure": departure,
|
||||||
|
"arrival": arrival,
|
||||||
|
"mode": mode,
|
||||||
|
"stops": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
trip_details["stops"][trip_from] = departure
|
||||||
|
for stop in legs["intermediateStops"]:
|
||||||
|
stop_arrival = convert_iso_string(stop["arrival"])
|
||||||
|
trip_details["stops"][stop["name"]] = stop_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
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from src.embeds import build_info_embed, build_error_embed
|
|
||||||
|
|
||||||
def setup_commands(bot: discord.Bot):
|
|
||||||
@bot.slash_command(description="Informationen über die aktuelle Fahrt")
|
|
||||||
async def info(ctx):
|
|
||||||
embed = build_info_embed()
|
|
||||||
if embed is None:
|
|
||||||
await build_error_embed("Noch keine Verbindung gesetzt.")
|
|
||||||
return
|
|
||||||
await ctx.respond(embed=embed)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
with open("config.json", "r") as file:
|
|
||||||
config = json.load(file)
|
|
||||||
+7
-16
@@ -1,18 +1,9 @@
|
|||||||
emoji_list = {
|
emoji_list = {
|
||||||
"Fallback": "🚉",
|
"Fallback": "💺",
|
||||||
"Nahverkehr": "🚃",
|
"BUS": "🚎",
|
||||||
"Höchstgeschwindigkeit": "🚄",
|
"TRAM": "🚈",
|
||||||
"Bus": "🚎"
|
"REGIONAL_RAIL": "🚊",
|
||||||
}
|
"HIGHSPEED_RAIL": "🚅",
|
||||||
|
"METRO": "🚇",
|
||||||
train_types = { # Unvollständig!
|
"SUBWAY": "🚇"
|
||||||
"ICE": emoji_list["Höchstgeschwindigkeit"],
|
|
||||||
"TGV": emoji_list["Höchstgeschwindigkeit"],
|
|
||||||
|
|
||||||
"RE": emoji_list["Nahverkehr"],
|
|
||||||
"RB": emoji_list["Nahverkehr"],
|
|
||||||
"ARV": emoji_list["Nahverkehr"],
|
|
||||||
"BRB": emoji_list["Nahverkehr"],
|
|
||||||
|
|
||||||
"Bus": emoji_list["Bus"]
|
|
||||||
}
|
}
|
||||||
@@ -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())
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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"])
|
||||||
|
|
||||||
|
color = trip.get("color", "keine farbe :(")
|
||||||
|
print(color)
|
||||||
|
return embed
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import discord
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from src.api import transitous
|
||||||
|
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()
|
||||||
|
connection = transitous.get_random_connection(station_id)
|
||||||
|
trip = transitous.get_trip_details(connection["trip_id"], connection["from_station"])
|
||||||
|
|
||||||
|
arrival = trip["arrival"]
|
||||||
|
long_name = trip["long_name"]
|
||||||
|
|
||||||
|
print("-----------------")
|
||||||
|
logger(f"Umstieg: {long_name}, Ankunft: {arrival}")
|
||||||
|
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!)")
|
||||||
|
|
||||||
|
formatting = channel_formatting(trip["mode"])
|
||||||
|
await voice_channel.edit(name=f"{formatting}{long_name}")
|
||||||
|
await voice_channel.set_status(f"Ankunft um {arrival}")
|
||||||
|
|
||||||
|
logger(f"Name geändert!")
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import discord
|
||||||
|
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)
|
||||||
|
channel = guild.get_channel(channel_id)
|
||||||
|
|
||||||
|
if guild is None:
|
||||||
|
logger(f"Es konnte kein Server mit der ID {server_id} gefunden werden", "fatal")
|
||||||
|
|
||||||
|
if not isinstance(channel, discord.VoiceChannel):
|
||||||
|
logger(f"Es konnte kein VC mit der id {channel_id} gefunden werden", "fatal")
|
||||||
|
return False
|
||||||
|
|
||||||
|
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")
|
||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
import random
|
|
||||||
import discord
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import src.handlers as handlers
|
|
||||||
from src.config import config
|
|
||||||
from src.utils import operator_metadata, format_via_list, resolve_operator, logger
|
|
||||||
|
|
||||||
def format_timestamp(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")
|
|
||||||
|
|
||||||
def format_iso_timestamp(isostr):
|
|
||||||
parsed_time = discord.utils.parse_time(isostr)
|
|
||||||
return discord.utils.format_dt(parsed_time, style="t")
|
|
||||||
|
|
||||||
def build_embed_footer(operator_slogans):
|
|
||||||
dbf = config.get("dbf", "https://dbf.finalrewind.org")
|
|
||||||
footer_notice = f"Daten großzügig bereitgestellt von {dbf} • Typ: {handlers.train_type}"
|
|
||||||
footer_text = (
|
|
||||||
f"{random.choice(operator_slogans)} • {footer_notice}"
|
|
||||||
if operator_slogans else
|
|
||||||
footer_notice
|
|
||||||
)
|
|
||||||
icon = f"{dbf}/static/icons/icon-96x96.png"
|
|
||||||
return {
|
|
||||||
"text": footer_text,
|
|
||||||
"icon": icon
|
|
||||||
}
|
|
||||||
|
|
||||||
def build_info_embed() -> discord.Embed | None:
|
|
||||||
conn = handlers.current
|
|
||||||
info = handlers.train_info
|
|
||||||
|
|
||||||
if handlers.current is None and handlers.train_info is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
current_operator = resolve_operator(info["operators"])
|
|
||||||
arrival = format_iso_timestamp(info["arrival"])
|
|
||||||
departure = format_timestamp(conn['departure'])
|
|
||||||
|
|
||||||
operator_infos = operator_metadata(current_operator)
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
|
||||||
title=handlers.train_name,
|
|
||||||
description=f"Abfahrt von {conn['station']} um {departure}. Ankunft um {arrival}",
|
|
||||||
color=operator_infos["color"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(conn['via']) > 0:
|
|
||||||
embed.add_field(name="Über", value=format_via_list(conn['via']), inline=False)
|
|
||||||
|
|
||||||
if operator_infos.get("unknown"):
|
|
||||||
logger(f"Metadaten für {current_operator} konten in operators.py nicht gefunden werden")
|
|
||||||
|
|
||||||
embed.set_author(name=current_operator)
|
|
||||||
embed.set_thumbnail(url=operator_infos["logo"])
|
|
||||||
|
|
||||||
route_lines = []
|
|
||||||
for stop in conn['route']:
|
|
||||||
stop_name = stop["name"]
|
|
||||||
if stop_name == conn['station']:
|
|
||||||
route_lines.append(f"• **{stop_name}**")
|
|
||||||
else:
|
|
||||||
route_lines.append(f"• {stop_name}")
|
|
||||||
|
|
||||||
embed.add_field(name="Route", value="\n".join(route_lines))
|
|
||||||
|
|
||||||
footer = build_embed_footer(operator_infos.get("slogan"))
|
|
||||||
embed.set_footer(text=footer["text"], icon_url=footer["icon"])
|
|
||||||
|
|
||||||
return embed
|
|
||||||
|
|
||||||
def build_announcement_embed(msg):
|
|
||||||
current_operator = resolve_operator(handlers.train_info["operators"])
|
|
||||||
operator_infos = operator_metadata(current_operator)
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
|
||||||
title = "Informationen zu Ihrer Fahrt",
|
|
||||||
description=msg,
|
|
||||||
color=operator_infos["color"]
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.set_author(name=current_operator)
|
|
||||||
embed.set_thumbnail(url=operator_infos["logo"])
|
|
||||||
|
|
||||||
footer = build_embed_footer(operator_infos.get("slogan"))
|
|
||||||
embed.set_footer(text=footer["text"], icon_url=footer["icon"])
|
|
||||||
|
|
||||||
return embed
|
|
||||||
|
|
||||||
def build_error_embed(errormsg) -> discord.Embed:
|
|
||||||
embed = discord.Embed(
|
|
||||||
title="Ein Fehler ist aufgetreten!",
|
|
||||||
description=errormsg,
|
|
||||||
color=discord.Colour.red()
|
|
||||||
)
|
|
||||||
|
|
||||||
return embed
|
|
||||||
-154
@@ -1,154 +0,0 @@
|
|||||||
import discord
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import asyncio
|
|
||||||
import random
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from src.config import config
|
|
||||||
from src.utils import random_connection, get_train_info, get_channel_formatting, logger
|
|
||||||
from src.embeds import build_announcement_embed, build_info_embed
|
|
||||||
|
|
||||||
current = None
|
|
||||||
_scheduled_task: asyncio.Task | None = None
|
|
||||||
|
|
||||||
async def announcer(bot, announcement):
|
|
||||||
voice_channel = bot.get_channel(config["vc"])
|
|
||||||
if len(voice_channel.members) > 0:
|
|
||||||
match announcement:
|
|
||||||
case "ende":
|
|
||||||
destination = current['destination']
|
|
||||||
embed = build_announcement_embed(
|
|
||||||
f'Sehr geehrte Fahrgäste,\nIn wenigen Minuten erreichen wir {destination}. Dieser Zug endet dort.\n\nWir wünschen Ihnen eine angenehme Weiterreise.\n\nVielen Dank für Ihr Vertrauen und auf Wiedersehen.')
|
|
||||||
if config["voice_announcements"][0]["enabled"]:
|
|
||||||
await voice_announcer(bot, destination, voice_channel)
|
|
||||||
case "umstieg":
|
|
||||||
embed = build_info_embed()
|
|
||||||
case _:
|
|
||||||
embed = None
|
|
||||||
if embed is None:
|
|
||||||
logger(f"Unbekanntes Announcement: {announcement}")
|
|
||||||
else:
|
|
||||||
await voice_channel.send(embed=embed)
|
|
||||||
else:
|
|
||||||
logger(f"Announcement {announcement} wird geskipped, keiner da")
|
|
||||||
return
|
|
||||||
|
|
||||||
async def voice_announcer(bot: discord.Bot, destination, voice_channel):
|
|
||||||
voice_announcement_config = config["voice_announcements"][0]
|
|
||||||
voice_stations = voice_announcement_config["stations"]
|
|
||||||
|
|
||||||
if destination in voice_stations:
|
|
||||||
announcement_for = destination
|
|
||||||
else:
|
|
||||||
if voice_stations.get("general", "") == "":
|
|
||||||
return
|
|
||||||
announcement_for = "general"
|
|
||||||
|
|
||||||
if voice_stations.values() == list:
|
|
||||||
sound_file = random.choice(voice_stations.get(announcement_for))
|
|
||||||
else:
|
|
||||||
sound_file = voice_stations.get(announcement_for)
|
|
||||||
|
|
||||||
sound_path = f"src/data/announcements/{sound_file}"
|
|
||||||
if Path(sound_path).is_file() is False:
|
|
||||||
logger(f"Konnte Datei {sound_path} nicht finden", "error")
|
|
||||||
return
|
|
||||||
|
|
||||||
logger(f"VC wird betreten, spiele {sound_path}")
|
|
||||||
vc = await voice_channel.connect(timeout=15, reconnect=True)
|
|
||||||
audio_source = discord.FFmpegPCMAudio(sound_path)
|
|
||||||
|
|
||||||
if not vc.is_playing():
|
|
||||||
def after_playing(error):
|
|
||||||
if error:
|
|
||||||
logger(f"Player error: {error}", "error")
|
|
||||||
bot.loop.create_task(vc.disconnect())
|
|
||||||
logger("VC wird verlassen")
|
|
||||||
|
|
||||||
vc.play(audio_source, after=after_playing)
|
|
||||||
|
|
||||||
async def rename_vc(bot: discord.Bot, from_scheduler: bool = False):
|
|
||||||
global current, train_name, train_info, train_type, _scheduled_task
|
|
||||||
|
|
||||||
guild = bot.get_guild(int(config["server"]))
|
|
||||||
if guild is None:
|
|
||||||
logger(f"Es konnte kein Server mit der ID {config['server']} gefunden werden! Ist der Bot ein Member?", "fatal")
|
|
||||||
return False
|
|
||||||
|
|
||||||
channel = guild.get_channel(int(config["vc"]))
|
|
||||||
if not isinstance(channel, discord.VoiceChannel):
|
|
||||||
logger(f"Es konnte kein VC mit der ID {config['vc']} auf dem Server gefunden werden", "fatal")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not from_scheduler and _scheduled_task and not _scheduled_task.done():
|
|
||||||
_scheduled_task.cancel()
|
|
||||||
|
|
||||||
attempt = 0
|
|
||||||
while True:
|
|
||||||
if attempt == 20:
|
|
||||||
logger("Zu viele Fehlversuche. Füge einen anderen Bahnhof hinzu.", "fatal")
|
|
||||||
return "Es konnte kein Zug gefunden werden."
|
|
||||||
|
|
||||||
attempt += 1
|
|
||||||
current = random_connection()
|
|
||||||
if current == None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parts = current['train'].split()
|
|
||||||
train_type = parts[0]
|
|
||||||
|
|
||||||
if parts[1].isdigit():
|
|
||||||
train = current['train']
|
|
||||||
train_ID = parts[1]
|
|
||||||
else:
|
|
||||||
train = parts[1]
|
|
||||||
train_ID = current['train_number']
|
|
||||||
|
|
||||||
station = current['station']
|
|
||||||
train_info = get_train_info(station=station, train_ID=train_ID, train_type=train_type)
|
|
||||||
if train_info and train_info.get('operators') and train_info.get('arrival'):
|
|
||||||
break
|
|
||||||
|
|
||||||
logger(f"Versuch {attempt}: Fehler bei {current['train']} von {station}, versuche neue Verbindung...")
|
|
||||||
|
|
||||||
train_name = f"{train} nach {current['destination']} von {current['station']}"
|
|
||||||
logger(f"Vorbereitung auf {train_name} (typ: {train_type})")
|
|
||||||
arrival = datetime.fromisoformat((train_info["arrival"]))
|
|
||||||
formatting = get_channel_formatting(train_type)
|
|
||||||
|
|
||||||
|
|
||||||
print("-----------------------------------------")
|
|
||||||
logger(f"Umstieg: {train_name}")
|
|
||||||
logger(f"Betreiber: {''.join(train_info['operators'])}")
|
|
||||||
logger(f"Train-Type: {train_type}")
|
|
||||||
logger(f"Wenn der Name nicht geändert wird bin ich im Cooldown")
|
|
||||||
await channel.edit(name=f"{formatting}{train_name}")
|
|
||||||
await channel.set_status(f"Ankunft um {arrival.strftime('%H:%M')}")
|
|
||||||
logger(f"Name geändert!")
|
|
||||||
|
|
||||||
if config.get("announcements", True):
|
|
||||||
await announcer(bot, "umstieg")
|
|
||||||
|
|
||||||
_scheduled_task = asyncio.create_task(_schedule_next_umstieg(bot, arrival))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def _schedule_next_umstieg(bot, arrival):
|
|
||||||
announcement = config.get("announcements", True)
|
|
||||||
announcement_countdown = random.randrange(180, 300) # letzte station announcement ist meistens 3-5min vor ankunft
|
|
||||||
wait_seconds = (arrival - datetime.now()).total_seconds()
|
|
||||||
if wait_seconds > 0:
|
|
||||||
remaining = str(timedelta(seconds=wait_seconds))
|
|
||||||
logger(f"Nächster Umstieg in {remaining.split('.')[0]} ({arrival.strftime('%H:%M:%S')} Uhr)")
|
|
||||||
|
|
||||||
if announcement and wait_seconds > announcement_countdown:
|
|
||||||
wait_until_end_announcement = wait_seconds - announcement_countdown
|
|
||||||
await asyncio.sleep(wait_until_end_announcement)
|
|
||||||
await announcer(bot, "ende")
|
|
||||||
await asyncio.sleep(announcement_countdown)
|
|
||||||
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(wait_seconds)
|
|
||||||
|
|
||||||
logger("Zug angekommen, wähle neue Verbindung...")
|
|
||||||
await rename_vc(bot, from_scheduler=True)
|
|
||||||
+56
-139
@@ -1,149 +1,66 @@
|
|||||||
import requests
|
import json
|
||||||
import random
|
|
||||||
import os
|
import os
|
||||||
import importlib
|
from datetime import datetime, timedelta
|
||||||
from datetime import datetime
|
|
||||||
from src.config import config
|
|
||||||
import src.data.operators as operators_module
|
|
||||||
import src.data.emojis as emojis
|
|
||||||
|
|
||||||
_operators_mtime = None
|
import src.data.operators as operators
|
||||||
dbf = config.get("dbf", "https://dbf.finalrewind.org")
|
from src.data.emojis import emoji_list
|
||||||
|
|
||||||
def _reload_operators_if_changed():
|
|
||||||
global _operators_mtime
|
|
||||||
|
|
||||||
path = operators_module.__file__
|
with open("config.json", "r") as file:
|
||||||
current_mtime = os.path.getmtime(path)
|
config = json.load(file)
|
||||||
|
|
||||||
if _operators_mtime is None:
|
def logger(msg, log_type="info") -> str:
|
||||||
_operators_mtime = current_mtime
|
|
||||||
return
|
|
||||||
|
|
||||||
if current_mtime != _operators_mtime:
|
|
||||||
importlib.reload(operators_module)
|
|
||||||
_operators_mtime = current_mtime
|
|
||||||
logger("operators.py wurde automatisch neu geladen (Änderungen erkannt)")
|
|
||||||
|
|
||||||
def random_connection():
|
|
||||||
available_stations = config["stations"].copy()
|
|
||||||
while True:
|
|
||||||
if not available_stations:
|
|
||||||
logger("Keine validen Bahnhöfe. Schlag den richtigen Bahnhofsnamen auf {dbf} nach", "fatal")
|
|
||||||
|
|
||||||
station = random.choice(available_stations)
|
|
||||||
url = f"{dbf}/{station}.json"
|
|
||||||
blacklist = config.get("blacklist", [])
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
except requests.RequestException as e:
|
|
||||||
logger(f"Fehler beim aussuchen der Verbindung: {e}", "fatal")
|
|
||||||
return
|
|
||||||
|
|
||||||
if response.status_code == 300:
|
|
||||||
logger(f"Bahnhof '{station}' konnte nicht gefunden werden ({url})", "error")
|
|
||||||
available_stations.remove(station)
|
|
||||||
continue
|
|
||||||
|
|
||||||
departures = [
|
|
||||||
d for d in data.get("departures", [])
|
|
||||||
if d.get("scheduledDeparture") and d.get("destination") != station
|
|
||||||
and not d.get("train", "").startswith(tuple(blacklist))
|
|
||||||
]
|
|
||||||
|
|
||||||
if not departures:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not config['random']:
|
|
||||||
dep = departures[0]
|
|
||||||
else:
|
|
||||||
dep = random.choice(departures)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"train": dep['train'],
|
|
||||||
"destination": dep['destination'],
|
|
||||||
"route": dep['route'],
|
|
||||||
"departure": dep['scheduledDeparture'],
|
|
||||||
"via": dep['via'],
|
|
||||||
"station": station,
|
|
||||||
"train_number": dep['trainNumber']
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_train_info(station, train_ID, train_type):
|
|
||||||
url = f"{dbf}/z/{train_type}%20{train_ID}/{station}.json"
|
|
||||||
logger(f"Fetche {url}")
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
except requests.RequestException as e:
|
|
||||||
logger(f"ReqestException Fehler: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
dep = data.get("departure", [])
|
|
||||||
if not dep:
|
|
||||||
logger("Kein departure Feld gefunden", "error")
|
|
||||||
return None
|
|
||||||
|
|
||||||
route_post = dep.get("route_post_diff")
|
|
||||||
if not route_post:
|
|
||||||
logger("Kein route_post_diff Feld gefunden", "error")
|
|
||||||
return None
|
|
||||||
|
|
||||||
arrival_iso = route_post[-1].get("sched_arr")
|
|
||||||
if not arrival_iso:
|
|
||||||
logger("Keine Ankunftszeit gefunden", "error")
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
|
||||||
"arrival": arrival_iso,
|
|
||||||
"operators": dep.get("operators"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def format_via_list(via: list[str]):
|
|
||||||
if not via:
|
|
||||||
return ""
|
|
||||||
if len(via) == 1:
|
|
||||||
return via[0]
|
|
||||||
return ", ".join(via[:-1]) + " und " + via[-1]
|
|
||||||
|
|
||||||
def resolve_operator(operators):
|
|
||||||
if not operators:
|
|
||||||
return None
|
|
||||||
for op in operators:
|
|
||||||
if op in operators_module.OPERATOR_ALIASES or op in operators_module.OPERATORS:
|
|
||||||
return op
|
|
||||||
|
|
||||||
return operators[0]
|
|
||||||
|
|
||||||
def operator_metadata(operator):
|
|
||||||
_reload_operators_if_changed()
|
|
||||||
|
|
||||||
if not operator:
|
|
||||||
return operators_module.OPERATORS["fallback"]
|
|
||||||
|
|
||||||
if operator in operators_module.OPERATOR_ALIASES:
|
|
||||||
return operators_module.OPERATOR_ALIASES[operator]
|
|
||||||
|
|
||||||
return operators_module.OPERATORS.get(operator, operators_module.OPERATORS["fallback"])
|
|
||||||
|
|
||||||
def get_channel_formatting(train_type):
|
|
||||||
formatting = config.get("formatting", "")
|
|
||||||
train_emoji = ""
|
|
||||||
|
|
||||||
if config.get("emojis", True):
|
|
||||||
train_emoji = emojis.train_types.get(train_type)
|
|
||||||
if train_emoji is None:
|
|
||||||
train_emoji = emojis.emoji_list.get("Fallback", "")
|
|
||||||
|
|
||||||
return f"{train_emoji}{formatting}"
|
|
||||||
|
|
||||||
def logger(msg, log_type="info"):
|
|
||||||
status = log_type.upper()
|
status = log_type.upper()
|
||||||
current_time = datetime.now().strftime('%X')
|
current_time = datetime.now().strftime('%X')
|
||||||
print(f"{current_time}: {status}: {msg}")
|
print(f"{current_time}: {status}: {msg}")
|
||||||
if status == "FATAL":
|
if status == "FATAL":
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
def convert_iso_string(isostring) -> str:
|
||||||
|
dt = datetime.fromisoformat(isostring.replace('Z', '+00:00'))
|
||||||
|
if dt.second >= 30:
|
||||||
|
dt += timedelta(minutes=1)
|
||||||
|
|
||||||
|
return dt.strftime('%H:%M')
|
||||||
|
|
||||||
|
def channel_formatting(mode: str) -> str:
|
||||||
|
formatting = config.get("formatting", "")
|
||||||
|
|
||||||
|
if config.get("emojis", True):
|
||||||
|
emoji = emoji_list.get(mode)
|
||||||
|
if emoji is None:
|
||||||
|
emoji = emoji_list.get("Fallback")
|
||||||
|
|
||||||
|
return f"{emoji}{formatting}"
|
||||||
|
|
||||||
|
def get_train_name(train_name: str, mode: str) -> str:
|
||||||
|
if train_name.isdigit():
|
||||||
|
train = f"{mode.capitalize()} {train_name}"
|
||||||
|
elif "(" in train_name:
|
||||||
|
train = train_name.split(" (")[0]
|
||||||
|
else:
|
||||||
|
train = train_name
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user