docs: update README and configuration documentation for clarity
All checks were successful
CI / lint-and-test (push) Successful in 19s
All checks were successful
CI / lint-and-test (push) Successful in 19s
- Revised the README to improve clarity in instructions for accessing the calendar miniapp, including changes to the phrasing for consistency. - Updated the configuration documentation to enhance the description of the `EXTERNAL_CALENDAR_ICS_URL` setting, ensuring users understand its purpose and usage. - Improved the import format documentation by translating terms to English for better accessibility to a wider audience. - Enhanced the runbook with clearer instructions regarding access issues when using direct links, emphasizing the importance of using the bot's menu button.
This commit is contained in:
28
README.md
28
README.md
@@ -38,7 +38,7 @@ A minimal Telegram bot boilerplate using [python-telegram-bot](https://github.co
|
|||||||
5. **Miniapp access (calendar)**
|
5. **Miniapp access (calendar)**
|
||||||
Set `ALLOWED_USERNAMES` (and optionally `ADMIN_USERNAMES`) to allow access to the calendar miniapp; if both are empty, no one can open it. Users can also be allowed by phone via `ALLOWED_PHONES` / `ADMIN_PHONES` after setting a phone with `/set_phone`.
|
Set `ALLOWED_USERNAMES` (and optionally `ADMIN_USERNAMES`) to allow access to the calendar miniapp; if both are empty, no one can open it. Users can also be allowed by phone via `ALLOWED_PHONES` / `ADMIN_PHONES` after setting a phone with `/set_phone`.
|
||||||
**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.
|
**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.
|
**How to open:** Users must open the calendar **via the bot's menu button** (⋮ → "Calendar" 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.
|
**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. **Other options**
|
6. **Other options**
|
||||||
@@ -63,7 +63,7 @@ The bot runs in polling mode. Send `/start` or `/help` to your bot in Telegram t
|
|||||||
- **`/start`** — Greeting and user registration in the database.
|
- **`/start`** — Greeting and user registration in the database.
|
||||||
- **`/help`** — Help on available commands.
|
- **`/help`** — Help on available commands.
|
||||||
- **`/set_phone [number]`** — Set or clear phone number (private chat only); used for access via `ALLOWED_PHONES` / `ADMIN_PHONES`.
|
- **`/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.
|
- **`/import_duty_schedule`** — Import duty schedule (only for `ADMIN_USERNAMES` / `ADMIN_PHONES`); see **Duty schedule import** below for the two-step flow.
|
||||||
- **`/pin_duty`** — Pin the current duty message in a group (reply to the bot’s duty message); time/timezone for the pinned message come from `DUTY_DISPLAY_TZ`.
|
- **`/pin_duty`** — Pin the current duty message in a group (reply to the bot’s duty message); time/timezone for the pinned message come from `DUTY_DISPLAY_TZ`.
|
||||||
|
|
||||||
## Run with Docker
|
## Run with Docker
|
||||||
@@ -121,16 +121,16 @@ High-level architecture (components, data flow, package relationships) is descri
|
|||||||
|
|
||||||
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`.
|
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)
|
## Duty schedule import (duty-schedule)
|
||||||
|
|
||||||
Команда **`/import_duty_schedule`** доступна только пользователям из `ADMIN_USERNAMES` или `ADMIN_PHONES`. Импорт выполняется в два шага:
|
The **`/import_duty_schedule`** command is available only to users in `ADMIN_USERNAMES` or `ADMIN_PHONES`. Import is done in two steps:
|
||||||
|
|
||||||
1. **Время пересменки** — бот просит указать время и при необходимости часовой пояс (например `09:00 Europe/Moscow` или `06:00 UTC`). Время приводится к UTC и используется для границ смен при создании записей.
|
1. **Handover time** — The bot asks for the shift handover time and optional timezone (e.g. `09:00 Europe/Moscow` or `06:00 UTC`). This is converted to UTC and used as the boundary between duty periods when creating records.
|
||||||
2. **Файл JSON** — отправьте файл в формате duty-schedule.
|
2. **JSON file** — Send a file in duty-schedule format.
|
||||||
|
|
||||||
Формат: в корне JSON — объект **meta** с полем `start_date` (YYYY-MM-DD) и массив **schedule** с объектами `name` (ФИО) и `duty` (строка с разделителем `;`, символы **в/В/б/Б** — дежурство, **Н** — недоступен, **О** — отпуск). Количество дней задаётся длиной строки `duty`. При повторном импорте дежурства в том же диапазоне дат для каждого пользователя заменяются новыми.
|
Format: at the root of the JSON — a **meta** object with `start_date` (YYYY-MM-DD) and a **schedule** array of objects with `name` (full name) and `duty` (string with separator `;`; characters **в/В/б/Б** = duty, **Н** = unavailable, **О** = vacation). The number of days is given by the length of the `duty` string. On re-import, duties in the same date range for each user are replaced by the new data.
|
||||||
|
|
||||||
**Полное описание формата и пример JSON:** [docs/import-format.md](docs/import-format.md).
|
**Full format description and example JSON:** [docs/import-format.md](docs/import-format.md).
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
@@ -145,12 +145,12 @@ Tests cover `api/telegram_auth` (validate_init_data, auth_date expiry), `config`
|
|||||||
|
|
||||||
**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/`).
|
**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/`).
|
||||||
|
|
||||||
## Разработка (Contributing)
|
## Contributing
|
||||||
|
|
||||||
- **Коммиты:** в формате [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:` и т.д.
|
- **Commits:** Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, etc.
|
||||||
- **Ветки:** по [Gitea Flow](https://docs.gitea.io/en-us/workflow-branching/): основная ветка `main`, фичи и фиксы — в отдельных ветках.
|
- **Branches:** Follow [Gitea Flow](https://docs.gitea.io/en-us/workflow-branching/): main branch `main`, features and fixes in separate branches.
|
||||||
- **Изменения:** через **Pull Request** в Gitea; перед мержем рекомендуется запустить линтеры и тесты (`ruff check .`, `pytest`).
|
- **Changes:** Via **Pull Request** in Gitea; run linters and tests (`ruff check .`, `pytest`) before merge.
|
||||||
|
|
||||||
## Логи и ротация
|
## Logs and rotation
|
||||||
|
|
||||||
Для соответствия политике хранения логов (не более 7 дней) настройте ротацию логов при развёртывании: например [logrotate](https://manpages.ubuntu.com/logrotate), настройки логирования systemd или Docker (ограничение размера/времени хранения). Храните логи приложения не дольше 7 дней.
|
To meet the 7-day log retention policy, configure log rotation at deploy time: e.g. [logrotate](https://manpages.ubuntu.com/logrotate), systemd logging settings, or Docker (size/time retention limits). Keep application logs for no more than 7 days.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ All configuration is read from the environment (e.g. `.env` via python-dotenv).
|
|||||||
| **MINI_APP_SKIP_AUTH** | `1`, `true`, or `yes` | *(unset)* | If set, `/api/duties` is allowed without Telegram initData (dev only; insecure). |
|
| **MINI_APP_SKIP_AUTH** | `1`, `true`, or `yes` | *(unset)* | If set, `/api/duties` is allowed without Telegram initData (dev only; insecure). |
|
||||||
| **INIT_DATA_MAX_AGE_SECONDS** | integer | `0` | Reject Telegram initData older than this many seconds. `0` = disabled. Example: `86400` for 24 hours. |
|
| **INIT_DATA_MAX_AGE_SECONDS** | integer | `0` | Reject Telegram initData older than this many seconds. `0` = disabled. Example: `86400` for 24 hours. |
|
||||||
| **CORS_ORIGINS** | comma-separated list | `*` | Allowed origins for CORS. Leave unset or set to `*` for allow-all. Example: `https://your-domain.com`. |
|
| **CORS_ORIGINS** | comma-separated list | `*` | Allowed origins for CORS. Leave unset or set to `*` for allow-all. Example: `https://your-domain.com`. |
|
||||||
| **EXTERNAL_CALENDAR_ICS_URL** | string (URL) | *(empty)* | 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. |
|
| **EXTERNAL_CALENDAR_ICS_URL** | string (URL) | *(empty)* | 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. |
|
||||||
| **DUTY_DISPLAY_TZ** | string (timezone name) | `Europe/Moscow` | Timezone for the pinned duty message in groups. Example: `Europe/Moscow`, `UTC`. |
|
| **DUTY_DISPLAY_TZ** | string (timezone name) | `Europe/Moscow` | Timezone for the pinned duty message in groups. Example: `Europe/Moscow`, `UTC`. |
|
||||||
| **DEFAULT_LANGUAGE** | `en` or `ru` (normalized) | `en` | Default UI language when the user's Telegram language is unknown. Values starting with `ru` are normalized to `ru`, otherwise `en`. |
|
| **DEFAULT_LANGUAGE** | `en` or `ru` (normalized) | `en` | Default UI language when the user's Telegram language is unknown. Values starting with `ru` are normalized to `ru`, otherwise `en`. |
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ The **duty-schedule** format is used by the `/import_duty_schedule` command. Onl
|
|||||||
|
|
||||||
| Value | Meaning | Notes |
|
| Value | Meaning | Notes |
|
||||||
|-------|----------------|--------------------------|
|
|-------|----------------|--------------------------|
|
||||||
| **в**, **В**, **б**, **Б** | Duty (дежурство) | Any of these four |
|
| **в**, **В**, **б**, **Б** | Duty | Any of these four |
|
||||||
| **Н** | Unavailable (недоступен) | Exactly `Н` |
|
| **Н** | Unavailable | Exactly `Н` |
|
||||||
| **О** | Vacation (отпуск) | Exactly `О` |
|
| **О** | Vacation | Exactly `О` |
|
||||||
| (empty/space/other) | No event | Ignored for import |
|
| (empty/space/other) | No event | Ignored for import |
|
||||||
|
|
||||||
The number of days in the schedule is the maximum length of any `duty` string when split by `;`. If `duty` is empty or missing, it is treated as an empty list of cells.
|
The number of days in the schedule is the maximum length of any `duty` string when split by `;`. If `duty` is empty or missing, it is treated as an empty list of cells.
|
||||||
@@ -38,7 +38,7 @@ The number of days in the schedule is the maximum length of any `duty` string wh
|
|||||||
},
|
},
|
||||||
"schedule": [
|
"schedule": [
|
||||||
{
|
{
|
||||||
"name": "Иванов Иван",
|
"name": "Ivanov Ivan",
|
||||||
"duty": ";;В;;;Н;;О;;В;;"
|
"duty": ";;В;;;Н;;О;;В;;"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ On container start, `entrypoint.sh` runs Alembic migrations then starts the app
|
|||||||
### Miniapp "Open in browser" or direct link — access denied
|
### Miniapp "Open in browser" or direct link — access denied
|
||||||
|
|
||||||
- **Cause:** When users open the calendar via “Open in browser” or a direct URL, Telegram may not send `tgWebAppData` (initData). The API requires initData (or `MINI_APP_SKIP_AUTH` / private IP in dev).
|
- **Cause:** When users open the calendar via “Open in browser” or a direct URL, Telegram may not send `tgWebAppData` (initData). The API requires initData (or `MINI_APP_SKIP_AUTH` / private IP in dev).
|
||||||
- **Action:** Users should open the calendar **via the bot’s menu button** (e.g. ⋮ → «Календарь») or a **Web App inline button** so Telegram sends user data.
|
- **Action:** Users should open the calendar **via the bot’s menu button** (e.g. ⋮ → "Calendar") or a **Web App inline button** so Telegram sends user data.
|
||||||
|
|
||||||
### 403 "Open from Telegram" / no initData
|
### 403 "Open from Telegram" / no initData
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user