Skip to content

Filters⚓︎

Filters are essential for routing updates to specific handlers. Handler lookup always stops at the first match with a set of filters. By default, all handlers have an empty filter set, so all updates will be passed to the first handler without filters.

Tip

Our filters, just like in aiogram, are built on the magic-filter library.

Combining Filters⚓︎

If you specify multiple filters in sequence, they will be checked with an "AND" condition:

@<router>.message(F.from_user.id == "bots_ru@video.example.com", F.text == 'admin')

Additionally, if you want to use two alternative ways to trigger the same handler (an "OR" condition), you can register the handler two or more times as needed:

@<router>.message(F.text == "hi")
@<router>.message(Command("/start"))

MagicFilter⚓︎

MagicFilter is a powerful and flexible tool from the magic-filter package, integrated into python-trueconf-bot. It allows you to build expressive and chainable filters that simplify message routing in your bot. Instead of manually checking update fields inside a handler, you can define conditions directly in the decorator.

The filter works "lazily": when a handler is declared, only the chain of checks is stored, not the result. The actual evaluation happens only when a new event arrives, so filters can be easily combined and remain readable. This approach makes the code shorter and clearer, showing exactly which updates will be processed by a given handler.

The idea behind MagicFilter is simple: describe an attribute chain and a condition, then apply it to an object. Imagine you have an object with nested fields. Instead of manually checking something like if obj.foo.bar.baz == "spam": ..., you can construct the filter declaratively:

F.foo.bar.baz == "spam"

The resulting filter is not an immediate check, but an object that "remembers" the condition. When processing an update, this filter is automatically applied to the object (the router handles the check under the hood). Technically, the .resolve(obj) method is used for this, but you don't need to call it manually — just define the condition in the decorator, and it will be executed during routing.

@r.message(F.text == "ping")
async def ping_handler(message):
    await message.answer("pong")

Here, the filter F.text == "ping" will be automatically checked for each incoming message. If the condition matches, the handler will be triggered.

Available Operations⚓︎

The MagicFilter object supports basic logical operations on object attributes.

Existence or not None⚓︎

Default behavior:

F.photo  # lambda message: message.photo

Equality⚓︎

F.text == "hello"        # lambda message: message.text == "hello"
F.from_user.id == 42     # lambda message: message.from_user.id == 42
F.text != "spam"         # lambda message: message.text != "spam"

Membership⚓︎

# lambda query: query.from_user.id in {42, 1000, 123123}
F.from_user.id.in_({42, 1000, 123123})  

# lambda query: query.data in {"foo", "bar", "baz"}
F.data.in_({"foo", "bar", "baz"})       

Contains⚓︎

F.text.contains("foo")  # lambda message: "foo" in message.text

Starts/Ends With⚓︎

F.text.startswith("foo")  # lambda message: message.text.startswith("foo")
F.text.endswith("bar")    # lambda message: message.text.endswith("bar")

Regular Expressions⚓︎

F.text.regexp(r"Hello, .+")  # lambda message: re.match(r"Hello, .+", message.text)

Custom Function⚓︎

# lambda message: (lambda chat: chat.id == -42)(message.chat)
F.chat.func(lambda chat: chat.id == -42)  

Result Inversion⚓︎

# lambda message: not message.text
~F.text        

# lambda message: not message.text.startswith("spam")
~F.text.startswith("spam")  

Condition Combination⚓︎

(F.from_user.id == 42) & (F.text == "admin")

F.text.startswith("a") | F.text.endswith("b")

(F.from_user.id.in_({42, 777, 911})) & (F.text.startswith("!") | F.text.startswith("/")) & F.text.contains("ban")

Attribute Modifiers (String)⚓︎

 # lambda message: message.text.lower() == "test"
F.text.lower() == "test"    

# lambda message: message.text.upper() in {"FOO", "BAR"}
F.text.upper().in_({"FOO", "BAR"})

 # lambda message: len(message.text) == 5
F.text.len() == 5