UdS Fahrplan Bot

UdS Fahrplan Bot

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

Read more
🍽 🥨 MensaarLecker -- A beloved tool to find out Mensa Ladies' favourite menu using Selenium🥨 🍽

🍽 🥨 MensaarLecker -- A beloved tool to find out Mensa Ladies' favourite menu using Selenium🥨 🍽

Repository: MensaarLecker

As an UdS Student,
Are you tired of seeing french fries🍟 3 times a week, or wondering when I can have the best pizza 🍕 in the Mensacafe?
MensaarLecker aims to collect all the data from Menu 1, 2, and Mensacafe to trace your favourite, or Mensa Ladies’, favourite menu!


🆕 Updates

05.08 – Telegram Bot @Mensaar_Bot are published.

(See my development blog in here! MensaarLecker Development Log 3 – Telegram Bot Deployment and Integration)

04.21 – HTW menus are now added to the statistics.


🥗 Description

A fully automated scraper and static website for the Saarbrücken Mensa, powered by Python, Selenium, Google Sheets, and GitHub Actions.

Read more
MensaarLecker Development Log (3) -- Telegram Bot Deployment and Integration

MensaarLecker Development Log (3) -- Telegram Bot Deployment and Integration

This blog post is trying to tell you:

  • My personal experience when developing a web crawler using Selenium
  • Explained with examples from my Repository: MensaarLecker
  • For further details, feel free to **Try the bot: @Mensaar_Bot

New Features

Previous post: MensaarLecker Development Log (2) – Web Developing and GitHub Workflow

HTW menu

After the website is published, we noticed that people now prefer to have lunch in HTW Campus Rotenbühl. Since their menu come from the same site, it is very easy to introduce new menu to our project.

New website layout

Before we used two pages to store today’s menu and the menu history. And we think in general, all menu are simple texts, so we can put all contents into the index page without any problem. We can switch the visibility using JavaScript:

switch.js
1
2
3
4
function show(id) {
document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
document.getElementById(id).classList.add('active');
}

And for the whole HTML code again we stuffed in the python script to for our daily Github workflow to run.

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<h1>Mensaar Menu - {today}</h1>
<div class="buttons">
<button class="button" onclick="show('today-uds')">📅 UDS Today</button>
<button class="button" onclick="show('today-htw')">📅 HTW Today</button>
<button class="button" onclick="show('full-uds')">📋 Full UDS</button>
<button class="button" onclick="show('full-htw')">📋 Full HTW</button>
</div>

<div id="today-uds" class="section active">
<h2>UDS – Today's Menu</h2>
{meal_cards(uds_data)}
</div>

<div id="today-htw" class="section">
<h2>HTW – Today's Menu</h2>
{meal_cards(htw_data)}
</div>

<div id="full-uds" class="section">
<h2>📋 Full UDS Menu</h2>
<table id="uds-table">
<thead><tr>
<th>Date</th>
<th>Counter</th>
<th>Meal</th>
<th>Component 1</th>
<th>Component 2</th>
<th>Component 3</th>
<th>Component 4</th>
<th>Component 5</th>
</tr></thead>
<tbody>{table_rows(uds_data)}</tbody>
</table>
</div>

<div id="full-htw" class="section">
<h2>📋 Full HTW Menu</h2>
<table id="htw-table">
<thead><tr>
<th>Date</th>
<th>Counter</th>
<th>Meal</th>
<th>Component 1</th>
<th>Component 2</th>
<th>Component 3</th>
<th>Component 4</th>
<th>Component 5</th>
</tr></thead>
<tbody>{table_rows(htw_data)}</tbody>
</table>
</div>
Read more
UdS Fahrplan Bot Development Log (5) -- Explanations on bot sessions and requests

UdS Fahrplan Bot Development Log (5) -- Explanations on bot sessions and requests

This blog post is trying to tell you:

  • Why session is needed when multiple command using similar workflow?
  • The details of HTTP request
  • Detailed explanations on the implementation of udsfahrbot

Previous post: UdS Fahrplan Bot Development Log (4) – Implementation on /sethome and /home function

Brief Walkthrough on Telegram Bot (Cont.)

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.

Read more
HAFAS Bitmask Calculator

HAFAS Bitmask Calculator

Web Demo

This is a simple web-based tool to help you decode and encode bitmasks used by the HAFAS (HaCon Fahrplan-Auskunfts-System) API.
It visually maps a decimal bitmask to the transport modes it represents and vice versa.

🔍 What is HAFAS?

HAFAS is a timetable and journey planning system widely used across European transport networks, including:

In HAFAS APIs, transport modes (ICE trains, buses, trams, ferries, etc.) are often encoded in a bitmask
a single number where each bit (1, 2, 4, 8, …) represents a specific type of transport.


✨ Features

  • 🔢 Enter a number to decode it into selected transport modes
  • ✅ Click transport icons to encode a bitmask value
  • 🔄 Switch between different mode sets (DB, SaarVV, Luxembourg, HAFAS client spec)
  • ⚠️ Input capped to valid 11-bit range (0–2047)
  • 💡 Uses the official hafas-client mode bitmask as one profile

Read more
UdS Fahrplan Bot Development Log (4) -- Implementation on /sethome and /home function

UdS Fahrplan Bot Development Log (4) -- Implementation on /sethome and /home function

This blog post is trying to tell you:

  • How to build your own telegram bot
  • Detailed explanations on the implementation of udsfahrbot

Previous post: UdS Fahrplan Bot Development Log (3) – Implementation on /trip and /depart function

Brief Walkthrough on Telegram Bot (Cont.)

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.

bot_spawn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
async def handle_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):
with open(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

if not user_found:
# Add new user entry
data_list.append({"user_id": user_id, "home_id": station, "home_name": name})

# Save updated data
with open(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}")

async def spawn(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")
)
Read more
UdS Fahrplan Bot Development Log (3) -- Implementation on /trip and /Depart function

UdS Fahrplan Bot Development Log (3) -- Implementation on /trip and /Depart function

This blog post is trying to tell you:

  • How to build your own telegram bot
  • Detailed explanations on the implementation of udsfahrbot

Previous post: UdS Fahrplan Bot Development Log (2) – Planning for telegram bot

Brief Walkthrough on Telegram Bot

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.

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.

bot_trip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def build_location_keyboard(session, step, exclude=None):
buttons = [
[InlineKeyboardButton(text=loc, callback_data=f"{session}:{step}:{loc}")]
for loc in keyboard_flat if loc != exclude
]
buttons += [[
InlineKeyboardButton(text="More Stations",
callback_data=f"{session}:more:{step}")
]]
if exclude is not None:
buttons += [[
InlineKeyboardButton(text="Back",
callback_data=f"{session}:{step}:back")
]]
return InlineKeyboardMarkup(buttons)

async def trips(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data["trip_session"] = {}

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.

Read more
UdS Fahrplan Bot Development Log (2) -- Planning for telegram bot

UdS Fahrplan Bot Development Log (2) -- Planning for telegram bot

This blog post is trying to tell you:

  • The motivation of making my own telegram bot
  • Is UdSfahrplan “Reinventing the wheel”?

Saarfahrplan v.s. Uni Students

Previous post: UdS Fahrplan Bot Development Log (1) – Fetching data from HAFAS and its APIs through POST requests

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.

Read more
UdS Fahrplan Bot Development Log (1) -- Fetching data from HAFAS and its APIs through POST requests

UdS Fahrplan Bot Development Log (1) -- Fetching data from HAFAS and its APIs through POST requests

This blog post is trying to tell you:

  • 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.

Read more
MensaarLecker Development Log (2) -- Web Developing and GitHub Workflow

MensaarLecker Development Log (2) -- Web Developing and GitHub Workflow

This blog post is trying to tell you:

  • My personal experience when developing a web crawler using Selenium
  • Explained with examples from my Repository: MensaarLecker

Fetching Data from Web Development

Previous post: MensaarLecker Development Log (1) – Web Crawling

Continuing from last post, we have already implemented a script that collect the Mensa menu and stored it on Google Sheets. It is time to build our web interface to connect the database.

Fetch Data from Google Sheets using Publish

First, we need to publish our spreadsheet so that it is public to fetch the data.

  1. In the Spreadsheet, click Share → Change access to Anyone with the link.

  2. Click FileSharePublish to the web.

  3. Select Entire DocumentComma-separated values (.csv) and click Publish.

  4. Copy the public CSV link.

menu.py
1
2
3
4
5
6
7
8
9
10
11
SCRIPT_URL = {PUBLISH_LINK}

# Fetch JSON data
def fetch_menu():
try:
response = requests.get(SCRIPT_URL)
response.raise_for_status() # Raise error if bad response
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching menu: {e}")
return []

However, the script return no data, why?

caret.ebnf
1
2
3
Access to fetch at 'https://docs.google.com/spreadsheets/...' from origin 'null' has been blocked 
by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Read more
MensaarLecker Development Log (1) -- Web Crawling

MensaarLecker Development Log (1) -- Web Crawling

This blog post is trying to tell you:

  • My personal experience when developing a web crawler using Selenium
  • Explained with examples from my Repository: MensaarLecker

Motivation

Me and my friends hatelove the UdS Mensa so much! The infinite frozen food and french fries menus give us so much energy and motivation for the 5-hour afternoon coding marathon. However, no one actually knows how many potatoes they have exterminated throughout the week. We have a genius webpage created by some Schnitzel lover. Personally, I like its minimalistic layout and determination on Schnitzel searching.

However, we want more.

It’s not just Schnitzel; we want to know everything about their menu. We want to know what’s inside the mensa ladies’ brains when they design next week’s menu.

The desire never ends. We need more data, more details, more, More, MORE!

Developing Process

Our Goal here is simple:

  1. Scrape the Mensa menu every weekday and store it to Google Sheets

  2. Fetch the Data Collection from Google Sheets and update the website

Web Scraping

Read more
hexo-zhruby -- Implementing HTML Ruby tag in Hexo

hexo-zhruby -- Implementing HTML Ruby tag in Hexo

Implement the HTML tag <ruby> for Hexo using Tag Plugin feature. Provide auto pronounciation indication for Jyutping (Cantonese), Zhuyin (Taiwanese Mandarin), and Pinyin (Chinese Mandarin), and the default setting for general usage. Support Traditonal and Simplified Chinese characters.

Inspired by the hexo-ruby-character by jamespan.

Install

terminal
1
npm install hexo-zhruby --save

Use cases

Ruby (ルビ) is also known as Furigana (振り仮名). It contains two basic use cases:

  1. To clarify or indicate the pronunciation for readers
  2. Gikun, in which the characters have different pronunciations than they seem due to convention or for a specific context. For example, the pronunciation of 煙草 in Japanese is tabako (tobacco).

Usage

TLDR: Usage: {% tag rb|rt %}; Tag options: ruby_def, ruby_jy, ruby_py, ruby_zy.


Read more
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×