Nikolay Tatarinov b60111462a feat: implement day detail panel for calendar
- Introduced a new `dayDetail.js` module to manage the day detail panel functionality, allowing users to view detailed information about duties and events for each calendar day.
- Enhanced the calendar rendering in `calendar.js` to include visual indicators for duties and events, improving user interaction and experience.
- Updated CSS in `style.css` to style the day detail panel and its components, ensuring a responsive design for both desktop and mobile views.
- Refactored `hints.js` to export the `getDutyMarkerRows` function, facilitating better integration with the new day detail features.
- Added localization support for the day detail panel in `i18n.js`, including new translations for close button and event titles.
- Enhanced the initialization process in `main.js` to set up the day detail panel on application load.
2026-02-19 16:23:46 +03:00

Duty Teller (Telegram Bot)

A minimal Telegram bot boilerplate using python-telegram-bot v22 with the Application API. The bot and web UI support Russian and English (language from Telegram or DEFAULT_LANGUAGE).

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).
    • HTTP_PORT HTTP server port (default: 8080).
    • 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; leave unset for *.
    • ALLOWED_PHONES / ADMIN_PHONES Access by phone (user sets via /set_phone); comma-separated; comparison uses digits only.
    • DUTY_DISPLAY_TZ Timezone for the pinned duty message in groups (e.g. Europe/Moscow).
    • DEFAULT_LANGUAGE Default language when user language is unknown: en or ru (default in code: en).
    • EXTERNAL_CALENDAR_ICS_URL URL of a public ICS calendar (e.g. holidays). If set, those days are highlighted on the duty grid; users can tap «i» on a cell to see the event summary. Empty = no external calendar.

Run

python main.py

Or after pip install -e .:

duty-teller

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

Bot commands

  • /start — Greeting and user registration in the database.
  • /help — Help on available commands.
  • /set_phone [number] — Set or clear phone number (private chat only); used for access via ALLOWED_PHONES / ADMIN_PHONES.
  • /import_duty_schedule — Import duty schedule (only for ADMIN_USERNAMES / ADMIN_PHONES); see «Импорт расписания» below for the two-step flow.
  • /pin_duty — Pin the current duty message in a group (reply to the bots duty message); time/timezone for the pinned message come from DUTY_DISPLAY_TZ.

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.

The image is built from Dockerfile; on start, entrypoint.sh runs Alembic migrations then starts the app as user botuser.

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.

API

The HTTP server is FastAPI; the miniapp is served at /app.

  • GET /api/duties — List of duties (date params; auth via Telegram initData or, in dev, MINI_APP_SKIP_AUTH / private IP).
  • GET /api/calendar-events — Calendar events (including external ICS when EXTERNAL_CALENDAR_ICS_URL is set).

For production, initData validation is required; see the reverse-proxy paragraph above for proxy/headers.

Project layout

  • main.py Entry point: calls duty_teller.run:main. Alternatively, after pip install -e ., run the console command duty-teller (see pyproject.toml and duty_teller/run.py). The runner builds the Application, registers handlers, runs polling and FastAPI in a thread, and calls duty_teller.config.require_bot_token() so the app exits with a clear message if BOT_TOKEN is missing.
  • duty_teller/ Main package (install with pip install -e .). Contains:
    • config.py Loads BOT_TOKEN, DATABASE_URL, ALLOWED_USERNAMES, etc. from env; no exit on import; use require_bot_token() in the entry point when running the bot. Optional Settings dataclass for tests. PROJECT_ROOT for webapp path.
    • api/ FastAPI app (/api/duties, /api/calendar-events), dependencies.py (DB session, auth, date validation), static webapp mounted from PROJECT_ROOT/webapp.
    • db/ SQLAlchemy models, session (session_scope), repository, schemas.
    • handlers/ Telegram command and chat handlers; register via register_handlers(app).
    • i18n/ Translations and language detection (ru/en); used by handlers and API.
    • services/ Business logic (group duty pin, import); accept session from caller.
    • utils/ Shared date, user, and handover helpers.
    • importers/ Duty-schedule JSON parser.
  • alembic/ Migrations; config in pyproject.toml under [tool.alembic]; URL and metadata from duty_teller.config and duty_teller.db.models.Base. Run: alembic -c pyproject.toml upgrade head.
  • webapp/ Miniapp UI (calendar, duty list); served at /app.
  • tests/ Tests; helpers.py provides make_init_data for auth tests.
  • pyproject.toml Installable package (pip install -e .).

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

Импорт расписания дежурств (duty-schedule)

Команда /import_duty_schedule доступна только пользователям из ADMIN_USERNAMES. Импорт выполняется в два шага:

  1. Время пересменки — бот просит указать время и при необходимости часовой пояс (например 09:00 Europe/Moscow или 06:00 UTC). Время приводится к UTC и используется для границ смен при создании записей.
  2. Файл JSON — отправьте файл в формате duty-schedule (см. ниже).

Формат duty-schedule:

  • meta: обязательное поле start_date (YYYY-MM-DD), опционально weeks; количество дней определяется по длине строки duty.
  • schedule: массив объектов с полями:
    • name — ФИО (строка);
    • duty — строка с разделителем ;: каждый элемент соответствует дню с start_date по порядку. Пусто или пробелы — нет события; в, В, б, Б — дежурство; Н — недоступен; О — отпуск.

При повторном импорте дежурства в том же диапазоне дат для каждого пользователя заменяются новыми.

Tests

Run from the repository root (no src/ directory; package is duty_teller at the root). Use PYTHONPATH=. if needed:

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

CI (Gitea Actions): Lint (ruff), tests (pytest), security (bandit). If the workflow uses PYTHONPATH: src or bandit -r src, update it to match the repo layout (no src/).

Description
No description provided
Readme 2.8 MiB
v2.1.3 Latest
2026-03-07 00:40:47 +03:00
Languages
HTML 52.1%
Python 25.8%
TypeScript 20.7%
CSS 1.2%
Dockerfile 0.1%