21 KiB
Техническая спецификация: max2telegram (v3.2)
1. Назначение продукта
max2telegram — асинхронный сервис-мост для двусторонней синхронизации переписки между мессенджером MAX и Telegram. Сторона Telegram представлена одним Форум-каналом с множеством тематических топиков.
Сервис использует WebSocket-подключение к API MAX для мгновенного получения событий и хранит маркеры (курсоры) последнего обработанного сообщения в базе данных. Это гарантирует, что ни одно сообщение не будет потеряно или обработано дважды во время перезапуска сервиса, сетевых сбоев или даунтайма. Вся маршрутизация выполняется строго по идентификаторам, а отправка в обе стороны регулируется асинхронными Redis-очередями для соблюдения лимитов API.
2. Участники и роли
| Участник | Роль в системе |
|---|---|
| Аккаунт MAX | Источник и приёмник сообщений в MAX (групповые чаты и личные диалоги). |
| Telegram-бот | Администратор Форум-канала; создаёт топики, читает сообщения по thread_id, ставит реакции, выполняет команды. |
| Доверенный оператор (fallback) | Получает системные уведомления об ошибках очередей; выполняет управляющие команды. В части пересылки сообщений имеет те же права, что и любой другой участник форума. |
| Участники форума Telegram | Могут писать в любые маппированные топики; все их сообщения асинхронно пересылаются в MAX. |
| Оператор инфраструктуры | Настраивает Docker Compose, управляет переменными окружения, уровнем логирования и контролирует логи. |
3. Архитектура и потоки данных
3.1. Общая схема
flowchart LR
subgraph MAX["MAX API (WebSocket)"]
MC[Чаты и ЛС MAX]
end
subgraph Bridge["max2telegram (Python App)"]
WS_LAYER[Слой клиента MAX]
TG_LAYER[Слой клиента Telegram]
ROUTER[Слой маршрутизации]
STATE[(Слой состояния: маркеры в SQLite)]
MAP[(Слой маппинга: ID в SQLite)]
QUEUE[(Слой очередей: Redis)]
end
subgraph TG["Telegram Bot API"]
FC[Форум-канал]
TOPICS[Топики]
FB[Личка fallback]
end
MC <-->|WS Stream| WS_LAYER
WS_LAYER <--> STATE
WS_LAYER --> ROUTER
ROUTER <--> MAP
ROUTER --> QUEUE
QUEUE --> TG_LAYER
TG_LAYER --> FC
FC --> TG_LAYER --> ROUTER
ROUTER --> QUEUE
QUEUE --> WS_LAYER
WS_LAYER --> MC
FB <--> TG_LAYER
3.2. Поток «MAX → Telegram» (с маркерами и очередью)
Слой клиента MAXподключается по WebSocket, запрашивает последние маркеры изСлоя состояния.- При получении нового события:
- Дедупликация: если
message_id<=last_message_id, событие игнорируется. Слой маршрутизациипроверяет маппинг вСлое маппинга. Если нет – запрашивает уСлоя клиента Telegramсоздание топика, сохраняет маппинг.- Задача сериализуется и передается в
Слой очередей(max2tg_queue). - Маркер атомарно обновляется в
Слое состояния.
- Дедупликация: если
Слой клиента Telegram(Воркер) забирает задачу, соблюдает лимит (≥ 3.5 сек), отправляет сообщение и сохраняет связьmax_message_id↔tg_message_idв БД.
3.3. Поток «Telegram → MAX» (с очередью)
Слой клиента Telegramполучает сообщение из топика (tg_chat_id+message_thread_id). Игнорирует сообщения бота.Слой маршрутизацииищет пару{tg_chat_id, message_thread_id}вСлое маппинга.- Если найдено: задача передается в
Слой очередей(tg2max_queue). Если нет: игнорируется. Слой клиента MAX(Воркер) забирает задачу, ждет настраиваемого лимита, отправляет сообщение в MAX.- Только после успеха от API MAX,
Слой клиента Telegramставит реакцию 🦄 на исходное сообщение.
4. Технические требования и стек технологий
4.1. Стек разработки
| Компонент | Технология | Обоснование / Требования |
|---|---|---|
| Язык | Python 3.10+ | Нативная поддержка asyncio, строгая типизация (type hints). |
| Клиент MAX | PyMAX |
Обязательная поддержка WebSocket для стриминга событий. Должен поддерживать переподключение и запрос истории по курсору/ID. |
| Клиент Telegram | aiogram 3.x |
Предпочтительно для Bot API (работа с форумами, очередями, поллингом/вебхуками). |
| База данных | SQLite | Хранение маппингов и маркеров состояния. Обязательно: режим PRAGMA journal_mode=WAL и PRAGMA synchronous=NORMAL. Библиотека: aiosqlite или SQLAlchemy (async). |
| Очереди | Redis | Хранение очередей max2tg_queue и tg2max_queue. Персистентность через appendonly yes (AOF). Библиотека: redis-py (async). |
| Логирование | structlog / logging |
Структурированные JSON-логи. Уровень логирования настраивается через переменную окружения. |
4.2. Архитектура приложения (Python)
Приложение строится на базе asyncio и состоит из следующих независимых задач (tasks):
- MAX WebSocket Listener: Поддерживает соединение, парсит события, управляет маркерами.
- Telegram Listener: Aiogram Dispatcher.
- TG Worker: Цикл
BLPOPизmax2tg_queueсasyncio.sleep(3.5). - MAX Worker: Цикл
BLPOPизtg2max_queueс настраиваемой задержкой. - Admin Handler: Обработчик команд только от
FALLBACK_USER_ID.
4.3. Требования к развертыванию (Docker + Docker Compose)
Сервис поставляется в виде готового к запуску окружения. Папка ./data монтируется для сохранения SQLite БД и сессии PyMAX между перезапусками.
version: '3.8'
services:
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_data:/data
command: redis-server --appendonly yes
app:
build: .
restart: always
depends_on:
- redis
environment:
- MAX_TOKEN=${MAX_TOKEN}
- MAX_DEVICE_ID=${MAX_DEVICE_ID}
- TG_BOT_TOKEN=${TG_BOT_TOKEN}
- TG_FORUM_CHANNEL_ID=${TG_FORUM_CHANNEL_ID}
- FALLBACK_USER_ID=${FALLBACK_USER_ID}
- DATABASE_URL=sqlite+aiosqlite:///data/bridge.db
- REDIS_URL=redis://redis:6379/0
- TG_RATE_LIMIT_DELAY_SEC=3.5
- MAX_RATE_LIMIT_DELAY_SEC=1.0
- MAX_RECONNECT_FETCH_LIMIT=50
- LOG_LEVEL=INFO
volumes:
- ./data:/app/data
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
redis_data:
4.4. Архитектурные принципы и стандарты разработки
Проект обязан строго следовать следующим инженерным стандартам:
-
Строгое разделение слоёв (Layered Architecture):
MAX Layerотвечает только за взаимодействие с API MAX (подключение WS, отправка/получение сообщений, сессия).Telegram Layerотвечает только за взаимодействие с Telegram Bot API (получение обновлений, отправка в топики, реакции, обработка команд).Router Layerотвечает только за логику сопоставления идентификаторов и принятие решений о маршруте.Queue Layerотвечает только за сериализацию, буферизацию и доставку задач между слоями.Storage Layerотвечает только за атомарные операции чтения/записи в SQLite/Redis.- Запрещено: вызывать методы одного слоя из другого напрямую. Например,
MAX Layerне может ставить реакции или создавать топики в Telegram. Вся межслойная коммуникация проходит через интерфейсы абстракций и очереди.
-
SOLID:
- Single Responsibility: Каждый модуль/класс выполняет одну четкую задачу.
- Open/Closed: Расширение функционала (новые типы медиа, новые команды) происходит без модификации стабильного кода ядерных компонентов.
- Liskov Substitution & Interface Segregation: Чёткие контракты между слоями, минимум методов в интерфейсах.
- Dependency Inversion: Слои зависят от абстракций (протоколов/интерфейсов), а не от конкретных реализаций клиентов.
-
KISS:
- Избегать избыточной абстракции и "over-engineering".
- Использовать стандартные паттерны
asyncio,aiogramиredis-py. - Конфигурация, бизнес-логика и инфраструктурный код должны быть явно разделены, но не размазаны по сотне файлов.
5. Бизнес-функции и правила
5.1. Двусторонняя пересылка и очереди
- Гарантированная доставка: Сообщения не теряются при перезапуске контейнера благодаря персистентным Redis-очередям.
- Rate Limiting: В Telegram ≥ 3.5 сек, в MAX — настраиваемая задержка.
- Подтверждение: Реакция 🦄 ставится только после успешного подтверждения отправки из очереди в MAX.
5.2. Маршрутизация и управление топиками
- Строгое сопоставление по ID: Ключ поиска:
max_chat_id↔{tg_chat_id, tg_thread_id}. - Автоматическое создание: При отсутствии маппинга создается топик. Имя:
"Название чата"или"👤 Имя контакта". Имя хранится только для UX (/list). - Игнорирование: Сообщения из TG-топиков без маппинга игнорируются.
5.3. Формат сообщений и контент
- MAX → TG: В групповых топиках:
<Имя отправителя>:\n<текст>. В ЛС-топиках: только текст. - TG → MAX: Всегда:
<Имя Фамилия> (@username):\n<текст>. - Медиа: Фото, видео, альбомы, файлы поддерживаются нативно. Ненативные типы передаются ссылками
[Telegram files]. - Треды: Поддерживаются через
max_message_id↔tg_message_idв БД.
5.4. Управление и команды
Команды обрабатываются исключительно в личной переписке с ботом и только от FALLBACK_USER_ID. В части пересылки сообщений все участники форума равны.
| Команда | Действие |
|---|---|
/start |
Приветствие, краткая справка, проверка статуса подключения |
/help |
Полная справка по доступным командам управления |
/list |
Список активных маппингов (ID, названия, статус) |
/join <ссылка> |
Вступление в MAX-группу или канал |
/leave <id или название> |
Выход из указанного MAX-канала |
/last_messages <id или название> |
Последние 10 сообщений из MAX-чата или ЛС |
5.5. Надежность и восстановление (WebSocket и маркеры)
- Хранение маркеров: Таблица
sync_markersв SQLite хранитlast_processed_message_idдля каждого чата. - Идемпотентность: События с
message_id<= маркера отбрасываются. - Восстановление после даунтайма: При старте читаются маркеры, запрашивается история (до
MAX_RECONNECT_FETCH_LIMIT), сообщения ставятся в очередь. - Атомарность: Маркер обновляется только после успешной постановки в Redis.
6. Конфигурация (Переменные окружения)
| Переменная | Описание | Пример / Значение по умолчанию |
|---|---|---|
MAX_TOKEN |
Токен авторизации аккаунта MAX | eyJhbGci... |
MAX_DEVICE_ID |
Уникальный идентификатор устройства/сессии | a1b2c3d4... |
TG_BOT_TOKEN |
Токен Telegram-бота от @BotFather | 123456:ABC-DEF1234... |
TG_FORUM_CHANNEL_ID |
ID Форум-канала (строго с -100) |
-1001234567890 |
FALLBACK_USER_ID |
Telegram ID администратора | 987654321 |
DATABASE_URL |
Строка подключения к SQLite (относительно WORKDIR=/app) |
sqlite+aiosqlite:///data/bridge.db |
REDIS_URL |
Строка подключения к Redis | redis://redis:6379/0 |
TG_RATE_LIMIT_DELAY_SEC |
Задержка между отправками в TG | 3.5 |
MAX_RATE_LIMIT_DELAY_SEC |
Задержка между отправками в MAX | 1.0 |
LS_TOPIC_PREFIX |
Префикс для именования ЛС-топиков | 👤 |
MAX_RECONNECT_FETCH_LIMIT |
Макс. кол-во сообщений для дозагрузки из истории | 50 |
LOG_LEVEL |
Уровень логирования (DEBUG, INFO, WARNING, ERROR) |
INFO |
7. Границы ответственности продукта
Входит в scope:
- Асинхронная двусторонняя пересылка текста и медиа через Redis-очереди.
- Подключение к MAX через WebSocket с обработкой разрывов связи.
- Хранение и использование маркеров последнего сообщения для предотвращения потери данных.
- Автоматическое создание топиков и строгая маршрутизация только по идентификаторам.
- Соблюдение rate limits и настройка уровня логирования.
- Поддержка тредов, дедупликация, равенство участников форума.
- Удалённое управление MAX (
/start,/help,/listи др.) только для fallback-пользователя. - Архитектурное соблюдение слоёв, SOLID, KISS.
- Контейнеризация (Docker + Docker Compose).
Не входит в scope:
- Маршрутизация по названиям чатов/контактов.
- Автоматическое переименование топиков при смене имени в MAX.
- Редактирование и удаление доставленных сообщений.
- Синхронизация статусов «прочитано», индикаторов набора текста, звонков.
8. Критерии приёмки (Бизнес и Технические)
| № | Критерий | Тип проверки |
|---|---|---|
| 1 | Приложение успешно запускается командой docker compose up -d. |
Технический |
| 2 | SQLite работает в режиме WAL, Redis сохраняет очередь между рестартами. | Технический |
| 3 | Подключение к MAX осуществляется по WebSocket, автоматическое переподключение при обрыве. | Технический |
| 4 | Архитектура: Отсутствуют прямые вызовы API одного слоя из другого. Код структурирован по слоям, соответствует SOLID и KISS. | Технический/Code Review |
| 5 | В SQLite корректно обновляется last_processed_message_id. Дубликаты при переподключении WS не попадают в очередь. |
Технический |
| 6 | Сценарий даунтайма: После 5-минутного простоя пропущенные сообщения доставляются без потерь и дублей. | Бизнес/Технический |
| 7 | Переименование чата в MAX не прерывает пересылку (маршрутизация строго по ID). | Бизнес |
| 8 | Сообщение из TG пересылается в MAX только если маппинг найден в БД. | Бизнес |
| 9 | При флуде (10+ сообщений) соблюдаются интервалы: ≥ 3.5 сек (TG) и ≥ MAX_RATE_LIMIT_DELAY_SEC (MAX). Ошибки 429 отсутствуют. |
Технический |
| 10 | Реакция 🦄 ставится в TG строго после успешной отправки из очереди в MAX. | Бизнес |
| 11 | Любой участник форума может писать в маппированный ЛС-топик, сообщение доходит до контакта в MAX. | Бизнес |
| 12 | Команды /start, /help, /list и др. работают только в личной переписке и только от FALLBACK_USER_ID. |
Бизнес |
| 13 | Уровень логирования (LOG_LEVEL) применяется ко всем компонентам, логи структурированы в JSON. |
Технический |
| 14 | При критической ошибке отправки fallback-пользователь получает уведомление в ЛС с деталями. | Бизнес |