mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 17:36:34 +03:00
Add a new "Mastering Telethon" section to the docs
This commit is contained in:
parent
52a4327769
commit
bb8f44f608
338
readthedocs/extra/advanced-usage/mastering-telethon.rst
Normal file
338
readthedocs/extra/advanced-usage/mastering-telethon.rst
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
.. _mastering-telethon:
|
||||||
|
|
||||||
|
==================
|
||||||
|
Mastering Telethon
|
||||||
|
==================
|
||||||
|
|
||||||
|
You've come far! In this section you will learn best practices, as well
|
||||||
|
as how to fix some silly (yet common) errors you may have found. Let's
|
||||||
|
start with a simple one.
|
||||||
|
|
||||||
|
Asyncio madness
|
||||||
|
***************
|
||||||
|
|
||||||
|
We promise ``asyncio`` is worth learning. Take your time to learn it.
|
||||||
|
It's a powerful tool that enables you to use this powerful library.
|
||||||
|
You need to be comfortable with it if you want to master Telethon.
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
AttributeError: 'coroutine' object has no attribute 'id'
|
||||||
|
|
||||||
|
You probably had a previous version, upgraded, and expected everything
|
||||||
|
to work. Remember, just add this line:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import telethon.sync
|
||||||
|
|
||||||
|
If you're inside an event handler you need to ``await`` **everything** that
|
||||||
|
*makes a network request*. Getting users, sending messages, and nearly
|
||||||
|
everything in the library needs access to the network, so they need to
|
||||||
|
be awaited:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def handler(event):
|
||||||
|
print((await event.get_sender()).username)
|
||||||
|
|
||||||
|
|
||||||
|
You may want to read https://lonamiwebs.github.io/blog/asyncio/ to help
|
||||||
|
you understand ``asyncio`` better. I'm open for `feedback
|
||||||
|
<https://t.me/LonamiWebs>`_ regarding that blog post
|
||||||
|
|
||||||
|
Entities
|
||||||
|
********
|
||||||
|
|
||||||
|
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 is an entire section on this at :ref:`entities` due to their
|
||||||
|
importance.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 short, unlike in most bot API libraries where you use the ID, you
|
||||||
|
**should not** use the ID *if* you have the input entity. This is OK:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def handler(event):
|
||||||
|
await client.send_message(event.sender_id, 'Hi')
|
||||||
|
|
||||||
|
However, **this is better**:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def handler(event):
|
||||||
|
await client.send_message(event.input_sender, 'Hi')
|
||||||
|
|
||||||
|
Note that this also works for `message <telethon.tl.custom.message.Message>`
|
||||||
|
instead of ``event``. Telegram may not send the sender information, so if you
|
||||||
|
want to be 99% confident that the above will work you should do this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def handler(event):
|
||||||
|
await client.send_message(await event.get_input_sender(), 'Hi')
|
||||||
|
|
||||||
|
Methods are able to make network requests to get information that
|
||||||
|
could be missing. Properties will never make a network request.
|
||||||
|
|
||||||
|
Of course, it is convenient to IDs or usernames for most purposes. It will
|
||||||
|
be fast enough and caching with `client.get_input_entity(...)
|
||||||
|
<telethon.client.users.UserMethods.get_input_entity>` will
|
||||||
|
be a micro-optimization. However it's worth knowing, and it
|
||||||
|
will also let you know if the entity cannot be found beforehand.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Sometimes Telegram doesn't send the access hash inside entities,
|
||||||
|
so using `chat <telethon.tl.custom.chatgetter.ChatGetter.chat>`
|
||||||
|
or `sender <telethon.tl.custom.sendergetter.SenderGetter.sender>`
|
||||||
|
may not work, but `input_chat
|
||||||
|
<telethon.tl.custom.chatgetter.ChatGetter.input_chat>`
|
||||||
|
and `input_sender
|
||||||
|
<telethon.tl.custom.sendergetter.SenderGetter.input_sender>`
|
||||||
|
while making requests definitely will since that's what they exist
|
||||||
|
for. If Telegram did not send information about the access hash,
|
||||||
|
you will get something like "Invalid channel object" or
|
||||||
|
"Invalid user object".
|
||||||
|
|
||||||
|
|
||||||
|
Debugging
|
||||||
|
*********
|
||||||
|
|
||||||
|
**Please enable logging**:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
|
||||||
|
Change it for ``logging.DEBUG`` if you are asked for logs. It will save you
|
||||||
|
a lot of headaches and time when you work with events. This is for errors.
|
||||||
|
|
||||||
|
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 line 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::
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 limitted too.
|
||||||
|
|
||||||
|
Chat or User From Messages
|
||||||
|
**************************
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Session Files
|
||||||
|
*************
|
||||||
|
|
||||||
|
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!).
|
||||||
|
|
||||||
|
However, some people have a lot of trouble with SQLite, especially in Windows:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
...some lines of traceback
|
||||||
|
'insert or replace into entities values (?,?,?,?,?)', rows)
|
||||||
|
sqlite3.OperationalError: database is locked
|
||||||
|
|
||||||
|
This error occurs when **two or more clients use the same session**,
|
||||||
|
that is, when you write the same session name to be used in the client:
|
||||||
|
|
||||||
|
* You have two scripts running (interactive sessions count too).
|
||||||
|
* You have two clients in the same script running at the same time.
|
||||||
|
|
||||||
|
The solution is, if you need two clients, use two sessions. If the
|
||||||
|
problem persists and you're on Linux, you can use ``fuser my.session``
|
||||||
|
to find out the process locking the file. As a last resort, you can
|
||||||
|
reboot your system.
|
||||||
|
|
||||||
|
If you really dislike SQLite, use a different session storage. There
|
||||||
|
is an entire section covering that at :ref:`sessions`.
|
||||||
|
|
||||||
|
Final Words
|
||||||
|
***********
|
||||||
|
|
||||||
|
Now you are aware of some common errors and use cases, this should help
|
||||||
|
you master your Telethon skills to get the most out of the library. Have
|
||||||
|
fun developing awesome things!
|
|
@ -69,6 +69,12 @@ you're able to just do this:
|
||||||
my_channel = client.get_entity(PeerChannel(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 the phone, username, ID or input
|
||||||
|
entity (preferred but not necessary), whatever you already have.
|
||||||
|
|
||||||
All methods in the :ref:`telegram-client` call `.get_input_entity()
|
All methods in the :ref:`telegram-client` call `.get_input_entity()
|
||||||
<telethon.client.users.UserMethods.get_input_entity>` prior
|
<telethon.client.users.UserMethods.get_input_entity>` prior
|
||||||
to sending the requst to save you from the hassle of doing so manually.
|
to sending the requst to save you from the hassle of doing so manually.
|
||||||
|
@ -95,20 +101,32 @@ Entities vs. Input Entities
|
||||||
other means like `client.get_dialogs()
|
other means like `client.get_dialogs()
|
||||||
<telethon.client.dialogs.DialogMethods.get_dialogs>`.
|
<telethon.client.dialogs.DialogMethods.get_dialogs>`.
|
||||||
|
|
||||||
|
|
||||||
On top of the normal types, the API also make use of what they call their
|
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.
|
``Input*`` versions of objects. The input version of an entity (e.g.
|
||||||
:tl:`InputPeerUser`, :tl:`InputChat`, etc.) only contains the minimum
|
:tl:`InputPeerUser`, :tl:`InputChat`, etc.) only contains the minimum
|
||||||
information that's required from Telegram to be able to identify
|
information that's required from Telegram to be able to identify
|
||||||
who you're referring to: a :tl:`Peer`'s **ID** and **hash**.
|
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.
|
||||||
|
|
||||||
This ID/hash pair is unique per user, so if you use the pair given by another
|
Entities' ID are the same for all user and bot accounts, however, the access
|
||||||
user **or bot** it will **not** work.
|
hash is **different for each account**, so trying to reuse the access hash
|
||||||
|
from one account in another will **not** work.
|
||||||
|
|
||||||
To save *even more* bandwidth, the API also makes use of the :tl:`Peer`
|
Sometimes, Telegram only needs to indicate the type of the entity along
|
||||||
versions, which just have an ID. This serves to identify them, but
|
with their ID. For this purpose, :tl:`Peer` versions of the entities also
|
||||||
peers alone are not enough to use them. You need to know their hash
|
exist, which just have the ID. You cannot get the hash out of them since
|
||||||
before you can "use them".
|
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
|
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,
|
about the entities, only their ID and hash. For this reason, another method,
|
||||||
|
|
|
@ -69,6 +69,7 @@ heavy job for you, so you can focus on developing an application.
|
||||||
extra/advanced-usage/accessing-the-full-api
|
extra/advanced-usage/accessing-the-full-api
|
||||||
extra/advanced-usage/sessions
|
extra/advanced-usage/sessions
|
||||||
extra/advanced-usage/update-modes
|
extra/advanced-usage/update-modes
|
||||||
|
extra/advanced-usage/mastering-telethon
|
||||||
|
|
||||||
|
|
||||||
.. _Examples:
|
.. _Examples:
|
||||||
|
|
|
@ -77,3 +77,11 @@ telethon\.tl\.custom\.sendergetter module
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
telethon\.tl\.custom\.conversation module
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: telethon.tl.custom.conversation
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user