Working with Updates¶
Important
Coming from Telethon before it reached its version 1.0? Make sure to read Compatibility and Convenience! Otherwise, you can ignore this note and just follow along.
The library comes with the telethon.events
module. Events are an abstraction
over what Telegram calls updates, and are meant to ease simple and common
usage when dealing with them, since there are many updates. If you’re looking
for the method reference, check telethon.events package, otherwise,
let’s dive in!
Important
The library logs by default no output, and any exception that occurs inside your handlers will be “hidden” from you to prevent the thread from terminating (so it can still deliver events). You should enable logging when working with events, at least the error level, to see if this is happening so you can debug the error.
When using updates, please enable logging:
import logging
logging.basicConfig(level=logging.ERROR)
Contents
Getting Started¶
from telethon import TelegramClient, events
client = TelegramClient('name', api_id, api_hash)
@client.on(events.NewMessage)
async def my_event_handler(event):
if 'hello' in event.raw_text:
await event.reply('hi!')
client.start()
client.run_until_disconnected()
Not much, but there might be some things unclear. What does this code do?
from telethon import TelegramClient, events
client = TelegramClient('name', api_id, api_hash)
This is normal creation (of course, pass session name, API ID and hash). Nothing we don’t know already.
@client.on(events.NewMessage)
This Python decorator will attach itself to the my_event_handler
definition, and basically means that on a NewMessage
event,
the callback function you’re about to define will be called:
async def my_event_handler(event):
if 'hello' in event.raw_text:
await event.reply('hi!')
If a NewMessage
event occurs,
and 'hello'
is in the text of the message, we .reply()
to the event
with a 'hi!'
message.
Do you notice anything different? Yes! Event handlers must be async
for them to work, and every method using the network needs to have an
await
, otherwise, Python’s asyncio
will tell you that you forgot
to do so, so you can easily add it.
client.start()
client.run_until_disconnected()
Finally, this tells the client that we’re done with our code. We run the
asyncio
loop until the client starts (this is done behind the scenes,
since the method is so common), and then we run it again until we are
disconnected. Of course, you can do other things instead of running
until disconnected. For this refer to Update Modes.
More on events¶
The NewMessage
event has much
more than what was shown. You can access the .sender
of the message
through that member, or even see if the message had .media
, a .photo
or a .document
(which you
could download with for example client.download_media(event.photo)
.
If you don’t want to .reply()
as a reply,
you can use the .respond()
method instead. Of course, there are more events such as ChatAction
or UserUpdate
, and they’re all
used in the same way. Simply add the @client.on(events.XYZ)
decorator on the top
of your handler and you’re done! The event that will be passed always
is of type XYZ.Event
(for instance, NewMessage.Event
), except for the Raw
event which just passes the Update object.
Note that .reply()
and .respond()
are just wrappers around the
client.send_message()
method which supports the file=
parameter.
This means you can reply with a photo if you do event.reply(file=photo)
.
You can put the same event on many handlers, and even different events on the same handler. You can also have a handler work on only specific chats, for example:
import ast
import random
# Either a single item or a list of them will work for the chats.
# You can also use the IDs, Peers, or even User/Chat/Channel objects.
@client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic')))
async def normal_handler(event):
if 'roll' in event.raw_text:
await event.reply(str(random.randint(1, 6)))
# Similarly, you can use incoming=True for messages that you receive
@client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True,
pattern='eval (.+)'))
async def admin_handler(event):
expression = event.pattern_match.group(1)
await event.reply(str(ast.literal_eval(expression)))
You can pass one or more chats to the chats
parameter (as a list or tuple),
and only events from there will be processed. You can also specify whether you
want to handle incoming or outgoing messages (those you receive or those you
send). In this example, people can say 'roll'
and you will reply with a
random number, while if you say 'eval 4+4'
, you will reply with the
solution. Try it!
Properties vs. Methods¶
The event shown above acts just like a custom.Message
, which means you
can access all the properties it has, like .sender
.
However events are different to other methods in the client, like
client.get_messages
.
Events may not send information about the sender or chat, which means it
can be None
, but all the methods defined in the client always have this
information so it doesn’t need to be re-fetched. For this reason, you have
get_
methods, which will make a network call if necessary.
In short, you should do this:
@client.on(events.NewMessage)
async def handler(event):
# event.input_chat may be None, use event.get_input_chat()
chat = await event.get_input_chat()
sender = await event.get_sender()
buttons = await event.get_buttons()
async def main():
async for message in client.iter_messages('me', 10):
# Methods from the client always have these properties ready
chat = message.input_chat
sender = message.sender
buttons = message.buttons
Notice, properties (message.sender
) don’t need an await
, but
methods (message.get_sender
) do need an await
,
and you should use methods in events for these properties that may need network.
Events Without the client¶
The code of your application starts getting big, so you decide to
separate the handlers into different files. But how can you access
the client from these files? You don’t need to! Just events.register
them:
# handlers/welcome.py
from telethon import events
@events.register(events.NewMessage('(?i)hello'))
async def handler(event):
client = event.client
await event.respond('Hey!')
await client.send_message('me', 'I said hello to someone')
Registering events is a way of saying “this method is an event handler”.
You can use telethon.events.is_handler
to check if any method is a handler.
You can think of them as a different approach to Flask’s blueprints.
It’s important to note that this does not add the handler to any client! You never specified the client on which the handler should be used. You only declared that it is a handler, and its type.
To actually use the handler, you need to client.add_event_handler
to the
client (or clients) where they should be added to:
# main.py
from telethon import TelegramClient
import handlers.welcome
with TelegramClient(...) as client:
client.add_event_handler(handlers.welcome.handler)
client.run_until_disconnected()
This also means that you can register an event handler once and then add it to many clients without re-declaring the event.
Events Without Decorators¶
If for any reason you don’t want to use telethon.events.register
,
you can explicitly pass the event handler to use to the mentioned
client.add_event_handler
:
from telethon import TelegramClient, events
async def handler(event):
...
with TelegramClient(...) as client:
client.add_event_handler(handler, events.NewMessage)
client.run_until_disconnected()
Similarly, you also have client.remove_event_handler
and client.list_event_handlers
.
The event
argument is optional in all three methods and defaults to
events.Raw
for adding, and None
when
removing (so all callbacks would be removed).
Note
The event
type is ignored in client.add_event_handler
if you have used telethon.events.register
on the callback
before, since that’s the point of using such method at all.
Stopping Propagation of Updates¶
There might be cases when an event handler is supposed to be used solitary and
it makes no sense to process any other handlers in the chain. For this case,
it is possible to raise a telethon.events.StopPropagation
exception which
will cause the propagation of the update through your handlers to stop:
from telethon.events import StopPropagation
@client.on(events.NewMessage)
async def _(event):
# ... some conditions
await event.delete()
# Other handlers won't have an event to work with
raise StopPropagation
@client.on(events.NewMessage)
async def _(event):
# Will never be reached, because it is the second handler
# in the chain.
pass
Remember to check telethon.events package if you’re looking for the methods reference.