Compare commits

..
10 Commits
Author SHA1 Message Date
Kaaninchen 199cedb8b1 commit so that the new branch uploads 2026-08-18 14:33:22 +02:00
Kaaninchen 172d6d5cf1 add README notice about maintenance status 2026-08-18 14:31:47 +02:00
Kaaninchen 72b4d3185a emojis.py: Bus emoji 2026-08-12 19:00:46 +02:00
Kaaninchen e2e7bf2a14 README: grammar 2026-08-12 14:02:25 +02:00
Kaaninchen 12f65eabc2 fix: .gitkeep damit announcements ordner gepushed wird 2026-08-12 14:01:40 +02:00
Kaaninchen 934ab0479d README: verbessert 2026-08-12 13:57:47 +02:00
Kaaninchen a011308c2f operators.py: Agilis und enno 2026-08-12 13:51:57 +02:00
Kaaninchen 453520c8d2 feat: voice_announcement 2026-08-12 13:47:50 +02:00
Kaaninchen e09ad38816 operators.py: SNCF 2026-08-11 23:23:21 +02:00
Kaaninchen 8dcac27338 operators.py: GoVolta 2026-08-10 12:34:38 +02:00
8 changed files with 133 additions and 6 deletions
+2
View File
@@ -1,3 +1,5 @@
venv/ venv/
__pycache__/ __pycache__/
src/data/announcements/*
!src/data/announcements/.gitkeep
config.json config.json
+58 -2
View File
@@ -1,4 +1,8 @@
# Gleiswechsel # Gleiswechsel
## THIS BRANCH IS NO LONGER MAINTAINED
## This is the code for the legacy Gleiswechsel bot, based on the much simpler dbf API.
Gleiswechel ist ein Discord Bot, welcher einen Sprachkanal zu einer real-existierenden, aktuell befahrenden Zugverbindung umbenennt. Diesen Namen behält der Kanal so lange, wie die Verbindung in echt dauert. Gleiswechel ist ein Discord Bot, welcher einen Sprachkanal zu einer real-existierenden, aktuell befahrenden Zugverbindung umbenennt. Diesen Namen behält der Kanal so lange, wie die Verbindung in echt dauert.
![Beispiel Kanal](.github/preview_kanal.png) ![Beispiel Kanal](.github/preview_kanal.png)
@@ -35,8 +39,16 @@ uv pip install -r requirements.txt
"vc": , // Server VC ID "vc": , // Server VC ID
"random": true, // Random Zug aus der Anzeigetafel (true) oder erster Zug, der angezeigt wird (false) "random": true, // Random Zug aus der Anzeigetafel (true) oder erster Zug, der angezeigt wird (false)
"emojis": true, // Emoji Namen beim Channel-Namen (true) oder nicht (false), "emojis": true, // Emoji Namen beim Channel-Namen (true) oder nicht (false),
"announcemenents": true, // Announcements zur aktuellen Zugreise im VC-Kanal ja (true) oder nein (false)
"formatting": "┇", // VC Name. Davor steht der Emoji, danach der Zug. "formatting": "┇", // VC Name. Davor steht der Emoji, danach der Zug.
"announcemenents": true, // Announcements zur aktuellen Zugreise im VC-Kanal ja (true) oder nein (false)
"voice_announcements": [ // siehe weitere config erklärungen in der README, benötigt extra setup
{
"enabled": false,
"stations": {
"general": ""
}
}
],
"blacklist": [] // Blacklist für bestimmte Zug-Typen "blacklist": [] // Blacklist für bestimmte Zug-Typen
} }
@@ -58,6 +70,50 @@ Wenn man nur einen Bahnhof hat ist es stark empfohlen random zu nutzen. Sonst k
Der Bot kann während der Zugreise Ankündigungen in den Textkanal vom Sprachkanal schicken. Das würde er aber auch nur machen wenn sich dort mindestens eine Person aufhält. Aktuelle Announcements: Der Bot kann während der Zugreise Ankündigungen in den Textkanal vom Sprachkanal schicken. Das würde er aber auch nur machen wenn sich dort mindestens eine Person aufhält. Aktuelle Announcements:
- 3-5 Min vor Umstieg gibt es eine Ankündigung dafür - 3-5 Min vor Umstieg gibt es eine Ankündigung dafür
- Info Embed bei Umstieg - Info Embed bei Umstieg
- voice_announcements
#### voice_announcements:
⚠️ Announcements muss aktiviert sein
Der Bot kann 3-5 Min vor Umstieg den VC joinen und eine Audiodatei abspielen. Dafür braucht der Bot ffmpeg.
```sh
$ sudo apt install ffmpeg # Debian/Ubuntu
$ brew install ffmpeg # macOS (brew)
```
##### Voice_announcements config:
```json
"voice_announcements": [
{
"enabled": true,
"stations": {
"general": "general.aac",
"Hannover": "hannover.aac"
}
}
],
```
Audiodateien werden in [src/data/announcements](src/data/announcements) platziert. In der Config wird der Name der Station (z.B. Hannover) zu dem Namen der Audiodatei (z.B. hannover.aac) zugewiesen. Die Audiodatei braucht keinen Path.
Der Eintrag `general` meint die allgemeine Audiodatei, welche bei jeder Endstation (mit Ausnahme der zugewiesenen) spielt. Sollte man nur Audios bei zugewiesenen Endstationen abspielen wollen kann man `general` leerlassen, der bot skipped das ganze dann:
```json
"stations": {
"general": ""
}
```
Es gibt außerdem die Möglichkeit, mehrere Audiodateien zu einer Endstation durch Listen zuzuweisen. Der Bot sucht sich dann jedes mal wenn er den vc joined eine davon aus.
```json
"stations": {
"general": ["general_1.aac", "general_2.aac"]
}
```
#### blacklist: #### blacklist:
Die Blacklist ist dafür gedacht, ganze Zugtypen zu ignorieren. Beispielsweise möchte man, dass der Bot keine ICE's, keine NightJets und keine European Sleepers auswählt, da diese sehr lange Strecken fahren und der VC somit lange unverändert bleibt: Die Blacklist ist dafür gedacht, ganze Zugtypen zu ignorieren. Beispielsweise möchte man, dass der Bot keine ICE's, keine NightJets und keine European Sleepers auswählt, da diese sehr lange Strecken fahren und der VC somit lange unverändert bleibt:
@@ -120,4 +176,4 @@ Der Bot empfängt durch die dbf API ein Operator Field, wo der Betreiber des Zü
Wenn mehrere Betreiber angezeigt werden (bspw `["SBB", "Nahreisezug"]`) versucht der Bot immer, den Embed den bekannten Betreiber (in dem Fall SBB) zuzuweisen. Sollte allerdings nur Nahreisezug angezeigt werden, kann damit nicht gearbeitet werden, wodurch die Fallback Metadaten genutzt werden. Wenn mehrere Betreiber angezeigt werden (bspw `["SBB", "Nahreisezug"]`) versucht der Bot immer, den Embed den bekannten Betreiber (in dem Fall SBB) zuzuweisen. Sollte allerdings nur Nahreisezug angezeigt werden, kann damit nicht gearbeitet werden, wodurch die Fallback Metadaten genutzt werden.
Die Fallback Metadaten kann man in [src/data/operators.py](src/data/operators.py) angepasst werden. Die Fallback Metadaten können in [src/data/operators.py](src/data/operators.py) angepasst werden.
+9 -1
View File
@@ -6,7 +6,15 @@
"vc": , "vc": ,
"random": true, "random": true,
"emojis": true, "emojis": true,
"announcements": true,
"formatting": "┇", "formatting": "┇",
"announcements": true,
"voice_announcements: [
{
"enabled": false,
"stations": {
"general": ""
}
}
]
"blacklist": [] "blacklist": []
} }
+4
View File
@@ -3,7 +3,9 @@ aiohttp==3.14.1
aiosignal==1.4.0 aiosignal==1.4.0
attrs==26.1.0 attrs==26.1.0
certifi==2026.6.17 certifi==2026.6.17
cffi==2.1.1
charset-normalizer==3.4.9 charset-normalizer==3.4.9
davey==0.1.6
frozenlist==1.8.0 frozenlist==1.8.0
idna==3.18 idna==3.18
multidict==6.7.1 multidict==6.7.1
@@ -11,6 +13,8 @@ numpy==2.5.1
pandas==3.0.3 pandas==3.0.3
propcache==0.5.2 propcache==0.5.2
py-cord==2.8.0 py-cord==2.8.0
pycparser==3.0
pynacl==1.6.2
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
requests==2.34.2 requests==2.34.2
six==1.17.0 six==1.17.0
View File
+5 -2
View File
@@ -1,10 +1,11 @@
emoji_list = { emoji_list = {
"Fallback": "🚉", "Fallback": "🚉",
"Nahverkehr": "🚃", "Nahverkehr": "🚃",
"Höchstgeschwindigkeit": "🚄" "Höchstgeschwindigkeit": "🚄",
"Bus": "🚎"
} }
train_types = { # Unvollständig! Benötigt testing train_types = { # Unvollständig!
"ICE": emoji_list["Höchstgeschwindigkeit"], "ICE": emoji_list["Höchstgeschwindigkeit"],
"TGV": emoji_list["Höchstgeschwindigkeit"], "TGV": emoji_list["Höchstgeschwindigkeit"],
@@ -12,4 +13,6 @@ train_types = { # Unvollständig! Benötigt testing
"RB": emoji_list["Nahverkehr"], "RB": emoji_list["Nahverkehr"],
"ARV": emoji_list["Nahverkehr"], "ARV": emoji_list["Nahverkehr"],
"BRB": emoji_list["Nahverkehr"], "BRB": emoji_list["Nahverkehr"],
"Bus": emoji_list["Bus"]
} }
+16
View File
@@ -82,6 +82,22 @@ OPERATORS = {
"European Sleeper": { "European Sleeper": {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/European_Sleeper_Logo.svg/960px-European_Sleeper_Logo.svg.png", "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/European_Sleeper_Logo.svg/960px-European_Sleeper_Logo.svg.png",
"color": 0xEB4A27 "color": 0xEB4A27
},
"GoVolta": {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/GoVolta_logo.svg/1280px-GoVolta_logo.svg.png",
"color": 0x0B70F6
},
"SNCF": {
"logo": "https://upload.wikimedia.org/wikipedia/en/thumb/f/f4/Sncf-logo.svg/960px-Sncf-logo.svg.png",
"color": 0x812B6D
},
"Agilis": {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a3/Agilis_Logo.svg/960px-Agilis_Logo.svg.png",
"color": 0xCAE15B
},
"enno": {
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Enno_logo.svg/960px-Enno_logo.svg.png",
"color": 0x88216F
} }
} }
+39 -1
View File
@@ -2,6 +2,8 @@ import discord
from datetime import datetime, timedelta from datetime import datetime, timedelta
import asyncio import asyncio
import random import random
from pathlib import Path
from src.config import config from src.config import config
from src.utils import random_connection, get_train_info, get_channel_formatting, logger from src.utils import random_connection, get_train_info, get_channel_formatting, logger
from src.embeds import build_announcement_embed, build_info_embed from src.embeds import build_announcement_embed, build_info_embed
@@ -14,8 +16,11 @@ async def announcer(bot, announcement):
if len(voice_channel.members) > 0: if len(voice_channel.members) > 0:
match announcement: match announcement:
case "ende": case "ende":
destination = current['destination']
embed = build_announcement_embed( embed = build_announcement_embed(
f'Sehr geehrte Fahrgäste,\nIn wenigen Minuten erreichen wir {current['destination']}. Dieser Zug endet dort.\n\nWir wünschen Ihnen eine angenehme Weiterreise.\n\nVielen Dank für Ihr Vertrauen und auf Wiedersehen.') 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": case "umstieg":
embed = build_info_embed() embed = build_info_embed()
case _: case _:
@@ -28,6 +33,39 @@ async def announcer(bot, announcement):
logger(f"Announcement {announcement} wird geskipped, keiner da") logger(f"Announcement {announcement} wird geskipped, keiner da")
return 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): async def rename_vc(bot: discord.Bot, from_scheduler: bool = False):
global current, train_name, train_info, train_type, _scheduled_task global current, train_name, train_info, train_type, _scheduled_task