Skip to content

Latest commit

 

History

History
115 lines (76 loc) · 3.88 KB

File metadata and controls

115 lines (76 loc) · 3.88 KB

Rules

Wow so in this part now we will learn how to filter the incoming events. That's pretty easy!

What we need to know is that in telegrinder event processing is parted into two stages. That is useful to firstly filter events broadly by types. For example, messages go into this view and callback queries (events when you press a callback button) go into another view.

Therefore, view is the first place the event is routed into.

Lets recall our example from the first part of the tutorial. There we declared a decorator:

@bot.on.message()

Basically it says:

We accept all events that are of view 'message'

Inside the view, we can have multiple handlers.

In order to filter events inside our view we use rules.

Rule - is an instance that tells whether an event follows it.


Lets import one of a very simple prebuilt rules.

from telegrinder.rules import Text

Rule may accept some arguments to specify how exactly it should work. Text accepts a string or a list of strings. If you use a proper IDE it will be easy for you to tell what exactly you should pass into a rule.

Lets write a simple handler using our rule.

@bot.on.message(Text("ping"))
async def ping_handler(message: Message):
    await message.answer("Pong")

Cool! Now if you write "ping" to your bot, you will receive "Pong" reply!

There are tons of other prebuilt rules in telegrinder, you may easily find something what you need.

However, it is important not to be limited by what is available out-of-box in the framework. Lets dive into writing your own rules.


Rule is a class that must implement check method. Rule must be derived from ABCRule. Lets write one!

from telegrinder import ABCRule

class IsMessageFromUserId(ABCRule):
    def __init__(self, user_id: int):
        self.user_id = user_id

    def check(self, message: Message) -> bool:
        return message.from_user.id == self.user_id

Done. This rule will check if the sender of the message. As you can see, there is an __init__ method to later specify which user_id is going to pass the check.

MY_ID = 123

@bot.on.message(IsMessageFromUserId(MY_ID), Text("/hey"))
async def hey_handler():
    return "Hey hey!"

Done! Here we just made a simple handler to catch "hey" message from user with user id = MY_ID.

I was also quite lazy and decided to use the telegrinder syntaxic sugar which is that you can simply return a string from the handler function and it will used as an answer.

Here when we passed multiple rules into the decorator they are BOTH required to hold in order for the handler to run.

However, rules can be easily combined into complex constructs with logical operators. For example:

from telegrinder.rules import Markup, FuzzyText, IsUserId

@bot.on.message(
    (
        Markup("<a:int> + <b:int>") | FuzzyText("example")
    ) & IsUserId(MY_ID)
)
async def sum_handler(message: Message, a: int, b: int):
    await message.answer(str(a + b))

In the example above you can also notice that Markup rule added 2 new arguments to our handler. Yes, that is actually possible - rules can add names to our context.

from telegrinder import Context

class IsIntegerText(ABCRule):
    async def check(self, message: Message, ctx: Context):
        if not message.text.unwrap_or("").is_digit():
            return False
        ctx["integer"] = int(message.text.unwrap())
        return True

@bot.on.message(IsIntegerText())
async def integer_handler(message: Message, integer: int):
    await message.reply(f"{integer} / 3 = {integer / 3}")

This is not a recommended way to work with context. That is considered kinda dirty and implicit to change context in this way, but we are going to get to the best way soon when we will be discussing nodes. Force-changing context is not recommended but definitely possible!

>> Next: Functional bits