Compare commits

...
5 Commits
Author SHA1 Message Date
Kaaninchen f65aa3cfa0 fix grammatical error in NS slogans (how embarassing)... 2026-08-28 19:53:49 +02:00
Kaaninchen e9708d435e fix example config 2026-08-28 19:42:32 +02:00
Kaaninchen d50cf0277b readme 2026-08-28 15:51:41 +02:00
Kaaninchen dde0c443de map improvements 2026-08-27 13:10:30 +02:00
Kaaninchen 93b9712394 up max_attempt to 30 2026-08-24 07:55:06 +02:00
7 changed files with 28 additions and 63 deletions
+7 -1
View File
@@ -4,7 +4,7 @@ Gleiswechsel [ˈɡlaɪsvɛksəl] (german for "platform change") is a selfhostabl
![Example Channel](.github/preview_kanal.png) ![Example Channel](.github/preview_kanal.png)
## Features ## Features
- [International support](https://transitous.org/sources/) for countries as well as and cities - [International support](https://transitous.org/sources/) for countries as well as cities
- Support for various types of transportations, including but not limited to Subways, Busses, Funiculars, Trams and Trains - Support for various types of transportations, including but not limited to Subways, Busses, Funiculars, Trams and Trains
- Multi language support (+ option to easily add more languages) - Multi language support (+ option to easily add more languages)
- Announcements, including the option to join the voice chat and play an audio file while arriving at a specific station - Announcements, including the option to join the voice chat and play an audio file while arriving at a specific station
@@ -179,6 +179,12 @@ To reduce spam, the bot will only send announcements if someone is in the voice
The bot can join the voice chat, play an audio file, and disconnect from the voice chat, at various points of your trip. You have to have [FFmpeg](https://www.ffmpeg.org/) installed for this to work. The bot can join the voice chat, play an audio file, and disconnect from the voice chat, at various points of your trip. You have to have [FFmpeg](https://www.ffmpeg.org/) installed for this to work.
Place the audio file of your desired station in [src/data/announcements](src/data/announcements/) with the EXACT name of the station. The bot will automatically check if an audio file with the stations name exists, and if it does, play it. Place the audio file of your desired station in [src/data/announcements](src/data/announcements/) with the EXACT name of the station. The bot will automatically check if an audio file with the stations name exists, and if it does, play it.
You don't have to restart the bot after placing new audio files in the folder.
#### `map:`
The bot can generate a map of your route in the `/info` embed. Set `map` to `true` to enable.
#### http #### http
- `"user_agent"`: The user agent of the bot for the API. If you don't know what that is, then you shouldn't have to change that. Modify the contact URL (in my case, the github repo) if you change a lot of the code, so that transitous can contact you in case of any issues - `"user_agent"`: The user agent of the bot for the API. If you don't know what that is, then you shouldn't have to change that. Modify the contact URL (in my case, the github repo) if you change a lot of the code, so that transitous can contact you in case of any issues
+6 -6
View File
@@ -1,8 +1,8 @@
{ {
"discord": { "discord": {
"token": "", "token": "",
"server": , "server": "",
"vc": , "vc": "",
"lang": "de", "lang": "de",
"formatting": "┇", "formatting": "┇",
"emojis": true "emojis": true
@@ -14,8 +14,7 @@
"Helsinki" "Helsinki"
], ],
"IDs": [], "IDs": [],
"priority: "{ "priority": {},
}",
"blacklist": [ "blacklist": [
"OTHER", "OTHER",
"RIDE_SHARING" "RIDE_SHARING"
@@ -27,9 +26,10 @@
}, },
"announcements": { "announcements": {
"text_announcements": true, "text_announcements": true,
"voice_announcements": false "voice_announcements": false,
"map": true
}, },
"http": { "http": {
"user_agent": "Gleiswechsel-Discord-Bot/1.0 (https://github.com/kaaninchen/Gleiswechsel)" "user_agent": "Gleiswechsel-Discord-Bot/1.0 (https://github.com/kaaninchen/Gleiswechsel)"
} }
} }
+1
View File
@@ -26,6 +26,7 @@ class ConnectionsConfig:
class AnnouncementConfig: class AnnouncementConfig:
text_announcements: bool text_announcements: bool
voice_announcements: bool voice_announcements: bool
map: bool
@dataclass @dataclass
class HttpConfig: class HttpConfig:
+4 -2
View File
@@ -74,7 +74,7 @@ OPERATORS = {
"NS": { "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 hebben een serieus probleem", "Neuken in de keuken"]
}, },
"Ostdeutsche Eisenbahn GmbH": { "Ostdeutsche Eisenbahn GmbH": {
"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",
@@ -165,6 +165,7 @@ OPERATOR_ALIASES = {
"Bayerische Regiobahn": OPERATORS["db_bayern"], "Bayerische Regiobahn": OPERATORS["db_bayern"],
"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": OPERATORS["db_allgemein"],
"DB Regio AG NRW": OPERATORS["db_allgemein"], "DB Regio AG NRW": 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"],
@@ -177,7 +178,8 @@ OPERATOR_ALIASES = {
"VR": OPERATORS["Vr"], "VR": OPERATORS["Vr"],
"FlixBus-gb": OPERATORS["Flixbus"], "FlixBus-gb": OPERATORS["Flixbus"],
"FlixBus-eu": OPERATORS["Flixbus"], "FlixBus-eu": OPERATORS["Flixbus"],
"S-Bahn Berlin GmbH": OPERATORS["S-Bahn"] "S-Bahn Berlin GmbH": OPERATORS["S-Bahn"],
"DB Regio AG S-Bahn München": OPERATORS["S-Bahn"]
} }
+4 -2
View File
@@ -4,11 +4,12 @@ import random
from src.utils import get_operator_metadata, get_next_station, format_via_list, format_stop_list from src.utils import get_operator_metadata, get_next_station, format_via_list, format_stop_list
from src.dc.helpers import format_timestamp_to_dc from src.dc.helpers import format_timestamp_to_dc
from src.lang.locales import lang from src.lang.locales import lang
from src.config import config
lang_embed = lang.embeds lang_embed = lang.embeds
def build_embed_footer(slogans): def build_embed_footer(slogans):
footer_notice = f"https://transitous.org/sources/ • Maps (C) CARTO (C) OpenStreetMap.org contributors" footer_notice = f"https://transitous.org/sources/"
icon = "https://api.transitous.org/favicon.png" icon = "https://api.transitous.org/favicon.png"
if slogans is not None: if slogans is not None:
@@ -61,7 +62,8 @@ def build_info_embed() -> discord.Embed:
embed.set_author(name=agency) embed.set_author(name=agency)
embed.set_thumbnail(url=metadata["logo"]) embed.set_thumbnail(url=metadata["logo"])
embed.set_image(url="attachment://ride.png") if config.announcements.map:
embed.set_image(url="attachment://ride.png")
return embed return embed
def build_announcement_embed(msg): def build_announcement_embed(msg):
+4 -3
View File
@@ -18,7 +18,7 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals
_scheduled_task.cancel() _scheduled_task.cancel()
attempt = 0 attempt = 0
max_attempt = 15 max_attempt = 30
trip = choose_connection() trip = choose_connection()
while trip is None and attempt < max_attempt: while trip is None and attempt < max_attempt:
attempt += 1 attempt += 1
@@ -28,7 +28,8 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals
logger(f"Failed to select route after {max_attempt} attempts", "fatal") logger(f"Failed to select route after {max_attempt} attempts", "fatal")
return False return False
generate_static_map(trip["stops"], trip["mode"], trip["agency"], trip["route_color"]) if config.announcements.map:
generate_static_map(trip["stops"], trip["mode"], trip["agency"], trip["route_color"])
arrival = trip["arrival"] arrival = trip["arrival"]
channel_name = trip["channel_name"] channel_name = trip["channel_name"]
@@ -70,7 +71,7 @@ async def announcer(announcement: str, voice_channel: discord.VoiceChannel, dest
embed = None embed = None
if embed: if embed:
if image: if image and config.announcements.map:
await voice_channel.send(file=image, embed=embed) await voice_channel.send(file=image, embed=embed)
else: else:
await voice_channel.send(embed=embed) await voice_channel.send(embed=embed)
+2 -49
View File
@@ -217,22 +217,7 @@ def generate_static_map(stops: dict, mode: str, operator: str, route_color: str)
context = staticmaps.Context() context = staticmaps.Context()
context.set_tile_provider( context.set_tile_provider(staticmaps.tile_provider_OSM)
staticmaps.TileProvider(
"carto-voyager",
url_pattern=(
"https://$s.basemaps.cartocdn.com/"
"rastertiles/voyager/$z/$x/$y.png"
),
shards=["a", "b", "c", "d"],
attribution="",
)
# It is against the law (and against your morals...) to not give
# OSM and carto credits for their great work
# I only removed the attribution text because I really dislike the
# ugly white box that py-staticmaps adds. Credits are still visible in the
# embeds footer
)
stop_coords = [ stop_coords = [
staticmaps.create_latlng( staticmaps.create_latlng(
@@ -246,7 +231,7 @@ def generate_static_map(stops: dict, mode: str, operator: str, route_color: str)
staticmaps.Line( staticmaps.Line(
stop_coords, stop_coords,
color=white, color=white,
width=20, width=15,
) )
) )
@@ -279,38 +264,6 @@ def generate_static_map(stops: dict, mode: str, operator: str, route_color: str)
size=12 size=12
) )
) )
lats = [c.lat().degrees for c in stop_coords]
lons = [c.lng().degrees for c in stop_coords]
lat_span = max(lats) - min(lats)
lon_span = max(lons) - min(lons)
MIN_SPAN = 0.01
if lat_span < MIN_SPAN or lon_span < MIN_SPAN:
center_lat = (max(lats) + min(lats)) / 2
center_lon = (max(lons) + min(lons)) / 2
pad = MIN_SPAN / 2
context.add_object(
staticmaps.Circle(
staticmaps.create_latlng(center_lat + pad, center_lon + pad),
radius_km=0.01,
fill_color=staticmaps.TRANSPARENT,
color=staticmaps.TRANSPARENT,
width=0,
)
)
context.add_object(
staticmaps.Circle(
staticmaps.create_latlng(center_lat - pad, center_lon - pad),
radius_km=0.01,
fill_color=staticmaps.TRANSPARENT,
color=staticmaps.TRANSPARENT,
width=0,
)
)
try: try:
image = context.render_cairo(400, 300) image = context.render_cairo(400, 300)
image.write_to_png("src/data/assets/current_map.png") image.write_to_png("src/data/assets/current_map.png")