Forum topics — это режим супергруппы, который превращает её в форум с разделами (темами). Каждая тема — отдельная ветка с собственным заголовком, иконкой и набором сообщений. Для бота это удобный инструмент маршрутизации: один чат для всей команды поддержки, но каждая заявка живёт в своей теме.
Разберём, как технически работают topics в Bot API, как создавать темы программно, как маршрутизировать сообщения и какие подводные ловят разработчики при миграции старых чатов.
Что такое forum topics
Forum mode включается у супергруппы через настройки админа: Settings → Topics → On. После этого каждое сообщение в чате привязано к теме (message_thread_id), и UI клиента показывает список тем как вкладки или отдельные ветки.
Технически в Bot API тема — это объект с полями:
{
"message_thread_id": 12345,
"name": "Заявка #842 — Иван Петров",
"icon_color": 7322096,
"icon_custom_emoji_id": "5312536423851630001"
}
message_thread_id — это ID первого сообщения темы, и одновременно идентификатор всей ветки. Все методы отправки (sendMessage, sendPhoto, sendInvoice) принимают опциональный параметр message_thread_id, чтобы положить сообщение в нужную тему.
Когда forum mode имеет смысл
Хорошие сценарии:
- Саппорт: каждая заявка → отдельная тема, видно историю в одном месте.
- Сообщество разработчиков: темы по технологиям (Python, Go, Frontend).
- Внутренние команды: одна тема на проект или клиента.
- Школы: тема на каждый класс или предмет.
Плохие сценарии:
- Канал с пассивной аудиторией — у форумов нет broadcast-режима.
- Маркетинговый чат — пользователи теряются в темах, лучше обычная группа.
- Менее 30 активных участников — overhead не оправдан.
Создание темы из бота: createForumTopic
Бот, добавленный админом с правом manage_topics, может создавать темы:
from aiogram import Bot
from aiogram.types import ForumTopic
async def create_ticket_topic(bot: Bot, chat_id: int, ticket_id: int, user_name: str) -> int:
topic: ForumTopic = await bot.create_forum_topic(
chat_id=chat_id,
name=f"Заявка #{ticket_id} — {user_name}",
icon_color=0x6FB9F0, # голубой
icon_custom_emoji_id=None,
)
return topic.message_thread_id
Топик-цвет выбирается из 6 фиксированных значений: 0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F. Для кастомного эмодзи нужен icon_custom_emoji_id из ваших премиум-стикеров.
Маршрутизация: входящее сообщение → нужная тема
Сценарий саппорта: юзер пишет в DM → бот создаёт тему в общем рабочем чате → команда отвечает в теме → бот пересылает ответ обратно в DM.
from aiogram import Router, F
from aiogram.types import Message
router = Router()
# 1. Юзер пишет в DM боту
@router.message(F.chat.type == "private")
async def from_user(message: Message, bot: Bot):
ticket = await tickets.get_or_create(user_id=message.from_user.id)
if not ticket.thread_id:
thread_id = await create_ticket_topic(
bot, SUPPORT_CHAT_ID, ticket.id, message.from_user.full_name
)
ticket.thread_id = thread_id
await tickets.save(ticket)
await bot.copy_message(
chat_id=SUPPORT_CHAT_ID,
message_thread_id=ticket.thread_id,
from_chat_id=message.chat.id,
message_id=message.message_id,
)
# 2. Оператор отвечает в теме
@router.message(F.chat.id == SUPPORT_CHAT_ID, F.message_thread_id)
async def from_operator(message: Message, bot: Bot):
ticket = await tickets.find_by_thread(message.message_thread_id)
if not ticket:
return
await bot.copy_message(
chat_id=ticket.user_id,
from_chat_id=message.chat.id,
message_id=message.message_id,
)
copy_message сохраняет содержимое (текст, медиа, кнопки), но не показывает «Forwarded from» — пользователь видит чистое сообщение.
Закрытие и переоткрытие темы
Когда заявка решена, тему можно закрыть, чтобы новые сообщения от юзера автоматически попадали в архив:
await bot.close_forum_topic(SUPPORT_CHAT_ID, message_thread_id=thread_id)
# При новой жалобе:
await bot.reopen_forum_topic(SUPPORT_CHAT_ID, message_thread_id=thread_id)
Закрытая тема перестаёт принимать сообщения от обычных юзеров (только от админов и бота). Это удобный способ сегментировать активные/архивные тикеты.
| Метод | Зачем |
|---|---|
createForumTopic | новая тема |
editForumTopic | переименовать, сменить иконку |
closeForumTopic | закрыть для записи |
reopenForumTopic | снова разрешить запись |
deleteForumTopic | удалить со всеми сообщениями |
unpinAllForumTopicMessages | снять все закрепы |
getForumTopicIconStickers | получить набор премиум-эмодзи |
События темы в апдейтах
Бот получает события forum_topic_created, forum_topic_edited, forum_topic_closed, forum_topic_reopened как сервисные сообщения:
@router.message(F.forum_topic_created)
async def on_topic_created(message: Message):
name = message.forum_topic_created.name
await db.log_topic(message.chat.id, message.message_thread_id, name)
Это полезно для аудита: видеть, кто и когда создал какую тему вручную.
Лимиты и подводные
- Максимум 1 тема в секунду на чат через
createForumTopic. При массовом создании нужна очередь. - Лимит 1024 тем на чат не задокументирован, но на практике после ~2000 тем UI клиента начинает тормозить.
- General topic (главная тема) удалить нельзя, можно только скрыть через
hideGeneralForumTopic. - Конвертация старого чата в форум необратима — все сообщения уезжают в General topic.
- Боты-юзеры (не клиент-сессии) не могут создавать темы без явного права
manage_topicsу админа.
Поиск по темам
Telegram не отдаёт через Bot API список тем — только через клиент-сессию (Telethon/Pyrogram, метод GetForumTopics). В простом сценарии бот сам ведёт реестр в БД:
class Topic(Base):
__tablename__ = "topics"
chat_id: int
thread_id: int
name: str
user_id: int
status: str # open|closed|deleted
created_at: datetime
Поиск по name/user_id/status выполняется через ваш индекс, не через Telegram.
Топ-5 ошибок при работе с темами
- Забывают передать
message_thread_idвsendMessage→ сообщение уходит в General. - Не различают
message_thread_idиmessage_id(это разные сущности с одинаковым числовым значением для первого сообщения темы). - Создают новую тему на каждое сообщение юзера вместо одной на тикет.
- Не закрывают темы после решения — чат разрастается до 1000+ открытых.
- Удаляют тему, забывая очистить
thread_idв БД — следующая заявка от того же юзера уходит в null.
Итого
Forum topics — мощный инструмент для саппорта, сообществ и внутренних команд: один чат, понятная структура, чистая маршрутизация. Для бота это значит грамотный createForumTopic, передачу message_thread_id во все методы отправки и хранение карты «юзер ↔ тема» в собственной БД. Сделайте это правильно — и команда из 5 операторов закроет в день в 3–4 раза больше тикетов, чем в плоской группе.
Частые вопросы
В чём разница между forum topic и обычным reply-thread?
Reply-thread — это виртуальная цепочка ответов, она не создаёт отдельную сущность в чате и не маршрутизируется через Bot API. Forum topic — это именованная вкладка с собственным message_thread_id, которую видят все участники как отдельный раздел. Для маршрутизации заявок и тематических обсуждений подходит только forum topic.
Можно ли превратить обычный чат в форум?
Да, через настройки супергруппы → Topics → On. Все существующие сообщения уезжают в General topic, и обратной конвертации нет. Перед миграцией убедитесь, что участникам объяснили новую структуру: форум сильно меняет привычный UX чата.
Как раздавать права на конкретные темы?
Bot API этого не умеет. Права (post, edit, delete) настраиваются на уровне всего чата через promoteChatMember, и распространяются на все темы. Если нужно ограничить доступ к темам — закройте их через closeForumTopic и пускайте писать только админов.
Сколько тем можно держать открытыми?
Жёсткого лимита нет, но клиенты Telegram комфортно работают до ~500 тем. После этого UI начинает тормозить, поиск по темам становится медленным. В саппорте лучше закрывать решённые тикеты сразу — это и UX-чище, и быстрее.
Можно ли отправить пост сразу во все темы?
Нет, broadcast по темам в Bot API нет. Нужно итерироваться по списку thread_id из вашей БД и слать каждое сообщение отдельно с пейсингом 1 мсг/сек на чат. Для рассылок такого типа форум — плохое решение, лучше канал.
Как боту узнать список всех тем чата?
Через Bot API — никак. Бот видит тему только когда в ней появляется новое сообщение или он сам её создал. Для полного списка нужна client-сессия (Telethon, метод GetForumTopics) от имени админа. Альтернатива — вести собственный реестр тем в БД с момента подключения бота.
Что делать с General topic?
General — главная тема, удалить нельзя. Можно скрыть через hideGeneralForumTopic, тогда у участников она пропадёт из списка, но сервисные сообщения (вход/выход, смена названия) всё равно будут уходить туда. В сценариях саппорта обычно General используется как «приёмная» для нераспределённых заявок.