Document asyncio better (#456)

This commit is contained in:
Lonami Exo 2018-03-24 14:04:25 +01:00
parent 5cb3a9af36
commit 8bf140ca74
10 changed files with 99 additions and 72 deletions

View File

@ -42,7 +42,7 @@ your disk. This is by default a database file using Python's ``sqlite3``.
Before using the client, you must be connected to Telegram. Before using the client, you must be connected to Telegram.
Doing so is very easy: Doing so is very easy:
``client.connect() # Must return True, otherwise, try again`` ``await client.connect() # Must return True, otherwise, try again``
You may or may not be authorized yet. You must be authorized You may or may not be authorized yet. You must be authorized
before you're able to send any request: before you're able to send any request:
@ -54,8 +54,8 @@ If you're not authorized, you need to ``.sign_in()``:
.. code-block:: python .. code-block:: python
phone_number = '+34600000000' phone_number = '+34600000000'
client.send_code_request(phone_number) await client.send_code_request(phone_number)
myself = client.sign_in(phone_number, input('Enter code: ')) myself = await client.sign_in(phone_number, input('Enter code: '))
# If .sign_in raises PhoneNumberUnoccupiedError, use .sign_up instead # If .sign_in raises PhoneNumberUnoccupiedError, use .sign_up instead
# If .sign_in raises SessionPasswordNeeded error, call .sign_in(password=...) # If .sign_in raises SessionPasswordNeeded error, call .sign_in(password=...)
# You can import both exceptions from telethon.errors. # You can import both exceptions from telethon.errors.
@ -78,10 +78,10 @@ As a full example:
.. code-block:: python .. code-block:: python
client = TelegramClient('anon', api_id, api_hash) client = TelegramClient('anon', api_id, api_hash)
assert client.connect() assert await client.connect()
if not client.is_user_authorized(): if not client.is_user_authorized():
client.send_code_request(phone_number) await client.send_code_request(phone_number)
me = client.sign_in(phone_number, input('Enter code: ')) me = await client.sign_in(phone_number, input('Enter code: '))
All of this, however, can be done through a call to ``.start()``: All of this, however, can be done through a call to ``.start()``:
@ -89,7 +89,7 @@ All of this, however, can be done through a call to ``.start()``:
.. code-block:: python .. code-block:: python
client = TelegramClient('anon', api_id, api_hash) client = TelegramClient('anon', api_id, api_hash)
client.start() await client.start()
The code shown is just what ``.start()`` will be doing behind the scenes The code shown is just what ``.start()`` will be doing behind the scenes
@ -103,6 +103,19 @@ is just a matter of taste, and how much control you need.
Remember that you can get yourself at any time with ``client.get_me()``. Remember that you can get yourself at any time with ``client.get_me()``.
Assuming you've written all of this in a ``async def main():``, you can
run it with:
.. code-block:: python
import asyncio
async def main():
...
asyncio.get_event_loop().run_until_complete(main())
.. warning:: .. warning::
Please note that if you fail to login around 5 times (or change the first Please note that if you fail to login around 5 times (or change the first
parameter of the ``TelegramClient``, which is the session name) you will parameter of the ``TelegramClient``, which is the session name) you will
@ -141,9 +154,9 @@ account, calling :meth:`telethon.TelegramClient.sign_in` will raise a
client.sign_in(phone) client.sign_in(phone)
try: try:
client.sign_in(code=input('Enter code: ')) await client.sign_in(code=input('Enter code: '))
except SessionPasswordNeededError: except SessionPasswordNeededError:
client.sign_in(password=getpass.getpass()) await client.sign_in(password=getpass.getpass())
The mentioned ``.start()`` method will handle this for you as well, but The mentioned ``.start()`` method will handle this for you as well, but
@ -168,7 +181,7 @@ take as example the following code snippet:
pw_salted = salt + pw + salt pw_salted = salt + pw + salt
pw_hash = sha256(pw_salted).digest() pw_hash = sha256(pw_salted).digest()
result = client(account.UpdatePasswordSettingsRequest( result = await client(account.UpdatePasswordSettingsRequest(
current_password_hash=salt, current_password_hash=salt,
new_settings=PasswordInputSettings( new_settings=PasswordInputSettings(
new_salt=salt, new_salt=salt,

View File

@ -32,17 +32,17 @@ you're able to just do this:
# Dialogs are the "conversations you have open". # Dialogs are the "conversations you have open".
# This method returns a list of Dialog, which # This method returns a list of Dialog, which
# has the .entity attribute and other information. # has the .entity attribute and other information.
dialogs = client.get_dialogs(limit=200) dialogs = await client.get_dialogs(limit=200)
# All of these work and do the same. # All of these work and do the same.
lonami = client.get_entity('lonami') lonami = await client.get_entity('lonami')
lonami = client.get_entity('t.me/lonami') lonami = await client.get_entity('t.me/lonami')
lonami = client.get_entity('https://telegram.dog/lonami') lonami = await client.get_entity('https://telegram.dog/lonami')
# Other kind of entities. # Other kind of entities.
channel = client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg') channel = await client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg')
contact = client.get_entity('+34xxxxxxxxx') contact = await client.get_entity('+34xxxxxxxxx')
friend = client.get_entity(friend_id) friend = await client.get_entity(friend_id)
# Using Peer/InputPeer (note that the API may return these) # Using Peer/InputPeer (note that the API may return these)
# users, chats and channels may all have the same ID, so it's # users, chats and channels may all have the same ID, so it's
@ -51,9 +51,9 @@ you're able to just do this:
# NOTICE how the IDs *must* be wrapped inside a Peer() so the # NOTICE how the IDs *must* be wrapped inside a Peer() so the
# library knows their type. # library knows their type.
from telethon.tl.types import PeerUser, PeerChat, PeerChannel from telethon.tl.types import PeerUser, PeerChat, PeerChannel
my_user = client.get_entity(PeerUser(some_id)) my_user = await client.get_entity(PeerUser(some_id))
my_chat = client.get_entity(PeerChat(some_id)) my_chat = await client.get_entity(PeerChat(some_id))
my_channel = client.get_entity(PeerChannel(some_id)) my_channel = await client.get_entity(PeerChannel(some_id))
.. warning:: .. warning::
@ -122,7 +122,7 @@ library, the raw requests you make to the API are also able to call
.. code-block:: python .. code-block:: python
client(SendMessageRequest('username', 'hello')) await client(SendMessageRequest('username', 'hello'))
The library will call the ``.resolve()`` method of the request, which will The library will call the ``.resolve()`` method of the request, which will
resolve ``'username'`` with the appropriated :tl:`InputPeer`. Don't worry if resolve ``'username'`` with the appropriated :tl:`InputPeer`. Don't worry if

View File

@ -27,7 +27,7 @@ Creating a client
api_hash = '0123456789abcdef0123456789abcdef' api_hash = '0123456789abcdef0123456789abcdef'
client = TelegramClient('session_name', api_id, api_hash) client = TelegramClient('session_name', api_id, api_hash)
client.start() await client.start()
**More details**: :ref:`creating-a-client` **More details**: :ref:`creating-a-client`
@ -38,30 +38,30 @@ Basic Usage
.. code-block:: python .. code-block:: python
# Getting information about yourself # Getting information about yourself
print(client.get_me().stringify()) print((await client.get_me()).stringify())
# Sending a message (you can use 'me' or 'self' to message yourself) # Sending a message (you can use 'me' or 'self' to message yourself)
client.send_message('username', 'Hello World from Telethon!') await client.send_message('username', 'Hello World from Telethon!')
# Sending a file # Sending a file
client.send_file('username', '/home/myself/Pictures/holidays.jpg') await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
# Retrieving messages from a chat # Retrieving messages from a chat
from telethon import utils from telethon import utils
for message in client.get_message_history('username', limit=10): async for message in client.get_message_history('username', limit=10):
print(utils.get_display_name(message.sender), message.message) print(utils.get_display_name(message.sender), message.message)
# Listing all the dialogs (conversations you have open) # Listing all the dialogs (conversations you have open)
for dialog in client.get_dialogs(limit=10): async for dialog in client.get_dialogs(limit=10):
print(utils.get_display_name(dialog.entity), dialog.draft.message) print(utils.get_display_name(dialog.entity), dialog.draft.message)
# Downloading profile photos (default path is the working directory) # Downloading profile photos (default path is the working directory)
client.download_profile_photo('username') await client.download_profile_photo('username')
# Once you have a message with .media (if message.media) # Once you have a message with .media (if message.media)
# you can download it using client.download_media(): # you can download it using client.download_media():
messages = client.get_message_history('username') messages = await client.get_message_history('username')
client.download_media(messages[0]) await client.download_media(messages[0])
**More details**: :ref:`telegram-client` **More details**: :ref:`telegram-client`
@ -71,17 +71,18 @@ Handling Updates
.. code-block:: python .. code-block:: python
import asyncio
from telethon import events from telethon import events
# We need to have some worker running # We need to have some worker running
client.updates.workers = 1 client.updates.workers = 1
@client.on(events.NewMessage(incoming=True, pattern='(?i)hi')) @client.on(events.NewMessage(incoming=True, pattern='(?i)hi'))
def handler(event): async def handler(event):
event.reply('Hello!') await event.reply('Hello!')
# If you want to handle updates you can't let the script end. # If you want to handle updates you can't let the script end.
input('Press enter to exit.') asyncio.get_event_loop().run_forever()
**More details**: :ref:`working-with-updates` **More details**: :ref:`working-with-updates`

View File

@ -45,7 +45,7 @@ how the library refers to either of these:
# The method will infer that you've passed an username # The method will infer that you've passed an username
# It also accepts phone numbers, and will get the user # It also accepts phone numbers, and will get the user
# from your contact list. # from your contact list.
lonami = client.get_entity('lonami') lonami = await client.get_entity('lonami')
The so called "entities" are another important whole concept on its own, The so called "entities" are another important whole concept on its own,
but for now you don't need to worry about it. Simply know that they are but for now you don't need to worry about it. Simply know that they are
@ -56,21 +56,21 @@ Many other common methods for quick scripts are also available:
.. code-block:: python .. code-block:: python
# Note that you can use 'me' or 'self' to message yourself # Note that you can use 'me' or 'self' to message yourself
client.send_message('username', 'Hello World from Telethon!') await client.send_message('username', 'Hello World from Telethon!')
client.send_file('username', '/home/myself/Pictures/holidays.jpg') await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
# The utils package has some goodies, like .get_display_name() # The utils package has some goodies, like .get_display_name()
from telethon import utils from telethon import utils
for message in client.get_message_history('username', limit=10): async for message in client.get_message_history('username', limit=10):
print(utils.get_display_name(message.sender), message.message) print(utils.get_display_name(message.sender), message.message)
# Dialogs are the conversations you have open # Dialogs are the conversations you have open
for dialog in client.get_dialogs(limit=10): async for dialog in client.get_dialogs(limit=10):
print(utils.get_display_name(dialog.entity), dialog.draft.message) print(utils.get_display_name(dialog.entity), dialog.draft.message)
# Default path is the working directory # Default path is the working directory
client.download_profile_photo('username') await client.download_profile_photo('username')
# Call .disconnect() when you're done # Call .disconnect() when you're done
client.disconnect() client.disconnect()

View File

@ -30,17 +30,18 @@ Getting Started
.. code-block:: python .. code-block:: python
import asyncio
from telethon import TelegramClient, events from telethon import TelegramClient, events
client = TelegramClient(..., update_workers=1, spawn_read_thread=False) client = TelegramClient(..., update_workers=1, spawn_read_thread=False)
client.start() await client.start()
@client.on(events.NewMessage) @client.on(events.NewMessage)
def my_event_handler(event): async def my_event_handler(event):
if 'hello' in event.raw_text: if 'hello' in event.raw_text:
event.reply('hi!') await event.reply('hi!')
client.idle() asyncio.get_event_loop().run_forever()
Not much, but there might be some things unclear. What does this code do? Not much, but there might be some things unclear. What does this code do?
@ -50,7 +51,7 @@ Not much, but there might be some things unclear. What does this code do?
from telethon import TelegramClient, events from telethon import TelegramClient, events
client = TelegramClient(..., update_workers=1, spawn_read_thread=False) client = TelegramClient(..., update_workers=1, spawn_read_thread=False)
client.start() await client.start()
This is normal initialization (of course, pass session name, API ID and hash). This is normal initialization (of course, pass session name, API ID and hash).
@ -67,9 +68,9 @@ the callback function you're about to define will be called:
.. code-block:: python .. code-block:: python
def my_event_handler(event): async def my_event_handler(event):
if 'hello' in event.raw_text: if 'hello' in event.raw_text:
event.reply('hi!') await event.reply('hi!')
If a ``NewMessage`` event occurs, and ``'hello'`` is in the text of the If a ``NewMessage`` event occurs, and ``'hello'`` is in the text of the
@ -77,10 +78,10 @@ message, we ``reply`` to the event with a ``'hi!'`` message.
.. code-block:: python .. code-block:: python
client.idle() asyncio.get_event_loop().run_forever()
Finally, this tells the client that we're done with our code, and want Finally, this tells the script that we're done with our code, and want
to listen for all these events to occur. Of course, you might want to to listen for all these events to occur. Of course, you might want to
do other things instead idling. For this refer to :ref:`update-modes`. do other things instead idling. For this refer to :ref:`update-modes`.
@ -119,17 +120,17 @@ for example:
# Either a single item or a list of them will work for the chats. # 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. # You can also use the IDs, Peers, or even User/Chat/Channel objects.
@client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic'))) @client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic')))
def normal_handler(event): async def normal_handler(event):
if 'roll' in event.raw_text: if 'roll' in event.raw_text:
event.reply(str(random.randint(1, 6))) await event.reply(str(random.randint(1, 6)))
# Similarly, you can use incoming=True for messages that you receive # Similarly, you can use incoming=True for messages that you receive
@client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True)) @client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True))
def admin_handler(event): async def admin_handler(event):
if event.raw_text.startswith('eval'): if event.raw_text.startswith('eval'):
expression = event.raw_text.replace('eval', '').strip() expression = event.raw_text.replace('eval', '').strip()
event.reply(str(ast.literal_eval(expression))) await event.reply(str(ast.literal_eval(expression)))
You can pass one or more chats to the ``chats`` parameter (as a list or tuple), You can pass one or more chats to the ``chats`` parameter (as a list or tuple),
@ -167,15 +168,15 @@ propagation of the update through your handlers to stop:
from telethon.events import StopPropagation from telethon.events import StopPropagation
@client.on(events.NewMessage) @client.on(events.NewMessage)
def _(event): async def _(event):
# ... some conditions # ... some conditions
event.delete() await event.delete()
# Other handlers won't have an event to work with # Other handlers won't have an event to work with
raise StopPropagation raise StopPropagation
@client.on(events.NewMessage) @client.on(events.NewMessage)
def _(event): async def _(event):
# Will never be reached, because it is the second handler # Will never be reached, because it is the second handler
# in the chain. # in the chain.
pass pass

View File

@ -32,4 +32,4 @@ times, in this case, ``22222`` so we can hardcode that:
client = TelegramClient(None, api_id, api_hash) client = TelegramClient(None, api_id, api_hash)
client.session.set_dc(2, '149.154.167.40', 80) client.session.set_dc(2, '149.154.167.40', 80)
client.start(phone='9996621234', code_callback=lambda: '22222') await client.start(phone='9996621234', code_callback=lambda: '22222')

View File

@ -19,7 +19,7 @@ not *interact* with a voting message), by making use of the
from telethon.tl.functions.messages import GetInlineBotResultsRequest from telethon.tl.functions.messages import GetInlineBotResultsRequest
bot_results = client(GetInlineBotResultsRequest( bot_results = await client(GetInlineBotResultsRequest(
bot, user_or_chat, 'query', '' bot, user_or_chat, 'query', ''
)) ))
@ -30,7 +30,7 @@ And you can select any of their results by using
from telethon.tl.functions.messages import SendInlineBotResultRequest from telethon.tl.functions.messages import SendInlineBotResultRequest
client(SendInlineBotResultRequest( await client(SendInlineBotResultRequest(
get_input_peer(user_or_chat), get_input_peer(user_or_chat),
obtained_query_id, obtained_query_id,
obtained_str_id obtained_str_id
@ -47,7 +47,7 @@ To interact with a message that has a special reply markup, such as
from telethon.tl.functions.messages import GetBotCallbackAnswerRequest from telethon.tl.functions.messages import GetBotCallbackAnswerRequest
client(GetBotCallbackAnswerRequest( await client(GetBotCallbackAnswerRequest(
user_or_chat, user_or_chat,
msg.id, msg.id,
data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data

View File

@ -26,11 +26,11 @@ to, you can make use of the `JoinChannelRequest`__ to join such channel:
.. code-block:: python .. code-block:: python
from telethon.tl.functions.channels import JoinChannelRequest from telethon.tl.functions.channels import JoinChannelRequest
client(JoinChannelRequest(channel)) await client(JoinChannelRequest(channel))
# In the same way, you can also leave such channel # In the same way, you can also leave such channel
from telethon.tl.functions.channels import LeaveChannelRequest from telethon.tl.functions.channels import LeaveChannelRequest
client(LeaveChannelRequest(input_channel)) await client(LeaveChannelRequest(input_channel))
For more on channels, check the `channels namespace`__. For more on channels, check the `channels namespace`__.
@ -49,7 +49,7 @@ example, is the ``hash`` of the chat or channel. Now you can use
.. code-block:: python .. code-block:: python
from telethon.tl.functions.messages import ImportChatInviteRequest from telethon.tl.functions.messages import ImportChatInviteRequest
updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) updates = await client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg'))
Adding someone else to such chat or channel Adding someone else to such chat or channel
@ -64,7 +64,7 @@ use is very straightforward, or `InviteToChannelRequest`__ for channels:
# For normal chats # For normal chats
from telethon.tl.functions.messages import AddChatUserRequest from telethon.tl.functions.messages import AddChatUserRequest
client(AddChatUserRequest( await client(AddChatUserRequest(
chat_id, chat_id,
user_to_add, user_to_add,
fwd_limit=10 # Allow the user to see the 10 last messages fwd_limit=10 # Allow the user to see the 10 last messages
@ -73,7 +73,7 @@ use is very straightforward, or `InviteToChannelRequest`__ for channels:
# For channels # For channels
from telethon.tl.functions.channels import InviteToChannelRequest from telethon.tl.functions.channels import InviteToChannelRequest
client(InviteToChannelRequest( await client(InviteToChannelRequest(
channel, channel,
[users_to_add] [users_to_add]
)) ))
@ -122,7 +122,7 @@ a fixed limit:
all_participants = [] all_participants = []
while True: while True:
participants = client(GetParticipantsRequest( participants = await client(GetParticipantsRequest(
channel, ChannelParticipantsSearch(''), offset, limit, channel, ChannelParticipantsSearch(''), offset, limit,
hash=0 hash=0
)) ))
@ -197,7 +197,7 @@ Giving or revoking admin permissions can be done with the `EditAdminRequest`__:
# ) # )
# Once you have a ChannelAdminRights, invoke it # Once you have a ChannelAdminRights, invoke it
client(EditAdminRequest(channel, user, rights)) await client(EditAdminRequest(channel, user, rights))
# User will now be able to change group info, delete other people's # User will now be able to change group info, delete other people's
# messages and pin messages. # messages and pin messages.
@ -230,7 +230,7 @@ use `GetMessagesViewsRequest`__, setting ``increment=True``:
# Obtain `channel' through dialogs or through client.get_entity() or anyhow. # Obtain `channel' through dialogs or through client.get_entity() or anyhow.
# Obtain `msg_ids' through `.get_message_history()` or anyhow. Must be a list. # Obtain `msg_ids' through `.get_message_history()` or anyhow. Must be a list.
client(GetMessagesViewsRequest( await client(GetMessagesViewsRequest(
peer=channel, peer=channel,
id=msg_ids, id=msg_ids,
increment=True increment=True

View File

@ -28,7 +28,7 @@ Message*s*, plural) *always*, since it is more powerful, as follows:
from_entity = bar() from_entity = bar()
to_entity = baz() to_entity = baz()
client(ForwardMessagesRequest( await client(ForwardMessagesRequest(
from_peer=from_entity, # who sent these messages? from_peer=from_entity, # who sent these messages?
id=[msg.id for msg in messages], # which are the messages? id=[msg.id for msg in messages], # which are the messages?
to_peer=to_entity # who are we forwarding them to? to_peer=to_entity # who are we forwarding them to?
@ -51,7 +51,7 @@ into issues_. A valid example would be:
from telethon.tl.types import InputMessagesFilterEmpty from telethon.tl.types import InputMessagesFilterEmpty
filter = InputMessagesFilterEmpty() filter = InputMessagesFilterEmpty()
result = client(SearchRequest( result = await client(SearchRequest(
peer=peer, # On which chat/conversation peer=peer, # On which chat/conversation
q='query', # What to search for q='query', # What to search for
filter=filter, # Filter to use (maybe filter for media) filter=filter, # Filter to use (maybe filter for media)
@ -95,20 +95,20 @@ send yourself the very first sticker you have:
.. code-block:: python .. code-block:: python
# Get all the sticker sets this user has # Get all the sticker sets this user has
sticker_sets = client(GetAllStickersRequest(0)) sticker_sets = await client(GetAllStickersRequest(0))
# Choose a sticker set # Choose a sticker set
sticker_set = sticker_sets.sets[0] sticker_set = sticker_sets.sets[0]
# Get the stickers for this sticker set # Get the stickers for this sticker set
stickers = client(GetStickerSetRequest( stickers = await client(GetStickerSetRequest(
stickerset=InputStickerSetID( stickerset=InputStickerSetID(
id=sticker_set.id, access_hash=sticker_set.access_hash id=sticker_set.id, access_hash=sticker_set.access_hash
) )
)) ))
# Stickers are nothing more than files, so send that # Stickers are nothing more than files, so send that
client(SendMediaRequest( await client(SendMediaRequest(
peer=client.get_me(), peer=client.get_me(),
media=InputMediaDocument( media=InputMediaDocument(
id=InputDocument( id=InputDocument(

View File

@ -19,6 +19,18 @@ when you upgrade!
looking for the method reference, you should check :ref:`telethon-package`. looking for the method reference, you should check :ref:`telethon-package`.
.. note::
We assume that you have some experience working with ``asyncio``,
if you don't you should probably use the threaded version of the
library, or either learn how to use ``asyncio``. All the code
here assumes you're writing the code inside an ``async def`` so
we can use ``await`` across the examples.
Then you can ``import asyncio`` and run
``asyncio.get_event_loop().run_until_complete(my_method())``
What is this? What is this?
************* *************