Files
max2telegram/tech-specs.md
T
kislovdm e7c32da1f9
Docker Hub / build-and-push (push) Failing after 13s
init
2026-06-12 00:42:55 +03:00

262 lines
21 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.
# Техническая спецификация: 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. Общая схема
```mermaid
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» (с маркерами и очередью)
1. `Слой клиента MAX` подключается по WebSocket, запрашивает последние маркеры из `Слоя состояния`.
2. При получении нового события:
- **Дедупликация:** если `message_id` <= `last_message_id`, событие игнорируется.
- `Слой маршрутизации` проверяет маппинг в `Слое маппинга`. Если нет – запрашивает у `Слоя клиента Telegram` создание топика, сохраняет маппинг.
- Задача сериализуется и передается в `Слой очередей` (`max2tg_queue`).
- **Маркер** атомарно обновляется в `Слое состояния`.
3. `Слой клиента Telegram` (Воркер) забирает задачу, соблюдает лимит (≥ 3.5 сек), отправляет сообщение и сохраняет связь `max_message_id` ↔ `tg_message_id` в БД.
### 3.3. Поток «Telegram → MAX» (с очередью)
1. `Слой клиента Telegram` получает сообщение из топика (`tg_chat_id` + `message_thread_id`). Игнорирует сообщения бота.
2. `Слой маршрутизации` ищет пару `{tg_chat_id, message_thread_id}` в `Слое маппинга`.
3. Если найдено: задача передается в `Слой очередей` (`tg2max_queue`). Если нет: игнорируется.
4. `Слой клиента MAX` (Воркер) забирает задачу, ждет настраиваемого лимита, отправляет сообщение в MAX.
5. **Только после успеха от 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):
1. **MAX WebSocket Listener**: Поддерживает соединение, парсит события, управляет маркерами.
2. **Telegram Listener**: Aiogram Dispatcher.
3. **TG Worker**: Цикл `BLPOP` из `max2tg_queue` с `asyncio.sleep(3.5)`.
4. **MAX Worker**: Цикл `BLPOP` из `tg2max_queue` с настраиваемой задержкой.
5. **Admin Handler**: Обработчик команд только от `FALLBACK_USER_ID`.
### 4.3. Требования к развертыванию (Docker + Docker Compose)
Сервис поставляется в виде готового к запуску окружения. Папка `./data` монтируется для сохранения SQLite БД и сессии PyMAX между перезапусками.
```yaml
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:///app/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. Архитектурные принципы и стандарты разработки
Проект обязан строго следовать следующим инженерным стандартам:
1. **Строгое разделение слоёв (Layered Architecture)**:
- `MAX Layer` отвечает **только** за взаимодействие с API MAX (подключение WS, отправка/получение сообщений, сессия).
- `Telegram Layer` отвечает **только** за взаимодействие с Telegram Bot API (получение обновлений, отправка в топики, реакции, обработка команд).
- `Router Layer` отвечает **только** за логику сопоставления идентификаторов и принятие решений о маршруте.
- `Queue Layer` отвечает **только** за сериализацию, буферизацию и доставку задач между слоями.
- `Storage Layer` отвечает **только** за атомарные операции чтения/записи в SQLite/Redis.
- **Запрещено**: вызывать методы одного слоя из другого напрямую. Например, `MAX Layer` не может ставить реакции или создавать топики в Telegram. Вся межслойная коммуникация проходит через интерфейсы абстракций и очереди.
2. **SOLID**:
- **S**ingle Responsibility: Каждый модуль/класс выполняет одну четкую задачу.
- **O**pen/Closed: Расширение функционала (новые типы медиа, новые команды) происходит без модификации стабильного кода ядерных компонентов.
- **L**iskov Substitution & **I**nterface Segregation: Чёткие контракты между слоями, минимум методов в интерфейсах.
- **D**ependency Inversion: Слои зависят от абстракций (протоколов/интерфейсов), а не от конкретных реализаций клиентов.
3. **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 и маркеры)
1. **Хранение маркеров**: Таблица `sync_markers` в SQLite хранит `last_processed_message_id` для каждого чата.
2. **Идемпотентность**: События с `message_id` <= маркера отбрасываются.
3. **Восстановление после даунтайма**: При старте читаются маркеры, запрашивается история (до `MAX_RECONNECT_FETCH_LIMIT`), сообщения ставятся в очередь.
4. **Атомарность**: Маркер обновляется только после успешной постановки в 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 | `sqlite+aiosqlite:///app/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-пользователь получает уведомление в ЛС с деталями. | Бизнес |