151 lines
15 KiB
Markdown
151 lines
15 KiB
Markdown
# Подключение будущего бота MAX
|
||
|
||
Серверная часть готова к подключению. Сам бот ещё не создан, секреты не заполнены,
|
||
реальная отправка не включена. Изменений интерфейса сайта в этой поставке нет.
|
||
Интеграция уведомляет **самого пациента**; врачебные оповещения остаются в существующем канале проекта.
|
||
|
||
## Что реализовано
|
||
|
||
- Одноразовая ссылка для привязки аккаунта MAX к текущему пациенту сайта, срок по умолчанию 10 минут.
|
||
- Подтверждение подключения, `/help`, `/stop`, отключение при остановке или удалении бота в MAX.
|
||
- Нейтральные напоминания из существующего планировщика сайта: тесты, дневник, приём лекарств и другие события outbox.
|
||
- Уведомление о завершении теста: сервер каждые примерно 30 секунд находит новые завершённые
|
||
`QuestionnaireRecord` и `ScaleResult`, начиная с момента подключения MAX. Незавершённые и старые тесты не отправляются.
|
||
- Постоянная очередь, восстановление после перезапуска, ограниченные повторы при сетевых ошибках и ответах 429/5xx.
|
||
- В сообщении только короткий текст и кнопка на сайт. Ответы, оценки, диагнозы, назначения и ключ входа на сайт
|
||
в MAX не передаются. Переписка с ботом не сохраняется и не отправляется в LLM.
|
||
|
||
## 1. Создать бота — будущему владельцу
|
||
|
||
1. Открыть [MAX для партнёров](https://business.max.ru/) и пройти регистрацию/проверку профиля.
|
||
Условия допуска зависят от типа владельца; актуальный порядок — в
|
||
[официальной инструкции подключения](https://dev.max.ru/help/platform_connection).
|
||
2. Создать чат-бота, оформить описание и пройти требуемую MAX модерацию.
|
||
3. В настройках бота получить токен и скопировать имя из его публичной ссылки `https://max.ru/ИМЯ`.
|
||
4. Передать администратору сервера имя бота, а токен — через защищённое хранилище секретов.
|
||
Токен не нужен в переписке, MR или репозитории.
|
||
|
||
## 2. Заполнить настройки сервера
|
||
|
||
Нужны доступная identity/clinical БД проекта, публичный HTTPS-адрес сайта и HTTPS-адрес API на порту 443.
|
||
Входящие POST от MAX должны доходить до API без браузерной CAPTCHA или страницы авторизации.
|
||
Прокси должен передавать заголовок `X-Max-Bot-Api-Secret` и не журналировать тело webhook/Authorization.
|
||
Дополнительные таблицы identity создаются существующим `init_db()` при запуске приложения.
|
||
|
||
В `.env` сервера (вне Git) заполнить:
|
||
|
||
```dotenv
|
||
MAX_ENABLED=false
|
||
MAX_BOT_TOKEN=
|
||
MAX_BOT_USERNAME=
|
||
MAX_WEBHOOK_SECRET=
|
||
MAX_WEBHOOK_URL=https://api.example.org/api/v1/webhooks/max
|
||
MAX_SITE_URL=https://patient.example.org/
|
||
MAX_LINK_TTL_SECONDS=600
|
||
# При необходимости — PEM-файл доверенного CA, доступный внутри контейнера:
|
||
MAX_CA_BUNDLE=
|
||
DEMO_CLOCK=false
|
||
```
|
||
|
||
- `MAX_BOT_USERNAME` — только имя, без `@`, домена или параметров ссылки.
|
||
- `MAX_WEBHOOK_SECRET` — отдельная случайная строка, не токен бота: 32–256 символов из `A–Z a–z 0–9 _ -`.
|
||
Сгенерировать локально, например `python -c 'import secrets; print(secrets.token_urlsafe(32))'`, сохранить в секреты.
|
||
- `MAX_SITE_URL` ведёт в обычный личный кабинет; не добавлять в адрес ключи доступа или данные пациента.
|
||
- Исходящие запросы идут на `https://platform-api2.max.ru`, токен передаётся заголовком `Authorization`.
|
||
Верификация TLS всегда включена. Если системе нужен корневой сертификат Минцифры, установить его по
|
||
официальной инструкции MAX и указать PEM-путь в `MAX_CA_BUNDLE`; не отключать проверку TLS.
|
||
- Переменные должны попасть именно в окружение API-контейнера/процесса, а не только панели сайта.
|
||
В текущем Compose сервис `app` уже получает `.env` через `env_file`.
|
||
После изменения окружения пересоздать процесс/контейнер API. Новые зависимости не требуются.
|
||
|
||
## 3. Проверить и включить
|
||
|
||
В каталоге проекта, в том же окружении, где работает API:
|
||
|
||
```sh
|
||
uv run python -m app.channels.max_setup check
|
||
```
|
||
|
||
Команда проверит токен и совпадение имени бота. Сообщений пользователям она не отправляет.
|
||
Затем установить `MAX_ENABLED=true`, перезапустить API и выполнить:
|
||
|
||
```sh
|
||
uv run python -m app.channels.max_setup subscribe
|
||
uv run python -m app.channels.max_setup status
|
||
```
|
||
|
||
`subscribe` регистрирует только заданный webhook с секретом и событиями
|
||
`bot_started`, `message_created`, `bot_stopped`, `dialog_removed`. Старые чужие подписки автоматически
|
||
не удаляются: при переносе сервера владелец должен проверить их отдельно через MAX API.
|
||
`status` проверяет регистрацию адреса; это ещё не проверка доступности сервера из MAX.
|
||
Команды не печатают токены, секрет webhook или сырые ответы платформы.
|
||
|
||
При запуске через Compose вместо `uv run python ...` использовать
|
||
`docker compose exec app python -m app.channels.max_setup check` (также `subscribe` и `status`).
|
||
После изменения `.env` пересоздать API: `docker compose up -d --build --force-recreate app`.
|
||
|
||
Запускать один процесс API с MAX на один токен бота: его worker отправляет до одного сообщения в секунду.
|
||
БД защищает очередь от параллельного захвата, но общий лимит скорости нескольких процессов не координируется.
|
||
Планировщик и MAX worker запускаются вместе с FastAPI, отдельный процесс бота не нужен.
|
||
При `MAX_ENABLED=false` сетевых вызовов MAX и фонового worker нет; сайт продолжает обычную работу.
|
||
|
||
## API для разработчика сайта
|
||
|
||
В проекте используется ключ текущего пациента, полученный при входе. Для этих ручек передавать его
|
||
как `Authorization: Bearer <patient_token>`; это существующая авторизация прототипа, не новая система SSO.
|
||
Не брать token другого пациента и не публиковать демонстрационные учётные записи для реального использования.
|
||
|
||
| Метод и путь | Запрос | Ответ |
|
||
|---|---|---|
|
||
| `GET /api/v1/me/notifications/max` | Authorization | `available`, `connected`, `last_delivery` |
|
||
| `POST /api/v1/me/notifications/max/link` | Authorization; JSON `{"consent": true}` | `url`, `expires_at` |
|
||
| `DELETE /api/v1/me/notifications/max` | Authorization | состояние с `connected: false` |
|
||
| `POST /api/v1/webhooks/max` | MAX JSON + `X-Max-Bot-Api-Secret` | `{"success": true}` после записи события |
|
||
|
||
Поток подключения: пациент явно соглашается на уведомления → сайт получает `url` → открывает ссылку MAX →
|
||
пользователь запускает бота → webhook подтверждает привязку → повторный GET показывает `connected: true`.
|
||
Ссылка одноразовая; новая ссылка отменяет предыдущую. Изначальный ключ пациента не входит в URL MAX.
|
||
Для привязки и доставки дополнительно нужны действующее согласие и подтверждение совершеннолетия в состоянии пациента.
|
||
Активную привязку нельзя молча переназначить другому аккаунту: сначала отключить её.
|
||
|
||
Коды ошибок: 401 — неизвестный/отсутствующий ключ пациента; 403 — нет согласия или неверный секрет webhook;
|
||
422 — неверный запрос; 413 — webhook больше 64 КиБ; 503 — MAX выключен или хранилище события недоступно.
|
||
Повтор webhook с тем же идентификатором события не создаёт новых отправок. Групповые чаты игнорируются.
|
||
|
||
`last_delivery`: `queued`, `sending`, `sent`, `failed`, `cancelled` или `null`.
|
||
`sent` означает принятие сообщения API MAX, а не прочтение человеком.
|
||
|
||
## Пробное подключение после создания бота
|
||
|
||
1. Войти на сайте синтетическим пациентом и дать необходимые согласия.
|
||
2. Получить ссылку через POST выше (Swagger `/docs` или будущий интерфейс сайта), перейти в MAX и запустить бота.
|
||
3. Убедиться, что GET показывает `connected: true` и приходит подтверждение.
|
||
4. Завершить тест на сайте; примерно через 30 секунд плюс очередь должно прийти сообщение без результатов.
|
||
5. Дождаться события планировщика и проверить нейтральное напоминание с кнопкой на сайт.
|
||
6. Отправить `/stop`: GET должен показать `connected: false`, ожидающие уведомления отменяются.
|
||
7. Повторить подключение новой ссылкой, проверить DELETE через API и остановку бота через интерфейс MAX.
|
||
|
||
При недоставке проверить настройки и подписку, доступность HTTPS, состояние очереди и коды ошибок в логах.
|
||
`http_401`/`http_403` требуют проверки токена/прав; `http_429` — ожидания; `transport` — сети/TLS;
|
||
`failed` после шести попыток требует вмешательства администратора. Детали ответов и идентификаторы в логи не выводятся.
|
||
Отключение отменяет очередь; уже принятый MAX запрос отозвать невозможно.
|
||
|
||
## Гарантии и ограничения
|
||
|
||
Очередь и подтверждения событий сохраняются в identity БД. Клинические записи остаются в clinical БД.
|
||
Для событий завершения тестов БД повторно просматривается после перезапуска; ключи уведомлений предотвращают
|
||
повторную постановку. Напоминания используют существующий механизм outbox/повторов планировщика.
|
||
При временных ошибках — до 6 попыток с увеличением задержки и учётом `Retry-After`; зависшая отправка получает
|
||
новую аренду через 60 секунд. Потеря процесса между принятием сообщения MAX и записью успеха в БД может
|
||
привести к повторному сообщению: MAX не предоставляет ключ идемпотентности отправки.
|
||
Не удалять строки дедупликации/очереди без согласованной политики хранения. При большом количестве пациентов
|
||
нужны пакетное сканирование завершённых тестов и общий ограничитель скорости; текущая реализация рассчитана на MVP.
|
||
|
||
Проверки разработчика: `make lint test`; тесты MAX работают с синтетикой и подменённым HTTP-транспортом.
|
||
Реальную доставку подтвердит только пробное подключение после выдачи токена и настройки публичного сервера.
|
||
|
||
Официальные контракты: [отправка сообщений](https://dev.max.ru/docs-api/methods/POST/messages),
|
||
[подписка webhook](https://dev.max.ru/docs-api/methods/POST/subscriptions),
|
||
[события](https://dev.max.ru/docs-api/objects/Update),
|
||
[ссылки запуска бота](https://dev.max.ru/docs/chatbots/bots-coding/prepare).
|