Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions gateway/platforms/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,14 @@ def _message_thread_id_for_send(cls, thread_id: Optional[str]) -> Optional[int]:

@classmethod
def _message_thread_id_for_typing(cls, thread_id: Optional[str]) -> Optional[int]:
# Mirrors _message_thread_id_for_send: the General forum topic (thread id
# "1") is represented as "no thread id" on the wire. User-created topics
# keep their real id so typing stays scoped to that topic.
if not thread_id or str(thread_id) == cls._GENERAL_TOPIC_THREAD_ID:
# Asymmetric with _message_thread_id_for_send on purpose. Telegram's
# sendMessage and sendChatAction treat thread id "1" (the forum General
# topic) differently: sends reject message_thread_id=1 and must omit it,
# but sendChatAction needs message_thread_id=1 to place the typing
# bubble in the General topic (omitting it hides the bubble entirely
# from the client's view of that topic). Preserve the real id here —
# sends still map "1" → None via _message_thread_id_for_send.
if not thread_id:
return None
return int(thread_id)

Expand Down
17 changes: 11 additions & 6 deletions tests/gateway/test_telegram_thread_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,17 @@ async def mock_send_message(**kwargs):


@pytest.mark.asyncio
async def test_send_typing_general_topic_uses_none_thread_id():
"""Typing for forum General should hit the API with message_thread_id=None directly.
async def test_send_typing_preserves_general_topic_thread_id():
"""Typing for forum General must send message_thread_id=1, not None.

_message_thread_id_for_typing() maps the General topic (thread id "1") to None
the same way _message_thread_id_for_send() does, so there's no retry path — the
first call is already correct.
Asymmetric with _message_thread_id_for_send: sendMessage rejects
message_thread_id=1, but sendChatAction needs it to scope the typing
bubble to the General topic. Omitting it (message_thread_id=None) hides
the bubble from the General-topic view entirely.

Regression guard for the d5357f816 refactor that mapped "1" → None in
the typing resolver and silently killed typing indicators in every
forum-group General topic.
"""
adapter = _make_adapter()
call_log = []
Expand All @@ -177,7 +182,7 @@ async def mock_send_chat_action(**kwargs):
await adapter.send_typing("-100123", metadata={"thread_id": "1"})

assert call_log == [
{"chat_id": -100123, "action": "typing", "message_thread_id": None},
{"chat_id": -100123, "action": "typing", "message_thread_id": 1},
]


Expand Down
Loading