262 lines
21 KiB
Markdown
262 lines
21 KiB
Markdown
# Техническая спецификация: 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:///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 (относительно `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-пользователь получает уведомление в ЛС с деталями. | Бизнес | |