DispelEvilBot/README.md

215 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DispelEvilBot
Telegram-бот клана Dispel Evil (aiogram 3 + Postgres). Монорепозиторий:
общий пакет `common` используется сервисом бота `bot` (и, в будущем, админкой `admin`).
## Структура
```
common/ # общий пакет: config, logging, db, repositories, models, texts, analytics
bot/ # сервис бота: handlers, services, keyboards, middlewares, notifications
admin/ # сервис админки: FastAPI + Jinja2/HTMX (Блок 4), свой Dockerfile
sql/ # миграции Flyway (V__*.sql, R__*.sql)
clickhouse/ # init-скрипты ClickHouse (схема аналитики)
docs/ # документация (docs/events.md — события аналитики)
scripts/ # эксплуатационные скрипты (проверка контура, бэкап событий)
images/ # статичные картинки (приветствие)
deploy/ # материалы деплоя (Caddyfile, инструкции)
tests/ # тесты
Dockerfile # образ бота
docker-compose.yml / docker-compose.prod.yml
.gitea/ # CI (Gitea Actions)
```
## Требования
- Python 3.12
- PostgreSQL 14+
- ClickHouse 25.3 (аналитика событий; можно выключить — `CLICKHOUSE_ENABLED=false`)
## Настройка
```bash
cp .env.example .env
# заполнить BOT_TOKEN, DB_*, чаты и ADMIN_IDS
pip install -r requirements.txt
```
## Запуск
```bash
APP_ENV=dev python -m bot # dev
APP_ENV=prod python -m bot # prod
```
или через `./start.sh` (режим берётся из `APP_ENV`).
Админка (FastAPI) запускается отдельным процессом: `python -m admin` (зависимости —
`requirements-admin.txt`), по умолчанию слушает `0.0.0.0:8000`, проверка живучести — `GET /healthz`.
## База данных
Схема управляется миграциями **Flyway** в `sql/` (`V__*.sql` — versioned, `R__*.sql` — repeatable).
При Docker-развёртывании накат выполняется автоматически сервисом `migrate` перед ботом.
## Админка (Блок 4)
Отдельный сервис `admin/` (FastAPI + Jinja2/HTMX), запуск `python -m admin`.
Раздел «Сообщения» читает и правит шаблоны из `bot.message_templates`, каждое изменение
пишется в `bot.message_template_history` (аудит и материал для отката). Стартовые тексты —
`sql/V15__seed_message_templates.sql` (генерируются из `common/texts`, тест сверяет их).
Бот читает тексты из БД через TTL-кеш (`TEMPLATE_CACHE_TTL`, по умолчанию 45 с), поэтому
правка применяется без перезапуска; при недоступной БД бот использует тексты из кода.
Предпросмотр использует тот же `render()`, что и бот.
**Вход** — через Telegram Login Widget: подпись `hash` проверяется токеном бота, доступ
есть только у записей в `bot.admin_users` (роль проверяется в БД на каждом запросе).
Первые администраторы засеваются из `ADMIN_IDS` при старте. Сессия — подписанная
HttpOnly-cookie (`ADMIN_SESSION_SECRET`), все формы защищены CSRF. Для входа нужны
`BOT_TOKEN`, `BOT_USERNAME` (без @) и HTTPS-адрес `ADMIN_PUBLIC_URL`.
Раздел «Расписания» — CRUD рассылок (`bot.schedules` + история): время, дни недели,
таймзона, шаблон и вкл/выкл. Бот раз в минуту читает активные расписания из БД; если
база недоступна — использует расписание из кода (совпадает с сидом `V16`).
Раздел «Пользователи» — список с поиском по нику/username/имени/Telegram-ID и карточка:
уровень, опыт и серия, баланс (`SUM(money.amount)`), яички, сообщения, очищения,
подписка на рассылки, дата вступления и последние операции по балансу. В карточке можно
сменить ник и звание, включить/выключить подписку и скорректировать баланс (запись в
журнал `money` с действием «Корректировка админом»). Каждое ручное изменение пишется в
`bot.user_changes`: кто, когда, было → стало и причина.
Раздел «Логи» — события из ClickHouse: фильтры по типу, пользователю (ID или username)
и датам, пагинация по 50. Время показывается в `TIMEZONE`, payload раскрывается как JSON,
ID пользователя ведёт в его карточку. ClickHouse читается отдельным ленивым клиентом;
если он недоступен, страница показывает ошибку, а не падает.
Раздел «Админы» — выдача и отзыв доступа к панели по Telegram-ID, роли и вкл/выкл.
В карточке пользователя есть исключение из чата (бан + разбан) с удалением данных и
уведомлением «кто кого выгнал»; историю изменений показывает, кто менял ник — сам
пользователь через `/ник` или админ. Новые шаблоны создаются в «Сообщениях»: ключ,
описание, текст и предпросмотр.
Раздел «Последовательности» — набор сообщений по порядку (приветствие и любые другие):
у каждого свой текст, картинка из общего volume `media`, вкл/выкл и несколько кнопок.
Редактор текста — textarea с тулбаром: жирный, курсив, подчёркивание, зачёркивание,
код, блок кода, цитата, спойлер и ссылка; есть предпросмотр и счётчик символов
(1024 для подписи к фото, 4096 для обычного сообщения).
## Аналитика (ClickHouse)
Все значимые действия бота пишутся в ClickHouse в единую таблицу `analytics.events`.
Контракт событий — [`docs/events.md`](docs/events.md) (типы, payload, примеры запросов).
- схема — `clickhouse/init/001_events.sql` (применяется при первой инициализации контейнера);
- клиент и батчинг — `common/analytics/` (запись неблокирующая, сбой ClickHouse не влияет на бота);
- сырые события живут на VPS 7 дней и ежедневно уезжают архивом на домашний сервер,
на VPS остаются агрегаты (`events_daily`, `money_daily`) — см. [DEPLOY.md](DEPLOY.md);
- выключается флагом `CLICKHOUSE_ENABLED=false`.
## Уведомления в чат логов
В `ALERT_CHAT_ID` (если задан) уходят: вход и выход участников, успешные очищения и
ежечасный пинг «бот живой». Отправка не критична: ошибки только пишутся в лог.
## Docker
Локальный запуск (postgres + migrate + clickhouse + bot + admin):
```bash
cp .env.example .env # заполнить
docker compose up --build
```
CI собирает два образа: `dispelevilbot-bot` и `dispelevilbot-admin`. Админка в compose
по умолчанию публикуется только на loopback (`127.0.0.1:${ADMIN_HTTP_PORT}`, test 8100 /
prod 8101); для nginx-proxy-manager задаётся `ADMIN_BIND_IP=172.17.0.1`, наружу выйдет
вместе с доменом и TLS (Блок 4.2). Картинки последовательностей лежат в общем
Docker-volume `media` (`/app/media`), который монтируется и боту, и админке.
Прод/test-контуры — один `docker-compose.prod.yml`, разделяются Compose-проектом и env-файлом:
```bash
docker compose -p prod --env-file env/prod.env -f docker-compose.prod.yml up -d
docker compose -p test --env-file env/test.env -f docker-compose.prod.yml up -d
```
Пошаговое развёртывание контуров `test`/`prod` — см. [`DEPLOY.md`](DEPLOY.md).
## Разработка
```bash
pip install -r requirements-dev.txt
ruff check .
black --check .
pytest
```
## Бэкап событий
Сырые события ClickHouse ежедневно забираются с VPS домашним сервером (`pull`), на VPS
остаются только агрегаты и последние дни сырых данных:
```bash
make backup E=test # на домашнем сервере: ./scripts/clickhouse_backup.sh test
```
Настройка, cron и SSH-доступ — см. [`DEPLOY.md`](DEPLOY.md).
## Версионирование
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.5.1`.
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
## Процесс работы с git
Проект ведётся в режиме **trunk-based** (один разработчик): основная работа — прямо в `master`.
- `master` — интеграционная ветка, всегда в рабочем состоянии.
- Релизы — аннотированные теги `vX.Y` (см. «Версионирование»).
- Для крупных/рискованных изменений — короткая ветка `feature/...` и merge обратно в `master`.
- Push в `master` → CI (Gitea Actions) собирает образ; **авто-выката в прод нет**.
- Выкатка: сначала контур `test`, затем `prod` по подтверждению (см. Блок 2.5 плана).
- Переход на «ветки + PR» и защиту ветки — когда появится второй разработчик.
```bash
# обычная работа: сообщение без префиксов, по существу изменения
git add -A
git commit -m "Краткое описание изменения"
git push origin master
# выпуск релиза
# 1) поднять __version__ в common/version.py
git commit -am "Версия X.Y"
git tag -a vX.Y -m "Release vX.Y"
git push origin master --tags
```
## Приветствие новых участников
Бот отправляет фото с короткой персональной подписью (первая строка шаблона `welcome.text`),
а затем одно сообщение со всем остальным текстом (`welcome.text` без первой строки +
`welcome.text2` + `welcome.text3`) и кнопкой на канал. Так приветствие остаётся одним
смысловым блоком, но не упирается в лимит подписи к фото (1024 символа).
## Пасхальное событие «Яички»
`/яички` работает только в сезон: по умолчанию с **1 апреля по православную Пасху**
(в 2026 году — 12 апреля). Вне окна бот отвечает, что ещё рано, а строка про яички
не показывается ни в `/инфо`, ни в карточке пользователя в админке.
Даты можно зафиксировать переменными `EGG_EVENT_START`/`EGG_EVENT_END` (ISO,
например `2027-04-01` и `2027-05-02`).
## Команды бота
`/ник`, `/список`, `/кто`, `/инфо`, `/помощь`, `/очищение`, `/яички`,
`/магазин`, `/рейтинг`, `/отписка`, `/подписка`.
## Переменные окружения
- общие — `.env.example` (включая `CLICKHOUSE_*` и параметры батчинга `ANALYTICS_*`);
- контуры — `env/prod.env.example`, `env/test.env.example`;
- бэкап событий — `env/backup.env.example`.