Telethon отправляет сообщение в «Главную», а не в тему: как указать topic_id

Сообщение успешно отправилось в форум-группу Telegram, но оказалось в «Главной» вместо поддержки или объявлений. Начните с маршрута запроса: адрес группы определяет чат, а выбранную тему нужно указать отдельно. Отсутствие исключения ещё не означает, что сообщение появилось в нужном разделе.

Ниже — небольшие функции для уже авторизованного клиента и собственной тестовой форум-группы. Основа примеров проверена для Python 3.14.6 и Telethon 1.45.0. Подготовка ограничена проверкой синтаксиса и формы запросов без подключения к Telegram: реальная отправка и отображение сообщения здесь не подтверждены.

1. Разделите ID группы, темы и сообщения

Перед исправлением подпишите три значения в конфигурации. Ошибка часто выглядит как проблема прав, хотя приложение просто использует подходящее число в неподходящем поле. Особенно легко перепутать значения, если настройка хранит все идентификаторы под одним именем chat_id.

В Telethon используется MTProto. Параметр message_thread_id из HTTP Bot API нельзя просто добавить к send_message. Параметр comment_to тоже решает другую задачу — комментарий к публикации канала в связанной группе обсуждений. Для такой схемы есть отдельный материал про комментарии канала.

ЗначениеЧто обозначаетГде используется
group / peerФорум-группа TelegramАдресат запроса
topic_idID служебного сообщения создания темы, кроме «Главной»reply_to для сообщения в корень темы
target_message_idКонкретное сообщение внутри темыЦель ответа, отдельно от корня

2. Найдите корень нужной темы

Получайте идентификатор темы в контексте известной группы. В установленной версии запрос списка тем называется functions.messages.GetForumTopicsRequest. В найденном объекте forumTopic используйте id; поле top_message не заменяет ID темы. При поиске по названию сначала убедитесь, что выбрана ровно нужная тема.

from telethon import functions, types


async def find_topics(client, group, title):
    result = await client(functions.messages.GetForumTopicsRequest(
        peer=await client.get_input_entity(group),
        offset_date=None,
        offset_id=0,
        offset_topic=0,
        limit=20,
        q=title,
    ))
    return [
        (topic.id, topic.title)
        for topic in result.topics
        if isinstance(topic, types.ForumTopic)
    ]

Функция возвращает совпадения одной страницы, максимум 20 объектов. Выведите пары ID и названия, затем явно выберите нужную тему; не берите первый элемент автоматически. Это не полный каталог: при пустом или неоднозначном результате уточните поиск и сверьте раздел в собственном чате.

Если исходная точка — уже полученное сообщение, изучите его reply_to и признаки контекста темы. reply_to_top_id может указывать корень; reply_to_msg_id может обозначать как корень, так и обычное сообщение. Не назначайте последнее число из произвольной ссылки значением topic_id без проверки.

Сохраните вместе группу, название темы и проверенный ID. Такая запись помогает заметить, что после пересоздания раздела программа продолжает использовать прежний корень. Совпадение чисел между разными группами не делает идентификаторы взаимозаменяемыми.

Маршрут в обычную тему: ID корня передаётся в reply_to_msg_id. Для ответа внутри темы ID сообщения идёт в reply_to_msg_id, а ID корня — в top_msg_id.
Корень темы и цель ответа выполняют разные роли. Схема относится к темам, отличным от «Главной».

3. Отправьте сообщение в корень темы

Для сообщения в обычную тему передайте её корень через reply_to. Для «Главной» используйте обычную отправку без этого аргумента; не подставляйте искусственно reply_to=1. Функции ниже вызываются из вашего существующего асинхронного кода, где client уже авторизован, а group обозначает собственную тестовую группу.

async def send_to_topic(client, group, topic_id, text):
    if type(topic_id) is not int or topic_id <= 1:
        raise ValueError("Expected a non-General topic root ID > 1")
    return await client.send_message(
        group, text, reply_to=topic_id
    )


async def send_to_general(client, group, text):
    return await client.send_message(group, text)

Вынесите выбор темы в одно место перед вызовом функции. Например, запись поддержки должна получать сохранённый корень поддержки, а запись объявлений — свой. При отсутствии настройки полезнее явно завершить операцию с ошибкой, чем незаметно направить рабочий текст в «Главную».

В Telethon 1.45.0 event.respond не добавляет reply_to автоматически. Поэтому ответ обработчика на сообщение из темы не обязательно продолжит её маршрут. Сначала определите нужный корень, затем передайте его явно; не выводите маршрут только из того, какое событие запустило функцию.

4. Ответ внутри темы требует отдельного маршрута

При ответе на конкретное сообщение цель ответа и корень различаются. Официальная схема Telegram предусматривает top_msg_id, чтобы сохранить тему, если целевое сообщение исчезнет до выполнения запроса. Для темы, отличной от «Главной», задайте оба поля явно.

import secrets
from telethon import functions, types


async def reply_in_topic(
    client, group, topic_id, target_message_id, text
):
    if type(topic_id) is not int or topic_id <= 1:
        raise ValueError("Expected a non-General topic root ID > 1")
    if type(target_message_id) is not int or target_message_id <= 0:
        raise ValueError("Expected a positive target message ID")
    if target_message_id == topic_id:
        raise ValueError("For the topic root, use send_to_topic")
    peer = await client.get_input_entity(group)
    return await client(functions.messages.SendMessageRequest(
        peer=peer,
        message=text,
        random_id=secrets.randbits(63),
        reply_to=types.InputReplyToMessage(
            reply_to_msg_id=target_message_id,
            top_msg_id=topic_id,
        ),
    ))

Эта функция предназначена только для обычной темы с topic_id > 1 и ответа на сообщение, отличное от её корня: target_message_id != topic_id. Для корня используйте send_to_topic, для «Главной» — отдельную отправку. Проверки чисел не подтверждают принадлежность сообщения: вызывающий код должен сверить выбранные группу и тему.

В проверенной версии send_message(reply_to=message_id) создаёт InputReplyToMessage без top_msg_id, а Message.reply подставляет ID исходного сообщения. Эти удобные методы не дают основания обещать такой же резервный маршрут при удалении цели. После обновления библиотеки повторно проверяйте форму запроса.

5. Проверьте результат в собственной группе

Перед одиночной пробой откройте нужную группу и тему своим тестовым аккаунтом. Убедитесь, что темы включены, раздел существует и аккаунту разрешено писать. Если требуется настройка интерфейса и доступа, используйте руководство по темам и правам группы.

Отправьте один узнаваемый тестовый текст и проверьте его расположение в клиенте Telegram. Зафиксируйте группу, ожидаемую тему, ID результата и фактически открытый раздел. Успех RPC, наличие сообщения в общей истории и правильная тема — отдельные наблюдения.

При ошибке сохраните тип исключения и этап операции. При тайм-ауте сначала проверьте историю: неопределённый результат не равен отсутствию отправки. Слепой повтор может создать дубль. Если выполнение задерживает FloodWait, разберите ожидание отдельно по инструкции диагностики FloodWait.

PSYKE предлагает проверку номеров Telegram как отдельную подготовительную операцию. Такая проверка не отправляет сообщения, не выбирает тему и не подтверждает права аккаунта. Для диагностики маршрута нужны данные вашего запроса и наблюдение в тестовой группе.

Проверка номеров Telegram

Частые вопросы

Почему event.respond отправляет сообщение в «Главную»?

В Telethon 1.45.0 respond не добавляет маршрут ответа автоматически. Проверьте корень выбранной темы и передайте его как reply_to. Отдельно убедитесь, что group обозначает нужную форум-группу.

Можно ли использовать ID последнего сообщения как topic_id?

Для обычной темы нужен корень, а не произвольное сообщение. В объекте forumTopic используйте id, а не top_message. Для ответа внутри темы передавайте ID цели и корень раздельно.

Источники и дополнительные материалы

Похожие руководства