# Техническое задание: двунаправленный мост MAX <-> Telegram ## 1. Назначение системы Система должна обеспечивать непрерывную двунаправленную синхронизацию сообщений между мессенджером MAX и Telegram: - направление A: входящие сообщения из MAX пересылаются в Telegram; - направление B: входящие сообщения из Telegram пересылаются в MAX; - поддерживаются текст, фото, видео, документы/файлы и ответы (reply) в пределах доступных API; - реализованы дедупликация, хранение связей между сообщениями в обоих направлениях и базовый health-monitoring; - предусмотрен fallback-маршрут в Telegram при отсутствии явного соответствия чатов. Система реализуется как один сервисный процесс (или контейнер), работающий постоянно. ## 2. Контекст и границы ### 2.1 Внешние зависимости Обязательные внешние системы: - API MAX (через клиентскую библиотеку или собственный API-клиент); - Telegram Bot API (через клиентскую библиотеку); - SQLite (или совместимое хранилище) для локального состояния; - переменные окружения для конфигурации. ### 2.2 Что входит в систему - запуск и аутентификация клиента MAX; - polling Telegram `getUpdates` (без webhook); - обработка входящих событий в обоих направлениях; - маршрутизация по названию чата и по явным биндам; - хранение mapping/дедупликации/маршрутов; - HTTP health endpoints (`/livez`, `/healthz`). ### 2.3 Что НЕ входит в систему - UI/панель администрирования; - сложная очередь сообщений (Kafka/RabbitMQ и т.п.); - гарантированная exactly-once доставка между платформами; - миграции БД с версионированием (в базовой реализации только `CREATE TABLE IF NOT EXISTS`); - хранение медиа в собственной файловой инфраструктуре. ## 3. Функциональные требования ## 3.1 MAX -> Telegram При получении сообщения из MAX система должна: 1. Распарсить сообщение: - `message_id`, `chat_id`, `sender_name`, `chat_name`, текст; - список `image_urls`, `video_urls`, `file_urls`; - список неизвестных вложений; - данные reply-контекста (`reply_to_max_message_id`, preview). 2. Обогатить данные через API MAX (best effort): - попытаться получить человекочитаемое имя отправителя; - попытаться получить реальное название чата; - извлечь дополнительные вложения через типизированные attach-объекты. 3. Проверить дедупликацию по паре `(message_id, chat_id)`; дубликаты не отправлять. 4. Выбрать Telegram-чат: - сначала через явный route (bind) по нормализованному названию MAX-чата; - затем через кэш чатов Telegram по совпадению заголовка; - если не найдено — отправка в `fallback_user_id`. 5. Сформировать текст: - если целевой Telegram-чат найден (не fallback): `"{sender}:\n{text}"`; - если fallback: `"{sender} / {chat}:\n{text}"`; - при неизвестных вложениях добавить уведомление в конец. 6. Обработать reply: - попытаться найти Telegram `reply_to_message_id` через mapping; - если mapping не найден — добавить текстовый контекст ответа. 7. Отправить контент: - при `image+video > 1` — отправить единым альбомом (`sendMediaGroup`); - иначе отправить текст/медиа/файлы поштучно; - при полностью пустом payload отправить служебный fallback-текст. 8. Сохранить mapping отправленных сообщений. 9. Пометить сообщение как forwarded. 10. При ошибке основного пути сделать аварийное best-effort уведомление в fallback-чат Telegram. ## 3.2 Telegram -> MAX Система должна запускать единственный polling-цикл `getUpdates` и: 1. При старте получить `bot_id` через `getMe`. 2. Обновить локальный кэш чатов MAX (`title -> id`) на основе доступного списка. 3. В цикле получать updates с `offset` и `allowed_updates=["message","channel_post"]`. 4. Для каждого сообщения: - отбросить неподдерживаемые типы; - отбросить сообщения бота (защита от петель); - обработать команду `/bind_max` в приоритетном порядке; - обработать служебные команды управления MAX (только личка + только fallback user); - для обычных сообщений найти чат MAX по нормализованному названию Telegram-чата; - если чат MAX не найден — не пересылать, логировать ошибку. 5. Обработать `media_group_id`: - буферизовать элементы альбома по ключу `(telegram_chat_id, media_group_id)`; - после grace-паузы (около 1.2 сек) отправить одним сообщением в MAX с несколькими вложениями. 6. Для одиночных сообщений: - сформировать текст `"{sender}:\n{text_or_caption}"`; - для фото/видео получить URL через `getFile` и вложить в MAX как native media attach; - для document/audio/voice/animation/sticker/video_note добавить URL-список в текстовый блок; - при reply попытаться найти соответствующее сообщение MAX через mapping. 7. После успешной отправки: - поставить реакцию на Telegram-сообщение (emoji, best effort); - сохранить mapping `(telegram chat/message -> max chat/message)`. ## 3.3 Управляющие команды в Telegram Команды обрабатываются только в приватном чате с ботом и только от пользователя `fallback_user_id`, кроме `/bind_max` (она может работать в целевом чате). Поддерживаемые команды: - `/help` — список команд; - `/list` — список активных чатов MAX; - `/join ` — вступление в группу/канал MAX по ссылке; - `/leave <НАЗВАНИЕ>` — выход из канала MAX (только если тип чата определен как канал); - `/last_messages <НАЗВАНИЕ>` — последние 10 сообщений; - `/bind_max <точное название MAX-чата>` — привязка текущего Telegram-чата к MAX-чату. Требования: - у команд должны быть понятные текстовые ответы; - ошибки внешних API должны возвращаться в ответе, не падая процессом; - `/bind_max` должна валидировать существование MAX-чата перед сохранением маршрута. ## 4. Нефункциональные требования - **Надежность:** сервис работает бесконечно, при ошибках polling применяет backoff. - **Идемпотентность (частичная):** дедупликация MAX->Telegram через БД. - **Наблюдаемость:** структурированные логи и health endpoint. - **Портируемость:** реализация возможна на любом языке при соблюдении контрактов. - **Производительность:** обработка событий в near real-time, без тяжелых batch-процессов. - **Отказоустойчивость:** best-effort при частичных отказах API/медиа. ## 5. Конфигурация (env contract) Обязательные параметры: - `MAX_PHONE` — телефон аккаунта MAX (для авторизации/сессии); - `MAX_WORK_DIR` — рабочая директория клиента MAX (по умолчанию `cache`); - `TELEGRAM_BOT_TOKEN` — токен Telegram-бота; - `TELEGRAM_FALLBACK_USER_ID` — Telegram user/chat id для fallback; - `SQLITE_PATH` — путь до SQLite БД (по умолчанию `${MAX_WORK_DIR}/max2telegram.db`). Дополнительно: - `TZ` — таймзона окружения; - `PYTHONUNBUFFERED` (или аналог) — политика буферизации логов. Валидация: - обязательные переменные должны проверяться при старте; - при отсутствии обязательной переменной процесс завершает запуск с явной ошибкой. ## 6. Архитектура ### 6.1 Компоненты 1. **Bootstrap / Main** - загружает конфиг; - создает клиентов MAX и Telegram; - инициализирует storage; - запускает health server; - подключает обработчики входящих сообщений MAX; - запускает Telegram->MAX poller как фоновую задачу. 2. **MAX->Telegram Bridge** - парсинг + enrich входящих MAX-сообщений; - маршрутизация в Telegram; - отправка текст/медиа/документы; - обработка миграции Telegram chat id; - сохранение mapping и dedup. 3. **Telegram API Client** - HTTP-обертка над Bot API; - методы отправки всех типов контента; - `getUpdates` + кэширование известных чатов; - `getFile` для медиа URL; - унифицированная модель ошибок с извлечением `migrate_to_chat_id`. 4. **Telegram->MAX Bridge** - polling updates; - фильтрация собственных сообщений; - обработка команд; - преобразование Telegram payload -> MAX message/attachments; - буферизация media group; - реакция и mapping. 5. **Parser MAX Message** - универсальный best-effort разбор разнородных форматов вложений; - классификация URL по типам; - извлечение reply-контекста. 6. **Storage** - таблица дедупликации; - таблица двустороннего mapping; - таблица явных маршрутов (binds). 7. **Health subsystem** - хранит отметки времени последнего успеха/ошибки по MAX и Telegram; - формирует snapshot; - HTTP endpoint отдает liveness/readiness. 8. **Auth utility** - отдельный скрипт первичной авторизации MAX; - сохраняет сессию в рабочем каталоге. ### 6.2 Логическая схема взаимодействия - MAX event -> Parser -> MAX->TG Bridge -> Telegram API -> Storage update. - Telegram update -> TG->MAX Bridge -> MAX API -> Storage update. - TG->MAX Bridge единолично вызывает `getUpdates`, одновременно наполняя кэш чатов Telegram. - Storage используется обоими мостами как разделяемый слой состояния. - Health обновляется из polling-циклов и MAX событий. ## 7. Модель данных и БД Используется SQLite (или эквивалент в другой СУБД). ### 7.1 Таблица `forwarded_messages` Назначение: дедупликация MAX->Telegram. Поля: - `message_id TEXT NOT NULL` - `chat_id TEXT NOT NULL` - `forwarded_at DATETIME DEFAULT CURRENT_TIMESTAMP` Ключ: - `PRIMARY KEY (message_id, chat_id)` ### 7.2 Таблица `message_mapping` Назначение: двусторонняя связка сообщений для reply и трассировки. Поля: - `telegram_chat_id TEXT NOT NULL` - `telegram_message_id TEXT NOT NULL` - `max_chat_id TEXT NOT NULL` - `max_message_id TEXT NOT NULL` - `media_group_id TEXT NULL` - `created_at DATETIME DEFAULT CURRENT_TIMESTAMP` Ключ: - `PRIMARY KEY (telegram_chat_id, telegram_message_id)` Индексы: - `(max_chat_id, max_message_id)` — поиск Telegram-сообщения по MAX; - `(telegram_chat_id, media_group_id)` — групповые операции альбомов. ### 7.3 Таблица `chat_routes` Назначение: явные маршруты MAX chat title -> Telegram chat id. Поля: - `max_chat_title_norm TEXT PRIMARY KEY` - `telegram_chat_id TEXT NOT NULL` - `telegram_chat_title TEXT NULL` - `created_at DATETIME DEFAULT CURRENT_TIMESTAMP` Нормализация ключа: - trim + casefold/lower (без учета регистра). ## 8. Алгоритмы и правила ## 8.1 Нормализация названий чатов Во всех маршрутизирующих сравнениях: - удалить крайние пробелы; - привести к регистронезависимой форме (`casefold`/`lower`); - сравнивать только в нормализованном виде. ## 8.2 Политика маршрутизации MAX -> Telegram Порядок выбора: 1. `chat_routes` по нормализованному MAX title; 2. локальный кэш Telegram title->id (наполняется из `getUpdates`); 3. fallback user/chat id. ## 8.3 Политика медиа - MAX->Telegram: - если суммарно фото+видео больше одного, использовать album API; - документы отправлять отдельными сообщениями; - caption добавлять только к первому элементу/первому отправляемому сообщению. - Telegram->MAX: - фото/видео отправлять как native attachments; - прочие типы файлов прикладывать ссылками в тексте. ## 8.4 Reply-семантика - Для Telegram->MAX: - если Telegram message является reply, искать соответствующий `max_message_id` в mapping; - если найден, отправлять `reply_to` в MAX. - Для MAX->Telegram: - если MAX message является reply, искать `telegram_message_id` в mapping; - если найден, отправлять `reply_to_message_id`; - если не найден, добавлять текстовую пометку с превью исходного сообщения. ## 8.5 Обработка migration в Telegram Если Telegram API возвращает ошибку с `parameters.migrate_to_chat_id`: 1. обновить маршрут в `chat_routes` на новый chat id; 2. повторить отправку в новый chat id; 3. считать повтор успешным итоговым результатом. ## 8.6 Дедупликация - применяется для MAX->Telegram по ключу `(max_message_id, max_chat_id)`; - после успешной отправки обязательно mark-forwarded; - на старте/рестарте состояния берутся из БД. ## 8.7 Buffering media group (Telegram) - ключ буфера: `(telegram_chat_id, media_group_id)`; - каждое сообщение альбома копится в списке; - по истечении grace-периода группа отправляется одним вызовом в MAX; - после отправки буфер очищается. ## 9. API-контракты внутренних модулей Ниже абстрактные контракты, независимые от языка: - `Settings load_settings()` - читает env; - валидирует обязательные поля; - возвращает immutable-конфигурацию. - `ParsedMessage parse_message(MaxMessage msg)` - best-effort преобразование сырого MAX-сообщения в каноническую структуру. - `BridgeStorage` - `was_forwarded(message_id, chat_id) -> bool` - `mark_forwarded(message_id, chat_id)` - `save_mapping(telegram_chat_id, telegram_message_id, max_chat_id, max_message_id, media_group_id?)` - `get_max_message_id_for_telegram(telegram_chat_id, telegram_message_id) -> str?` - `get_telegram_message_id_for_max(telegram_chat_id, max_chat_id, max_message_id) -> str?` - `set_chat_route(max_chat_title_norm, telegram_chat_id, telegram_chat_title?)` - `get_chat_route(max_chat_title_norm) -> str?` - `TelegramClient` - `resolve_target_chat_id(max_chat_name) -> (chat_id, matched_by_title)` - `send_text/send_photo/send_video/send_document/send_media_group(...)` - `get_updates(offset, timeout, limit) -> updates[]` - `get_file_url(file_id) -> url` - `add_reaction(chat_id, message_id, emoji)` - `MaxToTelegramBridge.forward_message(max_message)` - `TelegramToMaxBridge.start()` - `handle_control_command(message, max_client, telegram) -> str?` ## 10. Health-check модель Должны храниться timestamps: - telegram: `last_ok`, `last_error`; - max: `last_ok`, `last_error`, `last_event`; - `started_at`. Параметр: - `unhealthy_after_sec` (по умолчанию ~15 минут). Правила: - `telegram_healthy = last_ok exists && now-last_ok <= unhealthy_after_sec`; - `max_healthy = last_ok exists && now-last_ok <= unhealthy_after_sec`; - `overall_healthy = telegram_healthy && max_healthy`. HTTP: - `GET /livez` (и `/live`, `/`) -> 200, `{status:"live", uptime_sec}`. - `GET /healthz` (и `/health`) -> 200 или 503, детальный JSON по компонентам. ## 11. Поведение при ошибках и устойчивость - Любая ошибка обработки отдельного сообщения не должна останавливать сервис. - Polling Telegram при ошибке уходит в backoff (например 10 секунд). - Ошибка реакции в Telegram не влияет на основную доставку. - Ошибка аварийного уведомления логируется, но не роняет процесс. - Проблемы с разрешением URL медиа обрабатываются best-effort: - что удалось достать — отправляется; - что не удалось — отражается в тексте/unknown notices. ## 12. Логирование и диагностика Обязательные события логов: - старт/остановка компонентов; - маршрутизация сообщений; - обнаружение дубликатов; - ошибки API и stacktrace; - успешная пересылка с количеством вложений; - операции bind и migration chat id. Рекомендуемый формат: - timestamp + level + logger + message. ## 13. Сценарии запуска и деплой ## 13.1 Локальный запуск 1. Подготовить `.env`. 2. Установить зависимости. 3. Один раз пройти auth MAX (интерактивно), сохранить сессию. 4. Запустить основной процесс. ## 13.2 Контейнерный запуск - контейнер должен включать runtime + зависимости; - каталог `cache` должен быть volume для сохранения сессии и SQLite; - должен быть healthcheck через `GET /healthz`. ## 13.3 CI/CD (рекомендованно) - сборка Docker image при push в основные ветки; - публикация в registry с тегами для dev/release/latest; - кэширование слоев сборки. ## 14. Требования к переносимой реализации (на любом языке) Чтобы воссоздать проект в другом языке, необходимо сохранить: 1. Два независимых, но согласованных канала обработки: - event-driven для MAX сообщений; - polling-loop для Telegram updates. 2. Единое постоянное хранилище с тремя сущностями: - dedup; - mapping; - routes. 3. Идентичные правила нормализации названий чатов и выбора маршрута. 4. Reply-механику с fallback-текстом при отсутствии mapping. 5. Политику media group и порядок отправки вложений. 6. Обработку Telegram migration (`migrate_to_chat_id`) с обновлением маршрута. 7. Health-модель с независимыми метками MAX/Telegram. 8. Ограничение: только один consumer `getUpdates` на экземпляр бота. ## 15. Acceptance criteria Система считается реализованной, если: 1. Текст/фото/видео/файлы корректно ходят в обе стороны. 2. MAX->Telegram не дублирует уже пересланные сообщения после рестарта. 3. Reply в обе стороны сохраняется, если mapping существует. 4. При отсутствии Telegram-совпадения сообщение уходит в fallback. 5. `/bind_max` меняет маршрут и влияет на последующие MAX->Telegram сообщения. 6. Telegram media group приходит в MAX как одно сообщение с множеством вложений. 7. `/healthz` возвращает 200 при рабочем MAX+Telegram и 503 при деградации. 8. Ошибки отдельных сообщений не приводят к остановке процесса. ## 16. Известные ограничения текущей логики - Автопоиск Telegram-чата зависит от того, что чат уже встречался в `getUpdates`. - Для нестандартных вложений возможна частичная деградация в plain text + ссылки. - Дедупликация реализована только для потока MAX->Telegram. - Конкурентный доступ к SQLite через множество соединений допустим для небольших нагрузок, но для high-load может потребоваться иной storage backend. ## 17. Рекомендации для расширения (необязательно) - добавить миграции схемы БД; - добавить retry policy с классификацией transient/permanent ошибок; - добавить метрики (Prometheus/OpenTelemetry); - добавить интеграционные тесты с моками API MAX/Telegram; - добавить персистентный offset Telegram (если нужен recovery без повторов между рестартами).