Real-time Turin public transport (GTT) tracker on Telegram.
Just want to use it? Open @tramtramgtt_bot on Telegram and press Start — no setup needed.
If the hosted bot is no longer available, you can self-host your own instance using this repository (see Self-hosting below).
TramTram monitors GTT bus and tram arrivals in real time via the Muoversi a Torino OpenTripPlanner API and keeps Telegram messages updated in place every 15 seconds.
Each user gets their own independent dashboard — trips and data are fully isolated.
| Feature | Description |
|---|---|
| Live dashboard | One message per trip, edited in place every 15 s with next arrivals. Created on /start, auto-deleted after 30 minutes. Green dot (🟢) = GPS realtime. |
| Quick stop query | Send any stop number to see all lines at that stop, live for 15 minutes, with a STOP button to dismiss. |
| Add/remove wizard | /add and /remove guide you step by step — no config files to edit. |
| Multi-user | Every Telegram user has their own trips; data is stored per chat ID. |
| Persistent state | Trips and message IDs survive bot restarts. |
| Night pause | Optional: no API calls between 02:00 and 07:00. Set "night_pause": false in config for 24/7 updates. |
| Command | What it does |
|---|---|
/start |
Clean up old messages and create a live dashboard (auto-deleted after 30 min) |
/add |
Wizard to add a new trip or combo |
/remove |
Wizard to remove a trip or combo |
/refresh |
Force an immediate dashboard update |
/cancel |
Abort the current wizard |
<number> |
Send a stop ID to get live arrivals for 15 minutes |
You organize your monitored routes into trips, combos, and legs:
Trip (e.g. "Home → Office")
└── Combo (e.g. "Direct 42", "Combo 16 + 4")
└── Leg
├── line (e.g. "42")
├── stop_id_boarding (where you get on)
└── stop_id_alighting (where you get off)
- A trip is a named origin-destination pair (e.g. "Home → Office").
- A combo is one way to make that trip — it can have one or more legs (direct or with transfers).
- A leg is a single bus/tram ride: which line, where you board, and where you alight.
All of this is configured interactively through the /add wizard. To find GTT stop IDs, send any number to the bot and it will show you the stop name, or look them up on Muoversi a Torino.
Dashboard:
🚋 Home → Office
⏱ 08:32:15
⏳ expires in 28 min
━━━ Direct 42 ━━━
🚌 42
OSPEDALE MAURIZIANO ➜ PORTA NUOVA
⏳ 🟢3' 🟢15' 30'
Quick stop query:
🚏 PORTA NUOVA (40)
⏱ 08:32:15
⏳ expires in 14 min
🚌 42 ➜ SASSI
⏳ 🟢5' 🟢18' 32'
🚌 66 ➜ LINGOTTO
⏳ 🟢2' 12'
🚌 4 ➜ FALCHERA
⏳ 🟢8'
[🛑 STOP]
If the public bot goes offline, you can run your own instance. All you need is a server (or even your own computer), Python, and a Telegram bot token.
- Python 3.10+
- A Telegram bot token from @BotFather
git clone https://github.com/lucaosti/tramtram.git
cd tramtram
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Edit .env and set your BOT_TOKEN
python main.pytramtram/
├── main.py # Entire bot (single-file)
├── .env # BOT_TOKEN (git-ignored, you create this)
├── .env.example # Template for .env
├── config.json # Optional global settings (git-ignored)
├── data/ # Per-user data (git-ignored, auto-created)
│ └── <chat_id>.json # One file per Telegram user
├── requirements.txt # Python dependencies
├── README.md
└── .gitignore
The only required configuration. Create a .env file (or export the variable) with your bot token:
BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrSTUvwxYZ
If this file is absent, sensible defaults are used. Create it only to customize behavior:
{
"otp_base_url": "https://plan.muoversiatorino.it/otp/routers/mato/index",
"polling_interval_seconds": 15,
"night_pause": { "start_hour": 2, "end_hour": 7 }
}Set "night_pause": false to disable the pause and run 24/7.
| Field | Description | Default |
|---|---|---|
otp_base_url |
Base URL of the OTP API | https://plan.muoversiatorino.it/otp/routers/mato/index |
polling_interval_seconds |
Seconds between dashboard updates | 15 |
night_pause |
false = no pause (24/7). Object with start_hour/end_hour = pause in that window. |
{ "start_hour": 2, "end_hour": 7 } |
Each user's trips and message state are automatically saved in data/<chat_id>.json. This directory is created on first use — no manual setup needed.
Example file (data/123456789.json):
{
"trips": [
{
"name": "Home → Office",
"combos": [
{
"name": "Direct 42",
"legs": [
{ "line": "42", "stop_id_boarding": "1132", "stop_id_alighting": "40" }
]
}
]
}
],
"state": {
"dashboard_msgs": [101, 102],
"dashboard_expires": 1709500000,
"stop_msgs": {},
"all_msg_ids": [101, 102]
}
}The bot is a single Python file (main.py) built on python-telegram-bot and httpx.
Data flow:
- On startup, the bot loads all user files from
data/and starts a background update loop. - The update loop runs every 15 seconds (skipping night hours when
night_pauseis set in config). It collects all stop IDs needed across every active user, fetches stoptimes and stop names from the OTP API in a single parallel batch, and edits each user's Telegram messages with fresh data. - When a user sends
/start, old messages are deleted and new dashboard messages are created (one per trip). These messages are live-updated for 30 minutes, then automatically deleted. - When a user sends a stop number, a live message is created that auto-updates for 15 minutes, then self-destructs.
Key design decisions:
- Messages are edited in place (no spam) and deleted on cleanup.
- Stop IDs are deduplicated across users, so the same stop is only fetched once per cycle regardless of how many users monitor it.
- Wizards use a single-message editing pattern: one bot message is reused for every step, and user messages are deleted immediately.
- The Europe/Rome timezone is computed manually (EU DST rules) to avoid a
pytz/zoneinfodependency.
To keep the bot running permanently on a Linux server, use systemd:
Create /etc/systemd/system/tramtram.service:
[Unit]
Description=TramTram Telegram Bot
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/tramtram
EnvironmentFile=/path/to/tramtram/.env
ExecStart=/path/to/tramtram/.venv/bin/python main.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable tramtram
sudo systemctl start tramtram
# Check status
sudo systemctl status tramtram
# View logs
journalctl -u tramtram -fIf you're upgrading from an older version that had bot_token and chat_id inside config.json, the bot handles migration automatically on first startup:
- Trips are moved to
data/<chat_id>.json. config.jsonis rewritten with only global settings.state.jsonis deleted.
You just need to create the .env file with your BOT_TOKEN.
| Package | Purpose |
|---|---|
| python-telegram-bot | Telegram Bot API framework |
| httpx | Async HTTP client for OTP API calls |
| python-dotenv | Load .env file into environment variables |
MIT