Files
duty-teller/README.md
Nikolay Tatarinov dd960dc5cc Enhance Telegram bot functionality and improve error handling
- Introduced a new function to set the default menu button for the Telegram bot's Web App.
- Updated the initData validation process to provide detailed error messages for authorization failures.
- Refactored the validate_init_data function to return both username and reason for validation failure.
- Enhanced the web application to handle access denial more gracefully, providing users with hints on how to access the calendar.
- Improved the README with additional instructions for configuring the bot's menu button and Web App URL.
- Updated tests to reflect changes in the validation process and error handling.
2026-02-17 19:08:14 +03:00

4.8 KiB
Raw Blame History

Duty Teller (Telegram Bot)

A minimal Telegram bot boilerplate using python-telegram-bot v22 with the Application API.

Get a bot token

  1. Open Telegram and search for @BotFather.
  2. Send /newbot and follow the prompts to create a bot.
  3. Copy the token BotFather gives you.

Setup

  1. Clone and enter the project

    cd duty-teller
    
  2. Create a virtual environment (recommended)

    python -m venv venv
    source venv/bin/activate   # Linux/macOS
    # or: venv\Scripts\activate  # Windows
    
  3. Install dependencies

    pip install -r requirements.txt
    
  4. Configure the bot

    cp .env.example .env
    

    Edit .env and set BOT_TOKEN to the token from BotFather.

  5. Miniapp access (calendar)
    To allow access to the calendar miniapp, set ALLOWED_USERNAMES to a comma-separated list of Telegram usernames (without @). Users in ADMIN_USERNAMES also have access; the admin role is reserved for future bot commands and API features. If both are empty, no one can open the calendar.
    Mini App URL: When configuring the bot's menu button or Web App URL (e.g. in @BotFather or via setChatMenuButton), use the URL with a trailing slash, e.g. https://your-domain.com/app/. A redirect from /app to /app/ can cause the browser to drop the fragment that Telegram sends, which breaks authorization.
    How to open: Users must open the calendar via the bot's menu button (⋮ → «Календарь» or the configured label) or a Web App inline button. If they use «Open in browser» or a direct link, Telegram may not send user data (tgWebAppData), and access will be denied.
    BOT_TOKEN: The server that serves /api/duties (e.g. your production host) must have in .env the same bot token as the bot from which users open the Mini App. If the token differs (e.g. test vs production bot), validation returns "hash_mismatch" and access is denied.

  6. Optional env

    • DATABASE_URL DB connection (default: sqlite:///data/duty_teller.db).
    • MINI_APP_BASE_URL Base URL of the miniapp (for documentation / CORS).
    • MINI_APP_SKIP_AUTH Set to 1 to allow /api/duties without Telegram initData (dev only; insecure).
    • INIT_DATA_MAX_AGE_SECONDS Reject Telegram initData older than this (e.g. 86400 = 24h). 0 = disabled (default).
    • CORS_ORIGINS Comma-separated allowed origins for CORS, or leave unset for *.

Run

python main.py

The bot runs in polling mode. Send /start or /help to your bot in Telegram to test.

Run with Docker

Ensure .env exists (e.g. cp .env.example .env) and contains BOT_TOKEN.

  • Dev (volume mount; code changes apply without rebuild):

    docker compose -f docker-compose.dev.yml up --build
    

    Stop with Ctrl+C or docker compose -f docker-compose.dev.yml down.

  • Prod (no volume; runs the built image; restarts on failure):

    docker compose -f docker-compose.prod.yml up -d --build
    

    For production deployments you may use Docker secrets or your orchestrators env instead of a .env file.

Production behind a reverse proxy: When the app is behind nginx/Caddy etc., request.client.host is usually the proxy (e.g. 127.0.0.1). The "private IP" bypass (allowing requests without initData from localhost) then applies to the proxy, not the real client. Either ensure the Mini App always sends initData, or forward the real client IP (e.g. X-Forwarded-For) and use it for that check. See api/app.py _is_private_client for details.

Project layout

  • main.py Builds the Application, registers handlers, runs polling and FastAPI in a thread.
  • config.py Loads BOT_TOKEN, DATABASE_URL, ALLOWED_USERNAMES, ADMIN_USERNAMES, CORS_ORIGINS, etc. from env; exits if BOT_TOKEN is missing.
  • api/ FastAPI app (/api/duties), Telegram initData validation, static webapp mount.
  • db/ SQLAlchemy models, session, repository, schemas.
  • alembic/ Migrations (use config.DATABASE_URL).
  • handlers/ Command and error handlers; add new handlers here.
  • webapp/ Miniapp UI (calendar, duty list); served at /app.
  • requirements.txt Pinned dependencies (PTB, FastAPI, SQLAlchemy, Alembic, etc.).

To add commands, define async handlers in handlers/commands.py (or a new module) and register them in handlers/__init__.py.

Tests

Install dev dependencies and run pytest:

pip install -r requirements-dev.txt
pytest

Tests cover api/telegram_auth (validate_init_data, auth_date expiry), config (is_admin, can_access_miniapp), and the API (date validation, 403/200 with mocked auth, plus an E2E auth test without auth mocks).