Rewrite the first part of the docs for asyncio

This commit is contained in:
Lonami Exo 2018-06-21 21:54:54 +02:00
parent 5e322a6ca9
commit f733f8e565
11 changed files with 389 additions and 302 deletions

View File

@ -4,8 +4,11 @@ Telethon
⭐️ Thanks **everyone** who has starred the project, it means a lot! ⭐️ Thanks **everyone** who has starred the project, it means a lot!
**Telethon** is Telegram client implementation in **Python 3** which uses **Telethon** is an `asyncio <https://docs.python.org/3/library/asyncio.html>`_
the latest available API of Telegram. **Python 3** library to interact with Telegram's API.
If you don't like ``asyncio``, you can still use `a simpler version
<https://github.com/LonamiWebs/Telethon/tree/sync>`_ that uses threads instead.
What is this? What is this?
@ -22,7 +25,7 @@ Installing
.. code:: sh .. code:: sh
pip3 install telethon pip3 install telethon-aio
Creating a client Creating a client
@ -30,15 +33,18 @@ Creating a client
.. code:: python .. code:: python
from telethon import TelegramClient import asyncio
loop = asyncio.get_event_loop()
# These example values won't work. You must get your own api_id and from telethon import TelegramClient
# api_hash from https://my.telegram.org, under API Development.
api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef'
client = TelegramClient('session_name', api_id, api_hash) # These example values won't work. You must get your own api_id and
client.start() # api_hash from https://my.telegram.org, under API Development.
api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef'
client = TelegramClient('session_name', api_id, api_hash)
loop.run_until_complete(client.start())
Doing stuff Doing stuff
@ -46,20 +52,23 @@ Doing stuff
.. code:: python .. code:: python
print(client.get_me().stringify()) async def main():
me = await client.get_me()
print(me.stringify())
client.send_message('username', 'Hello! Talking to you from Telethon') await client.send_message('username', 'Hello! Talking to you from Telethon')
client.send_file('username', '/home/myself/Pictures/holidays.jpg') await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
client.download_profile_photo('me') await client.download_profile_photo('me')
messages = client.get_messages('username') messages = await client.get_messages('username')
client.download_media(messages[0]) await messages[0].download_media()
loop.run_until_complete(main())
Next steps Next steps
---------- ----------
Do you like how Telethon looks? Check out Do you like how Telethon looks? Check out `Read The Docs
`Read The Docs <http://telethon.rtfd.io/>`_ <http://telethon.rtfd.io/>`_ for a more in-depth explanation,
for a more in-depth explanation, with examples, with examples, troubleshooting issues, and more useful information.
troubleshooting issues, and more useful information.

View File

@ -69,7 +69,9 @@ Or we call `client.get_input_entity
.. code-block:: python .. code-block:: python
peer = client.get_input_entity('someone') import asyncio
loop = asyncio.get_event_loop()
peer = loop.run_until_complete(client.get_input_entity('someone'))
When you're going to invoke an API method, most require you to pass an When you're going to invoke an API method, most require you to pass an
:tl:`InputUser`, :tl:`InputChat`, or so on, this is why using :tl:`InputUser`, :tl:`InputChat`, or so on, this is why using
@ -81,7 +83,7 @@ instead:
.. code-block:: python .. code-block:: python
entity = client.get_entity('someone') entity = loop.run_until_complete(client.get_entity('someone'))
In the later case, when you use the entity, the library will cast it to In the later case, when you use the entity, the library will cast it to
its "input" version for you. If you already have the complete user and its "input" version for you. If you already have the complete user and
@ -110,7 +112,9 @@ request we do:
.. code-block:: python .. code-block:: python
result = client(SendMessageRequest(peer, 'Hello there!')) result = loop.run_until_complete(
client(SendMessageRequest(peer, 'Hello there!'))
)
# __call__ is an alias for client.invoke(request). Both will work # __call__ is an alias for client.invoke(request). Both will work
Message sent! Of course, this is only an example. There are nearly 250 Message sent! Of course, this is only an example. There are nearly 250
@ -119,19 +123,21 @@ as you wish. Remember to use the right types! To sum up:
.. code-block:: python .. code-block:: python
result = client(SendMessageRequest( result = loop.run_until_complete(client(SendMessageRequest(
client.get_input_entity('username'), 'Hello there!' client.get_input_entity('username'), 'Hello there!'
)) )))
This can further be simplified to: This can further be simplified to:
.. code-block:: python .. code-block:: python
result = client(SendMessageRequest('username', 'Hello there!')) async def main():
# Or even result = await client(SendMessageRequest('username', 'Hello there!'))
result = client(SendMessageRequest(PeerChannel(id), 'Hello there!')) # Or even
result = await client(SendMessageRequest(PeerChannel(id), 'Hello there!'))
loop.run_until_complete(main())
.. note:: .. note::

View File

@ -4,141 +4,46 @@
Update Modes Update Modes
============ ============
With ``asyncio``, the library has several tasks running in the background.
One task is used for sending requests, another task is used to receive them,
and a third one is used to handle updates.
The library can run in four distinguishable modes: To handle updates, you must keep your script running. You can do this in
several ways. For instance, if you are *not* running ``asyncio``'s event
loop, you should use `client.run_until_disconnected
<telethon.client.updates.UpdateMethods.run_until_disconnected>`:
- With no extra threads at all. .. code-block:: python
- With an extra thread that receives everything as soon as possible (default).
- With several worker threads that run your update handlers.
- A mix of the above.
Since this section is about updates, we'll describe the simplest way to import asyncio
work with them. from telethon import TelegramClient
client = TelegramClient(...)
...
client.run_until_disconnected()
Using multiple workers 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:
When you create your client, simply pass a number to the
``update_workers`` parameter:
``client = TelegramClient('session', api_id, api_hash, update_workers=2)``
You can set any amount of workers you want. The more you put, the more
update handlers that can be called "at the same time". One or two should
suffice most of the time, since setting more will not make things run
faster most of the times (actually, it could slow things down).
The next thing you want to do is to add a method that will be called when
an `Update`__ arrives:
.. code-block:: python
def callback(update):
print('I received', update)
client.add_event_handler(callback)
# do more work here, or simply sleep!
That's it! This is the old way to listen for raw updates, with no further
processing. If this feels annoying for you, remember that you can always
use :ref:`working-with-updates` but maybe use this for some other cases.
Now let's do something more interesting. Every time an user talks to us,
let's reply to them with the same text reversed:
.. code-block:: python
from telethon.tl.types import UpdateShortMessage, PeerUser
def replier(update):
if isinstance(update, UpdateShortMessage) and not update.out:
client.send_message(PeerUser(update.user_id), update.message[::-1])
client.add_event_handler(replier) .. code-block:: python
input('Press enter to stop this!')
client.disconnect()
We only ask you one thing: don't keep this running for too long, or your import asyncio
contacts will go mad. from telethon import TelegramClient
client = TelegramClient(...)
async def main():
await client.disconnected
asyncio.get_event_loop().run_until_complete(main())
Spawning no worker at all You could also run `client.disconnected
************************* <telethon.client.telegrambaseclient.disconnected>` until it completed.
All the workers do is loop forever and poll updates from a queue that is But if you don't want to ``await``, then you should know what you want
filled from the ``ReadThread``, responsible for reading every item off to be doing instead! What matters is that you shouldn't let your script
the network. If you only need a worker and the ``MainThread`` would be die. If you don't care about updates, you don't need any of this.
doing no other job, this is the preferred way. You can easily do the same
as the workers like so:
.. code-block:: python
while True:
try:
update = client.updates.poll()
if not update:
continue
print('I received', update)
except KeyboardInterrupt:
break
client.disconnect()
Note that ``poll`` accepts a ``timeout=`` parameter, and it will return
``None`` if other thread got the update before you could or if the timeout
expired, so it's important to check ``if not update``.
This can coexist with the rest of ``N`` workers, or you can set it to ``0``
additional workers:
``client = TelegramClient('session', api_id, api_hash, update_workers=0)``
You **must** set it to ``0`` (or higher), as it defaults to ``None`` and that
has a different meaning. ``None`` workers means updates won't be processed
*at all*, so you must set it to some integer value if you want
``client.updates.poll()`` to work.
Using the main thread instead the ``ReadThread``
************************************************
If you have no work to do on the ``MainThread`` and you were planning to have
a ``while True: sleep(1)``, don't do that. Instead, don't spawn the secondary
``ReadThread`` at all like so:
.. code-block:: python
client = TelegramClient(
...
spawn_read_thread=False
)
And then ``.idle()`` from the ``MainThread``:
``client.idle()``
You can stop it with :kbd:`Control+C`, and you can configure the signals
to be used in a similar fashion to `Python Telegram Bot`__.
As a complete example:
.. code-block:: python
def callback(update):
print('I received', update)
client = TelegramClient('session', api_id, api_hash,
update_workers=1, spawn_read_thread=False)
client.connect()
client.add_event_handler(callback)
client.idle() # ends with Ctrl+C
This is the preferred way to use if you're simply going to listen for updates.
__ https://lonamiwebs.github.io/Telethon/types/update.html
__ https://github.com/python-telegram-bot/python-telegram-bot/blob/4b3315db6feebafb94edcaa803df52bb49999ced/telegram/ext/updater.py#L460

View File

@ -0,0 +1,123 @@
.. _asyncio-crash-course:
===========================
A Crash Course into asyncio
===========================
Why asyncio?
************
Python's `asyncio <https://docs.python.org/3/library/asyncio.html>`_ is the
standard way to run asynchronous code from within Python. Since Python 3.5,
using ``async def`` and ``await`` became possible, and Python 3.6 further
improves what you can do with asynchronous code, although it's not the only
way (other projects like `Trio <https://github.com/python-trio>`_ also exist).
Telegram is a service where all API calls are executed in an asynchronous
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.
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
right and can cause issues. It also enables to use the powerful ``asyncio``
system such as futures, timeouts, cancellation, etc. in a natural way.
If you're still not convinced or you're just not ready for using ``asyncio``,
the library offers a synchronous interface without the need for all the
``async`` and ``await`` you would otherwise see. `Follow this link
<https://github.com/LonamiWebs/Telethon/tree/sync>`_ to find out more.
How do I get started?
*********************
To get started with ``asyncio``, all you need is to setup your main
``async def`` like so:
.. code-block:: python
import asyncio
async def main():
pass # Your code goes here
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(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:
.. code-block:: python
async def main():
client = TelegramClient(...)
# client.start() is a coroutine (async def), it needs an await
await client.start()
# Sending a message also interacts with the API, and needs an await
await client.send_message('me', 'Hello myself!')
If you don't know anything else about ``asyncio``, this will be enough
to get you started. Once you're ready to learn more about it, you will
be able to use that power and everything you've learnt with Telethon.
Just remember that if you use ``await``, you need to be inside of an
``async def``.
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>`
which is a property that you can wait on until you call
`await client.disconnect() <telethon.client.telegrambaseclient.disconnect>`:
.. code-block:: python
client = TelegramClient(...)
@client.on(events.NewMessage)
async def handler(event):
print(event)
async def main():
await client.start()
await client.disconnected
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
This is the same as using the ``run_until_disconnected()`` method:
.. code-block:: python
client = TelegramClient(...)
@client.on(events.NewMessage)
async def handler(event):
print(event)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(client.start())
client.run_until_disconnected()
More resources to learn asyncio
*******************************
If you would like to learn a bit more about why ``asyncio`` is something
you should learn, `check out my blog post
<https://lonamiwebs.github.io/blog/asyncio/>`_ that goes into more detail.

View File

@ -26,6 +26,14 @@ one is very simple:
.. code-block:: python .. code-block:: python
import asyncio
loop = asyncio.get_event_loop()
# Rename loop.run_until_complete(...) as rc(...), we will use it a lot.
# This basically lets us run the event loop (necessary in asyncio) to
# execute all the requests we need.
rc = loop.run_until_complete
from telethon import TelegramClient from telethon import TelegramClient
# Use your own values here # Use your own values here
@ -54,7 +62,7 @@ your disk. This is by default a database file using Python's ``sqlite3``.
.. code-block:: python .. code-block:: python
client.start() rc(client.start())
This is explained after going through the manual process. This is explained after going through the manual process.
@ -64,14 +72,14 @@ Doing so is very easy:
.. code-block:: python .. code-block:: python
client.connect() # Must return True, otherwise, try again rc(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:
.. code-block:: python .. code-block:: python
client.is_user_authorized() # Returns True if you can send requests rc(client.is_user_authorized()) # Returns True if you can send requests
If you're not authorized, you need to `.sign_in If you're not authorized, you need to `.sign_in
<telethon.client.auth.AuthMethods.sign_in>`: <telethon.client.auth.AuthMethods.sign_in>`:
@ -79,8 +87,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) rc(client.send_code_request(phone_number))
myself = client.sign_in(phone_number, input('Enter code: ')) myself = rc(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.
@ -103,10 +111,14 @@ 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()
if not client.is_user_authorized(): async def main():
client.send_code_request(phone_number) assert await client.connect()
me = client.sign_in(phone_number, input('Enter code: ')) if not await client.is_user_authorized():
await client.send_code_request(phone_number)
me = await client.sign_in(phone_number, input('Enter code: '))
loop.run_until_complete(main())
All of this, however, can be done through a call to `.start() All of this, however, can be done through a call to `.start()
@ -115,7 +127,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() loop.run_until_complete(client.start())
The code shown is just what `.start() The code shown is just what `.start()
@ -169,11 +181,12 @@ again with a ``password=``:
import getpass import getpass
from telethon.errors import SessionPasswordNeededError from telethon.errors import SessionPasswordNeededError
client.sign_in(phone) async def main():
try: await client.sign_in(phone)
client.sign_in(code=input('Enter code: ')) try:
except SessionPasswordNeededError: await client.sign_in(code=input('Enter code: '))
client.sign_in(password=getpass.getpass()) except SessionPasswordNeededError:
await client.sign_in(password=getpass.getpass())
The mentioned `.start() The mentioned `.start()
@ -196,32 +209,33 @@ See the examples below:
from telethon.errors import EmailUnconfirmedError from telethon.errors import EmailUnconfirmedError
# Sets 2FA password for first time: async def main():
client.edit_2fa(new_password='supersecurepassword') # Sets 2FA password for first time:
await client.edit_2fa(new_password='supersecurepassword')
# Changes password: # Changes password:
client.edit_2fa(current_password='supersecurepassword', await client.edit_2fa(current_password='supersecurepassword',
new_password='changedmymind') new_password='changedmymind')
# Clears current password (i.e. removes 2FA): # Clears current password (i.e. removes 2FA):
client.edit_2fa(current_password='changedmymind', new_password=None) await client.edit_2fa(current_password='changedmymind', new_password=None)
# Sets new password with recovery email: # Sets new password with recovery email:
try: try:
client.edit_2fa(new_password='memes and dreams', await client.edit_2fa(new_password='memes and dreams',
email='JohnSmith@example.com') email='JohnSmith@example.com')
# Raises error (you need to check your email to complete 2FA setup.) # Raises error (you need to check your email to complete 2FA setup.)
except EmailUnconfirmedError: except EmailUnconfirmedError:
# You can put email checking code here if desired. # You can put email checking code here if desired.
pass pass
# Also take note that unless you remove 2FA or explicitly # Also take note that unless you remove 2FA or explicitly
# give email parameter again it will keep the last used setting # give email parameter again it will keep the last used setting
# Set hint after already setting password: # Set hint after already setting password:
client.edit_2fa(current_password='memes and dreams', await client.edit_2fa(current_password='memes and dreams',
new_password='memes and dreams', new_password='memes and dreams',
hint='It keeps you alive') hint='It keeps you alive')
__ https://github.com/Anorov/PySocks#installation __ https://github.com/Anorov/PySocks#installation
__ https://github.com/Anorov/PySocks#usage-1 __ https://github.com/Anorov/PySocks#usage-1

View File

@ -40,33 +40,34 @@ Through the use of the :ref:`sessions`, the library will automatically
remember the ID and hash pair, along with some extra information, so remember the ID and hash pair, along with some extra information, so
you're able to just do this: you're able to just do this:
.. code-block:: python .. code-block:: python
async def main():
# 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() dialogs = await client.get_dialogs()
# 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)
# Getting entities through their ID (User, Chat or Channel) # Getting entities through their ID (User, Chat or Channel)
entity = client.get_entity(some_id) entity = await client.get_entity(some_id)
# You can be more explicit about the type for said ID by wrapping # You can be more explicit about the type for said ID by wrapping
# it inside a Peer instance. This is recommended but not necessary. # it inside a Peer instance. This is recommended but not necessary.
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))
All methods in the :ref:`telegram-client` call `.get_input_entity() All methods in the :ref:`telegram-client` call `.get_input_entity()
@ -134,9 +135,10 @@ library, the raw requests you make to the API are also able to call
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>` `client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
wherever needed, so you can even do things like: wherever needed, so you can even do things like:
.. code-block:: python .. code-block:: python
client(SendMessageRequest('username', 'hello')) async def main():
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

@ -21,6 +21,9 @@ Creating a client
.. code-block:: python .. code-block:: python
import asyncio
loop = asyncio.get_event_loop()
from telethon import TelegramClient from telethon import TelegramClient
# These example values won't work. You must get your own api_id and # These example values won't work. You must get your own api_id and
@ -29,7 +32,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() loop.run_until_complete(client.start())
**More details**: :ref:`creating-a-client` **More details**: :ref:`creating-a-client`
@ -39,31 +42,36 @@ Basic Usage
.. code-block:: python .. code-block:: python
# Getting information about yourself async def main():
print(client.get_me().stringify()) # Getting information about yourself
me = await client.get_me()
print(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.iter_messages('username', limit=10): async for message in client.iter_messages('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.text) print(dialog.name, dialog.draft.text)
# 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_messages('username') # or even using message.download_media():
client.download_media(messages[0]) messages = await client.get_messages('username')
await messages[0].download_media()
loop.run_until_complete(main())
**More details**: :ref:`telegram-client` **More details**: :ref:`telegram-client`
@ -77,15 +85,11 @@ Handling Updates
from telethon import events from telethon import events
# We need to have some worker running
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. client.run_until_disconnected()
input('Press enter to exit.')
**More details**: :ref:`working-with-updates` **More details**: :ref:`working-with-updates`

View File

@ -34,7 +34,14 @@ For instance, retrieving your own user can be done in a single line:
.. code-block:: python .. code-block:: python
myself = client.get_me() import asyncio
async def main():
myself = await client.get_me()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Internally, this method has sent a request to Telegram, who replied with Internally, this method has sent a request to Telegram, who replied with
the information about your own user, and then the desired information the information about your own user, and then the desired information
@ -46,10 +53,11 @@ how the library refers to either of these:
.. code-block:: python .. code-block:: python
# The method will infer that you've passed an username async def main():
# It also accepts phone numbers, and will get the user # The method will infer that you've passed an username
# from your contact list. # It also accepts phone numbers, and will get the user
lonami = client.get_entity('lonami') # from your contact list.
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
@ -59,30 +67,31 @@ 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 async def main():
client.send_message('username', 'Hello World from Telethon!') # Note that you can use 'me' or 'self' to message yourself
await client.send_message('username', 'Hello World from Telethon!')
# .send_message's parse mode defaults to markdown, so you # .send_message's parse mode defaults to markdown, so you
# can use **bold**, __italics__, [links](https://example.com), `code`, # can use **bold**, __italics__, [links](https://example.com), `code`,
# and even [mentions](@username)/[mentions](tg://user?id=123456789) # and even [mentions](@username)/[mentions](tg://user?id=123456789)
client.send_message('username', '**Using** __markdown__ `too`!') await client.send_message('username', '**Using** __markdown__ `too`!')
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.iter_messages('username', limit=10): async for message in client.iter_messages('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.text) print(dialog.name, dialog.draft.text)
# 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() await client.disconnect()
Remember that you can call ``.stringify()`` to any object Telegram returns Remember that you can call ``.stringify()`` to any object Telegram returns
to pretty print it. Calling ``str(result)`` does the same operation, but on to pretty print it. Calling ``str(result)`` does the same operation, but on

View File

@ -34,37 +34,38 @@ let's dive in!
Getting Started Getting Started
*************** ***************
.. code-block:: python .. code-block:: python
from telethon import TelegramClient, events import asyncio
from telethon import TelegramClient, events
client = TelegramClient(..., update_workers=1, spawn_read_thread=False) client = TelegramClient('name', api_id, api_hash)
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_until_complete(client.start())
client.run_until_disconnected()
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?
.. code-block:: python .. code-block:: python
from telethon import TelegramClient, events import asyncio
from telethon import TelegramClient, events
client = TelegramClient(..., update_workers=1, spawn_read_thread=False) client = TelegramClient('name', api_id, api_hash)
client.start()
This is normal initialization (of course, pass session name, API ID and hash). This is normal creation (of course, pass session name, API ID and hash).
Nothing we don't know already. Nothing we don't know already.
.. code-block:: python .. code-block:: python
@client.on(events.NewMessage) @client.on(events.NewMessage)
This Python decorator will attach itself to the ``my_event_handler`` This Python decorator will attach itself to the ``my_event_handler``
@ -72,11 +73,11 @@ definition, and basically means that *on* a `NewMessage
<telethon.events.newmessage.NewMessage>` *event*, <telethon.events.newmessage.NewMessage>` *event*,
the callback function you're about to define will be called: 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 If a `NewMessage
@ -84,14 +85,16 @@ If a `NewMessage
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`` to the event
with a ``'hi!'`` message. with a ``'hi!'`` message.
.. code-block:: python .. code-block:: python
client.idle() asyncio.get_event_loop().run_until_complete(client.start())
client.run_until_disconnected()
Finally, this tells the client that we're done with our code, and want Finally, this tells the client that we're done with our code. We run the
to listen for all these events to occur. Of course, you might want to ``asyncio`` loop until the client starts, and then we run it again until
do other things instead idling. For this refer to :ref:`update-modes`. we are disconnected. Of course, you can do other things instead of running
until disconnected. For this refer to :ref:`update-modes`.
More on events More on events
@ -130,17 +133,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): pattern='eval (.+)'))
if event.raw_text.startswith('eval'): async def admin_handler(event):
expression = event.raw_text.replace('eval', '').strip() expression = event.pattern_match.group(1)
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),
@ -178,23 +181,23 @@ 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 it is possible to raise a `telethon.events.StopPropagation` exception which
will cause the propagation of the update through your handlers to stop: will cause the propagation of the update through your handlers to stop:
.. code-block:: python .. code-block:: python
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
Remember to check :ref:`telethon-events-package` if you're looking for Remember to check :ref:`telethon-events-package` if you're looking for

View File

@ -22,6 +22,17 @@ when you upgrade!
contains the friendly methods that **you should use** most of the time. contains the friendly methods that **you should use** most of the time.
.. note::
The library uses `asyncio <https://docs.python.org/3/library/asyncio.html>`_
by default, but you if you don't know how to use ``asyncio`` you can use
`a simpler version <https://github.com/LonamiWebs/Telethon/tree/sync>`_
(select the "sync" version in ``readthedocs``' bottom left corner).
However, **you are encouraged to use asyncio**, it will make your scripts
faster and more powerful. :ref:`asyncio-crash-course` will teach you why
``asyncio`` is good and how to use it.
What is this? What is this?
************* *************
@ -39,6 +50,7 @@ heavy job for you, so you can focus on developing an application.
extra/basic/getting-started extra/basic/getting-started
extra/basic/installation extra/basic/installation
extra/basic/asyncio-crash-course
extra/basic/creating-a-client extra/basic/creating-a-client
extra/basic/telegram-client extra/basic/telegram-client
extra/basic/entities extra/basic/entities

View File

@ -174,8 +174,8 @@ class NewMessage(EventBuilder):
>>> from telethon import TelegramClient, events >>> from telethon import TelegramClient, events
>>> client = TelegramClient(...) >>> client = TelegramClient(...)
>>> >>>
>>> @client.on(events.NewMessage(pattern=r'hi (\w+)!')) >>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!'))
... def handler(event): ... async def handler(event):
... # In this case, the result is a ``Match`` object ... # In this case, the result is a ``Match`` object
... # since the ``str`` pattern was converted into ... # since the ``str`` pattern was converted into
... # the ``re.compile(pattern).match`` function. ... # the ``re.compile(pattern).match`` function.