mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-06-17 18:46:40 +00:00
Continue documentation and matching documented behaviour
This commit is contained in:
parent
6e88264b28
commit
2def0a169c
@ -29,10 +29,10 @@ Once you have a working Python 3 installation, you can install or upgrade the ``
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
python -m pip install --upgrade telethon
|
python -m pip install --upgrade "telethon~=2.0"
|
||||||
|
|
||||||
Be sure to use lock-files if your project!
|
Be sure to use lock-files if your project!
|
||||||
The above is just a quick way to get started and install Telethon globally.
|
The above is just a quick way to get started and install a `v2-compatible <https://peps.python.org/pep-0440/#compatible-release>`_ Telethon globally.
|
||||||
|
|
||||||
|
|
||||||
Installing development versions
|
Installing development versions
|
||||||
@ -47,7 +47,7 @@ If you want the *latest* unreleased changes, you can run the following command i
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The development version may have bugs and is not recommended for production use.
|
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/>`,
|
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.
|
you must reproduce the issue in this version before reporting the problem.
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ 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.
|
Be aware that some phone numbers are not eligible to register applications with.
|
||||||
|
|
||||||
|
|
||||||
|
.. _interactive login:
|
||||||
|
|
||||||
Interactive login
|
Interactive login
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@ -131,6 +133,11 @@ If you want to automatically login as a bot when needed, you can do so without a
|
|||||||
Manual login
|
Manual login
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
You can safely skip to :doc:`next-steps` if you've already completed the :ref:`interactive login`.
|
||||||
|
This section is only of interest if you want more control over how to manually login.
|
||||||
|
|
||||||
We've talked about the second and third parameters of the :class:`Client` constructor, but not the first:
|
We've talked about the second and third parameters of the :class:`Client` constructor, but not the first:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@ -143,7 +150,7 @@ The session path can contain directory separators and live anywhere in the file
|
|||||||
Telethon will automatically append the ``.session`` extension if you don't provide any.
|
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.
|
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.
|
This includes the data center belonging to the account logged-in, and the authorization key used for encryption, among other things.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ There is no HTTP connection, no "polling", and no "web hooks".
|
|||||||
We can compare the two visually:
|
We can compare the two visually:
|
||||||
|
|
||||||
.. graphviz::
|
.. graphviz::
|
||||||
:caption: Communication between a Client and the Bot API
|
:caption: Communication between a Client and the HTTP Bot API
|
||||||
|
|
||||||
digraph botapi {
|
digraph botapi {
|
||||||
rankdir=LR;
|
rankdir=LR;
|
||||||
@ -86,7 +86,7 @@ We can compare the two visually:
|
|||||||
}
|
}
|
||||||
|
|
||||||
.. graphviz::
|
.. graphviz::
|
||||||
:caption: Communication between a Client and the MTProto API
|
:caption: Communication between a Client and Telegram's API via MTProto
|
||||||
|
|
||||||
digraph botapi {
|
digraph botapi {
|
||||||
rankdir=LR;
|
rankdir=LR;
|
||||||
@ -119,7 +119,7 @@ If the above points convinced you to switch to Telethon, the following short gui
|
|||||||
It doesn't matter if you wrote your bot with `requests <https://pypi.org/project/requests/>`_
|
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
|
and you were making API requests manually, or if you used a wrapper library like
|
||||||
`python-telegram-bot <https://python-telegram-bot.readthedocs.io>`_
|
`python-telegram-bot <https://python-telegram-bot.readthedocs.io>`_
|
||||||
or `pyTelegramBotAPI <https://pytba.readthedocs.io/en/latest/index.html>`.
|
or `pyTelegramBotAPI <https://pytba.readthedocs.io/en/latest/index.html>`_.
|
||||||
You will surely be pleased with Telethon!
|
You will surely be pleased with Telethon!
|
||||||
|
|
||||||
If you were using an asynchronous library like `aiohttp <https://docs.aiohttp.org/en/stable/>`_
|
If you were using an asynchronous library like `aiohttp <https://docs.aiohttp.org/en/stable/>`_
|
||||||
|
@ -39,7 +39,7 @@ Telegram Chat
|
|||||||
The Telegram API is very confusing when it comes to the word "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`.
|
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`.
|
In the :term:`TL` 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 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:`chat` always refers to small groups.
|
||||||
@ -48,7 +48,7 @@ A bare :tl:`channel` can have either the ``broadcast`` or the ``megagroup`` flag
|
|||||||
A bare :tl:`channel` with the ``broadcast`` flag set to :data:`True` is known as a broadcast channel.
|
A bare :tl:`channel` with the ``broadcast`` flag set to :data:`True` is known as a broadcast channel.
|
||||||
A bare :tl:`channel` with the ``megagroup`` flag set to :data:`True` is known as a supergroup.
|
A bare :tl:`channel` with the ``megagroup`` flag set to :data:`True` is known as a supergroup.
|
||||||
|
|
||||||
A bare :tl:`chat` with has less features than a bare :tl:`channel` ``megagroup``.
|
A bare :tl:`chat` has less features available than a bare :tl:`channel` ``megagroup``.
|
||||||
Official clients are very good at hiding this difference.
|
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.
|
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).
|
Doing things like setting a username is actually a two-step process (migration followed by updating the username).
|
||||||
@ -70,11 +70,21 @@ The Bot API follows a certain convention when it comes to identifiers:
|
|||||||
|
|
||||||
* User IDs are positive.
|
* User IDs are positive.
|
||||||
* Chat IDs are negative.
|
* Chat IDs are negative.
|
||||||
* Channel IDs are prefixed with ``-100``.
|
* Channel IDs are *also* negative, but are prefixed by ``-100``.
|
||||||
|
|
||||||
Telethon encourages the use of :class:`~types.PackedChat` instead of naked identifiers.
|
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.
|
As a reminder, negative identifiers are not supported in Telethon's chat-like parameters.
|
||||||
|
|
||||||
|
If you got an Bot API-style ID from somewhere else, you will need to explicitly say what type it is:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# If -1001234 is your ID...
|
||||||
|
from telethon.types import PackedChat, PackedType
|
||||||
|
chat = PackedChat(PackedType.BROADCAST, 1234, None)
|
||||||
|
# ...you need to explicitly create a PackedChat with id=1234 and set the corresponding type (a channel).
|
||||||
|
# The access hash (see below) will be ``None``, which may or may not work.
|
||||||
|
|
||||||
|
|
||||||
Encountering chats
|
Encountering chats
|
||||||
------------------
|
------------------
|
||||||
@ -88,6 +98,7 @@ If you:
|
|||||||
* …know the username of the user, group, or channel, you can :meth:`~Client.resolve_username`.
|
* …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`.
|
* …are a bot responding to users, you will be able to access the :attr:`types.Message.sender`.
|
||||||
|
|
||||||
|
|
||||||
Chats access hash
|
Chats access hash
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
125
client/doc/concepts/datacenters.rst
Normal file
125
client/doc/concepts/datacenters.rst
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
Data centers
|
||||||
|
============
|
||||||
|
|
||||||
|
.. currentmodule:: telethon
|
||||||
|
|
||||||
|
Telegram has multiple servers, known as *data centers* or MTProto servers, all over the globe.
|
||||||
|
This makes it possible to have reasonably low latency when sending messages.
|
||||||
|
|
||||||
|
When an account is created, Telegram chooses the most appropriated data center for you.
|
||||||
|
This means you *cannot* change what your "home data center" is.
|
||||||
|
However, `Telegram may change it after prolongued use from other locations <https://core.telegram.org/api/datacenter>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Connecting behind a proxy
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
You can change the way Telethon opens a connection to Telegram's data center by setting a different :class:`~telethon._impl.mtsender.sender.Connector`.
|
||||||
|
|
||||||
|
A connector is a function returning an asynchronous reader-writer pair.
|
||||||
|
The default connector is :func:`asyncio.open_connection`, defined as:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def default_connector(ip: str, port: int):
|
||||||
|
return asyncio.open_connection(ip, port)
|
||||||
|
|
||||||
|
While proxies are not directly supported in Telethon, you can change the connector to use a proxy.
|
||||||
|
Any proxy library that supports :mod:`asyncio`, such as `python-socks[asyncio] <https://pypi.org/project/python-socks/>`_, can be used:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from functools import partial
|
||||||
|
from python_socks.async_.asyncio import Proxy
|
||||||
|
from telethon import Client
|
||||||
|
|
||||||
|
async def my_proxy_connector(ip, port, *, proxy_url):
|
||||||
|
# Refer to python-socks for an up-to-date way to define and use proxies.
|
||||||
|
# This is just an example of a custom connector.
|
||||||
|
proxy = Proxy.from_url(proxy_url)
|
||||||
|
sock = await proxy.connect(dest_host='example.com', dest_port=443)
|
||||||
|
return await asyncio.open_connection(
|
||||||
|
host=ip,
|
||||||
|
port=port,
|
||||||
|
sock=sock,
|
||||||
|
ssl=ssl.create_default_context(),
|
||||||
|
server_hostname='example.com',
|
||||||
|
)
|
||||||
|
|
||||||
|
client = Client(..., connector=partial(
|
||||||
|
my_proxy_connector,
|
||||||
|
proxy_url='socks5://user:password@127.0.0.1:1080'
|
||||||
|
))
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Proxies can be used with Telethon, but they are not directly supported.
|
||||||
|
Any connection errors you encounter while using a proxy are therefore very unlikely to be errors in Telethon.
|
||||||
|
Connection errors when using custom connectors will *not* be considered bugs in the Telethon.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Some proxies only support HTTP traffic.
|
||||||
|
Telethon by default does not transmit HTTP-encoded packets.
|
||||||
|
This means some HTTP-only proxies may not work.
|
||||||
|
|
||||||
|
|
||||||
|
Test servers
|
||||||
|
------------
|
||||||
|
|
||||||
|
While you cannot change the production data center assigned to your account, you can tell Telethon to connect to a different server.
|
||||||
|
|
||||||
|
This is most useful to connect to the official Telegram test servers or `even your own <https://github.com/DavideGalilei/piltover>`_.
|
||||||
|
|
||||||
|
You need to import and define the :class:`session.DataCenter` to connect to when creating the :class:`Client`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import Client
|
||||||
|
from telethon.session import DataCenter
|
||||||
|
|
||||||
|
client = Client(..., datacenter=DataCenter(id=2, ipv4_addr='149.154.167.40:443'))
|
||||||
|
|
||||||
|
This will override the value coming from the :class:`~session.Session`.
|
||||||
|
You can get the test address for your account from `My Telegram <https://my.telegram.org>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Make sure the :doc:`sessions` you use for this client had not been created for the production servers before.
|
||||||
|
The library will attempt to use the existing authorization key saved based on the data center identifier.
|
||||||
|
This will most likely fail if you mix production and test servers.
|
||||||
|
|
||||||
|
|
||||||
|
There are public phone numbers anyone can use, with the following format:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
:caption: 99966XYYYY test phone number, X being the datacenter identifier and YYYY random digits
|
||||||
|
|
||||||
|
99966 X YYYY
|
||||||
|
\___/ \_/ \__/
|
||||||
|
| | `- random number
|
||||||
|
| `- datacenter identifier
|
||||||
|
`- fixed digits
|
||||||
|
|
||||||
|
For example, the test phone number 1234 for the datacenter 2 would be 9996621234.
|
||||||
|
|
||||||
|
The confirmation code to complete the login is the datacenter identifier repeated five times, in this case, 22222.
|
||||||
|
|
||||||
|
Therefore, it is possible to automate the login procedure, assuming the account exists and there is no 2-factor authentication:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from random import randrange
|
||||||
|
from telethon import Client
|
||||||
|
from telethon.session import DataCenter
|
||||||
|
|
||||||
|
datacenter = DataCenter(id=2, ipv4_addr='149.154.167.40:443')
|
||||||
|
phone = f'{randrange(1, 9999):04}'
|
||||||
|
login_code = str(datacenter.id) * 5
|
||||||
|
client = Client(..., datacenter=datacenter)
|
||||||
|
|
||||||
|
async with client:
|
||||||
|
if not await client.is_authorized():
|
||||||
|
login_token = await client.request_login_code(phone_or_token)
|
||||||
|
await client.sign_in(login_token, login_code)
|
@ -9,7 +9,8 @@ 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.
|
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.
|
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.
|
Consequently, when using :term:`Raw API` directly, if a :class:`RpcError` occurs, it is *extremely unlikely* to be a bug in the library.
|
||||||
|
When :class:`RpcError`\ s are raised using the :term:`Raw API`, Telegram is the one that decided an error should occur.
|
||||||
|
|
||||||
:term:`RPC error` consist of an integer :attr:`~RpcError.code` and a string :attr:`~RpcError.name`.
|
: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.code` is roughly the same as `HTTP status codes <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`_.
|
||||||
@ -25,16 +26,15 @@ It occurs when you have attempted to use a request too many times during a certa
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from telethon import RpcError
|
from telethon import errors
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.send_message('me', 'Spam')
|
await client.send_message('me', 'Spam')
|
||||||
except RpcError as e:
|
except errors.FloodWait as e:
|
||||||
# If we get a flood error, sleep. Else, propagate the error.
|
# A flood error; sleep.
|
||||||
if e.name == 'FLOOD_WAIT':
|
await asyncio.sleep(e.value)
|
||||||
await asyncio.sleep(e.value)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
Note that the library can automatically handle and retry on ``FLOOD_WAIT`` for you.
|
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.
|
Refer to the ``flood_sleep_threshold`` of the :class:`Client` to learn how.
|
||||||
|
|
||||||
|
Refer to the documentation of the :data:`telethon.errors` pseudo-module for more details.
|
||||||
|
@ -10,7 +10,7 @@ Telethon concedes to this fact and implements only commonly-used features to kee
|
|||||||
Access to the entirity of Telegram's API via Telethon's :term:`Raw API` is a necessary evil.
|
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.
|
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.
|
It is not covered by the `semver <https://semver.org/>`_ 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.
|
If the :class:`Client` doesn't offer a method for what you need, using the :term:`Raw API` is inevitable.
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,43 @@ Messages
|
|||||||
Messages are at the heart of a messaging platform.
|
Messages are at the heart of a messaging platform.
|
||||||
In Telethon, you will be using the :class:`~types.Message` class to interact with them.
|
In Telethon, you will be using the :class:`~types.Message` class to interact with them.
|
||||||
|
|
||||||
|
|
||||||
|
Fetching messages
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The most common way to actively fetch messages using the :meth:`Client.get_messages` method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Get the last message in a chat (by setting the limit to 1).
|
||||||
|
last_message = (await client.get_messages(chat, 1))[0]
|
||||||
|
|
||||||
|
# Iterate over all messages in a chat, starting from the oldest message (by using reversed).
|
||||||
|
async for message in reversed(client.get_messages(chat)):
|
||||||
|
print(message.sender.name, message.text_html)
|
||||||
|
|
||||||
|
You can also perform a fuzzy text search with the :meth:`Client.search_messages` method.
|
||||||
|
The search will be performed server-side by Telegram, so the rules for how it works are also fuzzy.
|
||||||
|
|
||||||
|
If you want to search for messages in all the chats you're part of, you can use :meth:`Client.search_all_messages`.
|
||||||
|
|
||||||
|
Lastly, :meth:`Client.send_message` *also* returns the :class:`~types.Message` that you just sent.
|
||||||
|
|
||||||
|
The most common way to passively listen to incoming messages is using the :class:`~events.NewMessage` event:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import events
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def first(event):
|
||||||
|
print(event.chat.name, ':', event.text)
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
The :doc:`updates` concept for an in-depth explanation on using events.
|
||||||
|
|
||||||
|
|
||||||
.. _formatting:
|
.. _formatting:
|
||||||
|
|
||||||
Formatting messages
|
Formatting messages
|
||||||
|
@ -4,7 +4,7 @@ Sessions
|
|||||||
.. currentmodule:: telethon
|
.. currentmodule:: telethon
|
||||||
|
|
||||||
In Telethon, the word :term:`session` is used to refer to the set of data needed to connect to Telegram.
|
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.
|
This includes the server address of your home data center, 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.
|
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.
|
After login, Telegram remembers this authorization key as logged-in, so you don't need to login again.
|
||||||
|
|
||||||
@ -48,6 +48,9 @@ Telethon comes with two built-in storages:
|
|||||||
It's useful when you don't have file-system access.
|
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`.
|
If you would like to store the session state in a different way, you can subclass :class:`session.Storage`.
|
||||||
|
You may also find `custom third-party session storages in Telethon's wiki <https://github.com/LonamiWebs/Telethon/wiki/Session-Storages>`_.
|
||||||
|
Be careful with any third-party code you install, as they could steal the login credentials.
|
||||||
|
Only use session storages you trust, and pin the specific versions you have audited.
|
||||||
|
|
||||||
Some Python installations do not have the ``sqlite3`` module.
|
Some Python installations do not have the ``sqlite3`` module.
|
||||||
In this case, attempting to use the default :class:`~session.SqliteSession` will fail.
|
In this case, attempting to use the default :class:`~session.SqliteSession` will fail.
|
||||||
|
@ -23,6 +23,55 @@ Telethon abstracts away Telegram updates with :mod:`~telethon.events`.
|
|||||||
With the above, you will see all warnings and errors and when they happened.
|
With the above, you will see all warnings and errors and when they happened.
|
||||||
|
|
||||||
|
|
||||||
|
Listening to updates
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can define and register your own functions to be called when certain :mod:`telethon.events` occur.
|
||||||
|
|
||||||
|
The most common way is using the :meth:`Client.on` decorator to register your callback functions, often referred to as *handlers*:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import Client, events
|
||||||
|
from telethon.events import filters
|
||||||
|
|
||||||
|
bot = Client(...)
|
||||||
|
|
||||||
|
@bot.on(events.NewMessage, filters.Command('/start'))
|
||||||
|
async def handler(event: events.NewMessage):
|
||||||
|
await event.respond('Beep boop!')
|
||||||
|
|
||||||
|
The first parameter is the :class:`type` of one of the :mod:`telethon.events`, not an instance, so make sure you don't write parenthesis after it.
|
||||||
|
|
||||||
|
The second parameter is optional.
|
||||||
|
If provided, it must be a callable function that returns :data:`True` if the handler should run.
|
||||||
|
Built-in filter functions are available in the :mod:`~telethon.events.filters` module.
|
||||||
|
In this example, :class:`~events.filters.Command` means the handler will be called when the user sends */start* to the bot.
|
||||||
|
|
||||||
|
When your ``handler`` function is called, it will receive a single parameter, the event.
|
||||||
|
The event type is the same as the one you defined in the decorator when registering your handler.
|
||||||
|
You don't need to explicitly set the type hint, but you can do so if you want your IDE to assist in autocompletion.
|
||||||
|
|
||||||
|
If you cannot use decorators, you can use the :meth:`Client.add_event_handler` method instead.
|
||||||
|
The above code is equivalent to the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import Client, events
|
||||||
|
from telethon.events import filters
|
||||||
|
|
||||||
|
async def handler(event: events.NewMessage):
|
||||||
|
await event.respond('Beep boop!')
|
||||||
|
|
||||||
|
bot = Client(...)
|
||||||
|
bot.add_event_handler(handler, events.NewMessage, filters.Command('/start'))
|
||||||
|
|
||||||
|
|
||||||
|
Note how the above lets you defined the :class:`Client` instance *after* your handlers.
|
||||||
|
In other words, you can define your handlers without the :class:`Client` instance.
|
||||||
|
This may make it easier to place them in a separate file.
|
||||||
|
|
||||||
|
|
||||||
Filtering events
|
Filtering events
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -51,6 +100,12 @@ If you need state, you can use a class with a ``__call__`` method defined:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Anonymous filter which only handles messages with ID = 1000
|
||||||
|
client.add_event_handler(handler, events.NewMessage, lambda e: e.id == 1000)
|
||||||
|
# this parameter is the filter ^--------------------^
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
def only_odd_messages(event):
|
def only_odd_messages(event):
|
||||||
"A filter that only handles messages when their ID is divisible by 2"
|
"A filter that only handles messages when their ID is divisible by 2"
|
||||||
return event.id % 2 == 0
|
return event.id % 2 == 0
|
||||||
@ -75,6 +130,16 @@ You can use :func:`isinstance` if your filter can only deal with certain types o
|
|||||||
If you need to perform asynchronous operations, you can't use a filter.
|
If you need to perform asynchronous operations, you can't use a filter.
|
||||||
Instead, manually check for those conditions inside your handler.
|
Instead, manually check for those conditions inside your handler.
|
||||||
|
|
||||||
|
The filters work all the same when using :meth:`Client.on`.
|
||||||
|
This makes it very convenient to write custom filters using the :keyword:`lambda` syntax:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@client.on(events.NewMessage, lambda e: e.id == 1000)
|
||||||
|
async def handler(event):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Setting priority on handlers
|
Setting priority on handlers
|
||||||
----------------------------
|
----------------------------
|
||||||
@ -100,13 +165,26 @@ This is often the desired behaviour if you're using filters.
|
|||||||
|
|
||||||
If you have more complicated filters executed *inside* the handler,
|
If you have more complicated filters executed *inside* the handler,
|
||||||
Telethon believes your handler completed and will stop calling the rest.
|
Telethon believes your handler completed and will stop calling the rest.
|
||||||
If that's the case, you can instruct Telethon to check all your handlers:
|
If that's the case, you can :keyword:`return` :class:`events.Continue`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def first(event):
|
||||||
|
print('This is always called on new messages!')
|
||||||
|
return events.Continue
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def second(event):
|
||||||
|
print('Now this one runs as well!')
|
||||||
|
|
||||||
|
Alternatively, if this is *always* the behaviour you want, you can configure it in the :class:`Client`:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
client = Client(..., check_all_handlers=True)
|
client = Client(..., check_all_handlers=True)
|
||||||
# ^^^^^^^^^^^^^^^^^^^^^^^
|
# ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
# Now the code above will call both handlers
|
# Now the code above will call both handlers, even without returning events.Continue
|
||||||
|
|
||||||
If you need a more complicated setup, consider sorting all your handlers beforehand.
|
If you need a more complicated setup, consider sorting all your handlers beforehand.
|
||||||
Then, use :meth:`Client.add_event_handler` on all of them to ensure the correct order.
|
Then, use :meth:`Client.add_event_handler` on all of them to ensure the correct order.
|
||||||
|
@ -91,6 +91,7 @@ A more in-depth explanation of some of the concepts and words used in Telethon.
|
|||||||
concepts/errors
|
concepts/errors
|
||||||
concepts/botapi-vs-mtproto
|
concepts/botapi-vs-mtproto
|
||||||
concepts/full-api
|
concepts/full-api
|
||||||
|
concepts/datacenters
|
||||||
concepts/glossary
|
concepts/glossary
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@ The :class:`Client` class is the "entry point" of the library.
|
|||||||
|
|
||||||
Most client methods have an alias in the respective types.
|
Most client methods have an alias in the respective types.
|
||||||
For example, :meth:`Client.forward_messages` can also be invoked from :meth:`types.Message.forward`.
|
For example, :meth:`Client.forward_messages` can also be invoked from :meth:`types.Message.forward`.
|
||||||
With a few exceptions, "client.verb_object" methods also exist as "object.verb".
|
With a few exceptions, *client.verb_object* methods also exist as *object.verb*.
|
||||||
|
|
||||||
.. autoclass:: Client
|
.. autoclass:: Client
|
||||||
|
@ -152,6 +152,8 @@ class Client:
|
|||||||
:param catch_up:
|
:param catch_up:
|
||||||
Whether to "catch up" on updates that occured while the client was not connected.
|
Whether to "catch up" on updates that occured while the client was not connected.
|
||||||
|
|
||||||
|
If :data:`True`, all updates that occured while the client was offline will trigger your :doc:`event handlers </concepts/updates>`.
|
||||||
|
|
||||||
:param check_all_handlers:
|
:param check_all_handlers:
|
||||||
Whether to always check all event handlers or stop early.
|
Whether to always check all event handlers or stop early.
|
||||||
|
|
||||||
@ -159,21 +161,24 @@ class Client:
|
|||||||
By default, the library stops checking handlers as soon as a filter returns :data:`True`.
|
By default, the library stops checking handlers as soon as a filter returns :data:`True`.
|
||||||
|
|
||||||
By setting ``check_all_handlers=True``, the library will keep calling handlers after the first match.
|
By setting ``check_all_handlers=True``, the library will keep calling handlers after the first match.
|
||||||
|
Use :class:`telethon.events.Continue` instead if you only want this behaviour sometimes.
|
||||||
|
|
||||||
:param flood_sleep_threshold:
|
:param flood_sleep_threshold:
|
||||||
Maximum amount of time, in seconds, to automatically sleep before retrying a request.
|
Maximum amount of time, in seconds, to automatically sleep before retrying a request.
|
||||||
This sleeping occurs when ``FLOOD_WAIT`` :class:`~telethon.RpcError` is raised by Telegram.
|
This sleeping occurs when ``FLOOD_WAIT`` (and similar) :class:`~telethon.RpcError`\ s are raised by Telegram.
|
||||||
|
|
||||||
:param logger:
|
:param logger:
|
||||||
Logger for the client.
|
Logger for the client.
|
||||||
Any dependency of the client will use :meth:`logging.Logger.getChild`.
|
Any dependency of the client will use :meth:`logging.Logger.getChild`.
|
||||||
This effectively makes the parameter the root logger.
|
This effectively makes the parameter the root logger.
|
||||||
|
|
||||||
The default will get the logger for the package name from the root.
|
The default will get the logger for the package name from the root (usually *telethon*).
|
||||||
|
|
||||||
:param update_queue_limit:
|
:param update_queue_limit:
|
||||||
Maximum amount of updates to keep in memory before dropping them.
|
Maximum amount of updates to keep in memory before dropping them.
|
||||||
|
|
||||||
|
A warning will be logged on a cooldown if this limit is reached.
|
||||||
|
|
||||||
:param device_model:
|
:param device_model:
|
||||||
Device model.
|
Device model.
|
||||||
|
|
||||||
@ -184,19 +189,19 @@ class Client:
|
|||||||
Application version.
|
Application version.
|
||||||
|
|
||||||
:param system_lang_code:
|
:param system_lang_code:
|
||||||
ISO 639-1 language code of the system's language.
|
`ISO 639-1 <https://www.iso.org/iso-639-language-codes.html>`_ language code of the system's language.
|
||||||
|
|
||||||
:param lang_code:
|
:param lang_code:
|
||||||
ISO 639-1 language code of the application's language.
|
`ISO 639-1 <https://www.iso.org/iso-639-language-codes.html>`_ language code of the application's language.
|
||||||
|
|
||||||
:param datacenter:
|
:param datacenter:
|
||||||
Override the datacenter to connect to.
|
Override the :doc:`data center </concepts/datacenters>` to connect to.
|
||||||
Useful to connect to one of Telegram's test servers.
|
Useful to connect to one of Telegram's test servers.
|
||||||
|
|
||||||
:param connector:
|
:param connector:
|
||||||
Asynchronous function called to connect to a remote address.
|
Asynchronous function called to connect to a remote address.
|
||||||
By default, this is :func:`asyncio.open_connection`.
|
By default, this is :func:`asyncio.open_connection`.
|
||||||
In order to use proxies, you can set a custom connector.
|
In order to :doc:`use proxies </concepts/datacenters>`, you can set a custom connector.
|
||||||
|
|
||||||
See :class:`~telethon._impl.mtsender.sender.Connector` for more details.
|
See :class:`~telethon._impl.mtsender.sender.Connector` for more details.
|
||||||
"""
|
"""
|
||||||
@ -330,7 +335,13 @@ class Client:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
await client.bot_sign_in('12345:abc67DEF89ghi')
|
user = await client.bot_sign_in('12345:abc67DEF89ghi')
|
||||||
|
print('Signed in to bot account:', user.name)
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
Be sure to check :meth:`is_authorized` before calling this function.
|
||||||
|
Signing in often when you don't need to will lead to :doc:`/concepts/errors`.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@ -364,6 +375,7 @@ class Client:
|
|||||||
assert isinstance(password_token, PasswordToken)
|
assert isinstance(password_token, PasswordToken)
|
||||||
|
|
||||||
user = await client.check_password(password_token, '1-L0V3+T3l3th0n')
|
user = await client.check_password(password_token, '1-L0V3+T3l3th0n')
|
||||||
|
print('Signed in to 2FA-protected account:', user.name)
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@ -390,9 +402,9 @@ class Client:
|
|||||||
|
|
||||||
This lets you leave a group, unsubscribe from a channel, or delete a one-to-one private conversation.
|
This lets you leave a group, unsubscribe from a channel, or delete a one-to-one private conversation.
|
||||||
|
|
||||||
Note that the group or channel will not be deleted.
|
Note that the group or channel will not be deleted (other users will remain in it).
|
||||||
|
|
||||||
Note that bot accounts do not have dialogs, so this method will fail.
|
Note that bot accounts do not have dialogs, so this method will fail when used in a bot account.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` representing the dialog to delete.
|
The :term:`chat` representing the dialog to delete.
|
||||||
@ -420,8 +432,8 @@ class Client:
|
|||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
When deleting messages from private conversations or small groups,
|
When deleting messages from private conversations or small groups,
|
||||||
this parameter is ignored. This means the *message_ids* may delete
|
this parameter is currently ignored.
|
||||||
messages in different chats.
|
This means the *message_ids* may delete messages in different chats.
|
||||||
|
|
||||||
:param message_ids:
|
:param message_ids:
|
||||||
The list of message identifiers to delete.
|
The list of message identifiers to delete.
|
||||||
@ -437,11 +449,12 @@ class Client:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Delete two messages from chat for yourself
|
# Delete two messages from chat for yourself
|
||||||
await client.delete_messages(
|
delete_count = await client.delete_messages(
|
||||||
chat,
|
chat,
|
||||||
[187481, 187482],
|
[187481, 187482],
|
||||||
revoke=False,
|
revoke=False,
|
||||||
)
|
)
|
||||||
|
print('Deleted', delete_count, 'message(s)')
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@ -477,19 +490,19 @@ class Client:
|
|||||||
Note that the extension is not automatically added to the path.
|
Note that the extension is not automatically added to the path.
|
||||||
You can get the file extension with :attr:`telethon.types.File.ext`.
|
You can get the file extension with :attr:`telethon.types.File.ext`.
|
||||||
|
|
||||||
.. warning::
|
.. caution::
|
||||||
|
|
||||||
If the file already exists, it will be overwritten!
|
If the file already exists, it will be overwritten.
|
||||||
|
|
||||||
.. rubric:: Example
|
.. rubric:: Example
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if photo := message.photo:
|
if photo := message.photo:
|
||||||
await client.download(photo, 'picture.jpg')
|
await client.download(photo, f'picture{photo.ext}')
|
||||||
|
|
||||||
if video := message.video:
|
if video := message.video:
|
||||||
with open('video.mp4, 'wb') as file:
|
with open(f'video{video.ext}', 'wb') as file:
|
||||||
await client.download(video, file)
|
await client.download(video, file)
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
@ -530,15 +543,21 @@ class Client:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Edit message to have text without formatting
|
# Set a draft with no formatting and print the date Telegram registered
|
||||||
await client.edit_message(chat, msg_id, text='New text')
|
draft = await client.edit_draft(chat, 'New text')
|
||||||
|
print('Set current draft on', draft.date)
|
||||||
|
|
||||||
# Remove the link preview without changing the text
|
# Set a draft using HTML formatting, with a reply, and enabling the link preview
|
||||||
await client.edit_message(chat, msg_id, link_preview=False)
|
await client.edit_draft(
|
||||||
|
chat,
|
||||||
|
html='Draft with <em>reply</em> an URL https://example.com',
|
||||||
|
reply_to=message_id,
|
||||||
|
link_preview=True
|
||||||
|
)
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:meth:`telethon.types.Message.edit`
|
:meth:`telethon.types.Draft.edit`
|
||||||
"""
|
"""
|
||||||
return await edit_draft(
|
return await edit_draft(
|
||||||
self,
|
self,
|
||||||
@ -622,11 +641,12 @@ class Client:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Forward two messages from chat to the destination
|
# Forward two messages from chat to the destination
|
||||||
await client.forward_messages(
|
messages = await client.forward_messages(
|
||||||
destination,
|
destination,
|
||||||
[187481, 187482],
|
[187481, 187482],
|
||||||
chat,
|
chat,
|
||||||
)
|
)
|
||||||
|
print('Forwarded', len(messages), 'message(s)')
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@ -704,6 +724,7 @@ class Client:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Clear all drafts
|
||||||
async for draft in client.get_drafts():
|
async for draft in client.get_drafts():
|
||||||
await draft.delete()
|
await draft.delete()
|
||||||
"""
|
"""
|
||||||
@ -794,10 +815,12 @@ class Client:
|
|||||||
offset_date: Optional[datetime.datetime] = None,
|
offset_date: Optional[datetime.datetime] = None,
|
||||||
) -> AsyncList[Message]:
|
) -> AsyncList[Message]:
|
||||||
"""
|
"""
|
||||||
Get the message history from a :term:`chat`.
|
Get the message history from a :term:`chat`, from the newest message to the oldest.
|
||||||
|
|
||||||
|
The returned iterator can be :func:`reversed` to fetch from the first to the last instead.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message to edit is.
|
The :term:`chat` where the messages should be fetched from.
|
||||||
|
|
||||||
:param limit:
|
:param limit:
|
||||||
How many messages to fetch at most.
|
How many messages to fetch at most.
|
||||||
@ -818,12 +841,17 @@ class Client:
|
|||||||
|
|
||||||
# Get the last message in a chat
|
# Get the last message in a chat
|
||||||
last_message = (await client.get_messages(chat, 1))[0]
|
last_message = (await client.get_messages(chat, 1))[0]
|
||||||
|
print(message.sender.name, last_message.text)
|
||||||
|
|
||||||
# Print all messages before 2023 as HTML
|
# Print all messages before 2023 as HTML
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
async for message in client.get_messages(chat, offset_date=datetime(2023, 1, 1)):
|
async for message in client.get_messages(chat, offset_date=datetime(2023, 1, 1)):
|
||||||
print(message.sender.name, ':', message.html_text)
|
print(message.sender.name, ':', message.html_text)
|
||||||
|
|
||||||
|
# Print the first 10 messages in a chat as markdown
|
||||||
|
async for message in reversed(client.get_messages(chat)):
|
||||||
|
print(message.sender.name, ':', message.markdown_text)
|
||||||
"""
|
"""
|
||||||
return get_messages(
|
return get_messages(
|
||||||
self, chat, limit, offset_id=offset_id, offset_date=offset_date
|
self, chat, limit, offset_id=offset_id, offset_date=offset_date
|
||||||
@ -859,9 +887,11 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
Get the participants in a group or channel, along with their permissions.
|
Get the participants in a group or channel, along with their permissions.
|
||||||
|
|
||||||
Note that Telegram is rather strict when it comes to fetching members.
|
.. note::
|
||||||
It is very likely that you will not be able to fetch all the members.
|
|
||||||
There is no way to bypass this.
|
Telegram is rather strict when it comes to fetching members.
|
||||||
|
It is very likely that you will not be able to fetch all the members.
|
||||||
|
There is no way to bypass this.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` to fetch participants from.
|
The :term:`chat` to fetch participants from.
|
||||||
@ -955,11 +985,12 @@ class Client:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Interactive login from the terminal
|
||||||
me = await client.interactive_login()
|
me = await client.interactive_login()
|
||||||
print('Logged in as:', me.name)
|
print('Logged in as:', me.name)
|
||||||
|
|
||||||
# or, to make sure you're logged-in as a bot
|
# Automatic login to a bot account
|
||||||
await client.interactive_login('1234:ab56cd78ef90)
|
await client.interactive_login('54321:hJrIQtVBab0M2Yqg4HL1K-EubfY_v2fEVR')
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@ -979,6 +1010,11 @@ class Client:
|
|||||||
|
|
||||||
if not await client.is_authorized():
|
if not await client.is_authorized():
|
||||||
... # need to sign in
|
... # need to sign in
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:meth:`get_me` can be used to fetch up-to-date information about :term:`yourself`
|
||||||
|
and check if you're logged-in at the same time.
|
||||||
"""
|
"""
|
||||||
return await is_authorized(self)
|
return await is_authorized(self)
|
||||||
|
|
||||||
@ -1075,8 +1111,7 @@ class Client:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Mark all messages as read
|
# Mark all messages as read
|
||||||
message = await client.read_message(chat, 'all')
|
await client.read_message(chat, 'all')
|
||||||
await message.delete()
|
|
||||||
"""
|
"""
|
||||||
await read_message(self, chat, message_id)
|
await read_message(self, chat, message_id)
|
||||||
|
|
||||||
@ -1100,18 +1135,12 @@ class Client:
|
|||||||
client.remove_event_handler(my_handler)
|
client.remove_event_handler(my_handler)
|
||||||
else:
|
else:
|
||||||
print('still going!')
|
print('still going!')
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
:meth:`add_event_handler`, used to register existing functions as event handlers.
|
|
||||||
"""
|
"""
|
||||||
remove_event_handler(self, handler)
|
remove_event_handler(self, handler)
|
||||||
|
|
||||||
async def request_login_code(self, phone: str) -> LoginToken:
|
async def request_login_code(self, phone: str) -> LoginToken:
|
||||||
"""
|
"""
|
||||||
Request Telegram to send a login code to the provided phone number.
|
Request Telegram to send a login code to the provided phone number.
|
||||||
This is simply the opposite of :meth:`add_event_handler`.
|
|
||||||
Does nothing if the handler was not actually registered.
|
|
||||||
|
|
||||||
:param phone:
|
:param phone:
|
||||||
The phone number string, in international format.
|
The phone number string, in international format.
|
||||||
@ -1126,6 +1155,11 @@ class Client:
|
|||||||
login_token = await client.request_login_code('+1 23 456...')
|
login_token = await client.request_login_code('+1 23 456...')
|
||||||
print(login_token.timeout, 'seconds before code expires')
|
print(login_token.timeout, 'seconds before code expires')
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
Be sure to check :meth:`is_authorized` before calling this function.
|
||||||
|
Signing in often when you don't need to will lead to :doc:`/concepts/errors`.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:meth:`sign_in`, to complete the login procedure.
|
:meth:`sign_in`, to complete the login procedure.
|
||||||
@ -1159,7 +1193,7 @@ class Client:
|
|||||||
Resolve a username into a :term:`chat`.
|
Resolve a username into a :term:`chat`.
|
||||||
|
|
||||||
This method is rather expensive to call.
|
This method is rather expensive to call.
|
||||||
It is recommended to use it once and then ``chat.pack()`` the result.
|
It is recommended to use it once and then :meth:`types.Chat.pack` the result.
|
||||||
The packed chat can then be used (and re-fetched) more cheaply.
|
The packed chat can then be used (and re-fetched) more cheaply.
|
||||||
|
|
||||||
:param username:
|
:param username:
|
||||||
@ -1773,6 +1807,14 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self) -> bool:
|
def connected(self) -> bool:
|
||||||
|
"""
|
||||||
|
:data:`True` if :meth:`connect` has been called previously.
|
||||||
|
|
||||||
|
This property will be set back to :data:`False` after calling :meth:`disconnect`.
|
||||||
|
|
||||||
|
This property does *not* check whether the connection is alive.
|
||||||
|
The only way to check if the connection still works is to make a request.
|
||||||
|
"""
|
||||||
return connected(self)
|
return connected(self)
|
||||||
|
|
||||||
def _build_message_map(
|
def _build_message_map(
|
||||||
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union
|
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Self, Tuple, Union
|
||||||
|
|
||||||
from ...session import PackedChat
|
from ...session import PackedChat
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
@ -250,37 +250,36 @@ async def forward_messages(
|
|||||||
|
|
||||||
|
|
||||||
class MessageList(AsyncList[Message]):
|
class MessageList(AsyncList[Message]):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._reversed = False
|
||||||
|
|
||||||
def _extend_buffer(
|
def _extend_buffer(
|
||||||
self, client: Client, messages: abcs.messages.Messages
|
self, client: Client, messages: abcs.messages.Messages
|
||||||
) -> Dict[int, Chat]:
|
) -> Dict[int, Chat]:
|
||||||
if isinstance(messages, types.messages.Messages):
|
if isinstance(messages, types.messages.MessagesNotModified):
|
||||||
chat_map = build_chat_map(messages.users, messages.chats)
|
|
||||||
self._buffer.extend(
|
|
||||||
Message._from_raw(client, m, chat_map) for m in messages.messages
|
|
||||||
)
|
|
||||||
self._total = len(messages.messages)
|
|
||||||
self._done = True
|
|
||||||
return chat_map
|
|
||||||
elif isinstance(messages, types.messages.MessagesSlice):
|
|
||||||
chat_map = build_chat_map(messages.users, messages.chats)
|
|
||||||
self._buffer.extend(
|
|
||||||
Message._from_raw(client, m, chat_map) for m in messages.messages
|
|
||||||
)
|
|
||||||
self._total = messages.count
|
|
||||||
return chat_map
|
|
||||||
elif isinstance(messages, types.messages.ChannelMessages):
|
|
||||||
chat_map = build_chat_map(messages.users, messages.chats)
|
|
||||||
self._buffer.extend(
|
|
||||||
Message._from_raw(client, m, chat_map) for m in messages.messages
|
|
||||||
)
|
|
||||||
self._total = messages.count
|
|
||||||
return chat_map
|
|
||||||
elif isinstance(messages, types.messages.MessagesNotModified):
|
|
||||||
self._total = messages.count
|
self._total = messages.count
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
if isinstance(messages, types.messages.Messages):
|
||||||
|
self._total = len(messages.messages)
|
||||||
|
self._done = True
|
||||||
|
elif isinstance(
|
||||||
|
messages, (types.messages.MessagesSlice, types.messages.ChannelMessages)
|
||||||
|
):
|
||||||
|
self._total = messages.count
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unexpected case")
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
chat_map = build_chat_map(messages.users, messages.chats)
|
||||||
|
self._buffer.extend(
|
||||||
|
Message._from_raw(client, m, chat_map)
|
||||||
|
for m in (
|
||||||
|
reversed(messages.messages) if self._reversed else messages.messages
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return chat_map
|
||||||
|
|
||||||
def _last_non_empty_message(
|
def _last_non_empty_message(
|
||||||
self,
|
self,
|
||||||
) -> Union[types.Message, types.MessageService, types.MessageEmpty]:
|
) -> Union[types.Message, types.MessageService, types.MessageEmpty]:
|
||||||
@ -320,13 +319,14 @@ class HistoryList(MessageList):
|
|||||||
await self._client._resolve_to_packed(self._chat)
|
await self._client._resolve_to_packed(self._chat)
|
||||||
)._to_input_peer()
|
)._to_input_peer()
|
||||||
|
|
||||||
|
limit = min(max(self._limit, 1), 100)
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.messages.get_history(
|
functions.messages.get_history(
|
||||||
peer=self._peer,
|
peer=self._peer,
|
||||||
offset_id=self._offset_id,
|
offset_id=self._offset_id,
|
||||||
offset_date=self._offset_date,
|
offset_date=self._offset_date,
|
||||||
add_offset=0,
|
add_offset=-limit if self._reversed else 0,
|
||||||
limit=min(max(self._limit, 1), 100),
|
limit=limit,
|
||||||
max_id=0,
|
max_id=0,
|
||||||
min_id=0,
|
min_id=0,
|
||||||
hash=0,
|
hash=0,
|
||||||
@ -338,9 +338,20 @@ class HistoryList(MessageList):
|
|||||||
self._done |= not self._limit
|
self._done |= not self._limit
|
||||||
if self._buffer and not self._done:
|
if self._buffer and not self._done:
|
||||||
last = self._last_non_empty_message()
|
last = self._last_non_empty_message()
|
||||||
self._offset_id = self._buffer[-1].id
|
self._offset_id = last.id + (1 if self._reversed else 0)
|
||||||
if (date := getattr(last, "date", None)) is not None:
|
self._offset_date = 0
|
||||||
self._offset_date = date
|
|
||||||
|
def __reversed__(self) -> Self:
|
||||||
|
new = self.__class__(
|
||||||
|
self._client,
|
||||||
|
self._chat,
|
||||||
|
self._limit,
|
||||||
|
offset_id=1 if self._offset_id == 0 else self._offset_id,
|
||||||
|
offset_date=self._offset_date,
|
||||||
|
)
|
||||||
|
new._peer = self._peer
|
||||||
|
new._reversed = not self._reversed
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
def get_messages(
|
def get_messages(
|
||||||
|
@ -14,6 +14,7 @@ from typing import (
|
|||||||
|
|
||||||
from ...session import Gap
|
from ...session import Gap
|
||||||
from ...tl import abcs
|
from ...tl import abcs
|
||||||
|
from ..events import Continue
|
||||||
from ..events import Event as EventBase
|
from ..events import Event as EventBase
|
||||||
from ..events.filters import Filter
|
from ..events.filters import Filter
|
||||||
from ..types import build_chat_map
|
from ..types import build_chat_map
|
||||||
@ -152,6 +153,6 @@ async def dispatch_next(client: Client) -> None:
|
|||||||
if event := event_cls._try_from_update(client, update, chat_map):
|
if event := event_cls._try_from_update(client, update, chat_map):
|
||||||
for handler, filter in handlers:
|
for handler, filter in handlers:
|
||||||
if not filter or filter(event):
|
if not filter or filter(event):
|
||||||
await handler(event)
|
ret = await handler(event)
|
||||||
if client._shortcircuit_handlers:
|
if ret is Continue or client._shortcircuit_handlers:
|
||||||
return
|
return
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from .event import Event
|
from .event import Continue, Event
|
||||||
from .messages import MessageDeleted, MessageEdited, MessageRead, NewMessage
|
from .messages import MessageDeleted, MessageEdited, MessageRead, NewMessage
|
||||||
from .queries import ButtonCallback, InlineQuery
|
from .queries import ButtonCallback, InlineQuery
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"Continue",
|
||||||
"Event",
|
"Event",
|
||||||
"MessageDeleted",
|
"MessageDeleted",
|
||||||
"MessageEdited",
|
"MessageEdited",
|
||||||
|
@ -28,3 +28,35 @@ class Event(metaclass=NoPublicConstructor):
|
|||||||
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Continue:
|
||||||
|
"""
|
||||||
|
This is **not** an event type you can listen to.
|
||||||
|
|
||||||
|
This is a sentinel value used to signal that the library should *Continue* calling other handlers.
|
||||||
|
|
||||||
|
You can :keyword:`return` this from your handlers if you want handlers registered after to also run.
|
||||||
|
|
||||||
|
The primary use case is having asynchronous filters inside your handler:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import events
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def admin_only_handler(event):
|
||||||
|
allowed = await database.is_user_admin(event.sender.id)
|
||||||
|
if not allowed:
|
||||||
|
# this user is not allowed, fall-through the handlers
|
||||||
|
return events.Continue
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def everyone_else_handler(event):
|
||||||
|
... # runs if admin_only_handler was not allowed
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
raise TypeError(
|
||||||
|
f"Can't instantiate {self.__class__.__name__} class (the type is the sentinel value; remove the parenthesis)"
|
||||||
|
)
|
||||||
|
@ -14,7 +14,7 @@ class NewMessage(Event, Message):
|
|||||||
"""
|
"""
|
||||||
Occurs when a new message is sent or received.
|
Occurs when a new message is sent or received.
|
||||||
|
|
||||||
.. warning::
|
.. caution::
|
||||||
|
|
||||||
Messages sent with the :class:`~telethon.Client` are also caught,
|
Messages sent with the :class:`~telethon.Client` are also caught,
|
||||||
so be careful not to enter infinite loops!
|
so be careful not to enter infinite loops!
|
||||||
|
@ -134,7 +134,7 @@ def parse(message: str) -> Tuple[str, List[MessageEntity]]:
|
|||||||
elif token.type in ("s_close", "s_open"):
|
elif token.type in ("s_close", "s_open"):
|
||||||
push(MessageEntityStrike)
|
push(MessageEntityStrike)
|
||||||
elif token.type == "softbreak":
|
elif token.type == "softbreak":
|
||||||
message += " "
|
message += "\n"
|
||||||
elif token.type in ("strong_close", "strong_open"):
|
elif token.type in ("strong_close", "strong_open"):
|
||||||
push(MessageEntityBold)
|
push(MessageEntityBold)
|
||||||
elif token.type == "text":
|
elif token.type == "text":
|
||||||
|
@ -105,6 +105,10 @@ class Connector(Protocol):
|
|||||||
default_connector = lambda ip, port: asyncio.open_connection(ip, port)
|
default_connector = lambda ip, port: asyncio.open_connection(ip, port)
|
||||||
|
|
||||||
If your connector needs additional parameters, you can use either the :keyword:`lambda` syntax or :func:`functools.partial`.
|
If your connector needs additional parameters, you can use either the :keyword:`lambda` syntax or :func:`functools.partial`.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
The :doc:`/concepts/datacenters` concept has examples on how to combine proxy libraries with Telethon.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def __call__(self, ip: str, port: int) -> Tuple[AsyncReader, AsyncWriter]:
|
async def __call__(self, ip: str, port: int) -> Tuple[AsyncReader, AsyncWriter]:
|
||||||
|
@ -7,6 +7,7 @@ Classes related to the different event types that wrap incoming Telegram updates
|
|||||||
"""
|
"""
|
||||||
from .._impl.client.events import (
|
from .._impl.client.events import (
|
||||||
ButtonCallback,
|
ButtonCallback,
|
||||||
|
Continue,
|
||||||
Event,
|
Event,
|
||||||
InlineQuery,
|
InlineQuery,
|
||||||
MessageDeleted,
|
MessageDeleted,
|
||||||
@ -17,6 +18,7 @@ from .._impl.client.events import (
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ButtonCallback",
|
"ButtonCallback",
|
||||||
|
"Continue",
|
||||||
"Event",
|
"Event",
|
||||||
"InlineQuery",
|
"InlineQuery",
|
||||||
"MessageDeleted",
|
"MessageDeleted",
|
||||||
|
Loading…
Reference in New Issue
Block a user