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

@ -8,13 +8,15 @@ python tools/codegen.py
Formatting, type-checking and testing:
```sh
pip install isort black mypy pytest pytest-asyncio
pip install -e client/[dev]
python tools/check.py
```
Documentation:
Documentation (requires [sphinx](https://www.sphinx-doc.org) and [graphviz](https://www.graphviz.org)'s `dot`):
```sh
pip install sphinx_rtd_theme
pip install -e client/[doc]
python tools/docgen.py
```
Note that multiple optional dependency sets can be specified by separating them with a comma (`[dev,doc]`).

View File

@ -0,0 +1,59 @@
Installation
============
Telethon is a Python 3 library, which means you need to download and install Python to use it.
Installing Python, using virtual environments, and the basics of the language, are outside of the scope of this guide.
You can find the official resources to `download Python <https://www.python.org/downloads/>`_,
learn about the `Python Setup and Usage <https://docs.python.org/3/using/index.html>`_ for different platforms,
or follow the `The Python Tutorial <https://docs.python.org/3/tutorial/index.html>`_ to learn the basics.
These are not necessarily the best resources to learn, but they are official.
Be sure to search online if you prefer learning in video form or otherwise.
You can confirm that you have Python installed with:
.. code-block:: shell
python --version
Which should print something similar to ``Python 3.11.5`` (or newer).
Installing the latest stable version
------------------------------------
Once you have a working Python 3 installation, you can install or upgrade the ``telethon`` package with ``pip``:
.. code-block:: shell
python -m pip install --upgrade telethon
Be sure to use lock-files if your project depends on a specific, older version of the library!
Installing development versions
-------------------------------
If you want the *latest* unreleased changes, you can run the following command instead:
.. code-block:: shell
python -m pip install --upgrade https://github.com/LonamiWebs/Telethon/archive/v2.zip
.. note::
The development version may have bugs and is not recommended for production use.
However, when you are `reporting a library bug <https://github.com/LonamiWebs/Telethon/issues/>`,
you must reproduce the issue in this version before reporting the problem.
Verifying the installation
--------------------------
To verify that the library is installed correctly, run the following command:
.. code-block:: shell
python -c "import telethon; print(telethon.__version__)"
The version number of the library should show in the output.

View File

@ -0,0 +1,32 @@
Next steps
==========
.. currentmodule:: telethon
By now, you should have successfully gone through both the :doc:`installation` and :doc:`signing-in` processes.
With a :class:`Client` instance connected and authorized, you can send any request to Telegram.
Some requests are bot-specific, and some are user-specific, but most can be used by any account.
You will need to have the correct permissions and pass valid parameters, but after that, your imagination is the limit.
Telethon features extensive documentation for every public item offered by the library.
All methods within the :class:`Client` also contain one or more examples on how to use them.
Whatever you build, remember to comply with both `Telegram's Terms of Service <https://telegram.org/tos>`_
and `Telegram's API ToS <https://core.telegram.org/api/terms>`_.
There are `several requests that applications must make <https://core.telegram.org/api/config#terms-of-service>`_:
.. epigraph::
[…] when logging in as an existing user, apps are supposed to call :tl:`help.getTermsOfServiceUpdate`
to check for any updates to the Terms of Service;
this call should be repeated after ``expires`` seconds have elapsed.
If an update to the Terms Of Service is available, clients are supposed to show a consent popup;
if accepted, clients should call :tl:`help.acceptTermsOfService`,
providing the ``termsOfService id`` JSON object;
in case of denial, clients are to delete the account using :tl:`account.deleteAccount`,
providing Decline ToS update as deletion reason.
The library will not make these calls for you, as it cannot know how users interact with the application being developed.
If you use an official client alongside the application you are developing,
it should be safe to rely on that client making the requests instead.

View File

@ -0,0 +1,211 @@
Signing in
==========
.. currentmodule:: telethon
Most of Telegram's API methods are gated behind an account login.
But before you can interact with the API at all, you will need to obtain an API ID and hash pair for your application.
Registering your Telegram application
-------------------------------------
Before working with Telegram's API, you (as the application developer) need to get an API ID and hash:
1. `Login to your Telegram account <https://my.telegram.org/>`_ with the phone number of the developer account to use.
2. Click under *API Development tools*.
3. A *Create new application* window will appear. Fill in your application details.
There is no need to enter any *URL*, and only the first two fields (*App title* and *Short name*) can currently be changed later.
4. Click on *Create application* at the end.
Remember that your **API hash is secret** and Telegram won't let you revoke it.
Don't post it anywhere!
This API ID and hash can now be used to develop an application using Telegram's API.
Telethon consumes this API ID and hash in order to make the requests to Telegram.
It is important to note that this API ID and hash is attached to a developer account,
and can be used to develop applications or otherwise using libraries such as Telethon.
The *users* of the application you develop do *not* need to provide their own API ID and hash.
The API ID and hash values are meant to be hardcoded in the application.
Any user is then able to login with just their phone number or bot token, even if they have not registered an application themselves.
.. important::
The API ID and hash are meant to be *secret*, but Python is often distributed in source-code form.
These two things conflict with eachother!
You can opt to obfuscate the values somehow, or perhaps distribute an executable binary file instead.
Depending on what you are developing, it might be reasonable to expect users to provide their own API ID and hash instead.
Official applications *also* must embed the API ID and hash, but these are often distributed as binary files.
Whatever you do, **do not use other people's API ID and hash!**
Telegram may detect this as suspicious and ban the accounts.
If you receive an error, Telegram is likely blocking the registration of a new applications.
The best you can do is wait and try again later.
If the issue persists, you may try contacting them, using a proxy or using a VPN.
Be aware that some phone numbers are not eligible to register applications with.
Interactive login
-----------------
The library offers a method for "quick and dirty" scripts known as :meth:`~Client.interactive_login`.
This method will first check whether the account was previously logged-in, and if not, ask for a phone number to be input.
You can write the code in a file (such as ``hello.py``) and then run it, or use the built-in ``asyncio``-enabled REPL.
For this tutorial, we'll be using the ``asyncio`` REPL:
.. code-block:: shell
python -m asyncio
.. important::
If you opt to write your code in a file, do **not** call your script ``telethon.py``!
Python will try to import from there and it will fail with an error such as "ImportError: cannot import name ...".
The first thing we need to do is import the :class:`Client` class and create an instance of it:
.. code-block:: python
from telethon import Client
client = Client('name', 12345, '0123456789abcdef0123456789abcdef')
The second and third parameters must be the API ID and hash, respectively.
We have a client instance now, but we can't send requests to Telegram until we connect!
So the next step is to :meth:`~Client.connect`:
.. code-block:: python
await client.connect()
If all went well, you will have connected to one of Telegram's servers.
If you run into issues, you might need to try a different hosting provider or use some sort of proxy.
Once you're connected, we can begin the :meth:`~Client.interactive_login`:
.. code-block:: python
await client.interactive_login()
Do as the prompts say on the terminal, and you will have successfully logged-in!
Once you're done, make sure to :meth:`~Client.disconnect` for a graceful shutdown.
Manual login
------------
We've talked about the second and third parameters of the :class:`Client` constructor, but not the first:
.. code-block:: python
client = Client('name', 12345, '0123456789abcdef0123456789abcdef')
The first parameter is the "session".
When using a string or a :class:`~pathlib.Path`, the library will create a SQLite database in that path.
The session path can contain directory separators and live anywhere in the file system.
Telethon will automatically append the ``.session`` extension if you don't provide any.
Briefly, the session contains some of the information needed to connect to Telegram.
This includes the datacenter belonging to the account logged-in, and the authorization key used for encryption, among other things.
.. 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.
Let's take a look at what :meth:`~Client.interactive_login` does under the hood.
1. First, it's using an equivalent of :meth:`~Client.is_authorized` to check whether the session was logged-in previously.
2. Then, it will either :meth:`~Client.bot_sign_in` with a bot token or :meth:`~Client.request_login_code` with a phone number.
* If it logged-in as a bot account, a :class:`~types.User` is returned and we're done.
* Otherwise, a login code was sent. Go to step 3.
3. Attempt to complete the user sign-in with :meth:`~Client.sign_in`, by entering the login code.
* If a :class:`~types.User` is returned, we're done.
* Otherwise, a 2FA password is required. Go to step 4.
4. Use :meth:`Client.check_password` to check that the password is correct.
* If the password is correct, :class:`~types.User` is returned and we're done.
Put into code, a user can thus login as follows:
.. code-block:: python
from telethon import Client
from telethon.types import User
# SESSION, API_ID, API_HASH should be previously defined in your code
async with Client(SESSION, API_ID, API_HASH) as client:
if not await client.is_authorized():
phone = input('phone: ')
login_token = await client.request_login_code(phone_or_token)
code = input('code: ')
user_or_token = await client.sign_in(login_token, code)
if isinstance(user_or_token, User):
return user_or_token
# user_or_token is PasswordToken
password_token = user_or_token
import getpass
password = getpass.getpass("password: ")
user = await client.check_password(password_token, password)
return user
A bot account does not need to request login code and cannot have passwords, so the login flow is much simpler:
.. code-block:: python
from telethon import Client
# SESSION, API_ID, API_HASH should be previously defined in your code
async with Client(SESSION, API_ID, API_HASH) as client:
bot_token = input('token: ')
bot_user = await client.bot_sign_in(bot_token)
return bot_user
To get a bot account, you need to talk with `@BotFather <https://t.me/BotFather>`_.
You may have noticed the ``async with`` keywords.
The :class:`Client` can be used in a context-manager.
This will automatically call :meth:`Client.connect` and :meth:`Client.disconnect` for you.
A good way to structure your code is as follows:
.. code-block:: python
import asyncio
from telethon import Client
SESSION = ...
API_ID = ...
API_HASH = ...
async def main():
async with Client(SESSION, API_ID, API_HASH) as client:
... # use client to your heart's content
if __name__ == '__main__':
asyncio.run(main())
This way, both the :mod:`asyncio` event loop and the :class:`Client` will exit cleanly.
Otherwise, you might run into errors such as tasks being destroyed while pending.
.. note::
Once a :class:`Client` instance has been connected, you cannot change the :mod:`asyncio` event loop.
Methods like :func:`asyncio.run` setup and tear-down a new event loop every time.
If the loop changes, the client is likely to be "stuck" because its loop cannot advance.

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.

View File

@ -3,6 +3,11 @@
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import sys
sys.path.insert(0, os.path.abspath(os.curdir)) # for custom extensions
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
@ -14,11 +19,21 @@ release = "2.0.0a0"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
# extensions = []
templates_path = ["_templates"]
# exclude_patterns = []
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.graphviz",
"roles.tl",
]
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
tl_ref_url = "https://tl.telethon.dev"
autodoc_default_options = {
"members": True,
"undoc-members": True,
}
modindex_common_prefix = ["telethon."]
graphviz_output_format = "svg"
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

View File

@ -0,0 +1,8 @@
Changelog (Version History)
===========================
v2 alpha
--------
WIP!

View File

@ -0,0 +1,17 @@
Coding style
============
Knowledge of Python is a obviously a must to develop a Python library.
A good online resource is `Dive Into Python 3 <http://www.diveintopython3.net/>`_.
Telethon uses multiple tools to automatically format the code and check for linting rules.
This means you can simply ignore formatting and let the tools handle it for you.
You can find these tools under the ``tools/`` folder.
The documentation is written with mostly a newline after every period.
This is not a hard rule.
Lines can be cut earlier if they become too long to be comfortable.
Commit messages should be short and descriptive.
They should start with an action in the present ("Fix" and not "Fixed").
This saves a few characters and represents what the commit will "do" after applied.

View File

@ -0,0 +1,4 @@
Migrating from v1 to v2
=======================
WIP!

View File

@ -0,0 +1,10 @@
Philosophy
==========
* Dependencies should only be added when absolutely necessary.
* Dependencies written in anything other than Python cannot be mandatory.
* The library must work correctly with no system dependencies other than Python 3.
* Strict type-checking is required to pass everywhere in the library to make upgrades easier.
* The code structure must make use of hard and clear boundaries to keep the different parts decoupled.
* The API should cover only the most commonly used features to avoid bloat and reduce maintenance costs.
* Documentation must be a pleasure to use and contain plenty of code examples.

View File

@ -0,0 +1,95 @@
Project Structure
=================
.. currentmodule:: telethon
The repository contains several folders, each with their own "package".
benches/
--------
This folder contains different benchmarks.
Pretty straightforward.
stubs/
------
If a dependency doesn't support typing, files here must work around that.
tools/
------
Various utility scripts.
Each script should have a "comment" at the top explaining what they are for.
See ``DEVELOPING.md`` in the repository root to learn how to use some of the tools.
generator/
----------
A package that should not be published and is only used when developing the library.
The implementation is private and exists under the ``src/*/_impl/`` folder.
Only select parts are exported under public modules.
Tests live under ``tests/``.
The implementation consists of a parser and a code generator.
The parser is able to read parse ``.tl`` files (Type-Language definition files).
It doesn't do anything with the files other than to represent the content as Python objects.
The code generator uses the parsed definitions to generate Python code.
Most of the code to serialize and deserialize objects lives under ``serde/``.
An in-memory "filesystem" structure is kept before writing all files to disk.
This makes it possible to execute most of the process in a sans-io manner.
Once the code generation finishes, all files are written to disk at once.
See ``DEVELOPING.md`` in the repository root to learn how to generate code.
client/
-------
The Telethon client library and documentation lives here.
This is the package that gets published.
The implementation is private and exists under the ``src/*/_impl/`` folder.
Only select parts are exported under public modules.
Tests live under ``tests/``.
The client implementation consists of several subpackages.
The ``tl`` package sits at the bottom.
It is where the generated code is placed.
It also contains some of the definitions needed for the generated code to work.
Even though all the :term:`RPC` live here, this package can't do anything by itself.
The ``crypto`` package implements all the encryption and decryption rules used by Telegram.
Details concerning the :term:`MTProto` are mostly avoided, so the package can be generally useful.
The ``mtproto`` package implements the logic required to talk to Telegram.
It is implemented in a sans-io manner.
This package is responsible for generating an authorization key and serializing packets.
It also contains some optimizations which are not strictly necessary when implementing the library.
The ``mtsender`` package simply adds IO to ``mtproto``.
It is responsible for driving the network, enqueuing requests, and waiting for results.
The ``session`` crate implements what's needed to manage the :term:`session` state.
The logic to handle and correctly order updates also lives here, in a sans-io manner.
The ``client`` ties everything together.
This is what defines the Pythonic API to interact with Telegram.
Custom object and event types also live here.
Even though only common methods are implemented, the code is still huge.
For this reason, the :class:`Client` implementation is separated from the class definition.
The class definition only contains documentation and calls functions defined in other files.
A tool under ``tools/`` exists to make it easy to keep these two in sync.
If you plan to port the library to a different language, good luck!
You will need a code generator, the ``crypto``, ``mtproto`` and ``mtsender`` packages to have an initial working version.
The tests are your friend, write them too!

View File

@ -1,15 +1,126 @@
Telethon's documentation
========================
.. |svglogo| image:: ../../logo.svg
:width: 24pt
:height: 24pt
.. only:: html
.. highlights:: |svglogo| **Welcome to Telethon's documentation!**
.. only:: not html
.. highlights:: **Welcome to Telethon's documentation!**
.. code-block:: python
import asyncio
from telethon import Client, events
from telethon.events import filters
async def main():
async with Client('name', api_id, api_hash) as client:
me = await client.interactive_login()
await client.send_message(me, f'Hello, {me.full_name}!')
@client.on(events.NewMessage, filters.Text(r'(?i)hello'))
async def handler(event):
await event.reply('Hey!')
await client.run_until_disconnected()
asyncio.run(main())
* Are you new here? Jump straight into :doc:`basic/installation`!
* Looking for the Client API reference? See :doc:`modules/client`.
* Did you upgrade the library? Please read :doc:`developing/changelog`.
* Coming from Bot API or want to create new bots? See :doc:`concepts/botapi-vs-mtproto`.
* Used Telethon before v2.0? See :doc:`developing/migration-guide`.
* Want to hack away with the raw API? Search in `Telethon Raw API <https://tl.telethon.dev/>`_.
Preface
=======
.. rubric:: What is this?
Telegram is a popular messaging application.
This library is meant to make it easy for you to write Python programs that can interact with Telegram.
Think of it as a wrapper that has already done the hard work for you, so you can focus on developing an application.
.. rubric:: How should I use the documentation?
This documentation is divided in multiple sections. The first few sections are a guide, while others contain the API reference or a glossary of terms.
The documentation assumes some familiarity with Python.
If you are getting started with the library, you should follow the documentation in order by pressing the "Next" button at the bottom-right of every page.
You can also use the menu on the left to quickly skip over sections if you're looking for something in particular or want to continue where you left off.
First steps
===========
In this section you will learn how to install the library and login to your Telegram account.
:doc:`‣ Start reading Installation <basic/installation>`
.. toctree::
:maxdepth: 2
:caption: Contents:
:hidden:
:caption: First steps
basic/installation
basic/signing-in
basic/next-steps
API reference
=============
Indices and tables
==================
This section contains all the functions and types offered by the library.
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
:doc:`‣ Start reading Client API <modules/client>`
.. toctree::
:hidden:
:caption: API reference
modules/client
modules/events
modules/types
modules/sessions
Concepts
========
A more in-depth explanation of some of the concepts and words used in Telethon.
:doc:`‣ Start reading Chat concept <concepts/chats>`
.. toctree::
:hidden:
:caption: Concepts
concepts/chats
concepts/updates
concepts/sessions
concepts/errors
concepts/botapi-vs-mtproto
concepts/full-api
concepts/glossary
Developing
==========
Tips and tricks to develop both with the library and for the library.
:doc:`‣ Start reading Changelog <developing/changelog>`
.. toctree::
:hidden:
:caption: Developing
developing/changelog
developing/migration-guide
developing/philosophy.rst
developing/coding-style.rst
developing/project-structure.rst

View File

@ -0,0 +1,4 @@
Client
======
.. autoclass:: telethon.Client

View File

@ -0,0 +1,12 @@
Events and filters
==================
Events
------
.. automodule:: telethon.events
Filters
-------
.. automodule:: telethon.events.filters

View File

@ -0,0 +1,4 @@
Session storages
================
.. automodule:: telethon.session

View File

@ -0,0 +1,6 @@
Types
=====
.. automodule:: telethon.types
.. autoclass:: telethon.RpcError

34
client/doc/roles/tl.py Normal file
View File

@ -0,0 +1,34 @@
from docutils import nodes, utils
from docutils.parsers.rst.roles import set_classes
def make_link_node(rawtext, app, name, options):
try:
base = app.config.tl_ref_url
if not base:
raise AttributeError
except AttributeError as e:
raise ValueError("tl_ref_url config value is not set") from e
if base[-1] != "/":
base += "/"
set_classes(options)
node = nodes.reference(
rawtext, utils.unescape(name), refuri="{}?q={}".format(base, name), **options
)
return node
def tl_role(name, rawtext, text, lineno, inliner, options=None, content=None):
if options is None:
options = {}
app = inliner.document.settings.env.app
node = make_link_node(rawtext, app, text, options)
return [node], []
def setup(app):
app.add_role("tl", tl_role)
app.add_config_value("tl_ref_url", None, "env")

View File

@ -28,9 +28,16 @@ dynamic = ["version"]
[project.optional-dependencies]
cryptg = ["cryptg~=0.4"]
dev = [
"isort~=5.12",
"black~=23.3.0",
"mypy~=1.3",
"pytest~=7.3",
"pytest-asyncio~=0.21",
]
doc = [
"sphinx_rtd_theme~=1.2",
"types-docutils~=0.20",
]
[project.urls]
"Homepage" = "https://telethon.dev/"
@ -43,4 +50,4 @@ requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools.dynamic]
version = {attr = "telethon.__version__"}
version = {attr = "telethon.version.__version__"}