surfaces/docs/superpowers/specs/2026-03-31-forum-topics-design.md

6.9 KiB
Raw Permalink Blame History

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) один и тот же — платформа видит единый разговор

БД — изменения схемы

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:

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

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

Проверка прав

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. Ответить в теме: "✅ Чат зарегистрирован. Пиши здесь!"

Маршрутизация сообщений

Определение источника

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 (новый файл)

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 — добавить

class ForumSetupState(StatesGroup):
    waiting_for_group = State()

Что НЕ реализуем

  • Отслеживание создания тем пользователем без /new — Telegram не присылает событие создания темы в боте
  • Синхронизация удаления темы ↔ архивация DM-чата (только через команды)
  • Поддержка нескольких групп на одного пользователя

Порядок реализации

  1. db.py — миграция + 4 новых функции
  2. states.pyForumSetupState
  3. handlers/forum.py/forum + onboarding
  4. handlers/chat.pycmd_new_chat с ветвлением, handle_message с Forum-маршрутизацией
  5. converter.pyis_forum_message, resolve_chat_id