# Spec: tg-publisher-service ## Purpose Изолированный FastAPI-сервис публикации карточек в Telegram-каналы через Bot API. Единая точка вызова для всех компонентов (веб, cron, будущие боты). Один бот-контроллер публикует во все каналы, в которые добавлен администратором. Telegram доступен только через SOCKS5-прокси (127.0.0.1:1080). ## ADDED Requirements ### Requirement: Изолированный сервис публикации Сервис MUST быть отдельным FastAPI-приложением (контейнер), НЕ импортируемым модулем веба. Он MUST предоставлять `GET /healthz` (статус, бот, прокси, каналы) и `POST /api/v1/publish` (публикация карточки). #### Scenario: Healthcheck - **GIVEN** сервис запущен - **THEN** `GET /healthz` возвращает 200 с `{"status":"ok","bot":...,"channels":[...]}` и healthcheck в docker-compose (`curl -f`) проходит #### Scenario: Публикация карточки - **GIVEN** POST /api/v1/publish c `card: {text, direction, lang}` и `channels: ["@dedinit_vesti"]` - **THEN** сервис отправляет сообщение через Bot API в каждый канал и возвращает `{"ok":true,"results":{"@dedinit_vesti":{"message_id":,"views":0}}}` ### Requirement: Один бот, несколько каналов (конфиг) Сервис MUST поддерживать список каналов из конфигурации (`VESTI_BOT_CHANNELS`, через запятую). `channels` в запросе MAY переопределять список. Если бот добавлен в канал администратором — публикация MUST работать; иначе сервис MUST вернуть понятную ошибку (403). #### Scenario: Несколько каналов - **GIVEN** `VESTI_BOT_CHANNELS=@dedinit_vesti,@other_channel` (оба добавлены боту) - **WHEN** POST /api/v1/publish без поля channels - **THEN** карточка публикуется в оба канала; results содержит оба message_id #### Scenario: Бот не админ канала - **GIVEN** канал, в котором бот не администратор - **WHEN** публикация в него - **THEN** сервис возвращает 403 с текстом ошибки Bot API (sendMessage → Forbidden) ### Requirement: Прокси SOCKS5 для Bot API Все запросы к api.telegram.org MUST идти через прокси из `TG_PROXY=socks5://127.0.0.1:1080`. Если прокси недоступен — сервис MUST вернуть 502 (не падать). #### Scenario: Прокси недоступен - **GIVEN** прокси 127.0.0.1:1080 выключен - **WHEN** POST /api/v1/publish - **THEN** сервис возвращает 502 с ошибкой подключения (не 500, не краш) ### Requirement: Сбор views Сервис MUST предоставлять способ получения просмотров для опубликованных сообщений (через Bot API getMessage), чтобы веб показывал метрики. #### Scenario: Views после публикации - **GIVEN** сообщение опубликовано (message_id получен) - **WHEN** запрос views для этого message_id - **THEN** возвращается число просмотров (0 если ещё нет) ### Requirement: Режим подтверждения Сервис MUST НЕ публиковать автоматически: вызывается только по HTTP-запросу (из веба после клика «Опубликовать»). Автопостинга по таймеру внутри сервиса НЕТ. #### Scenario: Нет автопостинга - **GIVEN** сервис запущен без входящих запросов - **THEN** ничего не публикуется (процесс только слушает HTTP) ## Modified Requirements (из tg-publisher) - `publish_multi` и `get_views` переезжают в сервис (HTTP-интерфейс вместо импорта). - vesti-web: approve вызывает POST /api/v1/publish; ответ используется для distributed_dirs + views (как раньше, только источник данных — HTTP). ## NOT Requirements - Не реализуем чтение каналов (краулинг) — это остаётся в tg-crawler (Telethon). - Не реализуем веб-интерфейс сервиса (только API + healthz). - Не храним БД в сервисе (вся персистентность — в vesti.db через веб). - Не делаем автопостинг (см. Requirement выше).