mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-09 13:29:47 +00:00
Continue documentation and matching documented behaviour
This commit is contained in:
@@ -152,6 +152,8 @@ class Client:
|
||||
:param catch_up:
|
||||
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:
|
||||
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 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:
|
||||
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:
|
||||
Logger for the client.
|
||||
Any dependency of the client will use :meth:`logging.Logger.getChild`.
|
||||
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:
|
||||
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:
|
||||
Device model.
|
||||
|
||||
@@ -184,19 +189,19 @@ class Client:
|
||||
Application version.
|
||||
|
||||
: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:
|
||||
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:
|
||||
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.
|
||||
|
||||
:param connector:
|
||||
Asynchronous function called to connect to a remote address.
|
||||
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.
|
||||
"""
|
||||
@@ -330,7 +335,13 @@ class Client:
|
||||
|
||||
.. 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::
|
||||
|
||||
@@ -364,6 +375,7 @@ class Client:
|
||||
assert isinstance(password_token, PasswordToken)
|
||||
|
||||
user = await client.check_password(password_token, '1-L0V3+T3l3th0n')
|
||||
print('Signed in to 2FA-protected account:', user.name)
|
||||
|
||||
.. 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.
|
||||
|
||||
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:
|
||||
The :term:`chat` representing the dialog to delete.
|
||||
@@ -420,8 +432,8 @@ class Client:
|
||||
.. warning::
|
||||
|
||||
When deleting messages from private conversations or small groups,
|
||||
this parameter is ignored. This means the *message_ids* may delete
|
||||
messages in different chats.
|
||||
this parameter is currently ignored.
|
||||
This means the *message_ids* may delete messages in different chats.
|
||||
|
||||
:param message_ids:
|
||||
The list of message identifiers to delete.
|
||||
@@ -437,11 +449,12 @@ class Client:
|
||||
.. code-block:: python
|
||||
|
||||
# Delete two messages from chat for yourself
|
||||
await client.delete_messages(
|
||||
delete_count = await client.delete_messages(
|
||||
chat,
|
||||
[187481, 187482],
|
||||
revoke=False,
|
||||
)
|
||||
print('Deleted', delete_count, 'message(s)')
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -477,19 +490,19 @@ class Client:
|
||||
Note that the extension is not automatically added to the path.
|
||||
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
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if photo := message.photo:
|
||||
await client.download(photo, 'picture.jpg')
|
||||
await client.download(photo, f'picture{photo.ext}')
|
||||
|
||||
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)
|
||||
|
||||
.. seealso::
|
||||
@@ -530,15 +543,21 @@ class Client:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Edit message to have text without formatting
|
||||
await client.edit_message(chat, msg_id, text='New text')
|
||||
# Set a draft with no formatting and print the date Telegram registered
|
||||
draft = await client.edit_draft(chat, 'New text')
|
||||
print('Set current draft on', draft.date)
|
||||
|
||||
# Remove the link preview without changing the text
|
||||
await client.edit_message(chat, msg_id, link_preview=False)
|
||||
# Set a draft using HTML formatting, with a reply, and enabling the link preview
|
||||
await client.edit_draft(
|
||||
chat,
|
||||
html='Draft with <em>reply</em> an URL https://example.com',
|
||||
reply_to=message_id,
|
||||
link_preview=True
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`telethon.types.Message.edit`
|
||||
:meth:`telethon.types.Draft.edit`
|
||||
"""
|
||||
return await edit_draft(
|
||||
self,
|
||||
@@ -622,11 +641,12 @@ class Client:
|
||||
.. code-block:: python
|
||||
|
||||
# Forward two messages from chat to the destination
|
||||
await client.forward_messages(
|
||||
messages = await client.forward_messages(
|
||||
destination,
|
||||
[187481, 187482],
|
||||
chat,
|
||||
)
|
||||
print('Forwarded', len(messages), 'message(s)')
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -704,6 +724,7 @@ class Client:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Clear all drafts
|
||||
async for draft in client.get_drafts():
|
||||
await draft.delete()
|
||||
"""
|
||||
@@ -794,10 +815,12 @@ class Client:
|
||||
offset_date: Optional[datetime.datetime] = None,
|
||||
) -> 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:
|
||||
The :term:`chat` where the message to edit is.
|
||||
The :term:`chat` where the messages should be fetched from.
|
||||
|
||||
:param limit:
|
||||
How many messages to fetch at most.
|
||||
@@ -818,12 +841,17 @@ class Client:
|
||||
|
||||
# Get the last message in a chat
|
||||
last_message = (await client.get_messages(chat, 1))[0]
|
||||
print(message.sender.name, last_message.text)
|
||||
|
||||
# Print all messages before 2023 as HTML
|
||||
from datetime import datetime
|
||||
|
||||
async for message in client.get_messages(chat, offset_date=datetime(2023, 1, 1)):
|
||||
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(
|
||||
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.
|
||||
|
||||
Note that 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.
|
||||
.. note::
|
||||
|
||||
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:
|
||||
The :term:`chat` to fetch participants from.
|
||||
@@ -955,11 +985,12 @@ class Client:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Interactive login from the terminal
|
||||
me = await client.interactive_login()
|
||||
print('Logged in as:', me.name)
|
||||
|
||||
# or, to make sure you're logged-in as a bot
|
||||
await client.interactive_login('1234:ab56cd78ef90)
|
||||
# Automatic login to a bot account
|
||||
await client.interactive_login('54321:hJrIQtVBab0M2Yqg4HL1K-EubfY_v2fEVR')
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -979,6 +1010,11 @@ class Client:
|
||||
|
||||
if not await client.is_authorized():
|
||||
... # 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)
|
||||
|
||||
@@ -1075,8 +1111,7 @@ class Client:
|
||||
.. code-block:: python
|
||||
|
||||
# Mark all messages as read
|
||||
message = await client.read_message(chat, 'all')
|
||||
await message.delete()
|
||||
await client.read_message(chat, 'all')
|
||||
"""
|
||||
await read_message(self, chat, message_id)
|
||||
|
||||
@@ -1100,18 +1135,12 @@ class Client:
|
||||
client.remove_event_handler(my_handler)
|
||||
else:
|
||||
print('still going!')
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`add_event_handler`, used to register existing functions as event handlers.
|
||||
"""
|
||||
remove_event_handler(self, handler)
|
||||
|
||||
async def request_login_code(self, phone: str) -> LoginToken:
|
||||
"""
|
||||
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:
|
||||
The phone number string, in international format.
|
||||
@@ -1126,6 +1155,11 @@ class Client:
|
||||
login_token = await client.request_login_code('+1 23 456...')
|
||||
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::
|
||||
|
||||
:meth:`sign_in`, to complete the login procedure.
|
||||
@@ -1159,7 +1193,7 @@ class Client:
|
||||
Resolve a username into a :term:`chat`.
|
||||
|
||||
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.
|
||||
|
||||
:param username:
|
||||
@@ -1773,6 +1807,14 @@ class Client:
|
||||
|
||||
@property
|
||||
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)
|
||||
|
||||
def _build_message_map(
|
||||
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
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 ...tl import abcs, functions, types
|
||||
@@ -250,37 +250,36 @@ async def forward_messages(
|
||||
|
||||
|
||||
class MessageList(AsyncList[Message]):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._reversed = False
|
||||
|
||||
def _extend_buffer(
|
||||
self, client: Client, messages: abcs.messages.Messages
|
||||
) -> Dict[int, Chat]:
|
||||
if isinstance(messages, types.messages.Messages):
|
||||
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):
|
||||
if isinstance(messages, types.messages.MessagesNotModified):
|
||||
self._total = messages.count
|
||||
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:
|
||||
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(
|
||||
self,
|
||||
) -> Union[types.Message, types.MessageService, types.MessageEmpty]:
|
||||
@@ -320,13 +319,14 @@ class HistoryList(MessageList):
|
||||
await self._client._resolve_to_packed(self._chat)
|
||||
)._to_input_peer()
|
||||
|
||||
limit = min(max(self._limit, 1), 100)
|
||||
result = await self._client(
|
||||
functions.messages.get_history(
|
||||
peer=self._peer,
|
||||
offset_id=self._offset_id,
|
||||
offset_date=self._offset_date,
|
||||
add_offset=0,
|
||||
limit=min(max(self._limit, 1), 100),
|
||||
add_offset=-limit if self._reversed else 0,
|
||||
limit=limit,
|
||||
max_id=0,
|
||||
min_id=0,
|
||||
hash=0,
|
||||
@@ -338,9 +338,20 @@ class HistoryList(MessageList):
|
||||
self._done |= not self._limit
|
||||
if self._buffer and not self._done:
|
||||
last = self._last_non_empty_message()
|
||||
self._offset_id = self._buffer[-1].id
|
||||
if (date := getattr(last, "date", None)) is not None:
|
||||
self._offset_date = date
|
||||
self._offset_id = last.id + (1 if self._reversed else 0)
|
||||
self._offset_date = 0
|
||||
|
||||
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(
|
||||
|
@@ -14,6 +14,7 @@ from typing import (
|
||||
|
||||
from ...session import Gap
|
||||
from ...tl import abcs
|
||||
from ..events import Continue
|
||||
from ..events import Event as EventBase
|
||||
from ..events.filters import Filter
|
||||
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):
|
||||
for handler, filter in handlers:
|
||||
if not filter or filter(event):
|
||||
await handler(event)
|
||||
if client._shortcircuit_handlers:
|
||||
ret = await handler(event)
|
||||
if ret is Continue or client._shortcircuit_handlers:
|
||||
return
|
||||
|
@@ -1,8 +1,9 @@
|
||||
from .event import Event
|
||||
from .event import Continue, Event
|
||||
from .messages import MessageDeleted, MessageEdited, MessageRead, NewMessage
|
||||
from .queries import ButtonCallback, InlineQuery
|
||||
|
||||
__all__ = [
|
||||
"Continue",
|
||||
"Event",
|
||||
"MessageDeleted",
|
||||
"MessageEdited",
|
||||
|
@@ -28,3 +28,35 @@ class Event(metaclass=NoPublicConstructor):
|
||||
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
||||
) -> Optional[Self]:
|
||||
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.
|
||||
|
||||
.. warning::
|
||||
.. caution::
|
||||
|
||||
Messages sent with the :class:`~telethon.Client` are also caught,
|
||||
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"):
|
||||
push(MessageEntityStrike)
|
||||
elif token.type == "softbreak":
|
||||
message += " "
|
||||
message += "\n"
|
||||
elif token.type in ("strong_close", "strong_open"):
|
||||
push(MessageEntityBold)
|
||||
elif token.type == "text":
|
||||
|
@@ -105,6 +105,10 @@ class Connector(Protocol):
|
||||
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`.
|
||||
|
||||
.. 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]:
|
||||
|
@@ -7,6 +7,7 @@ Classes related to the different event types that wrap incoming Telegram updates
|
||||
"""
|
||||
from .._impl.client.events import (
|
||||
ButtonCallback,
|
||||
Continue,
|
||||
Event,
|
||||
InlineQuery,
|
||||
MessageDeleted,
|
||||
@@ -17,6 +18,7 @@ from .._impl.client.events import (
|
||||
|
||||
__all__ = [
|
||||
"ButtonCallback",
|
||||
"Continue",
|
||||
"Event",
|
||||
"InlineQuery",
|
||||
"MessageDeleted",
|
||||
|
Reference in New Issue
Block a user