Skip to content
Closed
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
20 changes: 18 additions & 2 deletions plugins/platforms/telegram/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,22 @@ class _MockContextTypes:
DEFAULT_TYPE = Any
ContextTypes = _MockContextTypes

# PTB's Update.ALL_TYPES is a hardcoded enumeration of update kinds PTB has a
# typed field for. Bot API 10.0's guest_message isn't one of them yet (see
# _handle_guest_message_update's docstring), so passing Update.ALL_TYPES
# verbatim as allowed_updates tells Telegram to send everything EXCEPT
# guest_message -- the update is dropped server-side, before it ever reaches
# this process, so guest mode fails with zero client-side error or log trace.
# Older PTB releases used an empty list for ALL_TYPES, which Telegram treats
# as "send every type including ones the client doesn't know about" -- this
# explicit append restores that behavior for the one type PTB can't name yet.
# getattr-guarded: some tests inject a minimal fake `telegram` module (e.g.
# Update = object) into sys.modules before importing this adapter, which
# satisfies `from telegram import Update` without raising ImportError but
# has no ALL_TYPES attribute -- this must degrade to [] there, not crash
# the whole module import.
_ALLOWED_UPDATES_WITH_GUEST = [*getattr(Update, "ALL_TYPES", []), "guest_message"] if TELEGRAM_AVAILABLE else []

import sys
from pathlib import Path as _Path
sys.path.insert(0, str(_Path(__file__).resolve().parents[3]))
Expand Down Expand Up @@ -2147,7 +2163,7 @@ def _generation_error_callback(error: Exception) -> None:
try:
await asyncio.wait_for(
app.updater.start_polling(
allowed_updates=Update.ALL_TYPES,
allowed_updates=_ALLOWED_UPDATES_WITH_GUEST,
drop_pending_updates=drop_pending_updates,
error_callback=_generation_error_callback,
),
Expand Down Expand Up @@ -3693,7 +3709,7 @@ def _with_limits(httpx_kwargs: Optional[dict] = None) -> dict:
url_path=webhook_path,
webhook_url=webhook_url,
secret_token=webhook_secret,
allowed_updates=Update.ALL_TYPES,
allowed_updates=_ALLOWED_UPDATES_WITH_GUEST,
# Webhooks are push-based — Telegram does not hold a
# server-side getUpdates queue, so this flag is a no-op
# in practice. Mirror the polling path's reconnect
Expand Down
40 changes: 40 additions & 0 deletions tests/gateway/test_telegram_guest_message_allowed_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Regression test: guest_message must be requested via allowed_updates.

PTB's ``Update.ALL_TYPES`` is a hardcoded enumeration of update kinds PTB has
a typed field for. Bot API 10.0's ``guest_message`` isn't one of them, so
passing ``Update.ALL_TYPES`` verbatim as ``allowed_updates`` tells Telegram to
send every OTHER update type and silently omit guest_message -- Telegram
drops it server-side, before it ever reaches this process. The
``_handle_guest_message_update`` TypeHandler registers fine and no exception
is ever raised; the bot just never receives a guest @mention, from any chat,
indistinguishable from an outage. There was no test coverage for
``allowed_updates`` at all before this, which is how the bug shipped.
"""
from __future__ import annotations

import inspect
import re

import plugins.platforms.telegram.adapter as tg_adapter


def test_allowed_updates_constant_includes_guest_message():
assert "guest_message" in tg_adapter._ALLOWED_UPDATES_WITH_GUEST
# And still covers everything PTB itself knows about, so this isn't a
# narrower request that drops some other update type instead.
for update_type in tg_adapter.Update.ALL_TYPES:
assert update_type in tg_adapter._ALLOWED_UPDATES_WITH_GUEST


def test_no_call_site_uses_bare_update_all_types_for_allowed_updates():
"""Bug-class contract: every ``allowed_updates=`` argument in the adapter
must route through ``_ALLOWED_UPDATES_WITH_GUEST``, not
``Update.ALL_TYPES`` directly -- a new call site written the naive way
silently drops guest_message again, with no error anywhere."""
src = inspect.getsource(tg_adapter)
bare = [
(i + 1, line.strip())
for i, line in enumerate(src.splitlines())
if re.search(r"allowed_updates\s*=\s*Update\.ALL_TYPES\b", line)
]
assert not bare, f"allowed_updates call sites bypassing the guest_message fix: {bare}"