A lightweight Telegram bot designed for Saarland University students — offering fast, clear, and focused public transport information using SaarVV and HAFAS APIs. — Try Now
🔍 What it is
UdS Fahrplan is a minimal alternative to the full Saarfahrplan app. No clutter. No overwhelming options. Just a clean Telegram interface for:
🔄 Trip planning with /trip
🕒 Live departure boards with /depart
🏠 One-tap departures from your home station with /home
Built with students and locals in mind.
✨ Features
/trip – Search from A to B in Saarland & Luxembourg
/depart – View all upcoming buses from any station
/home – Configure your home station and check buses in both directions (e.g., City ↔ Dudweiler)
/sethome – Set or update your personal home station
Intelligent Filtering – Excludes school buses, allows regional/suburban focus
Persistent Storage – Remembers user settings across bot restarts
In the previous notes, we’ve already implemented all the commands we need! But there’s a question, why we need to format out query in f”{session}:{step}:{loc}”? Also, we haven’t talked about the function that connects to the SaarVV api.
We also have another study notes for all of the telegram bot projects in here, it gives you the basics on how to create your own bot and further descriptions on different functions and attributes on the package python-telegram-bot.
HTTP request on SaarVV
In Application layer, if we send a GET request to the URL, it returns the HTML of the URL. For example:
request.py
1 2 3 4 5 6 7
import requests
try: res = requests.get("https://greenmeeple.github.io/") print(res.text) except Exception as e: print(f"❌ Error fetching location matches: {e}")
Then we are basically retriving the source code the website.
In the previous notes, we’ve already implemented /trip and /depart based on this. Now, let’s start explaining them one by one.
We also have another study notes for all of the telegram bot projects in here, it gives you the basics on how to create your own bot and further descriptions on different functions and attributes on the package python-telegram-bot.
/sethome – Storing users’ starting station
Similarly, our main function should have app.add_handler(CommandHandler("sethome", spawn)) and InlineKeyboardMarkup[] ready, these components are defined in previous notes and they are reusable.
Next, our function has to be able to distinguish different users. Users who interact with the bot using command or tagged message will have their id stored in message.from_user.id; and query.from_user.id if users are interacting using buttons.
Then, we also need to create a database or storage file to read and write users’ stations. We decide the tool to use based on our use case and needs. Here, the data we need to store are user_id, station_name, and station_id. Which the columns are stable and small size. Therefore, it can be handled by a simple JSON file. Before we store the station, remember to check if the user already stored any station before to avoid creating duplicates.
asyncdefhandle_spawn_start(query, context, data): station = locations[data] if data in locations else data name = context.user_data.get("spawn_session", {}).get("search_s", {}).get(data, data) user_id = query.from_user.id
if os.path.exists(SPAWN_DATA): withopen(SPAWN_DATA, "r") as file: try: data_list = json.load(file) except json.JSONDecodeError: data_list = [] else: data_list = []
# Check if user already exists user_found = False for entry in data_list: if entry["user_id"] == user_id: entry["home_id"] = station entry["home_name"] = name user_found = True break
ifnot user_found: # Add new user entry data_list.append({"user_id": user_id, "home_id": station, "home_name": name})
# Save updated data withopen(SPAWN_DATA, "w") as file: json.dump(data_list, file, indent=2)
await query.edit_message_text( f"✅ User {user_id}, your home is set to {name:<20}")
asyncdefspawn(update: Update, context: ContextTypes.DEFAULT_TYPE): context.user_data["spawn_session"] = {} await update.message.reply_text(f"Hello User {update.message.from_user.id}, Set your spawn point", reply_markup=build_location_keyboard("spawn","start") )
In the previous notes, we’ve talked about our motivation and planned functions we wanted to implement. Now, let’s start explaining them one by one.
We also have another study notes for all of the telegram bot projects in here, it gives you the basics on how to create your own bot and further descriptions on different functions and attributes on the package python-telegram-bot.
/trip – A basic Station A to Station B search
We start with the basic trip searching function just like the original app. We used CommandHandler to create the command, InlineKeyboardMarkup to create buttons for stations, CallbackQueryHandler to handle the button actions, and update.message.reply_text to send message back to the users.
await update.message.reply_text( "🚌 Choose Your Starting Station.", reply_markup=build_location_keyboard("trip","start") )
Buttons for stations and departure time
When the user called the command /trip, the bot will update the message into a list of buttons thanks to the InlineKeyboardMarkup, it passes a string of message with three components, seperated with colon. f”{session}:{step}:{loc}”, it will then pass into handle_callback() and used to indentify different command using session, then it identifies the current state using steps (since stations can be either start or destination), and eventually the station details in loc will be passed to next step.
SaarFahrplan is a public transport app to provide real-time information and services related to public transportation in Saarland. Its target users are the people who live and travel in Saarland for general purpose. When it comes to a smaller group of users, for example Uni students, some functionalities might be redundant and we may optimized some common use case for better user experience.
Common use cases for students
Instead of searching different connections, Uni students’ timetable are usually consistent and repetitive. There are a few places where they always go, for example:
Go to the Uni
Go to the Mensa
Go to the Dormintory
Go to the City
Go to the Supermarket
Another scenario will be when we are in somewhere new, we would like to know how to go back home (dormitory).
Key functions on our telegram bot
Based on the above use case, So we create our own app/bot that only accommodate them.
My personal study notes on HAFAS, a public transport management system used around europe
What is HAFAS?
The HaCon Timetable Information System (HAFAS) is a software for timetable information from the company HaCon (Hannover Consulting). – Wikipedia
Basically, the entire Germany, Luxembourg, and the surrounding countries/regions use HAFAS to obtain depatures and stations details. This centralized software system can be visited using APIs. Different service providers may create their own HAFAS backend endpoint that exposes a public transport API over HTTP for customized usage. For example, you can send your HTTP requests to Deutsche Bahn (DB) if you have the access of their API, which can be found in DB API Marketplace.
What can we do with HAFAS?
There are four basic functions that the system has provided:
TripSearch – return connections from location A to location B
StationBoard – return all arrivals and departures of a certain station
LocGeoPos – return list of stations in a give range of area
LocMatch – return list of stations based on the keyword given
These includes most of the functionalities for users. For example, when we tried to plan our journey on DB navigator, the app uses TripSearch to show you connections; when we type the stations in the search box, the app LocMatch to give you related results.