- Improved formatting and readability in config.py and other files by adding line breaks. - Introduced INIT_DATA_MAX_AGE_SECONDS to enforce replay protection for Telegram initData. - Updated validate_init_data function to include max_age_seconds parameter for validation. - Enhanced API to reject old initData based on the new max_age_seconds setting. - Added tests for auth_date expiry and validation of initData in test_telegram_auth.py. - Updated README with details on the new INIT_DATA_MAX_AGE_SECONDS configuration.
Duty Teller (Telegram Bot)
A minimal Telegram bot boilerplate using python-telegram-bot v22 with the Application API.
Get a bot token
- Open Telegram and search for @BotFather.
- Send
/newbotand follow the prompts to create a bot. - Copy the token BotFather gives you.
Setup
-
Clone and enter the project
cd duty-teller -
Create a virtual environment (recommended)
python -m venv venv source venv/bin/activate # Linux/macOS # or: venv\Scripts\activate # Windows -
Install dependencies
pip install -r requirements.txt -
Configure the bot
cp .env.example .envEdit
.envand setBOT_TOKENto the token from BotFather. -
Miniapp access (calendar)
To allow access to the calendar miniapp, setALLOWED_USERNAMESto a comma-separated list of Telegram usernames (without@). Users inADMIN_USERNAMESalso have access; the admin role is reserved for future bot commands and API features. If both are empty, no one can open the calendar. -
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 to1to allow/api/dutieswithout 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 --buildStop with
Ctrl+Cordocker 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 --buildFor production deployments you may use Docker secrets or your orchestrator’s env instead of a
.envfile.
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 theApplication, registers handlers, runs polling and FastAPI in a thread.config.py– LoadsBOT_TOKEN,DATABASE_URL,ALLOWED_USERNAMES,ADMIN_USERNAMES,CORS_ORIGINS, etc. from env; exits ifBOT_TOKENis missing.api/– FastAPI app (/api/duties), Telegram initData validation, static webapp mount.db/– SQLAlchemy models, session, repository, schemas.alembic/– Migrations (useconfig.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).