Add documentation

This commit is contained in:
Lonami Exo
2023-09-13 19:01:16 +02:00
parent e36c35c805
commit b62327308b
24 changed files with 1151 additions and 17 deletions

View File

@@ -0,0 +1,117 @@
HTTP Bot API vs MTProto
=======================
.. currentmodule:: telethon
Telethon is more than capable to develop bots for Telegram.
If you haven't decided which wrapper library for bots to use yet,
using Telethon from the beginning may save you some headaches later.
What is Bot API?
----------------
`Telegram's HTTP Bot API <https://core.telegram.org/bots/api>`_,
from now on referred to as simply "Bot API", is Telegram's official way for developers to control their own Telegram bots.
Quoting their main page:
.. epigraph::
The Bot API is an HTTP-based interface created for developers keen on building bots for Telegram.
To learn how to create and set up a bot, please consult our
`Introduction to Bots <https://core.telegram.org/bots>`_
and `Bot FAQ <https://core.telegram.org/bots/faq>`_.
Bot API is simply an HTTP endpoint offering a custom HTTP API.
Underneath, it uses `tdlib <https://core.telegram.org/tdlib>`_ to talk to Telegram's servers.
You can configure your bot details via `@BotFather <https://t.me/BotFather>`_.
This includes name, commands, and auto-completion.
What is MTProto?
----------------
`MTProto <https://core.telegram.org/mtproto>`_ stands for "Mobile Transport Protocol".
It is the language that the Telegram servers "speak".
You can think of it as an alternative to HTTP.
Telegram offers multiple APIs.
All user accounts must use the API offered via MTProto.
We will call this API the "MTProto API".
This is the canonical Telegram API.
The MTProto API is different from Bot API, but bot accounts can use either in the same way.
In fact, the Bot API is implemented to use the MTProto API to map the requests and responses.
Telethon implements the MTProto and offers classes and methods that can be called to send requests.
In Telethon, all the methods and types generated from Telegram's API definitions are also known as :term:`Raw API`.
This name was chosen because it gives you "raw" access to the MTProto API.
Telethon's :class:`Client` and other custom types are implemented using the :term:`Raw API`.
Advantages of MTProto over Bot API
----------------------------------
MTProto clients (like Telethon) connect directly to Telegram's servers via TCP or UDP.
There is no HTTP connection, no "polling", and no "web hooks".
We can compare the two visually:
.. graphviz::
:caption: Communication between a Client and the Bot API
digraph botapi {
rankdir=LR;
"Client" -> "HTTP API";
"HTTP API" -> "MTProto API";
"MTProto API" -> "Telegram Servers";
"Telegram Servers" -> "MTProto API" [label="IPC"];
"MTProto API" -> "HTTP API" [label="MTProto"];
"HTTP API" -> "Client" [label="JSON"];
}
.. graphviz::
:caption: Communication between a Client and the MTProto API
digraph botapi {
rankdir=LR;
"Client" -> "MTProto API";
"MTProto API" -> "Telegram Servers";
"Telegram Servers" -> "MTProto API" [label="IPC"];
"MTProto API" -> "Client" [label="MTProto"];
}
When interacting with the MTProto API directly, we can cut down one intermediary (the HTTP API).
This is less theoretical overhead and latency.
It also means that, even if the Bot API endpoint is down, talking to the MTProto API could still work.
The methods offered by the Bot API map to some of the methods in the MTProto API, but not all.
The Bot API is its own abstraction, and chooses to expose less details.
By talking to the MTProto API directly, you unlock the `full potential <https://github.com/LonamiWebs/Telethon/wiki/MTProto-vs-HTTP-Bot-API>`_.
The serialization format used by MTProto is more compact than JSON and can still be compressed.
Another benefit of avoiding the Bot API is the ease to switch to user accounts instead of bots.
The MTProto API is the same for users and bots, so by using Telethon, you don't need to learn to use a second library.
Migrating from Bot API to Telethon
----------------------------------
If the above points convinced you to switch to Telethon, the following short guides should help you make the switch!
It doesn't matter if you wrote your bot with `requests <https://pypi.org/project/requests/>`_
and you were making API requests manually, or if you used a wrapper library like
`python-telegram-bot <https://python-telegram-bot.readthedocs.io>`_
or `pyTelegramBotAPI <https://pytba.readthedocs.io/en/latest/index.html>`.
You will surely be pleased with Telethon!
If you were using an asynchronous library like `aiohttp <https://docs.aiohttp.org/en/stable>`_
or a wrapper like `aiogram <https://docs.aiohttp.org/en/stable>`_, the switch will be even easier.
Migrating from TODO
^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1,108 @@
Chats
=====
.. currentmodule:: telethon
The term :term:`chat` is extremely overloaded, so it's no surprise many are confused by what it means.
This section should hopefully clear that up.
Telethon Chat
-------------
The word :term:`chat` in Telethon is used to refer a place where messages are sent to.
Therefore, a Telethon :term:`chat` can be another user, a bot, a group, or a broadcast channel.
All of those are places where messages can be sent.
Of course, chats do more things than contain messages.
They often have a name, username, photo, description, and other information.
When a :term:`chat` appears in a parameter or as a property,
it means that it will be either a :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
When a parameter must be "chat-like", it means Telethon will accept anything that can be "converted" to a :term:`chat`.
The following types are chat-like:
* The ``'me'`` literal string. This represents the account that is logged in ("yourself").
* An ``'@username'``. The at-sign ``@`` is optional. Note that links are not supported.
* An ``'+1 23'`` phone number string. It must be an ``str`` and start with the plus-sign ``+`` character.
* An ``123`` integer identifier. It must be an ``int`` and cannot be negative.
* An existing :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
* A :class:`~types.PackedChat`.
Previous versions of Telethon referred to this term as "entity" or "entities" instead.
Telegram Chat
-------------
The Telegram API is very confusing when it comes to the word "chat".
You only need to know about this if you plan to use the :term:`Raw API`.
In the schema definitions, there are two boxed types, :tl:`User` and :tl:`Chat`.
A boxed :tl:`User` can only be the bare :tl:`user`, but the boxed :tl:`Chat` can be either a bare :tl:`chat` or a bare :tl:`channel`.
A bare :tl:`chat` always refers to small groups.
A bare :tl:`channel` can have either the ``broadcast`` or the ``megagroup`` flag set to ``True``.
A bare :tl:`channel` with the ``broadcast`` flag set to ``True`` is known as a broadcast channel.
A bare :tl:`channel` with the ``megagroup`` flag set to ``True`` is known as a supergroup.
A bare :tl:`chat` with has less features than a bare :tl:`channel` ``megagroup``.
Official clients are very good at hiding this difference.
They will implicitly convert bare :tl:`chat` to bare :tl:`channel` ``megagroup`` when doing certain operations.
Doing things like setting a username is actually a two-step process (migration followed by updating the username).
Official clients transparently merge the history of migrated :tl:`channel` with their old :tl:`chat`.
In Telethon:
* A :class:`~types.User` always corresponds to :tl:`user`.
* A :class:`~types.Group` represents either a :tl:`chat` or a :tl:`channel` ``megagroup``.
* A :class:`~types.Channel` represents a :tl:`channel` ``broadcast``.
Telethon classes aim to map to similar concepts in official applications.
Bot API chat
------------
The Bot API follows a certain convention when it comes to identifiers:
* User IDs are positive.
* Chat IDs are negative.
* Channel IDs are prefixed with ``-100``.
Telethon encourages the use of :class:`~types.PackedChat` instead of naked identifiers.
As a reminder, negative identifiers are not supported in Telethon's chat-like parameters.
Encountering chats
------------------
The way you encounter chats in Telethon is no different from official clients.
If you:
* …have joined a group or channel, or have sent private messages to some user, you can :meth:`~Client.get_dialogs`.
* …know the user is in your contact list, you can :meth:`~Client.get_contacts`.
* …know the user has a common chat with you, you can :meth:`~Client.get_participants` of the chat in common.
* …know the username of the user, group, or channel, you can :meth:`~Client.resolve_username`.
* …are a bot responding to users, you will be able to access the :attr:`types.Message.sender`.
Chats access hash
-----------------
Users, supergroups and channels all need an :term:`access hash`.
In Telethon, the :class:`~types.PackedChat` is the recommended way to deal with the identifier-hash pairs.
This compact type can be used anywhere a chat is expected.
It's designed to be easy to store and cache in any way your application chooses.
Bot accounts can get away with an invalid :term:`access hash` for certain operations under certain conditions.
The same is true for user accounts, although to a lesser extent.
When using just the identifier to refer to a chat, Telethon will attempt to retrieve its hash from its in-memory cache.
If this fails, an invalid hash will be used. This may or may not make the API call succeed.
For this reason, it is recommended that you always use :class:`~types.PackedChat` instead.
Remember that an :term:`access hash` is account-bound.
You cannot obtain an :term:`access hash` in Account-A and use it in Account-B.

View File

@@ -0,0 +1,40 @@
RPC Errors
==========
.. currentmodule:: telethon
:term:`RPC` stands for Remote Procedure Call.
By extension, RPC Errors occur when a RPC fails to execute in the server.
In Telethon, a :term:`RPC error` corresponds to the :class:`RpcError` class.
Telethon will only ever raise :class:`RpcError` when the result to a :term:`RPC` is an error.
If the error is raised, you know it comes from Telegram.
Consequently, when using :term:`Raw API`, if a :class:`RpcError` occurs, it is never a bug in the library.
:term:`RPC error` consist of an integer :attr:`~RpcError.code` and a string :attr:`~RpcError.name`.
The :attr:`RpcError.code` is roughly the same as `HTTP status codes <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`_.
The :attr:`RpcError.name` is often a string in ``SCREAMING_CASE`` and refers to what went wrong.
Certain error names also contain an integer value.
This value is removed from the :attr:`~RpcError.name` and put into :attr:`RpcError.value`.
If Telegram responds with ``FLOOD_WAIT_60``, the name would be ``'FLOOD_WAIT'`` and the value ``60``.
A very common error is ``FLOOD_WAIT``.
It occurs when you have attempted to use a request too many times during a certain window of time:
.. code-block:: python
import asyncio
from telethon import RpcError
try:
await client.send_message('me', 'Spam')
except RpcError as e:
# If we get a flood error, sleep. Else, propagate the error.
if e.name == 'FLOOD_WAIT':
await asyncio.sleep(e.value)
else:
raise
Note that the library can automatically handle and retry on ``FLOOD_WAIT`` for you.
Refer to the ``flood_sleep_threshold`` of the :class:`Client` to learn how.

View File

@@ -0,0 +1,66 @@
The Full API
============
.. currentmodule:: telethon
The API surface offered by Telethon is not exhaustive.
Telegram is constantly adding new features, and both implementing and documenting custom methods would an exhausting, never-ending job.
Telethon concedes to this fact and implements only commonly-used features to keep a lean API.
Access to the entirity of Telegram's API via Telethon's :term:`Raw API` is a necessary evil.
The ``telethon._tl`` module has a leading underscore to signal that it is private.
It is not covered by the semver guarantees of the library, but you may need to use it regardless.
If the :class:`Client` doesn't offer a method for what you need, using the :term:`Raw API` is inevitable.
Invoking Raw API methods
------------------------
The :term:`Raw API` can be *invoked* in a very similar way to other client methods:
.. code-block:: python
from telethon import _tl as tl
was_reset = await client(tl.functions.account.reset_wall_papers())
Inside ``telethon._tl.functions`` you will find a function for every single :term:`RPC` supported by Telegram.
The parameters are keyword-only and do not have defaults.
Whatever arguments you pass is exactly what Telegram will receive.
Whatever is returned is exactly what Telegram responded with.
All functions inside ``telethon._tl.functions`` will return the serialized request.
When calling a :class:`Client` instance with this request as input, it will be sent to Telegram and wait for a response.
Multiple requests may be in-flight at the same time, specially when using :mod:`asyncio`.
Telethon will attempt to combine these into a single "container" when possible as an optimization.
Exploring the Raw API
---------------------
Everything under ``telethon._tl.types`` implements :func:`repr`.
This means you can print any response and get the Python representation of that object.
All types are proper classes with attributes.
You do not need to use a regular expression on the string representation to access the field you want.
Most :term:`RPC` return an abstract class from ``telethon._tl.abcs``.
To check for a concrete type, you can use :func:`isinstance`:
.. code-block:: python
invite = await client(tl.functions.messages.check_chat_invite(hash='aBcDeF'))
if isinstance(invite, tl.types.ChatInviteAlready):
print(invite.chat)
The ``telethon._tl`` module is not documented here because it would result in tens of megabytes.
Instead, there are multiple alternatives:
* Use Telethon's separate site to search in the `Telethon Raw API <https://tl.telethon.dev/>`_.
This is the recommended way. It also features auto-generated examples.
* Use Python's built-in :func:`help` and :func:`dir` to help you navigate the module.
* Use an editor with autocompletion support.
* Choose the right layer from `Telegram's official API Layers <https://core.telegram.org/api/layers>`_.
Note that the `TL Schema <https://core.telegram.org/schema>`_ might not be up-to-date.

View File

@@ -0,0 +1,42 @@
Glossary
========
.. currentmodule:: telethon
.. glossary::
:sorted:
chat
A :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
.. seealso:: The :doc:`../concepts/chats` concept.
Raw API
Functions and types under ``telethon._tl`` that enable access to all of Telegram's API.
.. seealso:: The :doc:`../concepts/full-api` concept.
access hash
Account-bound integer tied to a specific resource.
Users, channels, photos and documents are all resources with an access hash.
The access hash doesn't change, but every account will see a different value for the same resource.
RPC
Remote Procedure Call.
Invoked when calling a :class:`Client` with a function from ``telethon._tl.functions``.
RPC Error
Error type returned by Telegram.
:class:`RpcError` contains an integer code similar to HTTP status codes and a name.
.. seealso:: The :doc:`../concepts/errors` concept.
session
Data used to securely connect to Telegram and other state related to the logged-in account.
.. seealso:: The :doc:`../concepts/sessions` concept.
MTProto
Mobile Transport Protocol used to interact with Telegram's API.
.. seealso:: The :doc:`../concepts/botapi-vs-mtproto` concept.

View File

@@ -0,0 +1,55 @@
Sessions
========
.. currentmodule:: telethon
In Telethon, the word :term:`session` is used to refer to the set of data needed to connect to Telegram.
This includes the server address of your home datacenter, as well as the authorization key bound to an account.
When you first connect to Telegram, an authorization key is generated to encrypt all communication.
After login, Telegram remembers this authorization key as logged-in, so you don't need to login again.
.. important::
**Do not leak the session file!**
Anyone with that file can login to the account stored in it.
If you believe someone else has obtained this file, immediately revoke all active sessions from an official client.
Some auxiliary information such as the user ID of the logged-in user is also kept.
The update state, which can change every time an update is received from Telegram, is also stored in the session.
Telethon needs this information to catch up on all missed updates while your code was not running.
This is why it's important to call :meth:`Client.disconnect`.
Doing so flushes all the update state to the session and saves it.
Session files
-------------
Telethon defaults to using SQLite to store the session state.
The session state is written to ``.session`` files, so make sure your VCS ignores them!
To make sure the ``.session`` file is saved, you should call :meth:`Client.disconnect` before exiting the program.
The first parameter in the :class:`Client` constructor is the session to use.
You can use a `str`, a :class:`pathlib.Path` or a :class:`session.Storage`.
The string or path are relative to the Current Working Directory.
You can use absolute paths or relative paths to folders elsewhere.
The ``.session`` extension is automatically added if the path has no extension.
Session storages
----------------
The :class:`session.Storage` abstract base class defines the required methods to create custom storages.
Telethon comes with two built-in storages:
* :class:`~session.SqliteSession`. This is used by default when a string or path is used.
* :class:`~session.MemorySession`. This is used by default when the path is ``None``.
You can also use it directly when you have a :class:`~session.Session` instance.
It's useful when you don't have file-system access.
If you would like to store the session state in a different way, you can subclass :class:`session.Storage`.
Some Python installations do not have the ``sqlite3`` module.
In this case, attempting to use the default :class:`~session.SqliteSession` will fail.
If this happens, you can try reinstalling Python.
If you still don't have the ``sqlite3`` module, you should use a different storage.

View File

@@ -0,0 +1,75 @@
Updates
=======
.. currentmodule:: telethon
Updates are an important topic in a messaging platform like Telegram.
After all, you want to be notified as soon as certain events happen, such as new message arrives.
Telethon abstracts away Telegram updates with :mod:`~telethon.events`.
.. important::
It is strongly advised to configure logging when working with events:
.. code-block:: python
import logging
logging.basicConfig(
format='[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s',
level=logging.WARNING
)
With the above, you will see all warnings and errors and when they happened.
Filtering events
----------------
There is no way to tell Telegram to only send certain updates.
Telethon must be received and process all updates to ensure correct ordering.
Filters are not magic.
They work all the same as ``if`` conditions inside your event handlers.
However, they offer a more convenient and consistent way to check for certain conditions.
All built-in filters can be found in :mod:`telethon.events.filters`.
When registering an event handler, you can optionally define the filter to use.
You can retrieve a handler's filter with :meth:`~Client.get_handler_filter`.
You can set (and overwrite) a handler's filter with :meth:`~Client.set_handler_filter`.
Filters are meant to be fast and never raise exceptions.
For this reason, filters cannot be asynchronous.
This reduces the chance a filter will do slow IO and potentially fail.
A filter is simply a callable function that takes an event as input and returns a boolean.
If the filter returns ``True``, the handler will be called.
Using this knowledge, you can create custom filters too.
If you need state, you can use a class with a ``__call__`` method defined:
.. code-block:: python
def only_odd_messages(event):
"A filter that only handles messages when their ID is divisible by 2"
return event.id % 2 == 0
client.add_event_handler(handler, events.NewMessage, only_odd_messages)
# ...
class OnlyDivisibleMessages:
"A filter that only handles messages when their ID is divisible by some amount"
def __init__(self, divisible_by):
self.divisible_by = divisible_by
def __call__(self, event):
return event.id % self.divisible_by == 0
client.add_event_handler(handler, events.NewMessage, OnlyDivisibleMessages(7))
Custom filters should accept any :class:`~events.Event`.
You can use :func:`isinstance` if your filter can only deal with certain types of events.
If you need to perform asynchronous operations, you can't use a filter.
Instead, manually check for those conditions inside your handler.