Updates

Updates are an important topic in a messaging platform like Telegram. After all, you want to be notified as soon as certain events happen, such as new message arrives.

Telethon abstracts away Telegram updates with events.

Important

It is strongly advised to configure logging when working with events:

import logging
logging.basicConfig(
    format='[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s',
    level=logging.WARNING
)

With the above, you will see all warnings and errors and when they happened.

Listening to updates

You can define and register your own functions to be called when certain telethon.events occur.

The most common way is using the Client.on() decorator to register your callback functions, often referred to as handlers:

from telethon import Client, events
from telethon.events import filters

bot = Client(...)

@bot.on(events.NewMessage, filters.Command('/start') | filters.Command('/help'))
async def handler(event: events.NewMessage):
    await event.respond('Beep boop!')

The first parameter is the type of one of the telethon.events, not an instance, so make sure you don’t write parenthesis after it.

The second parameter is optional. If provided, it must be a callable function that returns True if the handler should run. Built-in filter functions are available in the filters module. In this example, Command means the handler will be called when the user sends /start or /help to the bot.

Built-in filter functions are also Combinable. This means you can use |, & and the unary ~ to combine filters with or, and, and negate them, respectively. These operators correspond to events.filters.Any, events.filters.All and events.filters.Not.

When your handler function is called, it will receive a single parameter, the event. The event type is the same as the one you defined in the decorator when registering your handler. You don’t need to explicitly set the type hint, but you can do so if you want your IDE to assist in autocompletion.

If you cannot use decorators, you can use the Client.add_event_handler() method instead. The above code is equivalent to the following:

from telethon import Client, events
from telethon.events import filters

async def handler(event: events.NewMessage):
    await event.respond('Beep boop!')

bot = Client(...)
bot.add_event_handler(handler, events.NewMessage, filters.Command('/start'))

Note how the above lets you defined the Client instance after your handlers. In other words, you can define your handlers without the Client instance. This may make it easier to place them in a separate file.

Filtering events

There is no way to tell Telegram to only send certain updates. Telegram sends all updates to connected active clients as they occur. Telethon must be received and process all updates to ensure correct ordering.

Filters are not magic. They work all the same as if conditions inside your event handlers. However, they offer a more convenient and consistent way to check for certain conditions.

All built-in filters can be found in telethon.events.filters.

When registering an event handler, you can optionally define the filter to use. You can retrieve a handler’s filter with get_handler_filter(). You can set (and overwrite) a handler’s filter with set_handler_filter().

Filters are meant to be fast and never raise exceptions. For this reason, filters cannot be asynchronous. This reduces the chance a filter will do slow IO and potentially fail.

A filter is simply a callable function that takes an event as input and returns a boolean. If the filter returns True, the handler will be called. Using this knowledge, you can create custom filters too. If you need state, you can use a class with a __call__ method defined:

# Anonymous filter which only handles messages with ID = 1000
bot.add_event_handler(handler, events.NewMessage, lambda e: e.id == 1000)
#                   this parameter is the filter  ^--------------------^

# ...

def only_odd_messages(event):
    "A filter that only handles messages when their ID is divisible by 2"
    return event.id % 2 == 0

bot.add_event_handler(handler, events.NewMessage, only_odd_messages)

# ...

class OnlyDivisibleMessages:
    "A filter that only handles messages when their ID is divisible by some amount"
    def __init__(self, divisible_by):
        self.divisible_by = divisible_by

    def __call__(self, event):
        return event.id % self.divisible_by == 0

bot.add_event_handler(handler, events.NewMessage, OnlyDivisibleMessages(7))

Custom filters should accept any Event. You can use isinstance() if your filter can only deal with certain types of events.

If you need to perform asynchronous operations, you can’t use a filter. Instead, manually check for those conditions inside your handler.

The filters work all the same when using Client.on(). This makes it very convenient to write custom filters using the lambda syntax:

@bot.on(events.NewMessage, lambda e: e.id == 1000)
async def handler(event):
    ...

Setting priority on handlers

There is no explicit way to define a different priority for different handlers.

Instead, the library will call all your handlers in the order you added them. This means that, if you want a “catch-all” handler, it should be registered last.

By default, the library will stop calling the rest of handlers after one is called:

@bot.on(events.NewMessage)
async def first(event):
    print('This is always called on new messages!')

@bot.on(events.NewMessage)
async def second(event):
    print('This will never be called, because "first" already ran.')

This is often the desired behaviour if you’re using filters.

If you have more complicated filters executed inside the handler, Telethon believes your handler completed and will stop calling the rest. If that’s the case, you can return events.Continue:

@bot.on(events.NewMessage)
async def first(event):
    print('This is always called on new messages!')
    return events.Continue

@bot.on(events.NewMessage)
async def second(event):
    print('Now this one runs as well!')

Alternatively, if this is always the behaviour you want, you can configure it in the Client:

bot = Client(..., check_all_handlers=True)
#                 ^^^^^^^^^^^^^^^^^^^^^^^
# Now the code above will call both handlers, even without returning events.Continue

If you need a more complicated setup, consider sorting all your handlers beforehand. Then, use Client.add_event_handler() on all of them to ensure the correct order.