Skip to content

Add filters.IS_BOT — no filter exposes User.is_bot (needed for multi-bot group chats) #5315

Description

@johnnynunez

Feature request

Add a filters.IS_BOT (and its negation) so handlers can filter on whether the
message author is a bot, without hand-rolling a MessageFilter subclass.

Rationale

User.is_bot has been part of the User model for as long as the Bot API has
had it, and it is the natural discriminator for multi-bot group chats. But
filters.py never exposes it:

$ python -c "
import re
src = open('telegram/ext/filters.py').read()
print('is_bot occurrences in filters.py:', src.count('is_bot'))
m = re.search(r'__all__\s*=\s*\((.*?)\)', src, re.S)
print('total filters exported:', len([x for x in m.group(1).split(',') if x.strip()]))
"
is_bot occurrences in filters.py: 0
total filters exported: 66

66 exported filters, including several in exactly this neighbourhood —
ViaBot, SenderChat, ChatType, IS_AUTOMATIC_FORWARD, IS_TOPIC_MESSAGE,
IS_FROM_OFFLINE — and none of them answers "did a bot write this?".

The closest existing filters are not substitutes:

  • ViaBot matches inline messages sent via a bot (message.via_bot).
    A bot posting normally in a group has via_bot is None, so ViaBot misses it.
  • SenderChat covers channel/anonymous-admin senders, not bot users.
  • IS_AUTOMATIC_FORWARD is about channel-to-discussion-group forwards.

So today the only way is a custom filter:

class _IsBot(filters.MessageFilter):
    def filter(self, message):
        return bool(message.from_user and message.from_user.is_bot)

IS_BOT = _IsBot(name="filters.IS_BOT")

That is ~5 lines, but it is 5 lines every project rewrites, it is easy to get
subtly wrong (from_user is None for channel posts — a naive
message.from_user.is_bot raises AttributeError), and it is undiscoverable:
a user scanning the filters list reasonably concludes PTB cannot do it.

Use case

I run a fleet of machines (2x DGX Spark, 2x Jetson Thor, AGX Orin, Orin Nano)
each hosting an agent that joins one shared Telegram group. Agents need to
treat bot-authored messages differently from human ones — typically: read them
for context, but only act when a human addressed them, so two bots cannot
ping-pong into a loop.

That is a routing decision on is_bot, made at handler-registration time,
which is precisely what filters is for:

# react to humans
app.add_handler(MessageHandler(filters.TEXT & ~filters.IS_BOT, on_human))
# observe peers without replying
app.add_handler(MessageHandler(filters.TEXT & filters.IS_BOT, on_peer))

versus today, where the bot/human split has to happen inside the callback,
after the handler already matched.

Multi-bot groups are increasingly common (agent fleets, bridges, relays,
notification bots coexisting), and every one of them needs this discriminator.

Proposed change

Add to telegram/ext/filters.py, following the existing IS_AUTOMATIC_FORWARD
/ IS_TOPIC_MESSAGE pattern:

class _IsBot(MessageFilter):
    __slots__ = ()

    def filter(self, message: Message) -> bool:
        return bool(message.from_user and message.from_user.is_bot)


IS_BOT = _IsBot(name="filters.IS_BOT")
"""Messages whose sender is a bot (:attr:`telegram.User.is_bot`).

Note:
    Channel posts and anonymous-admin messages have no :attr:`Message.from_user`
    and therefore do **not** match; see :attr:`filters.SenderChat` for those.
"""

Then export IS_BOT in __all__. Negation already works via ~filters.IS_BOT,
so no separate IS_NOT_BOT is needed.

The from_user is None guard is the part worth having in the library rather
than in every downstream copy.

Caveat worth documenting either way

Independent of this filter: Telegram's privacy mode is ON by default, so a
bot in a group only receives messages that mention it or reply to it — including
messages from other bots. Bot.can_read_all_group_messages reports the current
setting.

Whether or not IS_BOT lands, a sentence in the group-chat docs noting that
can_read_all_group_messages must be True (BotFather → /setprivacy
Disable) for a bot to observe other bots would save people a long debugging
session — the failure is completely silent: correct code, no updates delivered,
no error.

I'm happy to open a PR with the filter, tests, and that docs note if the
direction is acceptable.

Environment

python-telegram-bot 22.6, Python 3.11.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions