From bb8f44f6087fda8dbaf0b885524a3c434237b6f6 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 24 Aug 2018 13:20:38 +0200 Subject: [PATCH] Add a new "Mastering Telethon" section to the docs --- .../advanced-usage/mastering-telethon.rst | 338 ++++++++++++++++++ readthedocs/extra/basic/entities.rst | 34 +- readthedocs/index.rst | 1 + readthedocs/telethon.tl.custom.rst | 8 + 4 files changed, 373 insertions(+), 8 deletions(-) create mode 100644 readthedocs/extra/advanced-usage/mastering-telethon.rst diff --git a/readthedocs/extra/advanced-usage/mastering-telethon.rst b/readthedocs/extra/advanced-usage/mastering-telethon.rst new file mode 100644 index 00000000..05dd9340 --- /dev/null +++ b/readthedocs/extra/advanced-usage/mastering-telethon.rst @@ -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 +`_ 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 + `, + `message.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() + `), + 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 ` +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(...) +` 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 ` + or `sender ` + may not work, but `input_chat + ` + and `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 +`_ +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 ` +and `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 ` +knows how to get the *chat* where a thing belongs to. + +So, a `Message ` is a +`ChatGetter `. +That means you can do this: + +.. code-block:: python + + message.is_private + message.chat_id + message.get_chat() + # ...etc + +`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 +` 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! diff --git a/readthedocs/extra/basic/entities.rst b/readthedocs/extra/basic/entities.rst index f1578465..897fe997 100644 --- a/readthedocs/extra/basic/entities.rst +++ b/readthedocs/extra/basic/entities.rst @@ -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() ` 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() `. - 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, diff --git a/readthedocs/index.rst b/readthedocs/index.rst index 8656a7a6..a23d4633 100644 --- a/readthedocs/index.rst +++ b/readthedocs/index.rst @@ -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: diff --git a/readthedocs/telethon.tl.custom.rst b/readthedocs/telethon.tl.custom.rst index ccc3f14c..571cf64f 100644 --- a/readthedocs/telethon.tl.custom.rst +++ b/readthedocs/telethon.tl.custom.rst @@ -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: