Routers and filters

Bot and SyncBot are routers themselves: everything on this page works on a bot and on a Router. The guide is Events, filters and middleware.

Router

class Router(name=None)

A set of handlers, with their middlewares and error handlers, and sub-routers.

Bot and SyncBot are routers, so every decorator here works on a bot. A separate Router splits a bot into modules: register handlers on it, then include it into the bot with include_router.

For each update, a router tries its own handlers first: higher priority first, then in the order they were registered. Then come the included routers, in the order they were included, each with the routers it includes (depth first). Only the handlers for the update’s kind are tried, along with those registered with on_update. The first one whose filters all match gets the update, and the others are not tried unless it raises SkipHandler. When no handler matches, nothing happens and the update counts as handled.

A handler’s first parameter gets the event (a Message, a CallbackQuery, …). The other parameters are filled by name:

  • bot: the bot running the handler;

  • update: the whole Update;

  • command: the ParsedCommand, for a command;

  • match: the re.Match of a regex filter;

  • any key that a filter or a middleware added.

A **kwargs parameter gets all of them. The first parameter must then have another name: async def log(update, **kwargs) in an on_update handler gets update twice, and the call fails with TypeError. A parameter that nothing fills keeps its default; without one the call raises TypeError, which goes to the error handlers.

A Bot runs async def handlers, error handlers and middlewares, and a SyncBot plain functions, with no async filters. The bot checks this when it starts and raises ConfigurationError on a mismatch.

name

The router’s name, shown in error messages and in repr().

Type:

str

Example

A module with its own router, included into the bot:

# shop.py
router = Router(name="shop")

@router.command("buy", description="Buy something")
async def buy(message, command):
    await message.reply(f"{command.args} is in your cart")

# main.py
import shop

bot = Bot()
bot.include_router(shop.router)
bot.run()

Makes an empty router.

Parameters:

name (str | None) – A name for error messages and repr(), such as "admin". None or "" gives "router-" followed by the object’s id() in hex.

include_router(router)

Adds a sub-router, whose handlers are tried after this router’s own.

They also come after the handlers of the routers included before it. The middlewares of this router wrap the handlers of router too, and run before its own. A router can be included only once.

Parameters:

router (Router) – The router to include.

Returns:

router itself, so a router can be made and included in one line.

Raises:

ValueError – When router is this router, is already included somewhere, or contains this router (that would make a loop).

Return type:

Router

include_routers(*routers)

Adds several sub-routers, in order, as include_router does.

Parameters:

*routers (Router) – The routers to include.

Raises:

ValueError – As for include_router. The routers before the one that failed stay included.

on_message(*filters, private=None, group=None, channel=None, priority=0)

Registers a handler for new messages.

The handler gets a Message (a SyncMessage on a SyncBot). Commands are messages too: register the command handlers first, or a handler here with no filters takes them.

Parameters:
  • *filters (Any) – Filters that must all match: the ready-made ones in pgpcbot.filters, Filter objects, or plain functions that take the message.

  • private (bool | None) – True takes only private chats, False anything but private chats, None (the default) does not look at the chat. Several True flags take either kind: private=True, group=True.

  • group (bool | None) – The same, for groups.

  • channel (bool | None) – The same, for channels.

  • priority (int) – Handlers with a higher number are tried first within this router. The same number keeps the order of registration.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

Example

An echo for private chats:

@bot.on_message(f.has_text, private=True)
async def echo(message):
    await message.reply(message.text)
on_edited_message(*filters, private=None, group=None, channel=None, priority=0)

Registers a handler for edits: somebody edited a message the bot had received.

The handler gets the Message as it is now, with the new text (a SyncMessage on a SyncBot). The arguments work as in on_message.

Parameters:
  • *filters (Any) – Filters that must all match.

  • private (bool | None) – True takes only private chats, False anything but private chats, None (the default) does not look at the chat.

  • group (bool | None) – The same, for groups.

  • channel (bool | None) – The same, for channels.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

command(*names, description=None, filters=(), ignore_mention=False, priority=0)

Registers a handler for one or more commands, /name and /name@yourbot.

The handler gets the Message (a SyncMessage on a SyncBot) and, if it has a parameter called command, the ParsedCommand with the arguments. A command addressed to another bot, such as /start@otherbot, is ignored unless ignore_mention is True. Only new messages count: a message edited into a command does not run it.

Parameters:
  • *names (str) – The command names, each 1 to 32 letters, digits or _, with or without /. Case does not matter. The names are not checked here: one that breaks this rule, such as "my-command", never matches.

  • description (str | None) – The line shown next to the command in the menu the app opens when someone types /, 1 to 256 characters. Bot.sync_commands publishes the commands that have one (see declared_commands), and checks the names and descriptions then; None leaves the command out of the menu.

  • filters (tuple[Any, ...]) – More filters that must match, as a tuple: filters=(f.private,).

  • ignore_mention (bool) – True also takes the commands addressed to another bot.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Raises:

ValueError – When no name is given. To take any command, use on_message(f.command()).

Return type:

Callable[[F], F]

Example

/weather milan answers for Milan, /weather alone for Rome:

@bot.command("weather", description="Forecast for a town")
async def weather(message, command):
    town = command.args or "Rome"
    await message.reply(f"{town}: sun.")
on_callback_query(*filters, data=None, prefix=None, pattern=None, priority=0)

Registers a handler for presses on callback buttons.

The handler gets a CallbackQuery (a SyncCallbackQuery on a SyncBot). Answer it with CallbackQuery.answer within 60 seconds. A press that nothing answered gets an empty answer once the handlers are done, even when they failed, so the button stops spinning (unless the bot was made with auto_answer_callbacks=False).

Parameters:
  • *filters (Any) – Filters that must all match.

  • data (str | None) – Takes only the presses whose data is exactly this, such as "menu:settings".

  • prefix (str | None) – Takes only the presses whose data starts with this, such as "vote:". Given with data, either one is enough.

  • pattern (str | None) – A regular expression searched in the data (re.search()); the handler can ask for the match. Given with data or prefix, both must match.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

Example

One handler for every vote: button:

@bot.on_callback_query(prefix="vote:")
async def vote(query):
    choice = query.data.removeprefix("vote:")
    await query.answer(f"You voted {choice}")
on_deleted_messages(*filters, priority=0)

Registers a handler for deletions: messages were deleted for everyone.

The handler gets a DeletedMessages, with the chat and the IDs of the messages.

Parameters:
  • *filters (Any) – Filters that must all match. Only the ones that look at the chat (f.private, f.chat(...)) can match here: a deletion has no text and no sender.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

on_chat_member(*filters, priority=0)

Registers a handler for changes to the bot’s own place in a chat.

The bot was added, promoted, restricted or removed, or a person blocked or unblocked it. The handler gets a ChatMemberUpdate.

Parameters:
  • *filters (Any) – Filters that must all match.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

Example

A thank-you when the bot is added:

@bot.on_chat_member()
async def membership(change):
    if change.joined:
        await bot.send_message(change.chat, "Thanks for adding me.")
on_update(*filters, priority=0)

Registers a handler for every update, of any kind.

The handler gets the Update itself, and so do its filters: the ones that look at the chat or at the sender (f.private, f.chat(...), f.user(...)) work, the others (text, media, button data) never match. It takes its turn like any other handler: registered before the others, it gets every update unless it raises SkipHandler.

Parameters:
  • *filters (Any) – Filters that must all match. They are called with the Update.

  • priority (int) – Handlers with a higher number are tried first within this router.

Returns:

A decorator that registers the function and gives it back unchanged.

Return type:

Callable[[F], F]

on_error(*exceptions)

Registers an error handler, for errors raised while an update is processed.

It takes the errors raised by filters, middlewares and handlers, and the ones raised while an encrypted message is opened (DecryptionError, SignatureError, and KeyChangedError with pin_keys=True), in which case no handler runs. The callback gets the error first, and can ask for bot, update and error by name; what filters and middlewares added does not reach it. With **kwargs, the first parameter must not be called error, or the call fails with TypeError.

Error handlers are tried in order: this router’s first, in the order they were registered, then those of the included routers, depth first. The first one whose types match runs; raise SkipHandler in it to pass the error on to the next one. If it returns normally, the update counts as handled. If it raises, or no error handler takes the error, the update fails: a running bot keeps it in its failed list with the error (Bot.failed_updates), and Bot.process_update raises the error.

Parameters:

*exceptions (type[BaseException]) – The exception classes to take, subclasses included. None at all takes any Exception. Errors that are not an Exception, such as KeyboardInterrupt, never reach an error handler.

Returns:

A decorator that registers the function and gives it back unchanged.

Raises:

TypeError – When one of exceptions is not an exception class.

Return type:

Callable[[F], F]

Example

A last resort that logs the error, after which the update counts as handled:

@bot.on_error()
async def everything_else(error, update):
    log.error("update %s failed", update.update_id, exc_info=error)

Note

Error handlers are not limited to their own router: the bot tries all of them, in the order above, for an error raised anywhere.

middleware(func)

Adds a middleware around every handler of this router and of its sub-routers.

A middleware is called as mw(handler, event, data). handler runs the rest of the chain (the inner middlewares, then the handler), event is what the handler gets, and data is the dict of extra handler arguments. Call await handler(event, data) to go on, and return what it returns. Not calling it stops the update there: no other handler is tried. Raising SkipHandler passes the update on to the next matching handler. A key added to data reaches the handler, which can ask for it by name.

Middlewares run only around a handler whose filters matched, from the outside in: the outer routers’ first, then this router’s, in the order they were added. On a SyncBot a middleware is a plain function that returns handler(event, data).

Parameters:

func (Callable[[...], Any]) – The middleware.

Returns:

func itself, so this method works as a decorator.

Raises:

TypeError – When func is not callable.

Return type:

Callable[[…], Any]

Example

Loading a profile that handlers can ask for:

@bot.middleware
async def load_profile(handler, event, data):
    data["profile"] = await db.load(event.from_user.id) if event.from_user else None
    return await handler(event, data)

@bot.on_message()
async def hello(message, profile): ...
remove_handler(callback)

Removes every handler and error handler of callback, here and in the sub-routers.

The decorators give the function back unchanged, so pass the decorated function. Middlewares are not touched.

Parameters:

callback (Callable[[...], Any]) – The function that was registered.

Returns:

True when something was removed.

Return type:

bool

property handlers: tuple[Handler, ...]

This router’s own handlers, in the order they are tried.

The handlers of included routers are not in it; all_handlers walks the whole tree.

iter_handlers(kind)

Yields the handlers that can take an update of kind, in the order they are tried.

The bot walks this for each update. The handlers registered with on_update come up for every kind.

Parameters:

kind (UpdateKind) – The kind of update.

Yields:

Pairs of a Handler and the tuple of middlewares around it, outermost first.

Return type:

Iterator[tuple[Handler, tuple[Callable[[…], Any], …]]]

iter_error_handlers()

Yields every error handler of this router and below, in the order they are tried.

This router’s come first, in the order they were registered, then those of each included router, depth first.

Yields:

The ErrorHandler records.

Return type:

Iterator[ErrorHandler]

all_handlers()

Yields every handler of this router and below, whatever kind of update it takes.

They come in the order they are tried: this router’s first, then those of each included router, depth first.

Yields:

The Handler records.

Return type:

Iterator[Handler]

all_middlewares()

Yields every middleware of this router and below.

This router’s come first, in the order they were added, then those of each included router, depth first.

Yields:

The middleware functions.

Return type:

Iterator[Callable[[…], Any]]

declared_commands()

Lists the commands registered with a description, in this router and below.

This is the menu that Bot.sync_commands publishes. Commands without a description are left out. When a name has several descriptions, the first one wins.

Returns:

(name, description) pairs in the order the handlers are tried, each name lowercased and without /.

Return type:

list[tuple[str, str]]

exception SkipHandler

Raised by a handler to pass the update on to the next matching handler.

The next handler is tried as if this one’s filters had not matched. When no handler is left, the update counts as handled. A middleware can raise it too.

Raised by an error handler, it passes the error on to the next error handler whose types match. When no error handler is left, the update fails, as if no error handler had taken the error.

A filter must not raise it: there it is an error like any other, which goes to the error handlers, and no handler runs. A filter that does not match returns False.

Example

A guard that deletes spam and lets everything else through:

@bot.on_message(priority=10)
async def spam_guard(message):
    if not looks_like_spam(message.text):
        raise SkipHandler  # not spam: the next handler gets it
    await message.delete()

Commands

class ParsedCommand(name, args, mention, token)

A command read from a message, such as /start@weatherbot rome.

A handler registered with Router.command, or with a command filter, gets one when it has a parameter called command. parse_command makes one from any text.

name

The command name, lowercased, without /: "start".

Type:

str

args

Everything after the command, with the spaces at both ends removed: "rome". An empty string when nothing follows.

Type:

str

mention

The handle after @, as written: "weatherbot". None when there is none.

Type:

str | None

token

The command exactly as written, with its @handle if it has one: "/start@weatherbot".

Type:

str

Example

>>> from pgpcbot.filters import parse_command
>>> command = parse_command("/Weather@WeatherBot rome  3")
>>> command.name, command.mention, command.args
('weather', 'WeatherBot', 'rome  3')
>>> command.argv
['rome', '3']
property argv: list[str]

args split on whitespace, ["rome", "3"] for /weather rome 3.

Reading the handlers back

Router.iter_handlers() and the other readers give back these records. The dispatcher uses them; in tests they show what a router holds.

class Handler(
callback,
kind,
filters,
priority=0,
commands=(),
_params=frozenset({}),
_takes_kwargs=False,
)[source]

One registered handler: the function, the kind of update it takes and its filters.

The decorators of Router make these, and handlers, iter_handlers and all_handlers give them back. There is no need to make one by hand.

callback

The decorated function, as it was given.

Type:

Callable[[…], Any]

kind

The kind of update it takes, or None for every kind (on_update).

Type:

UpdateKind | None

filters

The filters that must all match, each wrapped in a Filter. The ones that the decorator’s own arguments add (the command names, private=True, prefix=...) come first.

Type:

tuple[Filter, …]

priority

Higher numbers are tried first within the router.

Type:

int

commands

For a command handler, a (name, description) pair for each name, lowercased and without /, with None when there is no description. Empty for the other handlers.

Type:

tuple[tuple[str, str | None], …]

property is_async: bool[source]

True when the callback is async.

That is an async def function, or an object whose __call__ is async def. A Bot refuses to start with a handler that is not async, a SyncBot with one that is.

property name: str[source]

The callback’s __qualname__, or its repr() when it has none.

Error messages name the handler with it.

kwargs_for(data)[source]

Picks out of data the extra arguments the callback asks for.

The callback’s first parameter takes the event. Each other parameter that can be passed by keyword is filled by name, when data has that key. A callback with **kwargs, or one whose signature cannot be read, gets all of data.

Parameters:

data (dict[str, Any]) – What the dispatch knows about the update: bot, update, and what the filters and the middlewares added (command, match, …).

Returns:

The keyword arguments to call the callback with.

Return type:

dict[str, Any]

class ErrorHandler(callback, exceptions, _params=frozenset({}), _takes_kwargs=False)[source]

One registered error handler: the function and the exception types it takes.

on_error makes these, and iter_error_handlers gives them back. There is no need to make one by hand.

callback

The decorated function, as it was given.

Type:

Callable[[…], Any]

exceptions

The exception classes it takes. (Exception,) when on_error was given none.

Type:

tuple[type[BaseException], …]

property is_async: bool[source]

True when the callback is async.

That is an async def function, or an object whose __call__ is async def.

matches(error)[source]

Tells whether this error handler takes error.

Parameters:

error (BaseException) – The exception raised while the update was processed.

Returns:

True when error is an instance of one of exceptions, subclasses included.

Return type:

bool

kwargs_for(data)[source]

Picks out of data the extra arguments the callback asks for.

The callback’s first parameter takes the error. Each other parameter that can be passed by keyword is filled by name, when data has that key. A callback with **kwargs, or one whose signature cannot be read, gets all of data.

Parameters:

data (dict[str, Any]) – bot, update and error.

Returns:

The keyword arguments to call the callback with.

Return type:

dict[str, Any]

Filters

Filters decide which handler gets an update.

The filters go in the decorator, and all of them must match. They run in order, and the first one that does not match stops the others:

from pgpcbot import filters as f

@bot.on_message(f.private, f.has_text)
async def chat(message): ...

A filter is called with the event the handler would get (a Message, a CallbackQuery, …) and answers whether it matches. Any function that takes the event and returns True or False is a filter, lambdas included. It can be async, except on a SyncBot. A function with two positional parameters also gets a FilterContext, with the bot’s own handle.

A filter can answer with a dict instead of True. That is a match, and the handler can ask for each item of the dict by name: this is how command hands over command and regex hands over match. False, None and any other false value mean no match.

Filter objects combine with & (and), | (or) and ~ (not). A plain function combines with a Filter on the other side, so f.private & my_function works.

Ready-made filters, to use as they are:

Filters you make with your own values:

  • text (f.text("yes", "no")): the whole text is one of the values.

  • regex (f.regex(r"^order [0-9]+$")): the text, or a button’s data, contains a match.

  • command (f.command("start", "help")): one of these commands; with no names, any.

  • user (f.user(42, "@anna")): from one of these people.

  • chat (f.chat(7781, "@news")): in one of these chats.

  • callback_data (f.callback_data(prefix="vote:")): a button press with this data.

parse_command reads a command the way the server does.

class Filter(func, name=None)[source]

A condition on an event, which combines with others through operators.

The ready-made filters of this module are Filter objects. Plain functions given to a decorator are wrapped for you; wrap one yourself to give it a name, or to use ~ on it. The operators make new filters:

  • a & b matches when both match. b is not called when a does not match. The values that both hand over go to the handler.

  • a | b matches when either matches. b is called only when a does not match, and the handler gets the values of the one that matched.

  • ~a matches when a does not, and hands over nothing.

One side of & and | can be a plain function. A combination with an async part is async itself.

Example

Private messages from people who are not subscribers yet:

async def is_subscriber(message):
    return await db.is_subscriber(message.from_user.id)

@bot.on_message(f.private & ~f.Filter(is_subscriber))
async def pitch(message):
    await message.reply("Subscribe to read more.")

Wraps func as a filter.

Parameters:
  • func (FilterFunc) – The condition. It takes the event, or the event and a FilterContext when it has two or more positional parameters (or *args). It returns True, False or a dict of values for the handler, and may be async.

  • name (str | None) – The name shown by repr() and in error messages. None takes the function’s __name__, or "filter" when it has none.

property is_async: bool[source]

True when the function is async, or for a combination when any part is.

A SyncBot refuses to start with an async filter (ConfigurationError).

callback_data(*values, prefix=None)[source]

Makes a filter for button presses with this data.

pgpcbot.Router.on_callback_query does the same with its data and prefix arguments.

Parameters:
  • *values (str) – Data to take as it is, such as "vote:yes".

  • prefix (str | None) – Data to take by its start, such as "vote:".

Returns:

A filter that matches a button press whose data is one of values, or starts with prefix. With neither, nothing matches.

Return type:

Filter

channel = <Filter channel>

Matches events in a channel. It works for every kind of update.

Return type:

Any

chat(*which)[source]

Makes a filter for events in one of these chats.

Parameters:

*which (int | str) – Chat IDs (int) and the public handles of groups and channels (str, with or without @, in any case).

Returns:

A filter that matches when the event’s chat is one of them.

Return type:

Filter

command(*names, ignore_mention=False)[source]

Makes a filter for the commands /name and /name@yourbot.

The command must start the text and be followed by whitespace or nothing (see parse_command). A command addressed to another bot, such as /start@otherbot, does not match unless ignore_mention is True. pgpcbot.Router.command registers its handlers with this filter; use the filter directly to combine it with others.

Parameters:
  • *names (str) – The command names, each 1 to 32 letters, digits or _, with or without /. Case does not matter. With no names, any command matches.

  • ignore_mention (bool) – True also takes the commands addressed to another bot.

Returns:

A filter that matches those commands, and hands over the ParsedCommand as command.

Return type:

Filter

Example

An answer to every command that no handler before it took:

@bot.on_message(f.command(), private=True)
async def unknown(message, command):
    await message.reply(f"I don't know /{command.name}")
encrypted = <Filter encrypted>

Matches a message that came end-to-end encrypted.

The handler still gets it decrypted. Only messages match.

Return type:

Any

group = <Filter group>

Matches events in a group. It works for every kind of update.

Return type:

Any

has_media = <Filter has_media>

Matches a message with attachments, such as pictures, clips or files.

Return type:

Any

has_text = <Filter has_text>

Matches a message that has text, decrypted when the message came encrypted.

A message with attachments and no text does not match, and neither does a button press.

Return type:

Any

mentioned = <Filter mentioned>

Matches a message whose text mentions the bot’s @handle.

The rule is the one the server uses in groups: the @handle can be anywhere in the text, in any case, but not glued to a letter, a digit or _ on either side.

Return type:

Any

parse_command(text)[source]

Reads the command at the start of a text, by the same rule as the server.

A command is /, then 1 to 32 letters, digits or _, then optionally @ and a handle of 5 to 32 of the same characters, then whitespace or the end of the text. Only ASCII letters count, in any case. Nothing may come before the /.

Parameters:

text (str | None) – The text of a message. None and "" are fine.

Returns:

The command, or None when the text does not start with one.

Return type:

ParsedCommand | None

Example

>>> from pgpcbot.filters import parse_command
>>> parse_command("/start@WeatherBot rome")
ParsedCommand(name='start', args='rome', mention='WeatherBot', token='/start@WeatherBot')
>>> parse_command("/start")
ParsedCommand(name='start', args='', mention=None, token='/start')
>>> print(parse_command("hello /start"))
None
private = <Filter private>

Matches events in a private chat, between the bot and one person.

It works for every kind of update, since each one carries its chat. private=True in pgpcbot.Router.on_message does the same.

Return type:

Any

regex(pattern, flags=0)[source]

Makes a filter that searches the text of a message, or the data of a button press.

The pattern can match anywhere in the text (re.search()): anchor it with ^ and $ to match the whole text. The match is handed to the handler as match.

Parameters:
  • pattern (str | Pattern[str]) – A regular expression, as a string or compiled.

  • flags (int) – re flags, such as re.IGNORECASE, for a pattern given as a string. Ignored when pattern is compiled.

Returns:

A filter that matches when the pattern is found, and hands over the re.Match as match.

Return type:

Filter

Example

An order number, read from the match:

@bot.on_message(f.regex(r"^order ([0-9]+)$"))
async def order(message, match):
    await message.reply(f"Order {match[1]}: shipped")
reply = <Filter reply>

Matches a message that answers (quotes) another message.

Return type:

Any

signed = <Filter signed>

Matches an encrypted message with a valid signature from the sender’s key.

The sender’s key is the one the server lists for them in the chat. With verify_signatures="off" the bot checks no signature, so nothing matches.

Return type:

Any

text(*values, ignore_case=False)[source]

Makes a filter for messages whose whole text is one of values.

Spaces at both ends of the message are ignored. The values are compared as they are given.

Parameters:
  • *values (str) – The texts to take, such as "yes", "no".

  • ignore_case (bool) – True compares without case (str.casefold()).

Returns:

A filter that matches a message whose trimmed text equals one of values.

Return type:

Filter

Example

A yes or no answer, in any case:

@bot.on_message(f.text("yes", "no", ignore_case=True))
async def answer(message): ...
user(*who)[source]

Makes a filter for messages and button presses from one of these people.

Parameters:

*who (int | str) – User IDs (int) and handles (str, with or without @, in any case).

Returns:

A filter that matches when the sender (from_user) is one of them. A post that speaks as the channel has no sender, and never matches.

Return type:

Filter

Example

A command only the admins can use:

@bot.command("stats", filters=(f.user(*ADMINS),))
async def stats(message): ...
class FilterContext(bot_username, update, bot)[source]

What a filter can look at besides the event.

The bot makes one for each update, and passes it to every filter function that takes two positional parameters.

bot_username

The bot’s handle, without @. None until the bot has read its own profile from the server, which it does before the first update.

Type:

str | None

update

The whole Update being routed.

Type:

Any

bot

The Bot or SyncBot that runs the filter.

Type:

Any

Example

Messages that start with the bot’s handle:

def starts_with_my_name(message, context):
    handle = f"@{context.bot_username}".lower()
    return (message.text or "").lower().startswith(handle)

@bot.on_message(starts_with_my_name)
async def called(message): ...
type FilterFunc = Callable[..., bool | dict[str, Any] | None | Awaitable[bool | dict[str, Any] | None]]

What a Filter wraps: a function that takes the event, or the event and a FilterContext, and answers True, False, None or a dict. An async one answers with an awaitable.