# Forum Topics Mode Design **Date:** 2026-03-31 **Status:** Approved — ready for implementation **Scope:** `adapter/telegram/` — расширение существующего адаптера --- ## Контекст Forum Topics — опциональный advanced-режим поверх существующих виртуальных DM-чатов. Пользователь подключает свою Telegram-супергруппу с Topics — и его чаты появляются как нативные темы Telegram. DM и Forum работают **одновременно**: один контекст, две поверхности. --- ## Принцип работы Каждый чат (`chat_id` = UUID) получает опциональный `forum_thread_id`. - Пользователь пишет в DM → бот отвечает в DM с тегом `[Чат #N]` - Пользователь пишет в Forum-тему → бот отвечает в ту же тему (без тега) - Контекст (`chat_id`) один и тот же — платформа видит единый разговор --- ## БД — изменения схемы ```sql ALTER TABLE tg_users ADD COLUMN forum_group_id INTEGER; ALTER TABLE chats ADD COLUMN forum_thread_id INTEGER; ``` `forum_group_id` — ID супергруппы пользователя (NULL если группа не подключена). `forum_thread_id` — ID темы в форуме (NULL если чат создан только в DM). Новые функции в `db.py`: ```python def set_forum_group(tg_user_id: int, group_id: int) -> None def get_forum_group(tg_user_id: int) -> int | None def set_forum_thread(chat_id: str, thread_id: int) -> None def get_chat_by_thread(tg_user_id: int, thread_id: int) -> dict | None ``` --- ## Онбординг — `/forum` ### FSM ```python class ForumSetupState(StatesGroup): waiting_for_group = State() # ждём пересылку из группы ``` ### Флоу ``` /forum → FSM: ForumSetupState.waiting_for_group → "Создай супергруппу, включи Topics, добавь меня администратором с правом управления темами. Затем перешли мне любое сообщение из группы." [пользователь пересылает сообщение] → Проверить: forward_from_chat.type == "supergroup" → Проверить права бота (администратор + can_manage_topics) ❌ нет прав → объяснить что именно не так, остаться в состоянии → Сохранить forum_group_id в БД → Создать Forum-тему для каждого существующего активного DM-чата → Записать forum_thread_id для каждого чата → Ответить в DM: "✅ Группа подключена! Твои чаты теперь доступны в Forum-темах." → FSM: clear ``` ### Проверка прав ```python async def check_forum_admin(bot: Bot, group_id: int) -> bool: member = await bot.get_chat_member(group_id, (await bot.get_me()).id) return ( member.status in ("administrator", "creator") and getattr(member, "can_manage_topics", False) ) ``` --- ## Создание чатов — синхронизация ### `/new` в DM (группа подключена) 1. Создать UUID-запись в `chats` (как сейчас) 2. `create_forum_topic(bot, group_id, chat_name)` → получить `thread_id` 3. Записать `forum_thread_id` в БД 4. Переключить FSM на новый чат 5. Ответить в DM: `"✅ [chat_name] создан."` ### `/new` в DM (группа НЕ подключена) Без изменений — только DM-чат. ### `/new` в Forum-теме 1. Определить `thread_id` из `message.message_thread_id` 2. Создать UUID-запись в `chats` с `forum_thread_id = thread_id` 3. Название: из аргумента `/new Название` или из названия темы (`message.chat.forum_topic_created.name` при создании — иначе запросить у Telegram) 4. Ответить в теме: `"✅ Чат зарегистрирован. Пиши здесь!"` --- ## Маршрутизация сообщений ### Определение источника ```python def is_forum_message(message: Message) -> bool: return message.message_thread_id is not None def resolve_chat_id(message: Message, tg_user_id: int) -> str | None: if is_forum_message(message): chat = db.get_chat_by_thread(tg_user_id, message.message_thread_id) return chat["chat_id"] if chat else None else: # DM — берём active_chat_id из FSM StateData (как сейчас) return None # caller reads from FSM ``` ### Ответ - Пришло из DM → `bot.send_message(tg_user_id, f"[{chat_name}] {text}")` - Пришло из Forum-темы → `bot.send_message(group_id, text, message_thread_id=thread_id)` В Forum-теме тег `[Чат #N]` **не нужен** — тема сама является визуальным разделителем. --- ## Обработчики — изменения ### `handlers/forum.py` (новый файл) ```python router = Router(name="forum") @router.message(Command("forum")) async def cmd_forum(message, state): ... # запускает онбординг @router.message(ForumSetupState.waiting_for_group, F.forward_from_chat) async def handle_group_forward(message, state, dispatcher): ... # регистрирует группу ``` ### `handlers/chat.py` — изменения - `handle_message`: если `is_forum_message` → брать `chat_id` из БД по `thread_id`, отвечать в тему - `cmd_new_chat`: ветвление по источнику (DM vs Forum) и наличию `forum_group_id` ### `states.py` — добавить ```python class ForumSetupState(StatesGroup): waiting_for_group = State() ``` --- ## Что НЕ реализуем - Отслеживание создания тем пользователем без `/new` — Telegram не присылает событие создания темы в боте - Синхронизация удаления темы ↔ архивация DM-чата (только через команды) - Поддержка нескольких групп на одного пользователя --- ## Порядок реализации 1. `db.py` — миграция + 4 новых функции 2. `states.py` — `ForumSetupState` 3. `handlers/forum.py` — `/forum` + onboarding 4. `handlers/chat.py` — `cmd_new_chat` с ветвлением, `handle_message` с Forum-маршрутизацией 5. `converter.py` — `is_forum_message`, `resolve_chat_id`