6.9 KiB
6.9 KiB
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 (группа подключена)
- Создать UUID-запись в
chats(как сейчас) create_forum_topic(bot, group_id, chat_name)→ получитьthread_id- Записать
forum_thread_idв БД - Переключить FSM на новый чат
- Ответить в DM:
"✅ [chat_name] создан."
/new в DM (группа НЕ подключена)
Без изменений — только DM-чат.
/new в Forum-теме
- Определить
thread_idизmessage.message_thread_id - Создать UUID-запись в
chatsсforum_thread_id = thread_id - Название: из аргумента
/new Названиеили из названия темы (message.chat.forum_topic_created.nameпри создании — иначе запросить у Telegram) - Ответить в теме:
"✅ Чат зарегистрирован. Пиши здесь!"
Маршрутизация сообщений
Определение источника
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-чата (только через команды)
- Поддержка нескольких групп на одного пользователя
Порядок реализации
db.py— миграция + 4 новых функцииstates.py—ForumSetupStatehandlers/forum.py—/forum+ onboardinghandlers/chat.py—cmd_new_chatс ветвлением,handle_messageс Forum-маршрутизациейconverter.py—is_forum_message,resolve_chat_id