fix(send_message): map Telegram General topic id to None for forum groups (#22423)

Telegram forum supergroups address the General topic as
`message_thread_id="1"` on incoming updates, but the Bot API rejects
sends with `message_thread_id=1` ("Message thread not found"). The
gateway adapter has a `_message_thread_id_for_send` helper that maps
"1" to None for that reason; the standalone `_send_telegram` helper
used by the `send_message` tool never got the same mapping, so any
`send_message` call to a Topics-enabled group's General topic
(target shape `telegram:<chat_id>:1`) failed with "Message thread
not found."

Reuse the adapter's helper when available, with an explicit fallback
to the same mapping for environments where the adapter import path
fails (e.g. python-telegram-bot missing in this venv).

Fixes #22267
This commit is contained in:
kshitij 2026-05-09 01:58:33 -07:00 committed by GitHub
parent 8fb3e2d63a
commit ae005ec588
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 79 additions and 1 deletions

View file

@ -742,6 +742,64 @@ class TestSendTelegramHtmlDetection:
sleep_mock.assert_awaited_once()
class TestSendTelegramThreadIdMapping:
"""General-topic mapping in _send_telegram (issue #22267).
Telegram forum supergroups address the General topic as
``message_thread_id="1"`` on incoming updates, but the Bot API rejects
sends with ``message_thread_id=1`` ("Message thread not found"). The
gateway adapter's ``_message_thread_id_for_send`` helper maps "1" to
``None`` for that reason; the standalone ``_send_telegram`` helper used
by the ``send_message`` tool needs the same mapping.
"""
def _make_bot(self):
bot = MagicMock()
bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=1))
return bot
def test_general_topic_thread_id_omitted(self, monkeypatch):
"""thread_id="1" must be dropped before calling the Bot API."""
bot = self._make_bot()
_install_telegram_mock(monkeypatch, bot)
asyncio.run(_send_telegram("tok", "-1001234567890", "hello", thread_id="1"))
bot.send_message.assert_awaited_once()
kwargs = bot.send_message.await_args.kwargs
assert "message_thread_id" not in kwargs
def test_non_general_topic_thread_id_preserved(self, monkeypatch):
"""Real forum-topic thread ids (>1) still pass through as ints."""
bot = self._make_bot()
_install_telegram_mock(monkeypatch, bot)
asyncio.run(_send_telegram("tok", "-1001234567890", "hello", thread_id="17585"))
kwargs = bot.send_message.await_args.kwargs
assert kwargs["message_thread_id"] == 17585
def test_no_thread_id_no_kwarg(self, monkeypatch):
"""With no thread_id, message_thread_id must not appear in kwargs."""
bot = self._make_bot()
_install_telegram_mock(monkeypatch, bot)
asyncio.run(_send_telegram("tok", "-1001234567890", "hello"))
kwargs = bot.send_message.await_args.kwargs
assert "message_thread_id" not in kwargs
def test_general_topic_thread_id_int_input_also_dropped(self, monkeypatch):
"""thread_id passed as the int 1 (not str) must still be dropped."""
bot = self._make_bot()
_install_telegram_mock(monkeypatch, bot)
asyncio.run(_send_telegram("tok", "-1001234567890", "hello", thread_id=1))
kwargs = bot.send_message.await_args.kwargs
assert "message_thread_id" not in kwargs
# ---------------------------------------------------------------------------
# Tests for Discord thread_id support
# ---------------------------------------------------------------------------

View file

@ -710,7 +710,27 @@ async def _send_telegram(token, chat_id, message, media_files=None, thread_id=No
media_files = media_files or []
thread_kwargs = {}
if thread_id is not None:
thread_kwargs["message_thread_id"] = int(thread_id)
# Reuse the gateway adapter's General-topic mapping: in Telegram
# forum supergroups, the General topic is addressed as
# message_thread_id="1" on incoming updates, but Bot API
# sendMessage rejects message_thread_id=1 with "Message thread
# not found". The adapter's helper maps "1" to None for that
# reason; the send_message tool needs the same mapping or a
# send to a forum group's General topic always errors out
# (see issue #22267).
try:
from gateway.platforms.telegram import TelegramAdapter
effective_thread_id = TelegramAdapter._message_thread_id_for_send(
str(thread_id)
)
except Exception:
# Fallback: explicit mapping in case the adapter import
# fails (e.g. python-telegram-bot missing in this venv).
effective_thread_id = (
None if str(thread_id) == "1" else int(thread_id)
)
if effective_thread_id is not None:
thread_kwargs["message_thread_id"] = effective_thread_id
if disable_link_previews:
thread_kwargs["disable_web_page_preview"] = True