Examples with the Client¶
This section explores the methods defined in the TelegramClient
with some practical examples. The section assumes that you have imported
the telethon.sync
package and that you have a client ready to use.
Note
There are some very common errors (such as forgetting to add
import telethon.sync
) for newcomers to asyncio
:
# AttributeError: 'coroutine' object has no attribute 'first_name'
print(client.get_me().first_name)
# TypeError: 'AsyncGenerator' object is not iterable
for message in client.iter_messages('me'):
...
# RuntimeError: This event loop is already running
with client.conversation('me') as conv:
...
That error means you’re probably inside an async def
so you
need to use:
print((await client.get_me()).first_name)
async for message in client.iter_messages('me'):
...
async with client.conversation('me') as conv:
...
You can of course call other def
functions from your async def
event handlers, but if they need making API calls, make your own
functions async def
so you can await
things:
async def helper(client):
await client.send_message('me', 'Hi')
If you’re not inside an async def
you can enter one like so:
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(my_async_def())
Contents
- Examples with the Client
- Authorization
- Group Chats
- Open Conversations and Joined Channels
- Downloading Media
- Getting Messages
- Exporting Messages
- Sending Messages
- Sending Markdown or HTML messages
- Sending Messages with Media
- Reusing Uploaded Files
- Sending Messages with Buttons
- Making Inline Queries
- Clicking Buttons
- Answering Inline Queries
- Conversations: Waiting for Messages or Replies
- Forwarding Messages
- Editing Messages
- Deleting Messages
- Marking Messages as Read
- Getting Entities
- Getting the Admin Log
Authorization¶
Starting the client is as easy as calling client.start()
:
client.start()
... # code using the client
client.disconnect()
And you can even use a with
block:
with client:
... # code using the client
Note
Remember we assume you have import telethon.sync
. You can of course
use the library without importing it. The code would be rewritten as:
import asyncio
loop = asyncio.get_event_loop()
async def main():
await client.start()
...
await client.disconnect()
# or
async with client:
...
loop.run_until_complete(main())
All methods that need access to the network (e.g. to make an API call)
must be awaited (or their equivalent such as async for
and
async with
). You can do this yourself or you can let the library
do it for you by using import telethon.sync
. With event handlers,
you must do this yourself.
The cleanest way to delete your *.session
file is client.log_out
. Note that you will obviously
need to login again if you use this:
# Logs out and deletes the session file; you will need to sign in again
client.log_out()
# You often simply want to disconnect. You will not need to sign in again
client.disconnect()
Group Chats¶
You can easily iterate over all the User in a chat and
do anything you want with them by using client.iter_participants
:
for user in client.iter_participants(chat):
... # do something with the user
You can also search by their name:
for user in client.iter_participants(chat, search='name'):
...
Or by their type (e.g. if they are admin) with ChannelParticipantsFilter:
from telethon.tl.types import ChannelParticipantsAdmins
for user in client.iter_participants(chat, filter=ChannelParticipantsAdmins):
...
Open Conversations and Joined Channels¶
The conversations you have open and the channels you have joined
are in your “dialogs”, so to get them you need to client.get_dialogs
:
dialogs = client.get_dialogs()
first = dialogs[0]
print(first.title)
You can then use the dialog as if it were a peer:
client.send_message(first, 'hi')
You can access dialog.draft
or you can
get them all at once without getting the dialogs:
drafts = client.get_drafts()
Downloading Media¶
It’s easy to download_profile_photo
:
client.download_profile_photo(user)
Or download_media
from a message:
client.download_media(message)
client.download_media(message, filename)
# or
message.download_media()
message.download_media(filename)
Remember that these methods return the final filename where the media was downloaded (e.g. it may add the extension automatically).
Getting Messages¶
You can easily iterate over all the messages
of a chat with iter_messages
:
for message in client.iter_messages(chat):
... # do something with the message from recent to older
for message in client.iter_messages(chat, reverse=True):
... # going from the oldest to the most recent
You can also use it to search for messages from a specific person:
for message in client.iter_messages(chat, from_user='me'):
...
Or you can search by text:
for message in client.iter_messages(chat, search='hello'):
...
Or you can search by media with a MessagesFilter:
from telethon.tl.types import InputMessagesFilterPhotos
for message in client.iter_messages(chat, filter=InputMessagesFilterPhotos):
...
If you want a list instead, use the get variant. The second
argument is the limit, and None
means “get them all”:
from telethon.tl.types import InputMessagesFilterPhotos
# Get 0 photos and print the total
photos = client.get_messages(chat, 0, filter=InputMessagesFilterPhotos)
print(photos.total)
# Get all the photos
photos = client.get_messages(chat, None, filter=InputMessagesFilterPhotos)
Or just some IDs:
message_1337 = client.get_messages(chats, ids=1337)
Exporting Messages¶
If you plan on exporting data from your Telegram account, such as the entire message history from your private conversations, chats or channels, or if you plan to download a lot of media, you may prefer to do this within a takeout session. Takeout sessions let you export data from your account with lower flood wait limits.
To start a takeout session, simply call client.takeout()
:
from telethon import errors
try:
with client.takeout() as takeout:
for message in takeout.iter_messages(chat, wait_time=0):
... # Do something with the message
except errors.TakeoutInitDelayError as e:
print('Must wait', e.seconds, 'before takeout')
Depending on the condition of the session (for example, when it’s very
young and the method has not been called before), you may or not need
to except errors.TakeoutInitDelayError
. However, it is good practice.
Sending Messages¶
Just use send_message
:
client.send_message('lonami', 'Thanks for the Telethon library!')
The function returns the custom.Message
that was sent so you can do more things with it if you want.
You can also reply
or
respond
to messages:
message.reply('Hello')
message.respond('World')
Sending Markdown or HTML messages¶
Markdown ('md'
or 'markdown'
) is the default parse_mode
for the client. You can change the default parse mode like so:
client.parse_mode = 'html'
Now all messages will be formatted as HTML by default:
client.send_message('me', 'Some <b>bold</b> and <i>italic</i> text')
client.send_message('me', 'An <a href="https://example.com">URL</a>')
client.send_message('me', '<code>code</code> and <pre>pre\nblocks</pre>')
client.send_message('me', '<a href="tg://user?id=me">Mentions</a>')
You can override the default parse mode to use for special cases:
# No parse mode by default
client.parse_mode = None
# ...but here I want markdown
client.send_message('me', 'Hello, **world**!', parse_mode='md')
# ...and here I need HTML
client.send_message('me', 'Hello, <i>world</i>!', parse_mode='html')
The rules are the same as for Bot API, so please refer to https://core.telegram.org/bots/api#formatting-options.
Sending Messages with Media¶
Sending media can be done with send_file
:
client.send_file(chat, '/my/photos/me.jpg', caption="It's me!")
# or
client.send_message(chat, "It's me!", file='/my/photos/me.jpg')
You can send voice notes or round videos by setting the right arguments:
client.send_file(chat, '/my/songs/song.mp3', voice_note=True)
client.send_file(chat, '/my/videos/video.mp4', video_note=True)
You can set a JPG thumbnail for any document:
client.send_file(chat, '/my/documents/doc.txt', thumb='photo.jpg')
You can force sending images as documents:
client.send_file(chat, '/my/photos/photo.png', force_document=True)
You can send albums if you pass more than one file:
client.send_file(chat, [
'/my/photos/holiday1.jpg',
'/my/photos/holiday2.jpg',
'/my/drawings/portrait.png'
])
The caption can also be a list to match the different photos.
Reusing Uploaded Files¶
All files you send are automatically cached, so if you do:
client.send_file(first_chat, 'document.txt')
client.send_file(second_chat, 'document.txt')
The 'document.txt'
file will only be uploaded once. You
can disable this behaviour by settings allow_cache=False
:
client.send_file(first_chat, 'document.txt', allow_cache=False)
client.send_file(second_chat, 'document.txt', allow_cache=False)
Disabling cache is the only way to send the same document with different
attributes (for example, you send an .ogg
as a song but now you want
it to show as a voice note; you probably need to disable the cache).
However, you can upload the file once (not sending it yet!), and then you can send it with different attributes. This means you can send an image as a photo and a document:
file = client.upload_file('photo.jpg')
client.send_file(chat, file) # sends as photo
client.send_file(chat, file, force_document=True) # sends as document
file.name = 'not a photo.jpg'
client.send_file(chat, file, force_document=True) # document, new name
Or, the example described before:
file = client.upload_file('song.ogg')
client.send_file(chat, file) # sends as song
client.send_file(chat, file, voice_note=True) # sends as voice note
The file
returned by client.upload_file
represents the uploaded
file, not an immutable document (that’s why the attributes can change, because
they are set later). This handle can be used only for a limited amount of time
(somewhere within a day). Telegram decides this limit and it is not public.
However, a day is often more than enough.
Sending Messages with Buttons¶
You must sign in as a bot in order to add inline buttons (or normal
keyboards) to your messages. Once you have signed in as a bot specify
the Button
or buttons to use:
from telethon import events
from telethon.tl.custom import Button
@client.on(events.CallbackQuery)
async def callback(event):
await event.edit('Thank you for clicking {}!'.format(event.data))
client.send_message(chat, 'A single button, with "clk1" as data',
buttons=Button.inline('Click me', b'clk1'))
client.send_message(chat, 'Pick one from this grid', buttons=[
[Button.inline('Left'), Button.inline('Right')],
[Button.url('Check this site!', 'https://lonamiwebs.github.io')]
])
You can also use normal buttons (not inline) to request the user’s location, phone number, or simply for them to easily send a message:
client.send_message(chat, 'Welcome', buttons=[
Button.text('Thanks!', resize=True, single_use=True),
Button.request_phone('Send phone'),
Button.request_location('Send location')
])
Forcing a reply or removing the keyboard can also be done:
client.send_message(chat, 'Reply to me', buttons=Button.force_reply())
client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear())
Remember to check Button
for more.
Making Inline Queries¶
You can send messages via @bot
by first making an inline query:
results = client.inline_query('like', 'Do you like Telethon?')
Then access the result you want and click
it in the chat
where you want to send it to:
message = results[0].click('TelethonOffTopic')
Sending messages through inline bots lets you use buttons as a normal user.
It can look a bit strange at first, but you can make inline queries in no chat in particular, and then click a result to send it to some chat.
Clicking Buttons¶
Let’s click
the message we sent in the example above!
message.click(0)
This will click the first button in the message. You could also
click(row, column)
, using some text such as click(text='👍')
or even the data directly click(data=b'payload')
.
Answering Inline Queries¶
As a bot, you can answer to inline queries with events.InlineQuery
. You should make use of the
builder
property
to conveniently build the list of results to show to the user. Remember
to check the properties of the InlineQuery.Event
:
@bot.on(events.InlineQuery)
async def handler(event):
builder = event.builder
rev_text = event.text[::-1]
await event.answer([
builder.article('Reverse text', text=rev_text),
builder.photo('/path/to/photo.jpg')
])
Conversations: Waiting for Messages or Replies¶
This one is really useful for unit testing your bots, which you can
even write within Telethon itself! You can open a Conversation
in any chat as:
with client.conversation(chat) as conv:
...
Conversations let you program a finite state machine with the
higher-level constructs we are all used to, such as while
and if
conditionals instead setting the state and jumping
from one place to another which is less clean.
For instance, let’s imagine you
are the bot talking to usr
:
<you> Hi!
<usr> Hello!
<you> Please tell me your name
<usr> ?
<you> Your name didn't have any letters! Try again
<usr> Lonami
<you> Thanks Lonami!
This can be programmed as follows:
with bot.conversation(chat) as conv:
conv.send_message('Hi!')
hello = conv.get_response()
conv.send_message('Please tell me your name')
name = conv.get_response().raw_text
while not any(x.isalpha() for x in name):
conv.send_message("Your name didn't have any letters! Try again")
name = conv.get_response().raw_text
conv.send_message('Thanks {}!'.format(name))
Note how we sent a message with the conversation, not with the client. This is important so the conversation remembers what messages you sent.
The method reference for getting a response, getting a reply or marking
the conversation as read can be found by clicking here: Conversation
.
Sending a message or getting a response returns a Message
. Reading its documentation
will also be really useful!
If a reply never arrives or too many messages come in, getting
responses will raise asyncio.TimeoutError
or ValueError
respectively. You may want to except
these and tell the user
they were too slow, or simply drop the conversation.
Forwarding Messages¶
You can forward up to 100 messages with forward_messages
,
or a single one if you have the message with forward_to
:
# a single one
client.forward_messages(chat, message)
# or
client.forward_messages(chat, message_id, from_chat)
# or
message.forward_to(chat)
# multiple
client.forward_messages(chat, messages)
# or
client.forward_messages(chat, message_ids, from_chat)
You can also “forward” messages without showing “Forwarded from” by re-sending the message:
client.send_message(chat, message)
Editing Messages¶
With edit_message
or message.edit
:
client.edit_message(message, 'New text')
# or
message.edit('New text')
# or
client.edit_message(chat, message_id, 'New text')
Deleting Messages¶
With delete_messages
or message.delete
. Note that the
first one supports deleting entire chats at once!:
client.delete_messages(chat, messages)
# or
message.delete()
Marking Messages as Read¶
Marking messages up to a certain point as read with send_read_acknowledge
:
client.send_read_acknowledge(last_message)
# or
client.send_read_acknowledge(last_message_id)
# or
client.send_read_acknowledge(messages)
Getting Entities¶
Entities are users, chats, or channels. You can get them by their ID if you have seen them before (e.g. you probably need to get all dialogs or all the members from a chat first):
from telethon import utils
me = client.get_entity('me')
print(utils.get_display_name(me))
chat = client.get_input_entity('username')
for message in client.iter_messages(chat):
...
# Note that you could have used the username directly, but it's
# good to use get_input_entity if you will reuse it a lot.
for message in client.iter_messages('username'):
...
# Note that for this to work the phone number must be in your contacts
some_id = client.get_peer_id('+34123456789')
The documentation for shown methods are get_entity
, get_input_entity
and get_peer_id
.
Note that the utils package also has a get_peer_id
but it won’t work with things
that need access to the network such as usernames or phones,
which need to be in your contact list.
Getting the Admin Log¶
If you’re an administrator in a channel or megagroup, then you have access to the admin log. This is a list of events within the last 48 hours of different actions, such as joining or leaving members, edited or deleted messages, new promotions, bans or restrictions.
You can iterate over all the available actions like so:
for event in client.iter_admin_log(channel):
if event.changed_title:
print('The title changed from', event.old, 'to', event.new)
You can also filter to only show some text or actions. Let’s find people who swear to ban them:
# Get a list of deleted message events which said "heck"
events = client.get_admin_log(channel, search='heck', delete=True)
# Print the old message before it was deleted
print(events[0].old)
You can find here the documentation for client.iter_admin_log
, and be sure
to also check the properties of the returned AdminLogEvent
to know what
you can access.