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))
|
||||
|
||||
|
||||
.. 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()
|
||||
<telethon.client.users.UserMethods.get_input_entity>` prior
|
||||
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()
|
||||
<telethon.client.dialogs.DialogMethods.get_dialogs>`.
|
||||
|
||||
|
||||
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**.
|
||||
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
|
||||
user **or bot** it will **not** work.
|
||||
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.
|
||||
|
||||
To save *even more* bandwidth, the API also makes use of the :tl:`Peer`
|
||||
versions, which just have an ID. This serves to identify them, but
|
||||
peers alone are not enough to use them. You need to know their hash
|
||||
before you can "use them".
|
||||
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,
|
||||
|
|
|
@ -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/sessions
|
||||
extra/advanced-usage/update-modes
|
||||
extra/advanced-usage/mastering-telethon
|
||||
|
||||
|
||||
.. _Examples:
|
||||
|
|
|
@ -77,3 +77,11 @@ telethon\.tl\.custom\.sendergetter module
|
|||
:members:
|
||||
:undoc-members:
|
||||
: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