This commit is contained in:
alberto 2026-04-24 18:42:13 -05:00 committed by GitHub
commit b619b45d8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 4 deletions

View file

@ -2328,6 +2328,26 @@ class TelegramAdapter(BasePlatformAdapter):
user = getattr(entity, "user", None)
if user and getattr(user, "id", None) == bot_id:
return True
elif entity_type == "bot_command" and expected:
# Telegram's official group-disambiguation form for slash
# commands (``/cmd@botname``) is emitted as a single
# ``bot_command`` entity covering the whole span — there
# is no accompanying ``mention`` entity. Treat it as a
# direct address to this bot when the ``@botname`` suffix
# matches. This is the form Telegram's own command menu
# autocomplete produces in groups, so dropping it at the
# mention gate would break /new, /reset, /help, ... for
# every group that has ``require_mention`` enabled (#15415).
offset = int(getattr(entity, "offset", -1))
length = int(getattr(entity, "length", 0))
if offset < 0 or length <= 0:
continue
command_text = source_text[offset:offset + length]
at_index = command_text.find("@")
if at_index < 0:
continue
if command_text[at_index:].strip().lower() == expected:
return True
return False
def _message_matches_mention_patterns(self, message: Message) -> bool:

View file

@ -59,6 +59,17 @@ def _mention_entity(text, mention="@hermes_bot"):
return SimpleNamespace(type="mention", offset=offset, length=len(mention))
def _bot_command_entity(text, command):
"""Entity Telegram emits for a ``/cmd`` or ``/cmd@botname`` token.
Telegram parses slash commands server-side. For ``/cmd@botname`` the
client does NOT emit a separate ``mention`` entity the whole span
is a single ``bot_command`` entity.
"""
offset = text.index(command)
return SimpleNamespace(type="bot_command", offset=offset, length=len(command))
def test_group_messages_can_be_opened_via_config():
adapter = _make_adapter(require_mention=False)
@ -73,12 +84,34 @@ def test_group_messages_can_require_direct_trigger_via_config():
assert adapter._should_process_message(_group_message("replying", reply_to_bot=True)) is True
# Commands must also respect require_mention when it is enabled
assert adapter._should_process_message(_group_message("/status"), is_command=True) is False
# But commands with @mention still pass (Telegram emits a MENTION entity
# for /cmd@botname — the bot menu and python-telegram-bot's CommandHandler
# rely on this same mechanism)
# Telegram's group command menu sends ``/cmd@botname`` as a single
# ``bot_command`` entity spanning the whole token (no separate mention
# entity). We must accept it so the menu works when require_mention is on.
assert adapter._should_process_message(
_group_message("/status@hermes_bot", entities=[_mention_entity("/status@hermes_bot")])
_group_message(
"/status@hermes_bot",
entities=[_bot_command_entity("/status@hermes_bot", "/status@hermes_bot")],
),
is_command=True,
) is True
# A bot_command entity addressed at a different bot must not satisfy
# the mention gate — Telegram groups can host multiple bots that
# register the same command name.
assert adapter._should_process_message(
_group_message(
"/status@other_bot",
entities=[_bot_command_entity("/status@other_bot", "/status@other_bot")],
),
is_command=True,
) is False
# Bare ``/status`` (no @botname) must still be dropped in groups with
# require_mention=True — Telegram delivers it only when the bot's
# privacy mode is off, and even then we should not respond unless the
# user explicitly addressed the bot.
assert adapter._should_process_message(
_group_message("/status", entities=[_bot_command_entity("/status", "/status")]),
is_command=True,
) is False
# And commands still pass unconditionally when require_mention is disabled
adapter_no_mention = _make_adapter(require_mention=False)
assert adapter_no_mention._should_process_message(_group_message("/status"), is_command=True) is True