mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-11-11 19:40:36 +00:00
Completely overhaul the documentation
This commit is contained in:
362
readthedocs/concepts/asyncio.rst
Normal file
362
readthedocs/concepts/asyncio.rst
Normal file
@@ -0,0 +1,362 @@
|
||||
.. _mastering-asyncio:
|
||||
|
||||
=================
|
||||
Mastering asyncio
|
||||
=================
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
What's asyncio?
|
||||
===============
|
||||
|
||||
asyncio_ is a Python 3's built-in library. This means it's already installed if
|
||||
you have Python 3. Since Python 3.5, it is convenient to work with asynchronous
|
||||
code. Before (Python 3.4) we didn't have ``async`` or ``await``, but now we do.
|
||||
|
||||
asyncio_ stands for *Asynchronous Input Output*. This is a very powerful
|
||||
concept to use whenever you work IO. Interacting with the web or external
|
||||
APIs such as Telegram's makes a lot of sense this way.
|
||||
|
||||
|
||||
Why asyncio?
|
||||
============
|
||||
|
||||
Asynchronous IO makes a lot of sense in a library like Telethon.
|
||||
You send a request to the server (such as "get some message"), and
|
||||
thanks to asyncio_, your code won't block while a response arrives.
|
||||
|
||||
The alternative would be to spawn a thread for each update so that
|
||||
other code can run while the response arrives. That is *a lot* more
|
||||
expensive.
|
||||
|
||||
The code will also run faster, because instead of switching back and
|
||||
forth between the OS and your script, your script can handle it all.
|
||||
Avoiding switching saves quite a bit of time, in Python or any other
|
||||
language that supports asynchronous IO. It will also be cheaper,
|
||||
because tasks are smaller than threads, which are smaller than processes.
|
||||
|
||||
|
||||
What are asyncio basics?
|
||||
========================
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# First we need the asyncio library
|
||||
import asyncio
|
||||
|
||||
# Then we need a loop to work with
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# We also need something to run
|
||||
async def main():
|
||||
for char in 'Hello, world!\n':
|
||||
print(char, end='', flush=True)
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
# Then, we need to run the loop with a task
|
||||
loop.run_until_complete(main())
|
||||
|
||||
|
||||
What does telethon.sync do?
|
||||
===========================
|
||||
|
||||
The moment you import any of these:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import sync, ...
|
||||
# or
|
||||
from telethon.sync import ...
|
||||
# or
|
||||
import telethon.sync
|
||||
|
||||
The ``sync`` module rewrites most ``async def``
|
||||
methods in Telethon to something similar to this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def new_method():
|
||||
result = original_method()
|
||||
if loop.is_running():
|
||||
# the loop is already running, return the await-able to the user
|
||||
return result
|
||||
else:
|
||||
# the loop is not running yet, so we can run it for the user
|
||||
return loop.run_until_complete(result)
|
||||
|
||||
|
||||
That means you can do this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print(client.get_me().username)
|
||||
|
||||
Instead of this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
me = loop.run_until_complete(client.get_me())
|
||||
print(me.username)
|
||||
|
||||
|
||||
As you can see, it's a lot of boilerplate and noise having to type
|
||||
``run_until_complete`` all the time, so you can let the magic module
|
||||
to rewrite it for you. But notice the comment above: it won't run
|
||||
the loop if it's already running, because it can't. That means this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def main():
|
||||
# 3. the loop is running here
|
||||
print(
|
||||
client.get_me() # 4. this will return a coroutine!
|
||||
.username # 5. this fails, coroutines don't have usernames
|
||||
)
|
||||
|
||||
loop.run_until_complete( # 2. run the loop and the ``main()`` coroutine
|
||||
main() # 1. calling ``async def`` "returns" a coroutine
|
||||
)
|
||||
|
||||
|
||||
Will fail. So if you're inside an ``async def``, then the loop is
|
||||
running, and if the loop is running, you must ``await`` things yourself:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def main():
|
||||
print((await client.get_me()).username)
|
||||
|
||||
loop.run_until_complete(main())
|
||||
|
||||
|
||||
What are async, await and coroutines?
|
||||
=====================================
|
||||
|
||||
The ``async`` keyword lets you define asynchronous functions,
|
||||
also known as coroutines, and also iterate over asynchronous
|
||||
loops or use ``async with``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# ^ this declares the main() coroutine function
|
||||
|
||||
async with client:
|
||||
# ^ this is an asynchronous with block
|
||||
|
||||
async for message in client.iter_messages(chat):
|
||||
# ^ this is a for loop over an asynchronous generator
|
||||
|
||||
print(message.sender.username)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
# ^ this assigns the default event loop from the main thread to a variable
|
||||
|
||||
loop.run_until_complete(main())
|
||||
# ^ this runs the *entire* loop until the main() function finishes.
|
||||
# While the main() function does not finish, the loop will be running.
|
||||
# While the loop is running, you can't run it again.
|
||||
|
||||
|
||||
The ``await`` keyword blocks the *current* task, and the loop can run
|
||||
other tasks. Tasks can be thought of as "threads", since many can run
|
||||
concurrently:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
async def hello(delay):
|
||||
await asyncio.sleep(delay) # await tells the loop this task is "busy"
|
||||
print('hello') # eventually the loop resumes the code here
|
||||
|
||||
async def world(delay):
|
||||
# the loop decides this method should run first
|
||||
await asyncio.sleep(delay) # await tells the loop this task is "busy"
|
||||
print('world') # eventually the loop finishes all tasks
|
||||
|
||||
loop = asyncio.get_event_loop() # get the default loop for the main thread
|
||||
loop.create_task(world(2)) # create the world task, passing 2 as delay
|
||||
loop.create_task(hello(delay=1)) # another task, but with delay 1
|
||||
try:
|
||||
# run the event loop forever; ctrl+c to stop it
|
||||
# we could also run the loop for three seconds:
|
||||
# loop.run_until_complete(asyncio.sleep(3))
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
The same example, but without the comment noise:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
async def hello(delay):
|
||||
await asyncio.sleep(delay)
|
||||
print('hello')
|
||||
|
||||
async def world(delay):
|
||||
await asyncio.sleep(delay)
|
||||
print('world')
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(world(2))
|
||||
loop.create_task(hello(1))
|
||||
loop.run_until_complete(asyncio.sleep(3))
|
||||
|
||||
|
||||
Can I use threads?
|
||||
==================
|
||||
|
||||
Yes, you can, but you must understand that the loops themselves are
|
||||
not thread safe. and you must be sure to know what is happening. You
|
||||
may want to create a loop in a new thread and make sure to pass it to
|
||||
the client:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
|
||||
def go():
|
||||
loop = asyncio.new_event_loop()
|
||||
client = TelegramClient(..., loop=loop)
|
||||
...
|
||||
|
||||
threading.Thread(target=go).start()
|
||||
|
||||
|
||||
Generally, **you don't need threads** unless you know what you're doing.
|
||||
Just create another task, as shown above. If you're using the Telethon
|
||||
with a library that uses threads, you must be careful to use ``threading.Lock``
|
||||
whenever you use the client, or enable the compatible mode. For that, see
|
||||
:ref:`compatibility-and-convenience`.
|
||||
|
||||
You may have seen this error:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
RuntimeError: There is no current event loop in thread 'Thread-1'.
|
||||
|
||||
It just means you didn't create a loop for that thread, and if you don't
|
||||
pass a loop when creating the client, it uses ``asyncio.get_event_loop()``,
|
||||
which only works in the main thread.
|
||||
|
||||
|
||||
client.run_until_disconnected() blocks!
|
||||
=======================================
|
||||
|
||||
All of what `client.run_until_disconnected()
|
||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` does is
|
||||
run the asyncio_'s event loop until the client is disconnected. That means
|
||||
*the loop is running*. And if the loop is running, it will run all the tasks
|
||||
in it. So if you want to run *other* code, create tasks for it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
async def clock():
|
||||
while True:
|
||||
print('The time:', datetime.now())
|
||||
await asyncio.sleep(1)
|
||||
|
||||
loop.create_task(clock())
|
||||
...
|
||||
client.run_until_disconnected()
|
||||
|
||||
This creates a task for a clock that prints the time every second.
|
||||
You don't need to use `client.run_until_disconnected()
|
||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` either!
|
||||
You just need to make the loop is running, somehow. ``asyncio.run_forever``
|
||||
and ``asyncio.run_until_complete`` can also be used to run the loop, and
|
||||
Telethon will be happy with any approach.
|
||||
|
||||
Of course, there are better tools to run code hourly or daily, see below.
|
||||
|
||||
|
||||
What else can asyncio do?
|
||||
=========================
|
||||
|
||||
Asynchronous IO is a really powerful tool, as we've seen. There are plenty
|
||||
of other useful libraries that also use asyncio_ and that you can integrate
|
||||
with Telethon.
|
||||
|
||||
* `aiohttp <https://github.com/aio-libs/aiohttp>`_ is like the infamous
|
||||
`requests <https://github.com/requests/requests/>`_ but asynchronous.
|
||||
* `quart <https://gitlab.com/pgjones/quart>`_ is an asynchronous alternative
|
||||
to `Flask <http://flask.pocoo.org/>`_.
|
||||
* `aiocron <https://github.com/gawel/aiocron>`_ lets you schedule things
|
||||
to run things at a desired time, or run some tasks hourly, daily, etc.
|
||||
|
||||
And of course, `asyncio <https://docs.python.org/3/library/asyncio.html>`_
|
||||
itself! It has a lot of methods that let you do nice things. For example,
|
||||
you can run requests in parallel:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def main():
|
||||
last, sent, download_path = await asyncio.gather(
|
||||
client.get_messages('TelethonChat', 10),
|
||||
client.send_message('TelethonOfftopic', 'Hey guys!'),
|
||||
client.download_profile_photo('TelethonChat')
|
||||
)
|
||||
|
||||
loop.run_until_complete(main())
|
||||
|
||||
|
||||
This code will get the 10 last messages from `@TelethonChat
|
||||
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic
|
||||
<https://t.me/TelethonOfftopic>`_, and also download the profile
|
||||
photo of the main group. asyncio_ will run all these three tasks
|
||||
at the same time. You can run all the tasks you want this way.
|
||||
|
||||
A different way would be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loop.create_task(client.get_messages('TelethonChat', 10))
|
||||
loop.create_task(client.send_message('TelethonOfftopic', 'Hey guys!'))
|
||||
loop.create_task(client.download_profile_photo('TelethonChat'))
|
||||
|
||||
They will run in the background as long as the loop is running too.
|
||||
|
||||
You can also `start an asyncio server
|
||||
<https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server>`_
|
||||
in the main script, and from another script, `connect to it
|
||||
<https://docs.python.org/3/library/asyncio-stream.html#asyncio.open_connection>`_
|
||||
to achieve `Inter-Process Communication
|
||||
<https://en.wikipedia.org/wiki/Inter-process_communication>`_.
|
||||
You can get as creative as you want. You can program anything you want.
|
||||
When you use a library, you're not limited to use only its methods. You can
|
||||
combine all the libraries you want. People seem to forget this simple fact!
|
||||
|
||||
|
||||
Why does client.start() work outside async?
|
||||
===========================================
|
||||
|
||||
Because it's so common that it's really convenient to offer said
|
||||
functionality by default. This means you can set up all your event
|
||||
handlers and start the client without worrying about loops at all.
|
||||
|
||||
Using the client in a ``with`` block, `start
|
||||
<telethon.client.auth.AuthMethods.start>`, `run_until_disconnected
|
||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>`, and
|
||||
`disconnect <telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`
|
||||
all support this.
|
||||
|
||||
Where can I read more?
|
||||
======================
|
||||
|
||||
`Check out my blog post
|
||||
<https://lonamiwebs.github.io/blog/asyncio/>`_ about asyncio_, which
|
||||
has some more examples and pictures to help you understand what happens
|
||||
when the loop runs.
|
||||
|
||||
.. _asyncio: https://docs.python.org/3/library/asyncio.html
|
||||
308
readthedocs/concepts/entities.rst
Normal file
308
readthedocs/concepts/entities.rst
Normal file
@@ -0,0 +1,308 @@
|
||||
.. _entities:
|
||||
|
||||
========
|
||||
Entities
|
||||
========
|
||||
|
||||
The library widely uses the concept of "entities". An entity will refer
|
||||
to any :tl:`User`, :tl:`Chat` or :tl:`Channel` object that the API may return
|
||||
in response to certain methods, such as :tl:`GetUsersRequest`.
|
||||
|
||||
.. note::
|
||||
|
||||
When something "entity-like" is required, it means that you need to
|
||||
provide something that can be turned into an entity. These things include,
|
||||
but are not limited to, usernames, exact titles, IDs, :tl:`Peer` objects,
|
||||
or even entire :tl:`User`, :tl:`Chat` and :tl:`Channel` objects and even
|
||||
phone numbers **from people you have in your contact list**.
|
||||
|
||||
To "encounter" an ID, you would have to "find it" like you would in the
|
||||
normal app. If the peer is in your dialogs, you would need to
|
||||
`client.get_dialogs() <telethon.client.dialogs.DialogMethods.get_dialogs>`.
|
||||
If the peer is someone in a group, you would similarly
|
||||
`client.get_participants(group) <telethon.client.chats.ChatMethods.get_participants>`.
|
||||
|
||||
Once you have encountered an ID, the library will (by default) have saved
|
||||
their ``access_hash`` for you, which is needed to invoke most methods.
|
||||
This is why sometimes you might encounter this error when working with
|
||||
the library. You should ``except ValueError`` and run code that you know
|
||||
should work to find the entity.
|
||||
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
What is an Entity?
|
||||
==================
|
||||
|
||||
A lot of methods and requests require *entities* to work. For example,
|
||||
you send a message to an *entity*, get the username of an *entity*, and
|
||||
so on.
|
||||
|
||||
There are a lot of things that work as entities: usernames, phone numbers,
|
||||
chat links, invite links, IDs, and the types themselves. That is, you can
|
||||
use any of those when you see an "entity" is needed.
|
||||
|
||||
.. note::
|
||||
|
||||
Remember that the phone number must be in your contact list before you
|
||||
can use it.
|
||||
|
||||
You should use, **from better to worse**:
|
||||
|
||||
1. Input entities. For example, `event.input_chat
|
||||
<telethon.tl.custom.chatgetter.ChatGetter.input_chat>`,
|
||||
`message.input_sender
|
||||
<telethon.tl.custom.sendergetter.SenderGetter.input_sender>`,
|
||||
or caching an entity you will use a lot with
|
||||
``entity = await client.get_input_entity(...)``.
|
||||
|
||||
2. Entities. For example, if you had to get someone's
|
||||
username, you can just use ``user`` or ``channel``.
|
||||
It will work. Only use this option if you already have the entity!
|
||||
|
||||
3. IDs. This will always look the entity up from the
|
||||
cache (the ``*.session`` file caches seen entities).
|
||||
|
||||
4. Usernames, phone numbers and links. The cache will be
|
||||
used too (unless you force a `client.get_entity()
|
||||
<telethon.client.users.UserMethods.get_entity>`),
|
||||
but may make a request if the username, phone or link
|
||||
has not been found yet.
|
||||
|
||||
In recent versions of the library, the following two are equivalent:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def handler(event):
|
||||
await client.send_message(event.sender_id, 'Hi')
|
||||
await client.send_message(event.input_sender, 'Hi')
|
||||
|
||||
|
||||
If you need to be 99% sure that the code will work (sometimes it's
|
||||
simply impossible for the library to find the input entity), or if
|
||||
you will reuse the chat a lot, consider using the following instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def handler(event):
|
||||
# This method may make a network request to find the input sender.
|
||||
# Properties can't make network requests, so we need a method.
|
||||
sender = await event.get_input_sender()
|
||||
await client.send_message(sender, 'Hi')
|
||||
await client.send_message(sender, 'Hi')
|
||||
|
||||
|
||||
Getting Entities
|
||||
================
|
||||
|
||||
Through the use of the :ref:`sessions`, the library will automatically
|
||||
remember the ID and hash pair, along with some extra information, so
|
||||
you're able to just do this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Dialogs are the "conversations you have open".
|
||||
# This method returns a list of Dialog, which
|
||||
# has the .entity attribute and other information.
|
||||
#
|
||||
# This part is IMPORTANT, because it feels the entity cache.
|
||||
dialogs = client.get_dialogs()
|
||||
|
||||
# All of these work and do the same.
|
||||
lonami = client.get_entity('lonami')
|
||||
lonami = client.get_entity('t.me/lonami')
|
||||
lonami = client.get_entity('https://telegram.dog/lonami')
|
||||
|
||||
# Other kind of entities.
|
||||
channel = client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg')
|
||||
contact = client.get_entity('+34xxxxxxxxx')
|
||||
friend = client.get_entity(friend_id)
|
||||
|
||||
# Getting entities through their ID (User, Chat or Channel)
|
||||
entity = client.get_entity(some_id)
|
||||
|
||||
# You can be more explicit about the type for said ID by wrapping
|
||||
# it inside a Peer instance. This is recommended but not necessary.
|
||||
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
|
||||
|
||||
my_user = client.get_entity(PeerUser(some_id))
|
||||
my_chat = client.get_entity(PeerChat(some_id))
|
||||
my_channel = client.get_entity(PeerChannel(some_id))
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
You **don't** need to get the entity before using it! Just let the
|
||||
library do its job. Use a phone from your contacts, username, ID or
|
||||
input entity (preferred but not necessary), whatever you already have.
|
||||
|
||||
All methods in the :ref:`telethon-client` call `.get_input_entity()
|
||||
<telethon.client.users.UserMethods.get_input_entity>` prior
|
||||
to sending the request to save you from the hassle of doing so manually.
|
||||
That way, convenience calls such as `client.send_message('lonami', 'hi!')
|
||||
<telethon.client.messages.MessageMethods.send_message>`
|
||||
become possible.
|
||||
|
||||
Every entity the library encounters (in any response to any call) will by
|
||||
default be cached in the ``.session`` file (an SQLite database), to avoid
|
||||
performing unnecessary API calls. If the entity cannot be found, additonal
|
||||
calls like :tl:`ResolveUsernameRequest` or :tl:`GetContactsRequest` may be
|
||||
made to obtain the required information.
|
||||
|
||||
|
||||
Entities vs. Input Entities
|
||||
===========================
|
||||
|
||||
.. note::
|
||||
|
||||
This section is informative, but worth reading. The library
|
||||
will transparently handle all of these details for you.
|
||||
|
||||
On top of the normal types, the API also make use of what they call their
|
||||
``Input*`` versions of objects. The input version of an entity (e.g.
|
||||
:tl:`InputPeerUser`, :tl:`InputChat`, etc.) only contains the minimum
|
||||
information that's required from Telegram to be able to identify
|
||||
who you're referring to: a :tl:`Peer`'s **ID** and **hash**. They
|
||||
are named like this because they are input parameters in the requests.
|
||||
|
||||
Entities' ID are the same for all user and bot accounts, however, the access
|
||||
hash is **different for each account**, so trying to reuse the access hash
|
||||
from one account in another will **not** work.
|
||||
|
||||
Sometimes, Telegram only needs to indicate the type of the entity along
|
||||
with their ID. For this purpose, :tl:`Peer` versions of the entities also
|
||||
exist, which just have the ID. You cannot get the hash out of them since
|
||||
you should not be needing it. The library probably has cached it before.
|
||||
|
||||
Peers are enough to identify an entity, but they are not enough to make
|
||||
a request with them use them. You need to know their hash before you can
|
||||
"use them", and to know the hash you need to "encounter" them, let it
|
||||
be in your dialogs, participants, message forwards, etc.
|
||||
|
||||
.. note::
|
||||
|
||||
You *can* use peers with the library. Behind the scenes, they are
|
||||
replaced with the input variant. Peers "aren't enough" on their own
|
||||
but the library will do some more work to use the right type.
|
||||
|
||||
As we just mentioned, API calls don't need to know the whole information
|
||||
about the entities, only their ID and hash. For this reason, another method,
|
||||
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
|
||||
is available. This will always use the cache while possible, making zero API
|
||||
calls most of the time. When a request is made, if you provided the full
|
||||
entity, e.g. an :tl:`User`, the library will convert it to the required
|
||||
:tl:`InputPeer` automatically for you.
|
||||
|
||||
**You should always favour**
|
||||
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
|
||||
**over**
|
||||
`client.get_entity() <telethon.client.users.UserMethods.get_entity>`
|
||||
for this reason! Calling the latter will always make an API call to get
|
||||
the most recent information about said entity, but invoking requests don't
|
||||
need this information, just the :tl:`InputPeer`. Only use
|
||||
`client.get_entity() <telethon.client.users.UserMethods.get_entity>`
|
||||
if you need to get actual information, like the username, name, title, etc.
|
||||
of the entity.
|
||||
|
||||
To further simplify the workflow, since the version ``0.16.2`` of the
|
||||
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>`
|
||||
wherever needed, so you can even do things like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client(SendMessageRequest('username', 'hello'))
|
||||
|
||||
The library will call the ``.resolve()`` method of the request, which will
|
||||
resolve ``'username'`` with the appropriated :tl:`InputPeer`. Don't worry if
|
||||
you don't get this yet, but remember some of the details here are important.
|
||||
|
||||
|
||||
Full Entities
|
||||
=============
|
||||
|
||||
In addition to :tl:`PeerUser`, :tl:`InputPeerUser`, :tl:`User` (and its
|
||||
variants for chats and channels), there is also the concept of :tl:`UserFull`.
|
||||
|
||||
This full variant has additional information such as whether the user is
|
||||
blocked, its notification settings, the bio or about of the user, etc.
|
||||
|
||||
There is also :tl:`messages.ChatFull` which is the equivalent of full entities
|
||||
for chats and channels, with also the about section of the channel. Note that
|
||||
the ``users`` field only contains bots for the channel (so that clients can
|
||||
suggest commands to use).
|
||||
|
||||
You can get both of these by invoking :tl:`GetFullUser`, :tl:`GetFullChat`
|
||||
and :tl:`GetFullChannel` respectively.
|
||||
|
||||
|
||||
Accessing Entities
|
||||
==================
|
||||
|
||||
Although it's explicitly noted in the documentation that messages
|
||||
*subclass* `ChatGetter <telethon.tl.custom.chatgetter.ChatGetter>`
|
||||
and `SenderGetter <telethon.tl.custom.sendergetter.SenderGetter>`,
|
||||
some people still don't get inheritance.
|
||||
|
||||
When the documentation says "Bases: `telethon.tl.custom.chatgetter.ChatGetter`"
|
||||
it means that the class you're looking at, *also* can act as the class it
|
||||
bases. In this case, `ChatGetter <telethon.tl.custom.chatgetter.ChatGetter>`
|
||||
knows how to get the *chat* where a thing belongs to.
|
||||
|
||||
So, a `Message <telethon.tl.custom.message.Message>` is a
|
||||
`ChatGetter <telethon.tl.custom.chatgetter.ChatGetter>`.
|
||||
That means you can do this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message.is_private
|
||||
message.chat_id
|
||||
message.get_chat()
|
||||
# ...etc
|
||||
|
||||
`SenderGetter <telethon.tl.custom.sendergetter.SenderGetter>` is similar:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message.user_id
|
||||
message.get_input_user()
|
||||
message.user
|
||||
# ...etc
|
||||
|
||||
Quite a few things implement them, so it makes sense to reuse the code.
|
||||
For example, all events (except raw updates) implement `ChatGetter
|
||||
<telethon.tl.custom.chatgetter.ChatGetter>` since all events occur
|
||||
in some chat.
|
||||
|
||||
|
||||
Summary
|
||||
=======
|
||||
|
||||
TL;DR; If you're here because of *"Could not find the input entity for"*,
|
||||
you must ask yourself "how did I find this entity through official
|
||||
applications"? Now do the same with the library. Use what applies:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with client:
|
||||
# Does it have an username? Use it!
|
||||
entity = client.get_entity(username)
|
||||
|
||||
# Do you have a conversation open with them? Get dialogs.
|
||||
client.get_dialogs()
|
||||
|
||||
# Are they participant of some group? Get them.
|
||||
client.get_participants('TelethonChat')
|
||||
|
||||
# Is the entity the original sender of a forwarded message? Get it.
|
||||
client.get_messages('TelethonChat', 100)
|
||||
|
||||
# NOW you can use the ID, anywhere!
|
||||
entity = client.get_entity(123456)
|
||||
client.send_message(123456, 'Hi!')
|
||||
|
||||
Once the library has "seen" the entity, you can use their **integer** ID.
|
||||
You can't use entities from IDs the library hasn't seen. You must make the
|
||||
library see them *at least once* and disconnect properly. You know where
|
||||
the entities are and you must tell the library. It won't guess for you.
|
||||
77
readthedocs/concepts/errors.rst
Normal file
77
readthedocs/concepts/errors.rst
Normal file
@@ -0,0 +1,77 @@
|
||||
.. _rpc-errors:
|
||||
|
||||
==========
|
||||
RPC Errors
|
||||
==========
|
||||
|
||||
RPC stands for Remote Procedure Call, and when the library raises
|
||||
a ``RPCError``, it's because you have invoked some of the API
|
||||
methods incorrectly (wrong parameters, wrong permissions, or even
|
||||
something went wrong on Telegram's server). All the errors are
|
||||
available in :ref:`telethon-errors`, but some examples are:
|
||||
|
||||
- ``FloodWaitError`` (420), the same request was repeated many times.
|
||||
Must wait ``.seconds`` (you can access this attribute). For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
...
|
||||
from telethon import errors
|
||||
|
||||
try:
|
||||
print(client.get_messages(chat)[0].text)
|
||||
except errors.FloodWaitError as e:
|
||||
print('Have to sleep', e.seconds, 'seconds')
|
||||
time.sleep(e.seconds)
|
||||
|
||||
- ``SessionPasswordNeededError``, if you have setup two-steps
|
||||
verification on Telegram.
|
||||
- ``CdnFileTamperedError``, if the media you were trying to download
|
||||
from a CDN has been altered.
|
||||
- ``ChatAdminRequiredError``, you don't have permissions to perform
|
||||
said operation on a chat or channel. Try avoiding filters, i.e. when
|
||||
searching messages.
|
||||
|
||||
The generic classes for different error codes are:
|
||||
|
||||
- ``InvalidDCError`` (303), the request must be repeated on another DC.
|
||||
- ``BadRequestError`` (400), the request contained errors.
|
||||
- ``UnauthorizedError`` (401), the user is not authorized yet.
|
||||
- ``ForbiddenError`` (403), privacy violation error.
|
||||
- ``NotFoundError`` (404), make sure you're invoking ``Request``\ 's!
|
||||
|
||||
If the error is not recognised, it will only be an ``RPCError``.
|
||||
|
||||
You can refer to all errors from Python through the ``telethon.errors``
|
||||
module. If you don't know what attributes they have, try printing their
|
||||
dir (like ``print(dir(e))``).
|
||||
|
||||
Avoiding Limits
|
||||
===============
|
||||
|
||||
Don't spam. You won't get ``FloodWaitError`` or your account banned or
|
||||
deleted if you use the library *for legit use cases*. Make cool tools.
|
||||
Don't spam! Nobody knows the exact limits for all requests since they
|
||||
depend on a lot of factors, so don't bother asking.
|
||||
|
||||
Still, if you do have a legit use case and still get those errors, the
|
||||
library will automatically sleep when they are smaller than 60 seconds
|
||||
by default. You can set different "auto-sleep" thresholds:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client.flood_sleep_threshold = 0 # Don't auto-sleep
|
||||
client.flood_sleep_threshold = 24 * 60 * 60 # Sleep always
|
||||
|
||||
You can also except it and act as you prefer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.errors import FloodWaitError
|
||||
try:
|
||||
...
|
||||
except FloodWaitError as e:
|
||||
print('Flood waited for', e.seconds)
|
||||
quit(1)
|
||||
|
||||
VoIP numbers are very limited, and some countries are more limited too.
|
||||
229
readthedocs/concepts/full-api.rst
Normal file
229
readthedocs/concepts/full-api.rst
Normal file
@@ -0,0 +1,229 @@
|
||||
.. _full-api:
|
||||
|
||||
============
|
||||
The Full API
|
||||
============
|
||||
|
||||
.. important::
|
||||
|
||||
While you have access to this, you should always use the friendly
|
||||
methods listed on :ref:`client-ref` unless you have a better reason
|
||||
not to, like a method not existing or you wanting more control.
|
||||
|
||||
|
||||
The :ref:`telethon-client` doesn't offer a method for every single request
|
||||
the Telegram API supports. However, it's very simple to *call* or *invoke*
|
||||
any request. Whenever you need something, don't forget to `check the documentation`_
|
||||
and look for the `method you need`_. There you can go through a sorted list
|
||||
of everything you can do.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The reason to keep both https://lonamiwebs.github.io/Telethon and this
|
||||
documentation alive is that the former allows instant search results
|
||||
as you type, and a "Copy import" button. If you like namespaces, you
|
||||
can also do ``from telethon.tl import types, functions``. Both work.
|
||||
|
||||
|
||||
.. important::
|
||||
|
||||
All the examples in this documentation assume that you have
|
||||
``from telethon import sync`` or ``import telethon.sync`` for the
|
||||
sake of simplicity and that you understand what it does (see
|
||||
:ref:`compatibility-and-convenience` for more). Simply add
|
||||
either line at the beginning of your project and it will work.
|
||||
|
||||
|
||||
You should also refer to the documentation to see what the objects
|
||||
(constructors) Telegram returns look like. Every constructor inherits
|
||||
from a common type, and that's the reason for this distinction.
|
||||
|
||||
Say `client.send_message()
|
||||
<telethon.client.messages.MessageMethods.send_message>` didn't exist,
|
||||
we could `use the search`_ to look for "message". There we would find
|
||||
:tl:`SendMessageRequest`, which we can work with.
|
||||
|
||||
Every request is a Python class, and has the parameters needed for you
|
||||
to invoke it. You can also call ``help(request)`` for information on
|
||||
what input parameters it takes. Remember to "Copy import to the
|
||||
clipboard", or your script won't be aware of this class! Now we have:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.functions.messages import SendMessageRequest
|
||||
|
||||
If you're going to use a lot of these, you may do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl import types, functions
|
||||
# We now have access to 'functions.messages.SendMessageRequest'
|
||||
|
||||
We see that this request must take at least two parameters, a ``peer``
|
||||
of type :tl:`InputPeer`, and a ``message`` which is just a Python
|
||||
``str``\ ing.
|
||||
|
||||
How can we retrieve this :tl:`InputPeer`? We have two options. We manually
|
||||
construct one, for instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.types import InputPeerUser
|
||||
|
||||
peer = InputPeerUser(user_id, user_hash)
|
||||
|
||||
Or we call `client.get_input_entity()
|
||||
<telethon.client.users.UserMethods.get_input_entity>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import telethon.sync
|
||||
peer = client.get_input_entity('someone')
|
||||
|
||||
|
||||
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
|
||||
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
|
||||
is more straightforward (and often immediate, if you've seen the user before,
|
||||
know their ID, etc.). If you also **need** to have information about the whole
|
||||
user, use `client.get_entity() <telethon.client.users.UserMethods.get_entity>`
|
||||
instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
entity = client.get_entity('someone')
|
||||
|
||||
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
|
||||
want to cache its input version so the library doesn't have to do this
|
||||
every time its used, simply call `telethon.utils.get_input_peer`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import utils
|
||||
peer = utils.get_input_peer(entity)
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Since ``v0.16.2`` this is further simplified. The ``Request`` itself
|
||||
will call `client.get_input_entity
|
||||
<telethon.client.users.UserMethods.get_input_entity>` for you when
|
||||
required, but it's good to remember what's happening.
|
||||
|
||||
After this small parenthesis about `client.get_entity
|
||||
<telethon.client.users.UserMethods.get_entity>` versus
|
||||
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`,
|
||||
we have everything we need. To invoke our
|
||||
request we do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
result = client(SendMessageRequest(peer, 'Hello there!'))
|
||||
|
||||
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
|
||||
|
||||
result = client(SendMessageRequest(
|
||||
client.get_input_entity('username'), 'Hello there!'
|
||||
))
|
||||
|
||||
|
||||
This can further be simplified to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
result = client(SendMessageRequest('username', 'Hello there!'))
|
||||
# Or even
|
||||
result = client(SendMessageRequest(PeerChannel(id), 'Hello there!'))
|
||||
|
||||
.. note::
|
||||
|
||||
Note that some requests have a "hash" parameter. This is **not**
|
||||
your ``api_hash``! It likely isn't your self-user ``.access_hash`` either.
|
||||
|
||||
It's a special hash used by Telegram to only send a difference of new data
|
||||
that you don't already have with that request, so you can leave it to 0,
|
||||
and it should work (which means no hash is known yet).
|
||||
|
||||
For those requests having a "limit" parameter, you can often set it to
|
||||
zero to signify "return default amount". This won't work for all of them
|
||||
though, for instance, in "messages.search" it will actually return 0 items.
|
||||
|
||||
|
||||
Requests in Parallel
|
||||
====================
|
||||
|
||||
The library will automatically merge outgoing requests into a single
|
||||
*container*. Telegram's API supports sending multiple requests in a
|
||||
single container, which is faster because it has less overhead and
|
||||
the server can run them without waiting for others. You can also
|
||||
force using a container manually:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def main():
|
||||
|
||||
# Letting the library do it behind the scenes
|
||||
await asyncio.wait([
|
||||
client.send_message('me', 'Hello'),
|
||||
client.send_message('me', ','),
|
||||
client.send_message('me', 'World'),
|
||||
client.send_message('me', '.')
|
||||
])
|
||||
|
||||
# Manually invoking many requests at once
|
||||
await client([
|
||||
SendMessageRequest('me', 'Hello'),
|
||||
SendMessageRequest('me', ', '),
|
||||
SendMessageRequest('me', 'World'),
|
||||
SendMessageRequest('me', '.')
|
||||
])
|
||||
|
||||
Note that you cannot guarantee the order in which they are run.
|
||||
Try running the above code more than one time. You will see the
|
||||
order in which the messages arrive is different.
|
||||
|
||||
If you use the raw API (the first option), you can use ``ordered``
|
||||
to tell the server that it should run the requests sequentially.
|
||||
This will still be faster than going one by one, since the server
|
||||
knows all requests directly:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client([
|
||||
SendMessageRequest('me', 'Hello'),
|
||||
SendMessageRequest('me', ', '),
|
||||
SendMessageRequest('me', 'World'),
|
||||
SendMessageRequest('me', '.')
|
||||
], ordered=True)
|
||||
|
||||
If any of the requests fails with a Telegram error (not connection
|
||||
errors or any other unexpected events), the library will raise
|
||||
`telethon.errors.common.MultiError`. You can ``except`` this
|
||||
and still access the successful results:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.errors import MultiError
|
||||
|
||||
try:
|
||||
client([
|
||||
SendMessageRequest('me', 'Hello'),
|
||||
SendMessageRequest('me', ''),
|
||||
SendMessageRequest('me', 'World')
|
||||
], ordered=True)
|
||||
except MultiError as e:
|
||||
# The first and third requests worked.
|
||||
first = e.results[0]
|
||||
third = e.results[2]
|
||||
# The second request failed.
|
||||
second = e.exceptions[1]
|
||||
|
||||
.. _check the documentation: https://lonamiwebs.github.io/Telethon
|
||||
.. _method you need: https://lonamiwebs.github.io/Telethon/methods/index.html
|
||||
.. _use the search: https://lonamiwebs.github.io/Telethon/?q=message&redirect=no
|
||||
158
readthedocs/concepts/sessions.rst
Normal file
158
readthedocs/concepts/sessions.rst
Normal file
@@ -0,0 +1,158 @@
|
||||
.. _sessions:
|
||||
|
||||
==============
|
||||
Session Files
|
||||
==============
|
||||
|
||||
.. contents::
|
||||
|
||||
They are an important part for the library to be efficient, such as caching
|
||||
and handling your authorization key (or you would have to login every time!).
|
||||
|
||||
What are Sessions?
|
||||
==================
|
||||
|
||||
The first parameter you pass to the constructor of the
|
||||
:ref:`TelegramClient <telethon-client>` is
|
||||
the ``session``, and defaults to be the session name (or full path). That is,
|
||||
if you create a ``TelegramClient('anon')`` instance and connect, an
|
||||
``anon.session`` file will be created in the working directory.
|
||||
|
||||
Note that if you pass a string it will be a file in the current working
|
||||
directory, although you can also pass absolute paths.
|
||||
|
||||
The session file contains enough information for you to login without
|
||||
re-sending the code, so if you have to enter the code more than once,
|
||||
maybe you're changing the working directory, renaming or removing the
|
||||
file, or using random names.
|
||||
|
||||
These database files using ``sqlite3`` contain the required information to
|
||||
talk to the Telegram servers, such as to which IP the client should connect,
|
||||
port, authorization key so that messages can be encrypted, and so on.
|
||||
|
||||
These files will by default also save all the input entities that you've seen,
|
||||
so that you can get information about a user or channel by just their ID.
|
||||
Telegram will **not** send their ``access_hash`` required to retrieve more
|
||||
information about them, if it thinks you have already seem them. For this
|
||||
reason, the library needs to store this information offline.
|
||||
|
||||
The library will by default too save all the entities (chats and channels
|
||||
with their name and username, and users with the phone too) in the session
|
||||
file, so that you can quickly access them by username or phone number.
|
||||
|
||||
If you're not going to work with updates, or don't need to cache the
|
||||
``access_hash`` associated with the entities' ID, you can disable this
|
||||
by setting ``client.session.save_entities = False``.
|
||||
|
||||
|
||||
Different Session Storage
|
||||
=========================
|
||||
|
||||
If you don't want to use the default SQLite session storage, you can also use
|
||||
one of the other implementations or implement your own storage.
|
||||
|
||||
To use a custom session storage, simply pass the custom session instance to
|
||||
:ref:`TelegramClient <telethon-client>` instead of
|
||||
the session name.
|
||||
|
||||
Telethon contains three implementations of the abstract ``Session`` class:
|
||||
|
||||
.. currentmodule:: telethon.sessions
|
||||
|
||||
* `MemorySession <memory.MemorySession>`: stores session data within memory.
|
||||
* `SQLiteSession <sqlite.SQLiteSession>`: stores sessions within on-disk SQLite databases. Default.
|
||||
* `StringSession <string.StringSession>`: stores session data within memory,
|
||||
but can be saved as a string.
|
||||
|
||||
You can import these ``from telethon.sessions``. For example, using the
|
||||
`StringSession <string.StringSession>` is done as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.sync import TelegramClient
|
||||
from telethon.sessions import StringSession
|
||||
|
||||
with TelegramClient(StringSession(string), api_id, api_hash) as client:
|
||||
... # use the client
|
||||
|
||||
# Save the string session as a string; you should decide how
|
||||
# you want to save this information (over a socket, remote
|
||||
# database, print it and then paste the string in the code,
|
||||
# etc.); the advantage is that you don't need to save it
|
||||
# on the current disk as a separate file, and can be reused
|
||||
# anywhere else once you log in.
|
||||
string = client.session.save()
|
||||
|
||||
# Note that it's also possible to save any other session type
|
||||
# as a string by using ``StringSession.save(session_instance)``:
|
||||
client = TelegramClient('sqlite-session', api_id, api_hash)
|
||||
string = StringSession.save(client.session)
|
||||
|
||||
There are other community-maintained implementations available:
|
||||
|
||||
* `SQLAlchemy <https://github.com/tulir/telethon-session-sqlalchemy>`_:
|
||||
stores all sessions in a single database via SQLAlchemy.
|
||||
|
||||
* `Redis <https://github.com/ezdev128/telethon-session-redis>`_:
|
||||
stores all sessions in a single Redis data store.
|
||||
|
||||
|
||||
Creating your Own Storage
|
||||
=========================
|
||||
|
||||
The easiest way to create your own storage implementation is to use
|
||||
`MemorySession <memory.MemorySession>` as the base and check out how
|
||||
`SQLiteSession <sqlite.SQLiteSession>` or one of the community-maintained
|
||||
implementations work. You can find the relevant Python files under the
|
||||
``sessions/`` directory in the Telethon's repository.
|
||||
|
||||
After you have made your own implementation, you can add it to the
|
||||
community-maintained session implementation list above with a pull request.
|
||||
|
||||
|
||||
String Sessions
|
||||
===============
|
||||
|
||||
`StringSession <string.StringSession>` are a convenient way to embed your
|
||||
login credentials directly into your code for extremely easy portability,
|
||||
since all they take is a string to be able to login without asking for your
|
||||
phone and code (or faster start if you're using a bot token).
|
||||
|
||||
The easiest way to generate a string session is as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.sync import TelegramClient
|
||||
from telethon.sessions import StringSession
|
||||
|
||||
with TelegramClient(StringSession(), api_id, api_hash) as client:
|
||||
print(client.session.save())
|
||||
|
||||
|
||||
Think of this as a way to export your authorization key (what's needed
|
||||
to login into your account). This will print a string in the standard
|
||||
output (likely your terminal).
|
||||
|
||||
.. warning::
|
||||
|
||||
**Keep this string safe!** Anyone with this string can use it
|
||||
to login into your account and do anything they want to to do.
|
||||
|
||||
This is similar to leaking your ``*.session`` files online,
|
||||
but it is easier to leak a string than it is to leak a file.
|
||||
|
||||
|
||||
Once you have the string (which is a bit long), load it into your script
|
||||
somehow. You can use a normal text file and ``open(...).read()`` it or
|
||||
you can save it in a variable directly:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
string = '1aaNk8EX-YRfwoRsebUkugFvht6DUPi_Q25UOCzOAqzc...'
|
||||
with TelegramClient(StringSession(string), api_id, api_hash) as client:
|
||||
client.send_message('me', 'Hi')
|
||||
|
||||
|
||||
These strings are really convenient for using in places like Heroku since
|
||||
their ephemeral filesystem will delete external files once your application
|
||||
is over.
|
||||
100
readthedocs/concepts/strings.rst
Normal file
100
readthedocs/concepts/strings.rst
Normal file
@@ -0,0 +1,100 @@
|
||||
======================
|
||||
String-based Debugging
|
||||
======================
|
||||
|
||||
Debugging is *really* important. Telegram's API is really big and there
|
||||
is a lot of things that you should know. Such as, what attributes or fields
|
||||
does a result have? Well, the easiest thing to do is printing it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
user = client.get_entity('Lonami')
|
||||
print(user)
|
||||
|
||||
That will show a huge **string** similar to the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
User(id=10885151, is_self=False, contact=False, mutual_contact=False, deleted=False, bot=False, bot_chat_history=False, bot_nochats=False, verified=False, restricted=False, min=False, bot_inline_geo=False, access_hash=123456789012345678, first_name='Lonami', last_name=None, username='Lonami', phone=None, photo=UserProfilePhoto(photo_id=123456789012345678, photo_small=FileLocation(dc_id=4, volume_id=1234567890, local_id=1234567890, secret=123456789012345678), photo_big=FileLocation(dc_id=4, volume_id=1234567890, local_id=1234567890, secret=123456789012345678)), status=UserStatusOffline(was_online=datetime.datetime(2018, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc)), bot_info_version=None, restriction_reason=None, bot_inline_placeholder=None, lang_code=None)
|
||||
|
||||
That's a lot of text. But as you can see, all the properties are there.
|
||||
So if you want the username you **don't use regex** or anything like
|
||||
splitting ``str(user)`` to get what you want. You just access the
|
||||
attribute you need:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
username = user.username
|
||||
|
||||
Can we get better than the shown string, though? Yes!
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print(user.stringify())
|
||||
|
||||
Will show a much better:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
User(
|
||||
id=10885151,
|
||||
is_self=False,
|
||||
contact=False,
|
||||
mutual_contact=False,
|
||||
deleted=False,
|
||||
bot=False,
|
||||
bot_chat_history=False,
|
||||
bot_nochats=False,
|
||||
verified=False,
|
||||
restricted=False,
|
||||
min=False,
|
||||
bot_inline_geo=False,
|
||||
access_hash=123456789012345678,
|
||||
first_name='Lonami',
|
||||
last_name=None,
|
||||
username='Lonami',
|
||||
phone=None,
|
||||
photo=UserProfilePhoto(
|
||||
photo_id=123456789012345678,
|
||||
photo_small=FileLocation(
|
||||
dc_id=4,
|
||||
volume_id=123456789,
|
||||
local_id=123456789,
|
||||
secret=-123456789012345678
|
||||
),
|
||||
photo_big=FileLocation(
|
||||
dc_id=4,
|
||||
volume_id=123456789,
|
||||
local_id=123456789,
|
||||
secret=123456789012345678
|
||||
)
|
||||
),
|
||||
status=UserStatusOffline(
|
||||
was_online=datetime.datetime(2018, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc)
|
||||
),
|
||||
bot_info_version=None,
|
||||
restriction_reason=None,
|
||||
bot_inline_placeholder=None,
|
||||
lang_code=None
|
||||
)
|
||||
|
||||
Now it's easy to see how we could get, for example,
|
||||
the ``was_online`` time. It's inside ``status``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
online_at = user.status.was_online
|
||||
|
||||
You don't need to print everything to see what all the possible values
|
||||
can be. You can just search in http://lonamiwebs.github.io/Telethon/.
|
||||
|
||||
Remember that you can use Python's `isinstance
|
||||
<https://docs.python.org/3/library/functions.html#isinstance>`_
|
||||
to check the type of something. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import types
|
||||
|
||||
if isinstance(user.status, types.UserStatusOffline):
|
||||
print(user.status.was_online)
|
||||
229
readthedocs/concepts/updates.rst
Normal file
229
readthedocs/concepts/updates.rst
Normal file
@@ -0,0 +1,229 @@
|
||||
================
|
||||
Updates in Depth
|
||||
================
|
||||
|
||||
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 the client
|
||||
=========================
|
||||
|
||||
The code of your application starts getting big, so you decide to
|
||||
separate the handlers into different files. But how can you access
|
||||
the client from these files? You don't need to! Just `events.register
|
||||
<telethon.events.register>` them:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# handlers/welcome.py
|
||||
from telethon import events
|
||||
|
||||
@events.register(events.NewMessage('(?i)hello'))
|
||||
async def handler(event):
|
||||
client = event.client
|
||||
await event.respond('Hey!')
|
||||
await client.send_message('me', 'I said hello to someone')
|
||||
|
||||
|
||||
Registering events is a way of saying "this method is an event handler".
|
||||
You can use `telethon.events.is_handler` to check if any method is a handler.
|
||||
You can think of them as a different approach to Flask's blueprints.
|
||||
|
||||
It's important to note that this does **not** add the handler to any client!
|
||||
You never specified the client on which the handler should be used. You only
|
||||
declared that it is a handler, and its type.
|
||||
|
||||
To actually use the handler, you need to `client.add_event_handler
|
||||
<telethon.client.updates.UpdateMethods.add_event_handler>` to the
|
||||
client (or clients) where they should be added to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# main.py
|
||||
from telethon import TelegramClient
|
||||
import handlers.welcome
|
||||
|
||||
with TelegramClient(...) as client:
|
||||
client.add_event_handler(handlers.welcome.handler)
|
||||
client.run_until_disconnected()
|
||||
|
||||
|
||||
This also means that you can register an event handler once and
|
||||
then add it to many clients without re-declaring the event.
|
||||
|
||||
|
||||
Events Without Decorators
|
||||
=========================
|
||||
|
||||
If for any reason you don't want to use `telethon.events.register`,
|
||||
you can explicitly pass the event handler to use to the mentioned
|
||||
`client.add_event_handler
|
||||
<telethon.client.updates.UpdateMethods.add_event_handler>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import TelegramClient, events
|
||||
|
||||
async def handler(event):
|
||||
...
|
||||
|
||||
with TelegramClient(...) as client:
|
||||
client.add_event_handler(handler, events.NewMessage)
|
||||
client.run_until_disconnected()
|
||||
|
||||
|
||||
Similarly, you also have `client.remove_event_handler
|
||||
<telethon.client.updates.UpdateMethods.remove_event_handler>`
|
||||
and `client.list_event_handlers
|
||||
<telethon.client.updates.UpdateMethods.list_event_handlers>`.
|
||||
|
||||
The ``event`` argument is optional in all three methods and defaults to
|
||||
`events.Raw <telethon.events.raw.Raw>` for adding, and ``None`` when
|
||||
removing (so all callbacks would be removed).
|
||||
|
||||
.. note::
|
||||
|
||||
The ``event`` type is ignored in `client.add_event_handler
|
||||
<telethon.client.updates.UpdateMethods.add_event_handler>`
|
||||
if you have used `telethon.events.register` on the ``callback``
|
||||
before, since that's the point of using such method at all.
|
||||
|
||||
|
||||
Stopping Propagation of Updates
|
||||
===============================
|
||||
|
||||
There might be cases when an event handler is supposed to be used solitary and
|
||||
it makes no sense to process any other handlers in the chain. For this case,
|
||||
it is possible to raise a `telethon.events.StopPropagation` exception which
|
||||
will cause the propagation of the update through your handlers to stop:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.events import StopPropagation
|
||||
|
||||
@client.on(events.NewMessage)
|
||||
async def _(event):
|
||||
# ... some conditions
|
||||
await event.delete()
|
||||
|
||||
# Other handlers won't have an event to work with
|
||||
raise StopPropagation
|
||||
|
||||
@client.on(events.NewMessage)
|
||||
async def _(event):
|
||||
# Will never be reached, because it is the second handler
|
||||
# in the chain.
|
||||
pass
|
||||
|
||||
|
||||
Remember to check :ref:`telethon-events` if you're looking for
|
||||
the methods reference.
|
||||
|
||||
Understanding asyncio
|
||||
=====================
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
from telethon import TelegramClient
|
||||
|
||||
client = TelegramClient(...)
|
||||
...
|
||||
client.run_until_disconnected()
|
||||
|
||||
|
||||
Behind the scenes, this method is ``await``'ing on the `client.disconnected
|
||||
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>` property,
|
||||
so the code above and the following are equivalent:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
from telethon import TelegramClient
|
||||
|
||||
client = TelegramClient(...)
|
||||
|
||||
async def main():
|
||||
await client.disconnected
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
|
||||
|
||||
You could also run `client.disconnected
|
||||
<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
|
||||
die. If you don't care about updates, you don't need any of this.
|
||||
|
||||
Notice that unlike `client.disconnected
|
||||
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>`,
|
||||
`client.run_until_disconnected
|
||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` will
|
||||
handle ``KeyboardInterrupt`` with you. This method is special and can
|
||||
also be ran while the loop is running, so you can do this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def main():
|
||||
await client.run_until_disconnected()
|
||||
|
||||
loop.run_until_complete(main())
|
||||
|
||||
Sequential Updates
|
||||
==================
|
||||
|
||||
If you need to process updates sequentially (i.e. not in parallel),
|
||||
you should set ``sequential_updates=True`` when creating the client:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with TelegramClient(..., sequential_updates=True) as client:
|
||||
...
|
||||
Reference in New Issue
Block a user