Finish up asyncio docs

This commit is contained in:
Lonami Exo 2018-06-22 14:44:59 +02:00
parent 3d3698562b
commit f614d3836b
12 changed files with 255 additions and 109 deletions

View File

@ -93,7 +93,7 @@ every time its used, simply call `telethon.utils.get_input_peer`:
.. code-block:: python
from telethon import utils
peer = utils.get_input_user(entity)
peer = utils.get_input_peer(entity)
.. note::
@ -117,8 +117,8 @@ request we do:
)
# __call__ is an alias for client.invoke(request). Both will work
Message sent! Of course, this is only an example. There are nearly 250
methods available as of layer 73, and you can use every single of them
Message sent! Of course, this is only an example. There are over 250
methods available as of layer 80, and you can use every single of them
as you wish. Remember to use the right types! To sum up:
.. code-block:: python

View File

@ -24,8 +24,8 @@ loop, you should use `client.run_until_disconnected
Behind the scenes, this method is ``await``'ing on the `client.disconnected
<telethon.client.telegrambaseclient.disconnected>` property, so the code above
and the following are equivalent:
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>` property,
so the code above and the following are equivalent:
.. code-block:: python
@ -42,7 +42,8 @@ and the following are equivalent:
You could also run `client.disconnected
<telethon.client.telegrambaseclient.disconnected>` until it completed.
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>`
until it completed.
But if you don't want to ``await``, then you should know what you want
to be doing instead! What matters is that you shouldn't let your script

View File

@ -19,9 +19,9 @@ way. You send your request, and eventually, Telegram will process it and
respond to it. It feels natural to make a library that also behaves this
way: you send a request, and you can ``await`` for its result.
Having understood that Telegram's API follows an asynchronous model and
developing a library that does the same greatly simplifies the internal
code and eases working with the API.
Now that we know that Telegram's API follows an asynchronous model, you
should understand the benefits of developing a library that does the same,
it greatly simplifies the internal code and eases working with the API.
Using ``asyncio`` keeps a cleaner library that will be easier to understand,
develop, and that will be faster than using threads, which are harder to get
@ -54,7 +54,7 @@ To get started with ``asyncio``, all you need is to setup your main
Inside ``async def main():``, you can use the ``await`` keyword. Most
methods in the :ref:`TelegramClient <telethon-client>` are ``async def``.
You must ``await`` all ``async def``, also known as a coroutine:
You must ``await`` all ``async def``, also known as a *coroutines*:
.. code-block:: python
@ -78,9 +78,9 @@ Another way to use ``async def`` is to use ``loop.run_until_complete(f())``,
but the loop must not be running before.
If you want to handle updates (and don't let the script die), you must
`await client.disconnected <telethon.client.telegrambaseclient.disconnected>`
`await client.disconnected <telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>`
which is a property that you can wait on until you call
`await client.disconnect() <telethon.client.telegrambaseclient.disconnect>`:
`await client.disconnect() <telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`:
.. code-block:: python
@ -115,6 +115,68 @@ This is the same as using the ``run_until_disconnected()`` method:
client.run_until_disconnected()
Which methods should I use and when?
************************************
Something to note is that you must always get an event loop if you
want to be able to make any API calls. This is done as follows:
.. code-block:: python
import asyncio
loop = asyncio.get_event_loop()
The loop must be running, or things will never get sent.
Normally, you use ``run_until_complete``:
.. code-block:: python
async def coroutine():
await asyncio.sleep(1)
loop.run_until_complete(coroutine())
Note that ``asyncio.sleep`` is in itself a coroutine, so this will
work too:
.. code-block:: python
loop.run_until_complete(asyncio.sleep(1))
Generally, you make an ``async def main()`` if you need to ``await``
a lot of things, instead of typing ``run_until_complete`` all the time:
.. code-block:: python
async def main():
message = await client.send_message('me', 'Hi')
await asyncio.sleep(1)
await message.delete()
loop.run_until_complete(main())
# vs
message = loop.run_until_complete(client.send_message('me', 'Hi'))
loop.run_until_complete(asyncio.sleep(1))
loop.run_until_complete(message.delete())
You can see that the first version has more lines, but you had to type
a lot less. You can also rename the run method to something shorter:
.. code-block:: python
# Note no parenthesis (), we're not running it, just copying the method
rc = loop.run_until_complete
message = rc(client.send_message('me', 'Hi'))
rc(asyncio.sleep(1))
rc(message.delete())
The documentation will use all these three styles so you can get used
to them. Which one to use is up to you, but generally you should work
inside an ``async def main()`` and just run the loop there.
More resources to learn asyncio
*******************************

View File

@ -30,14 +30,16 @@ this is, any "method" listed on the API. There are a few methods (and
growing!) on the :ref:`TelegramClient <telethon-client>` class that abstract
you from the need of manually importing the requests you need.
For instance, retrieving your own user can be done in a single line:
For instance, retrieving your own user can be done in a single line
(if we ignore the boilerplate needed to setup ``asyncio``, which only
needs to be done once for your entire program):
.. code-block:: python
import asyncio
async def main():
myself = await client.get_me()
myself = await client.get_me() # <- a single line!
if __name__ == '__main__':
loop = asyncio.get_event_loop()

View File

@ -82,7 +82,8 @@ the callback function you're about to define will be called:
If a `NewMessage
<telethon.events.newmessage.NewMessage>` event occurs,
and ``'hello'`` is in the text of the message, we ``reply`` to the event
and ``'hello'`` is in the text of the message, we `.reply()
<telethon.tl.custom.message.Message.reply>` to the event
with a ``'hi!'`` message.
.. code-block:: python
@ -101,23 +102,35 @@ More on events
**************
The `NewMessage <telethon.events.newmessage.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) <telethon.client.downloads.DownloadMethods.download_media>`.
more than what was shown. You can access the `.sender
<telethon.tl.custom.message.Message.sender>` of the message
through that member, or even see if the message had `.media
<telethon.tl.custom.message.Message.media>`, a `.photo
<telethon.tl.custom.message.Message.photo>` or a `.document
<telethon.tl.custom.message.Message.document>` (which you
could download with for example `client.download_media(event.photo)
<telethon.client.downloads.DownloadMethods.download_media>`.
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.
If you don't want to `.reply()
<telethon.tl.custom.message.Message.reply>` as a reply,
you can use the `.respond() <telethon.tl.custom.message.Message.respond>`
method instead. Of course, there are more events such as `ChatAction
<telethon.events.chataction.ChatAction>` or `UserUpdate
<telethon.events.userupdate.UserUpdate>`, and they're all
used in the same way. Simply add the `@client.on(events.XYZ)
<telethon.client.updates.UpdateMethods.on>` 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
<telethon.events.newmessage.NewMessage.Event>`), except for the `Raw
<telethon.events.raw.Raw>` event which just passes the :tl:`Update` object.
Note that ``.reply()`` and ``.respond()`` are just wrappers around the
Note that `.reply()
<telethon.tl.custom.message.Message.reply>` and `.respond()
<telethon.tl.custom.message.Message.respond>` are just wrappers around the
`client.send_message() <telethon.client.messages.MessageMethods.send_message>`
method which supports the ``file=`` parameter.
This means you can reply with a photo if you do ``event.reply(file=photo)``.
This means you can reply with a photo if you do `event.reply(file=photo)
<telethon.tl.custom.message.Message.reply>`.
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,
@ -154,6 +167,45 @@ 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
<telethon.tl.custom.message.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 <telethon.client.messages.MessageMethods.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:
.. code-block:: python
@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
<telethon.tl.custom.message.Message.sender>`) don't need an ``await``, but
methods (`message.get_sender
<telethon.tl.custom.message.Message.get_sender>`) **do** need an ``await``,
and you should use methods in events for these properties that may need network.
Events without decorators
*************************

View File

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

View File

@ -19,9 +19,9 @@ not *interact* with a voting message), by making use of the
from telethon.tl.functions.messages import GetInlineBotResultsRequest
bot_results = client(GetInlineBotResultsRequest(
bot_results = loop.run_until_complete(client(GetInlineBotResultsRequest(
bot, user_or_chat, 'query', ''
))
)))
And you can select any of their results by using
:tl:`SendInlineBotResultRequest`:
@ -30,16 +30,27 @@ And you can select any of their results by using
from telethon.tl.functions.messages import SendInlineBotResultRequest
client(SendInlineBotResultRequest(
loop.run_until_complete(client(SendInlineBotResultRequest(
get_input_peer(user_or_chat),
obtained_query_id,
obtained_str_id
))
)))
Talking to Bots with special reply markup
*****************************************
Generally, you just use the `message.click()
<telethon.tl.custom.message.Message.click>` method:
.. code-block:: python
async def main():
messages = await client.get_messages('somebot')
await messages[0].click(0)
You can also do it manually.
To interact with a message that has a special reply markup, such as
`@VoteBot`__ polls, you would use :tl:`GetBotCallbackAnswerRequest`:
@ -47,11 +58,11 @@ To interact with a message that has a special reply markup, such as
from telethon.tl.functions.messages import GetBotCallbackAnswerRequest
client(GetBotCallbackAnswerRequest(
loop.run_until_complete(client(GetBotCallbackAnswerRequest(
user_or_chat,
msg.id,
data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data
))
)))
It's a bit verbose, but it has all the information you would need to
show it visually (button rows, and buttons within each row, each with

View File

@ -12,7 +12,7 @@ Joining a chat or channel
*************************
Note that :tl:`Chat` are normal groups, and :tl:`Channel` are a
special form of ``Chat``, which can also be super-groups if
special form of :tl:`Chat`, which can also be super-groups if
their ``megagroup`` member is ``True``.
@ -25,11 +25,11 @@ to, you can make use of the :tl:`JoinChannelRequest` to join such channel:
.. code-block:: python
from telethon.tl.functions.channels import JoinChannelRequest
client(JoinChannelRequest(channel))
loop.run_until_complete(client(JoinChannelRequest(channel)))
# In the same way, you can also leave such channel
from telethon.tl.functions.channels import LeaveChannelRequest
client(LeaveChannelRequest(input_channel))
loop.run_until_complete(client(LeaveChannelRequest(input_channel)))
For more on channels, check the `channels namespace`__.
@ -51,7 +51,9 @@ example, is the ``hash`` of the chat or channel. Now you can use
.. code-block:: python
from telethon.tl.functions.messages import ImportChatInviteRequest
updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg'))
updates = loop.run_until_complete(
client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg'))
)
Adding someone else to such chat or channel
@ -68,19 +70,19 @@ use is very straightforward, or :tl:`InviteToChannelRequest` for channels:
# Note that ``user_to_add`` is NOT the name of the parameter.
# It's the user you want to add (``user_id=user_to_add``).
client(AddChatUserRequest(
loop.run_until_complete(client(AddChatUserRequest(
chat_id,
user_to_add,
fwd_limit=10 # Allow the user to see the 10 last messages
))
)))
# For channels (which includes megagroups)
from telethon.tl.functions.channels import InviteToChannelRequest
client(InviteToChannelRequest(
loop.run_until_complete(client(InviteToChannelRequest(
channel,
[users_to_add]
))
)))
Checking a link without joining
@ -102,6 +104,14 @@ Retrieving all chat members (channels too)
This method will handle different chat types for you automatically.
Here is the easy way to do it:
.. code-block:: python
participants = loop.run_until_complete(client.get_participants(group))
Now we will show how the method works internally.
In order to get all the members from a mega-group or channel, you need
to use :tl:`GetParticipantsRequest`. As we can see it needs an
:tl:`InputChannel`, (passing the mega-group or channel you're going to
@ -123,10 +133,9 @@ a fixed limit:
all_participants = []
while True:
participants = client(GetParticipantsRequest(
channel, ChannelParticipantsSearch(''), offset, limit,
hash=0
))
participants = loop.run_until_complete(client(GetParticipantsRequest(
channel, ChannelParticipantsSearch(''), offset, limit, hash=0
)))
if not participants.users:
break
all_participants.extend(participants.users)
@ -193,7 +202,7 @@ Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest`
# )
# Once you have a ChannelAdminRights, invoke it
client(EditAdminRequest(channel, user, rights))
loop.run_until_complete(client(EditAdminRequest(channel, user, rights)))
# User will now be able to change group info, delete other people's
# messages and pin messages.
@ -252,7 +261,7 @@ banned rights of an user through :tl:`EditBannedRequest` and its parameter
embed_links=True
)
client(EditBannedRequest(channel, user, rights))
loop.run_until_complete(client(EditBannedRequest(channel, user, rights)))
Kicking a member
@ -267,9 +276,11 @@ is enough:
from telethon.tl.functions.channels import EditBannedRequest
from telethon.tl.types import ChannelBannedRights
client(EditBannedRequest(channel, user, ChannelBannedRights(
until_date=None,
view_messages=True
loop.run_until_complete(client(EditBannedRequest(
channel, user, ChannelBannedRights(
until_date=None,
view_messages=True
)
)))
@ -291,11 +302,11 @@ use :tl:`GetMessagesViewsRequest`, setting ``increment=True``:
# Obtain `channel' through dialogs or through client.get_entity() or anyhow.
# Obtain `msg_ids' through `.get_messages()` or anyhow. Must be a list.
client(GetMessagesViewsRequest(
loop.run_until_complete(client(GetMessagesViewsRequest(
peer=channel,
id=msg_ids,
increment=True
))
)))
Note that you can only do this **once or twice a day** per account,

View File

@ -19,11 +19,12 @@ you should use :tl:`GetFullUser`:
from telethon.tl.functions.users import GetFullUserRequest
full = client(GetFullUserRequest(user))
# or even
full = client(GetFullUserRequest('username'))
async def main():
full = await client(GetFullUserRequest(user))
# or even
full = await client(GetFullUserRequest('username'))
bio = full.about
bio = full.about
See :tl:`UserFull` to know what other fields you can access.
@ -39,7 +40,9 @@ request. Omitted fields won't change after invoking :tl:`UpdateProfile`:
from telethon.tl.functions.account import UpdateProfileRequest
client(UpdateProfileRequest(about='This is a test from Telethon'))
loop.run_until_complete(client(UpdateProfileRequest(a
bout='This is a test from Telethon'
)))
Updating your username
@ -51,7 +54,7 @@ You need to use :tl:`account.UpdateUsername`:
from telethon.tl.functions.account import UpdateUsernameRequest
client(UpdateUsernameRequest('new_username'))
loop.run_until_complete(client(UpdateUsernameRequest('new_username')))
Updating your profile photo
@ -65,6 +68,6 @@ through :tl:`UploadProfilePhoto`:
from telethon.tl.functions.photos import UploadProfilePhotoRequest
client(UploadProfilePhotoRequest(
loop.run_until_complete(client(UploadProfilePhotoRequest(
client.upload_file('/path/to/some/file')
))
)))

View File

@ -13,38 +13,39 @@ Forwarding messages
.. note::
Use the `telethon.telegram_client.TelegramClient.forward_messages`
Use the `telethon.client.messages.MessageMethods.forward_messages`
friendly method instead unless you have a better reason not to!
This method automatically accepts either a single message or many of them.
.. code-block:: python
# If you only have the message IDs
client.forward_messages(
entity, # to which entity you are forwarding the messages
message_ids, # the IDs of the messages (or message) to forward
from_entity # who sent the messages?
)
async def main():
# If you only have the message IDs
await client.forward_messages(
entity, # to which entity you are forwarding the messages
message_ids, # the IDs of the messages (or message) to forward
from_entity # who sent the messages?
)
# If you have ``Message`` objects
client.forward_messages(
entity, # to which entity you are forwarding the messages
messages # the messages (or message) to forward
)
# If you have ``Message`` objects
await client.forward_messages(
entity, # to which entity you are forwarding the messages
messages # the messages (or message) to forward
)
# You can also do it manually if you prefer
from telethon.tl.functions.messages import ForwardMessagesRequest
# You can also do it manually if you prefer
from telethon.tl.functions.messages import ForwardMessagesRequest
messages = foo() # retrieve a few messages (or even one, in a list)
from_entity = bar()
to_entity = baz()
messages = foo() # retrieve a few messages (or even one, in a list)
from_entity = bar()
to_entity = baz()
client(ForwardMessagesRequest(
from_peer=from_entity, # who sent these messages?
id=[msg.id for msg in messages], # which are the messages?
to_peer=to_entity # who are we forwarding them to?
))
await client(ForwardMessagesRequest(
from_peer=from_entity, # who sent these messages?
id=[msg.id for msg in messages], # which are the messages?
to_peer=to_entity # who are we forwarding them to?
))
The named arguments are there for clarity, although they're not needed because
they appear in order. You can obviously just wrap a single message on the list
@ -56,7 +57,7 @@ Searching Messages
.. note::
Use the `telethon.telegram_client.TelegramClient.iter_messages`
Use the `telethon.client.messages.MessageMethods.iter_messages`
friendly method instead unless you have a better reason not to!
This method has ``search`` and ``filter`` parameters that will
@ -71,7 +72,7 @@ into issues_. A valid example would be:
from telethon.tl.types import InputMessagesFilterEmpty
filter = InputMessagesFilterEmpty()
result = client(SearchRequest(
result = loop.run_until_complete(client(SearchRequest(
peer=peer, # On which chat/conversation
q='query', # What to search for
filter=filter, # Filter to use (maybe filter for media)
@ -84,7 +85,7 @@ into issues_. A valid example would be:
min_id=0, # Minimum message ID
from_id=None, # Who must have sent the message (peer)
hash=0 # Special number to return nothing on no-change
))
)))
It's important to note that the optional parameter ``from_id`` could have
been omitted (defaulting to ``None``). Changing it to :tl:`InputUserEmpty`, as one
@ -99,7 +100,7 @@ you tried setting the ``from_id`` filter, and as the error says, you can't
do that. Leave it set to ``None`` and it should work.
As with every method, make sure you use the right ID/hash combination for
your ``InputUser`` or ``InputChat``, or you'll likely run into errors like
your :tl:`InputUser` or :tl:`InputChat`, or you'll likely run into errors like
``UserIdInvalidError``.
@ -115,29 +116,25 @@ send yourself the very first sticker you have:
.. code-block:: python
# Get all the sticker sets this user has
sticker_sets = client(GetAllStickersRequest(0))
async def main():
# Get all the sticker sets this user has
from telethon.tl.functions.messages import GetAllStickersRequest
sticker_sets = await client(GetAllStickersRequest(0))
# Choose a sticker set
sticker_set = sticker_sets.sets[0]
# Choose a sticker set
from telethon.tl.functions.messages import GetStickerSetRequest
from telethon.tl.types import InputStickerSetID
sticker_set = sticker_sets.sets[0]
# Get the stickers for this sticker set
stickers = client(GetStickerSetRequest(
stickerset=InputStickerSetID(
id=sticker_set.id, access_hash=sticker_set.access_hash
)
))
# Stickers are nothing more than files, so send that
client(SendMediaRequest(
peer=client.get_me(),
media=InputMediaDocument(
id=InputDocument(
id=stickers.documents[0].id,
access_hash=stickers.documents[0].access_hash
# Get the stickers for this sticker set
stickers = await client(GetStickerSetRequest(
stickerset=InputStickerSetID(
id=sticker_set.id, access_hash=sticker_set.access_hash
)
)
))
))
# Stickers are nothing more than files, so send that
await client.send_file('me', stickers.documents[0])
.. _issues: https://github.com/LonamiWebs/Telethon/issues/215

View File

@ -59,6 +59,11 @@ their methods.
:undoc-members:
:show-inheritance:
.. automodule:: telethon.client.downloads
:members:
:undoc-members:
:show-inheritance:
.. automodule:: telethon.client.uploads
:members:
:undoc-members:

View File

@ -22,7 +22,7 @@ FILE_PART_X_MISSING=Part {} of the file is missing from storage
FILE_PART_INVALID=The file part number is invalid
FIRSTNAME_INVALID=The first name is invalid
INPUT_METHOD_INVALID=The invoked method does not exist anymore or has never existed
INPUT_REQUEST_TOO_LONG=The input request was too long. This may be a bug in the library as it can occur when serializing more bytes than it should (likeappending the vector constructor code at the end of a message)
INPUT_REQUEST_TOO_LONG=The input request was too long. This may be a bug in the library as it can occur when serializing more bytes than it should (like appending the vector constructor code at the end of a message)
LASTNAME_INVALID=The last name is invalid
LIMIT_INVALID=An invalid limit was provided. See https://core.telegram.org/api/files#downloading-files
LOCATION_INVALID=The location given for a file was invalid. See https://core.telegram.org/api/files#downloading-files