Бот для мессенджера MAX (dev.max.ru), «всё в одном»: совместные списки дел, «Слово дня», конвертер изображений и рецензии на напитки.
Описание бота (используется при регистрации у MasterBot): «Совместные списки дел, "Слово дня" и конвертер файлов — всё в одном боте. /help — список команд.»
-
Совместный список дел — общий todo-лист на чат:
/todo add <текст>— добавить дело/todo list— показать список (нумерованный, с инлайн-кнопками ✅/🗑)/todo done <n>— отметить дело выполненным/todo del <n>— удалить дело
-
«Слово дня» — ежедневная рассылка слова с определением и примером:
/word— показать слово дня прямо сейчас/word sub— подписать чат на ежедневную рассылку (09:00 по времени изTZ)/word unsub— отписать чат
-
Конвертер изображений — пришлите картинку, бот предложит кнопками формат (PNG/JPG/WEBP/PDF) и вернёт файл. Лимит входного файла — 20 МБ.
-
Рецензии на напитки — общий журнал впечатлений о напитках на чат:
/drink add— пошаговое добавление (категория → название → описание → оценка → фаворит)/drink list— список с фильтром по категории/только избранное и сортировкой (дата/оценка/название/фаворит)/drink search <текст>— поиск по названию, описанию и категории/drink fav— только избранные/drink del <id>— удалить рецензию/drink help— краткая справка
Категория вводится пользователем свободным текстом — никаких предустановленных категорий нет. Значение нормализуется (обрезаются пробелы, приводится к нижнему регистру для сравнения и дедупликации), в фильтрах показываются только категории, которые реально встречаются среди рецензий чата.
-
Админ-рассылка (
/broadcast, только дляADMIN_IDS) — мастеровый сценарий: текст → превью с числом активных получателей → подтверждение → фоновая рассылка в личку всем, кто писал боту за последниеBROADCAST_ACTIVE_DAYSдней. -
/v(только дляADMIN_IDS) — версия, git sha и время сборки текущего деплоя. -
Mini App (
/app) — веб-версия части механик: список дел (чтение и добавление), слово дня с подпиской, рецензии на напитки (список, добавление, поиск, фильтр по категориям и сортировка), рассылка для админов. Открывается кнопкой прямо в чате, работает на отдельном путиMINIAPP_PATHтого же домена. -
/start,/help— список команд.
Python 3.12+ / uv, библиотека maxapi, FastAPI + uvicorn (webhook), PostgreSQL 16 + SQLAlchemy 2.x (async) + Alembic, APScheduler, Docker + Caddy (авто-HTTPS). Подробности — в CLAUDE.md.
make install # uv sync
cp .env.example .env # заполните BOT_TOKEN своим токеном для локальных проверок
make migrate # alembic upgrade head (нужен запущенный postgres, см. make up-local)
make run # MODE=polling, локальный запуск без вебхукаПолезные команды (см. полный список через make help):
make lint && make format # ruff check + ruff format
make test # тесты (нужен запущенный postgres)
make up-local # docker compose -f docker/docker-compose.local.yml up --build (bot + postgres)
make migration m="..." # новая миграция- Откройте MasterBot в MAX и создайте нового бота.
- Укажите имя MaxHub и описание: «Совместные списки дел, "Слово дня" и конвертер файлов — всё в одном боте. /help — список команд.»
- Сохраните выданный токен — он понадобится как
BOT_TOKENв.envна сервере.
Бот при старте дополнительно синхронизирует имя и описание через API (best-effort, метод не входит в официальную swagger-спецификацию MAX — при ошибке синхронизации бот просто логирует предупреждение и продолжает работать).
Требования: Docker + плагин docker compose, доменное имя с A-записью, указывающей на сервер, открытые порты 80 и 443.
По умолчанию используется каталог /opt/maxbot — при необходимости его можно
изменить через секрет DEPLOY_PATH (см. раздел «GitHub Secrets» ниже).
sudo mkdir -p /opt/maxbot
cd /opt/maxbotСоздайте /opt/maxbot/.env:
BOT_TOKEN=... # токен от MasterBot
MAX_API_URL=https://platform-api2.max.ru # хост API MAX, менять только если MAX попросит перейти на другой
DOMAIN=bot.example.com # ваш домен
WEBHOOK_PATH=/webhook
MINIAPP_PATH=/miniapp
PORT=8080
MODE=webhook
ADMIN_IDS=111111,222222 # user_id админов через запятую
DATABASE_URL=postgresql+asyncpg://maxhub:${POSTGRES_PASSWORD}@postgres:5432/maxhub
TZ=Europe/Moscow
BROADCAST_ACTIVE_DAYS=30
POSTGRES_PASSWORD=... # придумайте пароль для Postgres
IMAGE_TAG=latestdocker-compose.prod.nginx.yml не нужно копировать вручную — workflow deploy.yml сам обновляет его на сервере при каждом релизе (см. «Процесс релиза» ниже) и запускает docker compose -f docker-compose.prod.nginx.yml pull && up -d. Для самой первой раскатки до первого тега можно скопировать файл и поднять стек вручную:
docker compose -f docker-compose.prod.nginx.yml pull
docker compose -f docker-compose.prod.nginx.yml up -dБот слушает только 127.0.0.1:${PORT} — HTTPS и сертификат Let's Encrypt обслуживает уже работающий на хосте nginx (см. docker/nginx/README.md), а не Caddy. Бот при старте сам подпишется на вебхук https://{DOMAIN}{WEBHOOK_PATH} и применит миграции БД (alembic upgrade head выполняется в entrypoint контейнера перед запуском приложения).
Если порт 443 на сервере свободен и вы хотите, чтобы сертификат получал сам контейнер — используйте docker/docker-compose.prod.caddyfile.yml + docker/Caddyfile вместо nginx-варианта (потребует ручной правки deploy.yml, по умолчанию workflow настроен на nginx-сценарий).
В настройках репозитория → Settings → Secrets and variables → Actions добавьте:
| Secret | Назначение |
|---|---|
SSH_HOST |
адрес сервера для деплоя |
SSH_USER |
пользователь для SSH |
SSH_KEY |
приватный SSH-ключ |
SSH_PORT |
(опционально) порт SSH, если не 22 |
DEPLOY_PATH |
(опционально) каталог с .env/docker-compose.prod.nginx.yml на сервере, если не /opt/max-bot |
GITHUB_TOKEN для пуша образа в GHCR передаётся автоматически, ничего настраивать не нужно.
Пушим тег вида v*:
git tag v1.0.0
git push --tagsЭто запускает ci.yml (тесты, линт, docker build). deploy.yml слушает не сам пуш тега, а завершение ci.yml (on: workflow_run) и стартует, только если тот прогон завершился успешно и был именно пушем тега — деплой упавшего/ещё выполняющегося CI невозможен. Дальше он:
- Соберёт образ и запушит его в
ghcr.io/unseen-social-network/maxhubс тегами{tag}иlatest. - Скопирует актуальный
docker-compose.prod.nginx.ymlиз репозитория на сервер (секреты в.envне трогает). - Обновит в
.envIMAGE_TAG,APP_VERSION,GIT_SHA,BUILD_TIME— без этого шага/vпоказывал бы устаревшие значения из.env, а не то, что реально запечено в образ (docker-compose.prod.nginx.ymlпробрасывает эти переменные в контейнер черезenvironment:). - Допишет строку в
deploy_history.logна сервере (timestamp tag sha) — по нему видно историю релизов и можно вручную откатиться, поставив в.envпредыдущийIMAGE_TAG(старые образы остаются в GHCR). - Перезапустит стек:
docker compose -f docker-compose.prod.nginx.yml pull && up -d.
- Напишите боту в личку из аккаунта, указанного в
ADMIN_IDS, команду/broadcast. - Пришлите текст рассылки одним сообщением.
- Бот покажет превью текста и число активных получателей (писавших боту в личку за последние
BROADCAST_ACTIVE_DAYSдней и не заблокировавших его), с кнопками «✅ Отправить» / «❌ Отменить». - После подтверждения рассылка уходит фоновой задачей строго через общий лимитер (не быстрее 2 сообщений в секунду на получателя, до 30 запросов в секунду суммарно) с промежуточным прогрессом каждые ~50 получателей.
- По завершении бот пришлёт итог: сколько отправлено и сколько не доставлено. Пользователи, заблокировавшие бота, автоматически помечаются и исключаются из будущих рассылок.
Каждая рассылка логируется в таблицу broadcasts (админ, текст, время, счётчики отправленных/недоставленных).
Веб-версия части механик бота — список дел (просмотр и добавление), слово дня с подпиской, рецензии на напитки (список, добавление, поиск, фильтр по категориям, сортировка) и рассылка для админов. Открывается кнопкой «📱 Открыть приложение» по команде /app прямо внутри чата (MAX-кнопка open_app), работает на пути MINIAPP_PATH (по умолчанию /miniapp) того же домена, что и вебхук — отдельного домена/nginx-конфига не требуется.
Как это работает:
- Библиотека MAX Bridge (
https://st.max.ru/js/max-web-app.js) подключается на фронтенде и сразу даёт доступ кwindow.WebApp.initData— подписанной строке с данными о пользователе и чате, откуда открыли приложение. - Фронтенд передаёт
initDataна бэкенд в заголовкеX-Init-Dataс каждым запросом к/miniapp/api/.... - Бэкенд (
src/bot/miniapp/auth.py) проверяет подпись двухшаговымHMAC_SHA256(как в Telegram WebApp, формула изdev.max.ru/docs/webapps/validation):secret_key = HMAC_SHA256("WebAppData", BOT_TOKEN), затемhash = HMAC_SHA256(secret_key, launch_params), гдеlaunch_params— парыkey=valueиз initData (кромеhash), отсортированные по алфавиту и объединённые через\n. Запрос отклоняется, если подпись не совпадает илиauth_dateстарше часа. - Список дел, слово дня и рецензии на напитки берутся из
initData.chat.id— того чата, откуда была открыта кнопка. Рассылка доступна только еслиinitData.user.idесть вADMIN_IDS. - API рецензий (
GET/POST /miniapp/api/drinks,GET /miniapp/api/drinks/categories,PATCH/DELETE /miniapp/api/drinks/{id}) строго скоуплены наchat_idизinitData— обратиться к чужой рецензии из другого чата нельзя, попытка возвращает404.