DispelEvilBot/README.md

15 KiB
Raw Permalink Blame History

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), включая схему analytics
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+ (аналитика — в схеме analytics)

Настройка

cp .env.example .env
# заполнить BOT_TOKEN, DB_*, чаты и ADMIN_IDS
pip install -r requirements.txt

Запуск

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_sequences): миграция V24 переносит каждый старый шаблон в последовательность из одного сообщения, а бот отправляет их через общий sender. Старые message_templates остаются фолбэком в коде. Правки применяются без перезапуска; предпросмотр использует тот же render(), что и бот.

Вход — через Telegram Login Widget: подпись hash проверяется токеном бота, доступ есть только у записей в bot.admin_users (роль проверяется в БД на каждом запросе). Первые администраторы засеваются из ADMIN_IDS при старте. Сессия — подписанная HttpOnly-cookie (ADMIN_SESSION_SECRET), все формы защищены CSRF. Для входа нужны BOT_TOKEN, BOT_USERNAME (без @) и HTTPS-адрес ADMIN_PUBLIC_URL.

Дополнительно — вход через установленный Telegram: кнопка «Открыть Telegram» создаёт одноразовый токен (5 минут) и открывает t.me/<bot>?start=login_<token>; бот подтверждает вход, сайт забирает токен и открывает сессию. Токен в БД хранится как SHA-256 и используется один раз.

Раздел «Расписания» — CRUD рассылок (bot.schedules + история): время, дни недели, таймзона, шаблон и вкл/выкл. Бот раз в минуту читает активные расписания из БД; если база недоступна — использует расписание из кода (совпадает с сидом V16). Каждая отправленная минута фиксируется в bot.schedule_runs, поэтому перезапуск бота в ту же минуту не повторяет рассылку.

Раздел «Пользователи» — список с поиском по нику/username/имени/Telegram-ID и карточка: уровень, опыт и серия, баланс (SUM(money.amount)), яички, сообщения, очищения, подписка на рассылки, дата вступления и последние операции по балансу. В карточке можно сменить ник и звание, включить/выключить подписку и скорректировать баланс (запись в журнал money с действием «Корректировка админом»). Каждое ручное изменение пишется в bot.user_changes: кто, когда, было → стало и причина.

Раздел «Звания» — справочник магазина: название, цена и видимость. Изменения сразу видны боту (он читает bot.titles). Удалить можно только звание, которое ни у кого не стоит; используемое — скрывается (visible = false).

Раздел «Логи» — события из Postgres и отдельная вкладка «Сообщения по дням»: фильтры по типу (для событий), пользователю (ID или username) и датам, пагинация по 50. Время показывается в TIMEZONE, payload раскрывается как JSON, ID пользователя ведёт в его карточку. Если Postgres недоступен, страница показывает ошибку, а не падает.

Раздел «Админы» — выдача и отзыв доступа к панели по Telegram-ID, роли и вкл/выкл. В карточке пользователя есть исключение из чата (бан + разбан) с удалением данных и уведомлением «кто кого выгнал»; историю изменений показывает, кто менял ник — сам пользователь через /ник или админ.

Раздел «Шаблоны сообщений» — набор сообщений по порядку (приветствие и любые другие): у каждого свой текст, картинка из общего volume media, вкл/выкл и несколько кнопок. Все правки — текст, кнопки, картинки, добавление/удаление и порядок сообщений — хранятся на странице и применяются одной кнопкой «Сохранить всё». Редактор текста — textarea с тулбаром: жирный, курсив, подчёркивание, зачёркивание, код, блок кода, цитата, спойлер и ссылка; есть предпросмотр и счётчик символов (1024 для подписи к фото, 4096 для обычного сообщения).

Интерфейс адаптирован под мобильные: бургер-меню, второстепенные колонки таблиц скрываются на узких экранах, формы складываются в одну колонку.

Раздел «Отправить» — ручное сообщение в основной чат от имени бота: текст с тем же редактором, картинка и URL-кнопки, предпросмотр и подтверждение. Каждая отправка (успешная или нет) пишется в bot.admin_messages.

Аналитика (Postgres)

Все значимые действия бота пишутся в Postgres, схема analytics: analytics.events (события, партиции по дням) и analytics.messages_daily (количество сообщений пользователя по дням). Контракт событий — docs/events.md (типы, payload, примеры запросов).

  • схема — sql/V26__analytics_schema.sql;
  • клиент и батчинг — common/analytics/ (запись неблокирующая, сбой Postgres не влияет на бота);
  • сообщения не пишутся поштучно: на каждое сообщение инкрементится дневной счётчик;
  • построчная bot.messages удалена (V27), старые данные перенесены в analytics.messages_daily;
  • партиции создаются приложением и не удаляются — история хранится бессрочно;
  • выключается флагом ANALYTICS_ENABLED=false.

Уведомления в чат логов

В ALERT_CHAT_ID (если задан) уходят: вход и выход участников, успешные очищения и ежечасный пинг «бот живой». Отправка не критична: ошибки только пишутся в лог.

Docker

Локальный запуск (postgres + migrate + bot + admin):

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-файлом:

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.

Разработка

pip install -r requirements-dev.txt
ruff check .
black --check .
pytest

Бэкап

Аналитика теперь в Postgres, отдельного бэкапа событий нет. Бэкап БД — стандартными средствами Postgres; история analytics не чистится, поэтому следите за размером БД и диском. Подробности — DEPLOY.md.

Версионирование

  • Единый источник версии — common/version.py (__version__), текущая — 0.7.2.
  • Схема — SemVer-подобная (MAJOR.MINOR.PATCH); git-тег = v + версия (v0.1).
  • Docker-образы тегируются версией (Блок 2), события аналитики получают bot_version.
  • Выпуск: изменить 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» и защиту ветки — когда появится второй разработчик.
# обычная работа: сообщение без префиксов, по существу изменения
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).

Команды бота

/ник, /список, /кто, /инфо, /помощь, /очищение, /яички, /магазин, /рейтинг, /отписка, /подписка.

В меню Telegram показываются латинские алиасы (/help, /info, /nick, /list, /who, /purify, /eggs, /shop, /top, /sub, /unsub) с русскими описаниями: Telegram не принимает кириллицу в именах команд. Русские команды работают при вводе вручную. Набор сверяется с ожидаемым при каждом старте бота (ensure_commands).

Переменные окружения

  • общие — .env.example (включая ANALYTICS_*);
  • контуры — env/prod.env.example, env/test.env.example.