Skip to content

aoyn1xw/Untis-watcher

Repository files navigation

Untis Watcher

A Python-based Telegram bot that monitors WebUntis for timetable changes and sends AI-generated notifications when updates are detected.

Features

  • Fetches timetable data from WebUntis via JSON-RPC API, with optional REST API token support
  • Detects changes in lessons, rooms, teachers, or cancellations
  • AI-powered summaries using GitHub Models (GPT-5)
  • Automatic Telegram notifications
  • Stateful watcher with persistent state.json storage to avoid duplicate notifications across restarts
  • Continuous monitoring with configurable polling interval
  • Secure error handling: credentials and tokens are automatically scrubbed from all log output
  • Optional system tray integration on Windows; automatically falls back to headless mode when unavailable

Requirements

  • Python 3.9+
  • A WebUntis account
  • A Telegram bot token and your chat ID
  • A GitHub personal access token (for GitHub Models AI)

Installation

Clone the repository:

git clone https://github.com/aoyn1xw/Untis-watcher.git
cd Untis-watcher

Create and activate a virtual environment:

python -m venv venv
venv\Scripts\activate   # Windows
source venv/bin/activate  # macOS/Linux

Install dependencies:

pip install -r requirements.txt

Note: On Windows, the bot can use pystray to run in the system tray, which requires Pillow. If tray dependencies are unavailable (for example on headless Linux/VPS), it runs directly in terminal mode.

Configuration

1. Create a Telegram Bot

  1. Open Telegram and search for @BotFather
  2. Send /newbot and follow the instructions
  3. Save the bot token (looks like 123456789:ABCdefGHIjklMNOpqrsTUVwxyz)
  4. Start a chat with your new bot

2. Get Your Telegram Chat ID

The most reliable method is to use the Telegram API directly:

  1. Add your bot token to .env first (see step 5)
  2. Send any message to your bot in Telegram (e.g. "Hi")
  3. Run this in the project directory:
python -c "
import os, requests
for line in open('.env'):
    line = line.strip()
    if line and not line.startswith('#') and '=' in line:
        k, v = line.split('=', 1)
        os.environ[k.strip()] = v.strip()
token = os.getenv('TELEGRAM_TOKEN')
r = requests.get(f'https://api.telegram.org/bot{token}/getUpdates')
print(r.json())
"
  1. Look for 'chat': {'id': 123456789, ...} in the output — that number is your chat ID.

3. Get GitHub Token for AI Models

  1. Go to GitHub Settings → Developer Settings → Personal Access Tokens
  2. Click "Generate new token" → "Generate new token (classic)"
  3. Give it a name like "Untis Watcher"
  4. Select the models:read scope
  5. Generate and save the token (looks like github_pat_...)

4. Find Your WebUntis Element ID

  1. Log into WebUntis in your browser
  2. Open Developer Tools (F12)
  3. Go to the Network tab
  4. Navigate to your timetable in WebUntis
  5. Look for a request to data or grid?timetableType=MY_TIMETABLE
  6. Check the Request URL or look in the JWT token for person_id or elementId
  7. Save this number (e.g., 616)

5. Create .env File

Create a .env file in the project root with the following content:

# WebUntis Configuration
UNTIS_SERVER="your-school.webuntis.com"
UNTIS_SCHOOL="your-school-slug"
UNTIS_USER="your.username"
UNTIS_PASSWORD="your_password"
UNTIS_ELEMENT_TYPE="5"
UNTIS_ELEMENT_ID="your_element_id"

# GitHub Models (AI)
GITHUB_TOKEN="your_github_token"
AI_MODEL="gpt-5"

# Telegram
TELEGRAM_TOKEN="your_telegram_bot_token"
TELEGRAM_CHAT_ID="your_telegram_chat_id"

# Optional Settings
POLL_INTERVAL="300"
DAYS_AHEAD="7"

Example with placeholder values:

UNTIS_SERVER="example-school.webuntis.com"
UNTIS_SCHOOL="example-school"
UNTIS_USER="student.username"
UNTIS_PASSWORD="replace_me"
UNTIS_ELEMENT_TYPE="5"
UNTIS_ELEMENT_ID="123456"

GITHUB_TOKEN="github_pat_replace_me"
AI_MODEL="gpt-5"

TELEGRAM_TOKEN="123456789:replace_me"
TELEGRAM_CHAT_ID="123456789"

POLL_INTERVAL="300"
DAYS_AHEAD="7"

Configuration Notes:

  • UNTIS_SERVER: Your WebUntis domain (from the URL)
  • UNTIS_SCHOOL: The school slug (short name in the URL, not the full name)
  • UNTIS_ELEMENT_TYPE: Usually 5 for student, 1 for class
  • UNTIS_ELEMENT_ID: Your student/person ID from WebUntis
  • POLL_INTERVAL: Seconds between checks (300 = 5 minutes)
  • DAYS_AHEAD: How many days of timetable to fetch

Usage

Windows (System Tray)

Run the bot:

python main.py

Or double-click main.py to run it directly.

The bot will:

  1. Start in the background with a system tray icon
  2. Load or create its persistent state.json baseline
  3. Monitor your timetable every 5 minutes
  4. Show notifications only for real timetable changes

To quit: Right-click the system tray icon and select "Quit"

First run: The bot will save your current timetable to state.json as a baseline and won't send notifications until actual changes are detected.

Linux/macOS (Terminal)

Run in the foreground:

python main.py

Or use screen/tmux to run in background (see Deployment Options below).

Deployment Options

Windows (System Tray - Recommended)

If tray dependencies are available, the bot runs in the system tray when started. Look for the blue circle icon in your taskbar notification area.

To start automatically on boot:

  1. Press Win + R and type shell:startup
  2. Create a shortcut to your Python script:
    • Right-click → New → Shortcut
    • Location: C:\Users\ayon1xw\Documents\GitHub\Untis-watcher\venv\Scripts\pythonw.exe "C:\Users\ayon1xw\Documents\GitHub\Untis-watcher\main.py"
    • Name it "Untis Watcher"
  3. The bot will now start silently when you log in

Alternative: Task Scheduler

  1. Open Task Scheduler (Win + R, type taskschd.msc)
  2. Click "Create Basic Task"
  3. Name: "Untis Watcher"
  4. Trigger: "When I log on"
  5. Action: "Start a program"
  6. Program: C:\Users\ayon1xw\Documents\GitHub\Untis-watcher\venv\Scripts\pythonw.exe
  7. Arguments: main.py
  8. Start in: C:\Users\ayon1xw\Documents\GitHub\Untis-watcher
  9. Finish

VPS/Cloud Server (Linux)

Using screen (simple):

screen -S untis-watcher
python main.py
# Press Ctrl+A, then D to detach
# To reattach: screen -r untis-watcher

Using systemd (auto-start):

Create a service file:

sudo nano /etc/systemd/system/untis-watcher.service

Add this content:

[Unit]
Description=Untis Watcher Bot
After=network.target

[Service]
Type=simple
User=youruser
WorkingDirectory=/path/to/Untis-watcher
ExecStart=/path/to/Untis-watcher/venv/bin/python main.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable untis-watcher
sudo systemctl start untis-watcher
sudo systemctl status untis-watcher

Docker (Optional)

Can be containerized for easier deployment on cloud platforms.

Building Executable (Optional)

You can build a standalone Windows executable using PyInstaller:

python build_exe.py

Or use the batch file:

.\build.bat

The executable will be created in the dist/ folder. Make sure to place your .env file in the same directory as the executable.

Note: The UntisWatcher.spec file includes all necessary hidden imports including dotenv to ensure the executable works correctly.

Troubleshooting

403 Forbidden Error

  • Check that UNTIS_SCHOOL is the short slug (e.g., school-name), not the full name
  • Verify your UNTIS_ELEMENT_ID is correct
  • Make sure your WebUntis credentials are valid

Bot Can't Send Messages

  • Ensure TELEGRAM_CHAT_ID is your personal ID, not the bot's ID
  • Make sure you've started a chat with your bot first (send any message to it)
  • If you see [WARNING] Could not send startup greeting, your TELEGRAM_TOKEN is invalid — create a new bot via @BotFather and update .env

Invalid Telegram Token

Logs will show:

[WARNING] Could not send startup greeting: The token `<TELEGRAM_TOKEN>` was rejected by the server.

Note: The actual token value is automatically redacted from logs for security. To fix:

  1. Open @BotFather in Telegram → /mybots → select your bot → API Token
  2. Copy the token and update TELEGRAM_TOKEN in .env

AI Model Errors

  • Verify your GitHub token has models:read permissions
  • Check that AI_MODEL is set to gpt-5 or another valid model
  • Ensure you're within GitHub Models rate limits

No Changes Detected

  • The bot only notifies on deterministic changes between state.json and the latest WebUntis fetch, not on every poll or restart
  • Check that POLL_INTERVAL isn't too long
  • Verify the timetable data is being fetched correctly

Executable "No module named 'dotenv'" Error

  • This has been fixed in the latest version by adding dotenv to hiddenimports in UntisWatcher.spec
  • Rebuild the executable using python build_exe.py

Project Structure

Untis-watcher/
├── main.py          # Entry point and stateful watcher loop
├── timetable.py     # WebUntis API integration (JSON-RPC)
├── detector.py      # Change detection logic
├── ai.py           # GitHub Models integration
├── notifier.py      # Telegram notifications
├── storage.py       # Persistent timetable storage
├── config.py        # Environment variable loading
├── requirements.txt # Python dependencies
├── .env            # Configuration (not in git)
└── state.json       # Last known WebUntis state (not in git; generated on first run)

How It Works

  1. Authentication: Logs into WebUntis using a WebUntis-style JSON-RPC session (or optional REST bearer token credentials)
  2. State Loading: Reads the previous WebUntis snapshot from state.json if it exists
  3. Fetching: Validates the session, retrieves the weekly timetable for your student ID, and normalizes lesson fields
  4. Deep Comparison: Normalizes previous and current datasets before comparing them deterministically
  5. Notification: Sends an AI-generated Telegram summary only when real differences exist
  6. Storage: Overwrites state.json with the latest data after a successful fetch; failed fetches keep the previous state intact
  7. Secure Logging: All log output automatically redacts TELEGRAM_TOKEN, UNTIS_PASSWORD, and AI_API_KEY to prevent credential leaks

Manual CI/CD (GitHub Actions)

This repository includes a manual-only workflow at .github/workflows/manual-ci-cd.yml.

  • It does not run on push or pull request.
  • Trigger it from GitHub: ActionsManual CI/CDRun workflow.
  • Input run_live_untis_check:
    • true: run a live WebUntis smoke fetch using GitHub Secrets
    • false: skip live API checks
  • Input build_windows_exe:
    • true: after CI passes, build/upload UntisWatcher.exe as an artifact
    • false: run CI stages only

CI stages (step by step)

  1. CI 1/3 – Static Checks
    • installs dependencies
    • compiles all core Python files with py_compile
    • verifies core imports
  2. CI 2/3 – Logic Smoke Tests
    • runs detector hash/diff assertions
    • runs storage save/load roundtrip assertion
  3. CI 3/3 – Live WebUntis Smoke Check (optional)
    • validates required secrets
    • logs in via current timetable path (REST or JSON-RPC based on env)
    • fetches timetable once and verifies lesson schema

GitHub Secrets to add

Required for live WebUntis check:

  • UNTIS_SERVER
  • UNTIS_SCHOOL
  • UNTIS_USER
  • UNTIS_PASSWORD
  • UNTIS_ELEMENT_ID

Optional but recommended:

  • UNTIS_ELEMENT_TYPE (defaults to 5 when omitted)
  • UNTIS_TENANT_ID
  • UNTIS_CLIENT_ID
  • UNTIS_API_PASSWORD

The uploaded artifact appears in the workflow run summary under Artifacts.

Change Types Detected

  • Cancellations (Entfall): Free periods
  • Changes (Änderung): Room, teacher, or time modifications
  • Exams (Prüfung): Detected by keywords in subject names
  • Additions: New lessons added to timetable
  • Removals: Lessons removed from timetable

Disclaimer

This project uses WebUntis JSON-RPC API endpoints that are not officially documented, plus optional WebUntis REST endpoints when REST credentials are configured. Functionality may break if WebUntis changes its internal API.

Important:

  • Use responsibly and ensure compliance with your school's policies
  • Keep your credentials secure (never commit .env to git)
  • Be mindful of API rate limits
  • This is for personal use only

Contributing

Contributions are welcome! Feel free to:

  • Report bugs
  • Suggest features
  • Submit pull requests

License

See LICENSE file for details.

Support

If you encounter issues:

  1. Check the troubleshooting section above
  2. Review your .env configuration
  3. Open an issue on GitHub with error details

Credits

The WebUntis JSON-RPC session flow, timetable options, session validation idea, and cookie handling in this project were improved with inspiration from SchoolUtils/WebUntis, an MIT-licensed Node.js WebUntis API wrapper by Nils Bergmann and contributors. This project is not affiliated with Untis GmbH or SchoolUtils.

About

A simple bot that watches you timetable and notifies you when something changes via telegram (shit is not stable)

Topics

Resources

License

Stars

Watchers

Forks

Contributors