docs/specs/BRIDGE.md

400 lines
12 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.

# Team Board — Telegram Bridge
## Назначение
**Telegram Bridge** — микросервис для интеграции Team Board с Telegram. Обеспечивает двустороннюю синхронизацию сообщений между проектными чатами Tracker и топиками Telegram группы.
**Основной файл:** `bridge.py`
**Архитектура:** Standalone Python сервис
---
## Архитектура
```
Telegram Bot API ↔ Bridge ↔ Tracker WebSocket
Topic Mapping
(JSON файл хранение)
```
### Компоненты:
1. **bridge.py** — основной сервис с двусторонней синхронизацией
2. **config.py** — конфигурация через environment переменные
3. **topic_map.py** — персистентное маппинг Telegram топик ↔ Tracker проект
4. **tracker_client.py** — WebSocket клиент для подключения к Tracker
---
## Конфигурация
**Файл:** `.env`
```bash
# Telegram Bot
TELEGRAM_BOT_TOKEN=1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ
TELEGRAM_GROUP_ID=-1001234567890
# Tracker
TRACKER_URL=https://dev.team.uix.su
TRACKER_WS_URL=wss://dev.team.uix.su/ws
BRIDGE_TOKEN=tb-bridge-dev-token
```
### Переменные окружения:
- **TELEGRAM_BOT_TOKEN** — токен бота от @BotFather
- **TELEGRAM_GROUP_ID** — ID группы с топиками (отрицательное число)
- **TRACKER_URL** — HTTP URL Tracker API
- **TRACKER_WS_URL** — WebSocket URL для подключения к Tracker
- **BRIDGE_TOKEN** — Bearer токен агента типа "bridge" в Tracker
---
## Маппинг топиков
**Файл:** `data/topic_map.json`
### Структура:
```json
{
"123": "550e8400-e29b-41d4-a716-446655440000",
"456": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
```
**Формат:** `{telegram_topic_id: tracker_project_uuid}`
### Класс TopicMap:
```python
class TopicMap:
def set(topic_id: int, project_uuid: str) # создать маппинг
def get_project(topic_id: int) -> str | None # получить проект по топику
def get_topic(project_uuid: str) -> int | None # получить топик по проекту
def remove_by_topic(topic_id: int) # удалить маппинг
def all() -> dict[int, str] # все маппинги
```
**Персистентность:** автоматическое сохранение в JSON файл при изменениях
---
## Telegram → Tracker
### Обработка сообщений:
```python
async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Фильтрация по группе и наличию topic
if message.chat_id != config.group_id or not message.message_thread_id:
return
# Поиск соответствующего проекта
project_uuid = topic_map.get_project(message.message_thread_id)
if not project_uuid:
return
# Формат сообщения: [Имя пользователя] текст
text = f"[{user.full_name}] {message.text}"
await tracker.send_message(project_uuid, text)
```
### Telegram команды:
#### `/link <project_slug>`
Привязать текущий топик к проекту
**Использование:**
```
/link team-board
```
**Логика:**
1. Проверка, что команда выполнена в топике
2. Поиск проекта по slug через Tracker API
3. Создание маппинга в TopicMap
4. Подтверждение в чате
#### `/unlink`
Отвязать текущий топик от проекта
**Логика:**
1. Проверка выполнения в топике
2. Удаление маппинга из TopicMap
3. Подтверждение в чате
#### `/bridge_status`
Показать статус bridge и все активные маппинги
**Пример ответа:**
```
Привязки топик → проект:
• topic 123 → 550e8400...
• topic 456 → 6ba7b810...
```
---
## Tracker → Telegram
### Поддерживаемые события:
#### `message.new`
Новое сообщение в проектном чате
**Фильтрация:**
- Пропуск сообщений от самого bridge (избежание эха)
- Пропуск сообщений с префиксом `[Username]` (отправленных bridge'ом)
**Формат в Telegram:**
```
[Автор]: текст сообщения
```
**Silent режим:** если есть онлайн участники-люди в web интерфейсе
#### `task.created`
Создание новой задачи
**Формат в Telegram:**
```
📋 Новая задача XX-123: Название задачи
```
#### `project.created`
Создание нового проекта
**Автоматическое действие:**
1. Создание топика в Telegram с именем проекта
2. Автоматический маппинг топик → проект
3. Уведомление о привязке
#### `agent.status`
Изменение статуса участника
**Отслеживание:** обновление множества онлайн участников для silent режима
---
## TrackerClient
**Файл:** `tracker_client.py`
### WebSocket подключение:
```python
class TrackerClient:
async def connect() # подключение с авто-переподключением
async def send_message(project_uuid, text) # отправка сообщения в проект
def stop() # остановка клиента
```
### Особенности:
- **Auto-reconnect:** переподключение каждые 5 секунд при разрыве
- **Heartbeat:** отправка heartbeat каждые 25 секунд
- **Queue система:** очередь сообщений до успешного подключения
- **Project caching:** кэширование project_uuid → chat_id маппингов
### Аутентификация:
```python
url = f"{config.tracker_ws_url}?token={config.bridge_token}"
```
**Тип участника:** `bridge` в Tracker системе
---
## Работа с проектами
### Кэширование chat_id:
```python
self._project_chat_ids: dict[str, str] = {} # project_uuid -> chat_id
```
**Загрузка при подключении:**
```python
GET /api/v1/projects
Authorization: Bearer {bridge_token}
```
**Lazy loading:** автоматическая подгрузка chat_id при необходимости
### Отправка сообщений:
1. Получение chat_id для проекта
2. Формирование WebSocket сообщения:
```json
{
"type": "chat.send",
"chat_id": "project_chat_uuid",
"content": "[Username] message text"
}
```
---
## Silent режим
**Логика:** если в системе есть онлайн участники-люди (не агенты/bridge), уведомления в Telegram отправляются беззвучно
**Отслеживание онлайн:**
```python
_online_members: set[str] = set() # member slugs
# При получении agent.status события:
if status == "online":
_online_members.add(slug)
else:
_online_members.discard(slug)
# При отправке в Telegram:
silent = any(s for s in _online_members if s not in ("coder", "architect", "bridge"))
```
**Исключения:** агенты `coder`, `architect`, `bridge` не считаются "людьми"
---
## HTML Escaping
Все сообщения в Telegram экранируются для HTML:
```python
def _escape_html(text: str) -> str:
return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
```
**Формат отправки:** `parse_mode="HTML"`
---
## Предотвращение эха
### Проблема:
Bridge отправляет сообщение в Tracker → Tracker рассылает `message.new` event → Bridge получает и отправляет в Telegram → возможное дублирование
### Решения:
#### 1. Пропуск сообщений от bridge:
```python
author_slug = author.get("slug", "")
if author_slug == "bridge":
return
```
#### 2. Пропуск сообщений с Telegram префиксом:
```python
if text_content.startswith("[") and "] " in text_content[:50]:
return
```
#### 3. Исключение собственной сессии в WebSocket:
Tracker автоматически не отправляет события отправителю
---
## Логирование
**Уровень:** INFO
**Формат:** `%(asctime)s [%(name)s] %(levelname)s: %(message)s`
### Ключевые логи:
```
Connected to Tracker WS
Authenticated as bridge
TG->Tracker: topic=123 project=550e8400
Auto-created topic 456 for project team-board (6ba7b810)
Linked topic 123 -> project team-board (550e8400)
```
---
## Запуск и развёртывание
### Зависимости:
```txt
python-telegram-bot[webhooks]
websockets
httpx
python-dotenv
```
**Файл:** `requirements.txt`
### Запуск:
```bash
python bridge.py
```
### Архитектура событий:
```python
async def main():
# Запуск Telegram polling
await app.updater.start_polling()
# Запуск Tracker WebSocket (блокирует)
await tracker.connect()
```
**Concurrent execution:** Telegram bot и Tracker WebSocket клиент работают параллельно
---
## Обработка ошибок
### Telegram API:
```python
try:
await _bot.send_message(...)
except Exception as e:
logger.error("Failed to send to topic %d: %s", topic_id, e)
```
### Tracker WebSocket:
```python
try:
# WebSocket операции
except websockets.ConnectionClosed:
logger.warning("Tracker WS closed, reconnecting in 5s...")
await asyncio.sleep(5)
```
### API запросы:
```python
try:
async with httpx.AsyncClient() as client:
resp = await client.get(...)
if resp.status_code != 200:
return None
except Exception as e:
logger.error("API error: %s", e)
```
**Стратегия:** логирование ошибок + graceful degradation без остановки сервиса
---
## Масштабирование
### Ограничения:
- Один bridge instance на Telegram группу
- JSON файл для маппингов (не подходит для кластера)
- In-memory кэш проектов
### Потенциальные улучшения:
- Redis для маппингов и кэша
- Database-backed конфигурация
- Multiple bot support
- Rate limiting для Telegram API
---
## Безопасность
### Аутентификация:
- Bridge токен для Tracker API/WebSocket
- Фильтрация по group_id в Telegram
- Только авторизованные команды
### Данные:
- Маппинги хранятся локально в JSON
- Логирование без чувствительной информации
- Автоматическое переподключение при разрыве
Этот документ описывает Telegram Bridge на основе исходного кода в `/root/projects/team-board/bridge/` на дату создания спецификации.