Document most methods and classes

This commit is contained in:
Lonami Exo 2023-09-14 21:17:24 +02:00
parent 569ff3d372
commit 604d95a807
28 changed files with 942 additions and 52 deletions

View File

@ -11,6 +11,9 @@ Glossary
.. seealso:: The :doc:`../concepts/chats` concept. .. seealso:: The :doc:`../concepts/chats` concept.
yourself
The logged-in account, whether that represents a bot or a user with a phone number.
Raw API Raw API
Functions and types under ``telethon._tl`` that enable access to all of Telegram's API. Functions and types under ``telethon._tl`` that enable access to all of Telegram's API.

View File

@ -128,11 +128,8 @@ async def sign_in(
async def interactive_login(self: Client) -> User: async def interactive_login(self: Client) -> User:
try: if me := await self.get_me():
return await self.get_me() return me
except RpcError as e:
if e.code != 401:
raise
phone_or_token = "" phone_or_token = ""
while not re.match(r"\+?[\s()]*\d", phone_or_token): while not re.match(r"\+?[\s()]*\d", phone_or_token):

View File

@ -11,6 +11,10 @@ if TYPE_CHECKING:
class InlineResults(metaclass=NoPublicConstructor): class InlineResults(metaclass=NoPublicConstructor):
"""
:final:
"""
def __init__( def __init__(
self, self,
client: Client, client: Client,
@ -119,7 +123,7 @@ class InlineResult(metaclass=NoPublicConstructor):
async def inline_query( async def inline_query(
self: Client, bot: ChatLike, query: str, *, chat: Optional[ChatLike] = None self: Client, bot: ChatLike, query: str = "", *, chat: Optional[ChatLike] = None
) -> AsyncIterator[InlineResult]: ) -> AsyncIterator[InlineResult]:
packed_bot = await self._resolve_to_packed(bot) packed_bot = await self._resolve_to_packed(bot)
packed_chat = await self._resolve_to_packed(chat) if chat else None packed_chat = await self._resolve_to_packed(chat) if chat else None

View File

@ -107,6 +107,59 @@ T = TypeVar("T")
class Client: class Client:
"""
A client capable of connecting to Telegram and sending requests.
This is the "entry point" of the library.
This class can be used as an asynchronous context manager to automatically :meth:`connect` and :meth:`disconnect`:
.. code-block:: python
async with Client(session, api_id, api_hash) as client:
... # automatically connect()-ed
... # after exiting the block, disconnect() was automatically called
:param session:
A name or path to a ``.session`` file, or a different storage.
:param api_id:
The API ID. See :doc:`/basic/signing-in` to learn how to obtain it.
:param api_hash:
The API hash. See :doc:`/basic/signing-in` to learn how to obtain it.
:param device_model:
Device model.
:param system_version:
System version.
:param app_version:
Application version.
:param system_lang_code:
ISO 639-1 language code of the system's language.
:param lang_code:
ISO 639-1 language code of the application's language.
:param catch_up:
Whether to "catch up" on updates that occured while the client was not connected.
:param server_addr:
Override the server address ``'ip:port'`` pair to connect to.
Useful to connect to one of Telegram's test servers.
: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.
:param update_queue_limit:
Maximum amount of updates to keep in memory before dropping them.
"""
def __init__( def __init__(
self, self,
session: Optional[Union[str, Path, Storage]], session: Optional[Union[str, Path, Storage]],
@ -152,17 +205,114 @@ class Client:
event_cls: Type[Event], event_cls: Type[Event],
filter: Optional[Filter] = None, filter: Optional[Filter] = None,
) -> None: ) -> None:
"""
Register a callable to be invoked when the provided event type occurs.
:param handler:
The callable to invoke when an event occurs.
This is often just a function object.
:param event_cls:
The event type to bind to the handler.
When Telegram sends an update corresponding to this type,
*handler* is called with an instance of this event type as the only argument.
:param filter:
Filter function to call with the event before calling *handler*.
If it returns `False`, *handler* will not be called.
See the :mod:`~telethon.events.filters` module to learn more.
.. rubric:: Example
.. code-block:: python
async def my_print_handler(event):
print(event.chat.full_name, event.text)
# Register a handler to be called on new messages
client.add_event_handler(my_print_handler, events.NewMessage)
# Register a handler to be called on new messages if they contain "hello" or "/start"
from telethon.events import filters
client.add_event_handler(
my_print_handler,
events.NewMessage,
filters.Any(filters.Text(r'hello'), filters.Command('/start')),
)
.. seealso::
:meth:`on`, used to register handlers with the decorator syntax.
"""
add_event_handler(self, handler, event_cls, filter) add_event_handler(self, handler, event_cls, filter)
async def bot_sign_in(self, token: str) -> User: async def bot_sign_in(self, token: str) -> User:
"""
Sign in to a bot account.
:param token:
The bot token obtained from `@BotFather <https://t.me/BotFather>`_.
It's a string composed of digits, a colon, and characters from the base-64 alphabet.
:return: The bot user corresponding to :term:`yourself`.
.. rubric:: Example
.. code-block:: python
await client.bot_sign_in('12345:abc67DEF89ghi')
.. seealso::
:meth:`request_login_code`, used to sign in as a user instead.
"""
return await bot_sign_in(self, token) return await bot_sign_in(self, token)
async def check_password( async def check_password(
self, token: PasswordToken, password: Union[str, bytes] self, token: PasswordToken, password: Union[str, bytes]
) -> User: ) -> User:
"""
Check the two-factor-authentication (2FA) password.
If it is correct, completes the login.
:param token:
The return value from :meth:`sign_in`.
:param password:
The 2FA password.
:return: The user corresponding to :term:`yourself`.
.. rubric:: Example
.. code-block:: python
from telethon.types import PasswordToken
login_token = await client.request_login_code('+1 23 456')
password_token = await client.sign_in(login_token, input('code: '))
assert isinstance(password_token, PasswordToken)
user = await client.check_password(password_token, '1-L0V3+T3l3th0n')
.. seealso::
:meth:`request_login_code` and :meth:`sign_in`
"""
return await check_password(self, token, password) return await check_password(self, token, password)
async def connect(self) -> None: async def connect(self) -> None:
"""
Connect to the Telegram servers.
.. rubric:: Example
.. code-block:: python
await client.connect()
# success!
"""
await connect(self) await connect(self)
async def delete_dialog(self) -> None: async def delete_dialog(self) -> None:
@ -171,17 +321,84 @@ class Client:
async def delete_messages( async def delete_messages(
self, chat: ChatLike, message_ids: List[int], *, revoke: bool = True self, chat: ChatLike, message_ids: List[int], *, revoke: bool = True
) -> int: ) -> int:
"""
Delete messages.
:param chat:
The :term:`chat` where the messages are.
.. 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.
:param message_ids:
The list of message identifiers to delete.
:param revoke:
When set to :data:`True`, the message will be deleted for everyone that is part of *chat*.
Otherwise, the message will only be deleted for :term:`yourself`.
:return: The amount of messages that were deleted.
.. rubric:: Example
.. code-block:: python
# Delete two messages from chat for yourself
await client.delete_messages(
chat,
[187481, 187482],
revoke=False,
)
.. seealso::
:meth:`telethon.types.Message.delete`
"""
return await delete_messages(self, chat, message_ids, revoke=revoke) return await delete_messages(self, chat, message_ids, revoke=revoke)
async def disconnect(self) -> None: async def disconnect(self) -> None:
"""
Disconnect from the Telegram servers.
This call will only fail if saving the :term:`session` fails.
.. rubric:: Example
.. code-block:: python
await client.disconnect()
# success!
"""
await disconnect(self) await disconnect(self)
async def download(self, media: MediaLike, file: OutFileLike) -> None: async def download(self, media: MediaLike, file: OutFileLike) -> None:
""" """
Download a file. Download a file.
This is simply a more convenient method to `iter_download`, :param media:
as it will handle dealing with the file chunks and writes by itself. The media to download.
This will often come from :attr:`telethon.types.Message.file`.
:param file:
The output file path or :term:`file-like object`.
.. rubric:: Example
.. code-block:: python
if photo := message.photo:
await client.download(photo, 'picture.jpg')
if video := message.video:
with open('video.mp4, 'wb') as file:
await client.download(video, file)
.. seealso::
:meth:`iter_download`, for fine-grained control over the download.
""" """
await download(self, media, file) await download(self, media, file)
@ -195,6 +412,33 @@ class Client:
html: Optional[str] = None, html: Optional[str] = None,
link_preview: Optional[bool] = None, link_preview: Optional[bool] = None,
) -> Message: ) -> Message:
"""
Edit a message.
:param chat:
The :term:`chat` where the message to edit is.
:param message_id:
The identifier of the message to edit.
The rest of parameters behave the same as they do in `send_message` or `send_file`.
:return: The edited message.
.. rubric:: Example
.. code-block:: python
# Edit message to have text without formatting
await client.edit_message(chat, msg_id, text='New text')
# Remove the link preview without changing the text
await client.edit_message(chat, msg_id, link_preview=False)
.. seealso::
:meth:`telethon.types.Message.edit`
"""
return await edit_message( return await edit_message(
self, self,
chat, chat,
@ -208,9 +452,50 @@ class Client:
async def forward_messages( async def forward_messages(
self, target: ChatLike, message_ids: List[int], source: ChatLike self, target: ChatLike, message_ids: List[int], source: ChatLike
) -> List[Message]: ) -> List[Message]:
"""
Forward messages from one :term:`chat` to another.
:param target:
The :term:`chat` where the messages will be forwarded to.
:param message_ids:
The list of message identifiers to forward.
:param source:
The source :term:`chat` where the messages to forward exist.
:return: The forwarded messages.
.. rubric:: Example
.. code-block:: python
# Forward two messages from chat to the destination
await client.forward_messages(
destination,
[187481, 187482],
chat,
)
.. seealso::
:meth:`telethon.types.Message.forward_to`
"""
return await forward_messages(self, target, message_ids, source) return await forward_messages(self, target, message_ids, source)
async def get_contacts(self) -> AsyncList[User]: async def get_contacts(self) -> AsyncList[User]:
"""
Get the users in your contact list.
:return: Your contacts.
.. rubric:: Example
.. code-block:: python
async for user in client.get_contacts():
print(user.full_name, user.id)
"""
return await get_contacts(self) return await get_contacts(self)
def get_dialogs(self) -> None: def get_dialogs(self) -> None:
@ -219,9 +504,51 @@ class Client:
def get_handler_filter( def get_handler_filter(
self, handler: Callable[[Event], Awaitable[Any]] self, handler: Callable[[Event], Awaitable[Any]]
) -> Optional[Filter]: ) -> Optional[Filter]:
"""
Get the filter associated to the given event handler.
:param handler:
The callable that was previously added as an event handler.
:return:
The filter, if *handler* was actually registered and had a filter.
.. rubric:: Example
.. code-block:: python
from telethon.events import filters
# Get the current filter...
filt = client.get_handler_filter(my_handler)
# ...and "append" a new filter that also must match.
client.set_handler_filter(my_handler, filters.All(filt, filt.Text(r'test')))
"""
return get_handler_filter(self, handler) return get_handler_filter(self, handler)
async def get_me(self) -> User: async def get_me(self) -> Optional[User]:
"""
Get information about :term:`yourself`.
:return:
The user associated with the logged-in account, or :data:`None` if the client is not authorized.
.. rubric:: Example
.. code-block:: python
me = await client.get_me()
assert me is not None, "not logged in!"
if me.bot:
print('I am a bot')
print('My name is', me.full_name)
if me.phone:
print('My phone number is', me.phone)
"""
return await get_me(self) return await get_me(self)
def get_messages( def get_messages(
@ -232,6 +559,40 @@ class Client:
offset_id: Optional[int] = None, offset_id: Optional[int] = None,
offset_date: Optional[datetime.datetime] = None, offset_date: Optional[datetime.datetime] = None,
) -> AsyncList[Message]: ) -> AsyncList[Message]:
"""
Get the message history from a :term:`chat`.
Edit a message.
:param chat:
The :term:`chat` where the message to edit is.
:param limit:
How many messages to fetch at most.
:param offset_id:
Start getting messages with an identifier lower than this one.
This means only messages older than the message with ``id = offset_id`` will be fetched.
:param offset_date:
Start getting messages with a date lower than this one.
This means only messages sent before *offset_date* will be fetched.
:return: The message history.
.. rubric:: Example
.. code-block:: python
# Get the last message in a chat
last_message = (await client.get_messages(chat, 1))[0]
# 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.full_name, ':', message.html_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
) )
@ -245,14 +606,70 @@ class Client:
get_participants(self) get_participants(self)
async def inline_query( async def inline_query(
self, bot: ChatLike, query: str, *, chat: Optional[ChatLike] = None self, bot: ChatLike, query: str = "", *, chat: Optional[ChatLike] = None
) -> AsyncIterator[InlineResult]: ) -> AsyncIterator[InlineResult]:
"""
Perform a *@bot inline query*.
It's known as inline because clients with a GUI display the results *inline*,
after typing on the message input textbox, without sending any message.
:param bot:
The bot to sent the query string to.
:param query:
The query string to send to the bot.
:param chat:
Where the query is being made and will be sent.
Some bots display different results based on the type of chat.
:return: The query results returned by the bot.
.. rubric:: Example
.. code-block:: python
i = 0
# This is equivalent to typing "@bot songs" in an official client
async for result in client.inline_query(bot, 'songs'):
if 'keyword' in result.title:
await result.send(chat)
break
i += 1
if i == 10:
break # did not find 'keyword' in the first few results
"""
return await inline_query(self, bot, query, chat=chat) return await inline_query(self, bot, query, chat=chat)
async def interactive_login(self) -> User: async def interactive_login(self) -> User:
"""
Begin an interactive login if needed.
If the account was already logged-in, this method simply returns :term:`yourself`.
:return: The user corresponding to :term:`yourself`.
.. rubric:: Example
.. code-block:: python
me = await client.interactive_login()
print('Logged in as:', me.full_name)
.. seealso::
In-depth explanation for :doc:`/basic/signing-in`.
"""
return await interactive_login(self) return await interactive_login(self)
async def is_authorized(self) -> bool: async def is_authorized(self) -> bool:
"""
Check whether the client instance is authorized (i.e. logged-in).
:return: :data:`True` if the client instance has signed-in.
"""
return await is_authorized(self) return await is_authorized(self)
async def iter_download(self) -> None: async def iter_download(self) -> None:
@ -263,18 +680,137 @@ class Client:
) -> Callable[ ) -> Callable[
[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]] [Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]
]: ]:
"""
Register the decorated function to be invoked when the provided event type occurs.
:param event_cls:
The event type to bind to the handler.
When Telegram sends an update corresponding to this type,
the decorated function is called with an instance of this event type as the only argument.
:param filter:
Filter function to call with the event before calling *handler*.
If it returns `False`, *handler* will not be called.
See the :mod:`~telethon.events.filters` module to learn more.
:return: The decorator.
.. rubric:: Example
.. code-block:: python
# Register a handler to be called on new messages
@client.on(events.NewMessage)
async def my_print_handler(event):
print(event.chat.full_name, event.text)
# Register a handler to be called on new messages if they contain "hello" or "/start"
from telethon.events.filters import Any, Text, Command
@client.on(events.NewMessage, Any(Text(r'hello'), Command('/start')))
async def my_other_print_handler(event):
print(event.chat.full_name, event.text)
.. seealso::
:meth:`add_event_handler`, used to register existing functions as event handlers.
"""
return on(self, event_cls, filter) return on(self, event_cls, filter)
async def pin_message(self, chat: ChatLike, message_id: int) -> Message: async def pin_message(self, chat: ChatLike, message_id: int) -> Message:
"""
Pin a message to be at the top.
:param chat:
The :term:`chat` where the message to pin is.
:param message_id:
The identifier of the message to pin.
:return: The service message announcing the pin.
.. rubric:: Example
.. code-block:: python
# Pin a message, then delete the service message
message = await client.pin_message(chat, 187481)
await message.delete()
"""
return await pin_message(self, chat, message_id) return await pin_message(self, chat, message_id)
def remove_event_handler(self, handler: Callable[[Event], Awaitable[Any]]) -> None: def remove_event_handler(self, handler: Callable[[Event], Awaitable[Any]]) -> None:
"""
Remove the handler as a function to be called when events occur.
This is simply the opposite of :meth:`add_event_handler`.
Does nothing if the handler was not actually registered.
:param handler:
The callable to stop invoking when events occur.
.. rubric:: Example
.. code-block:: python
# Register a handler that removes itself when it receives 'stop'
@client.on(events.NewMessage)
async def my_handler(event):
if 'stop' in event.text:
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) 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.
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.
The plus-sign ``+`` can be kept in the string.
:return: Information about the sent code.
.. rubric:: Example
.. code-block:: python
login_token = await client.request_login_code('+1 23 456...')
print(login_token.timeout, 'seconds before code expires')
.. seealso::
:meth:`sign_in`, to complete the login procedure.
"""
return await request_login_code(self, phone) return await request_login_code(self, phone)
async def resolve_to_packed(self, chat: ChatLike) -> PackedChat: async def resolve_to_packed(self, chat: ChatLike) -> PackedChat:
"""
Resolve a :term:`chat` and return a compact, reusable reference to it.
:param chat:
The :term:`chat` to resolve.
:return: An efficient, reusable version of the input.
.. rubric:: Example
.. code-block:: python
friend = await client.resolve_to_packed('@cat')
# Now you can use `friend` to get or send messages, files...
.. seealso::
In-depth explanation for :doc:`/concepts/chats`.
"""
return await resolve_to_packed(self, chat) return await resolve_to_packed(self, chat)
async def resolve_username(self) -> Chat: async def resolve_username(self) -> Chat:
@ -291,6 +827,30 @@ class Client:
offset_id: Optional[int] = None, offset_id: Optional[int] = None,
offset_date: Optional[datetime.datetime] = None, offset_date: Optional[datetime.datetime] = None,
) -> AsyncList[Message]: ) -> AsyncList[Message]:
"""
Perform a global message search.
This is used to search messages in no particular chat (i.e. everywhere possible).
:param chat:
The :term:`chat` where the message to edit is.
:param limit:
How many messages to fetch at most.
:param query:
Text query to use for fuzzy matching messages.
The rules for how "fuzzy" works are an implementation detail of the server.
:param offset_id:
Start getting messages with an identifier lower than this one.
This means only messages older than the message with ``id = offset_id`` will be fetched.
:param offset_date:
Start getting messages with a date lower than this one.
This means only messages sent before *offset_date* will be fetched.
:return: The found messages.
"""
return search_all_messages( return search_all_messages(
self, limit, query=query, offset_id=offset_id, offset_date=offset_date self, limit, query=query, offset_id=offset_id, offset_date=offset_date
) )
@ -304,6 +864,29 @@ class Client:
offset_id: Optional[int] = None, offset_id: Optional[int] = None,
offset_date: Optional[datetime.datetime] = None, offset_date: Optional[datetime.datetime] = None,
) -> AsyncList[Message]: ) -> AsyncList[Message]:
"""
Search messages in a chat.
:param chat:
The :term:`chat` where messages will be searched.
:param limit:
How many messages to fetch at most.
:param query:
Text query to use for fuzzy matching messages.
The rules for how "fuzzy" works are an implementation detail of the server.
:param offset_id:
Start getting messages with an identifier lower than this one.
This means only messages older than the message with ``id = offset_id`` will be fetched.
:param offset_date:
Start getting messages with a date lower than this one.
This means only messages sent before *offset_date* will be fetched.
:return: The found messages.
"""
return search_messages( return search_messages(
self, chat, limit, query=query, offset_id=offset_id, offset_date=offset_date self, chat, limit, query=query, offset_id=offset_id, offset_date=offset_date
) )
@ -325,8 +908,16 @@ class Client:
""" """
Send an audio file. Send an audio file.
Unlike `send_file`, this method will attempt to guess the values for Unlike :meth:`send_file`, this method will attempt to guess the values for
duration, title and performer if they are not provided. duration, title and performer if they are not provided.
:param chat:
The :term:`chat` where the message will be sent to.
:param path:
A local file path or :class:`~telethon.types.File` to send.
The rest of parameters behave the same as they do in :meth:`send_file`.
""" """
return await send_audio( return await send_audio(
self, self,
@ -372,15 +963,32 @@ class Client:
""" """
Send any type of file with any amount of attributes. Send any type of file with any amount of attributes.
This method will not attempt to guess any of the file metadata such as This method will *not* attempt to guess any of the file metadata such as width, duration, title, etc.
width, duration, title, etc. If you want to let the library attempt to If you want to let the library attempt to guess the file metadata, use the type-specific methods to send media:
guess the file metadata, use the type-specific methods to send media:
`send_photo`, `send_audio` or `send_file`. `send_photo`, `send_audio` or `send_file`.
Unlike `send_photo`, image files will be sent as documents by default. Unlike `send_photo`, image files will be sent as documents by default.
The parameters are used to construct a `File`. See the documentation :param chat:
for `File.new` to learn what they do and when they are in effect. The :term:`chat` where the message will be sent to.
:param path:
A local file path or :class:`~telethon.types.File` to send.
:param caption:
Caption text to display under the media, with no formatting.
:param caption_markdown:
Caption text to display under the media, parsed as markdown.
:param caption_html:
Caption text to display under the media, parsed as HTML.
The rest of parameters are passed to :meth:`telethon.types.File.new`
if *path* isn't a :class:`~telethon.types.File`.
See the documentation of :meth:`~telethon.types.File.new` to learn what they do.
Note that only one *caption* parameter can be provided.
""" """
return await send_file( return await send_file(
self, self,
@ -418,6 +1026,23 @@ class Client:
html: Optional[str] = None, html: Optional[str] = None,
link_preview: Optional[bool] = None, link_preview: Optional[bool] = None,
) -> Message: ) -> Message:
"""
Send a message.
:param chat:
The :term:`chat` where the message will be sent to.
:param text:
Message text, with no formatting.
:param text_markdown:
Message text, parsed as markdown.
:param text_html:
Message text, parsed as HTML.
Note that exactly one *text* parameter must be provided.
"""
return await send_message( return await send_message(
self, self,
chat, chat,
@ -443,16 +1068,20 @@ class Client:
""" """
Send a photo file. Send a photo file.
Exactly one of path, url or file must be specified.
A `File` can also be used as the second parameter.
By default, the server will be allowed to `compress` the image. By default, the server will be allowed to `compress` the image.
Only compressed images can be displayed as photos in applications. Only compressed images can be displayed as photos in applications.
Images that cannot be compressed will be sent as file documents, If *compress* is set to :data:`False`, the image will be sent as a file document.
with a thumbnail if possible.
Unlike `send_file`, this method will attempt to guess the values for Unlike `send_file`, this method will attempt to guess the values for
width and height if they are not provided and the can't be compressed. width and height if they are not provided.
:param chat:
The :term:`chat` where the message will be sent to.
:param path:
A local file path or :class:`~telethon.types.File` to send.
The rest of parameters behave the same as they do in :meth:`send_file`.
""" """
return await send_photo( return await send_photo(
self, self,
@ -487,6 +1116,14 @@ class Client:
Unlike `send_file`, this method will attempt to guess the values for Unlike `send_file`, this method will attempt to guess the values for
duration, width and height if they are not provided. duration, width and height if they are not provided.
:param chat:
The :term:`chat` where the message will be sent to.
:param path:
A local file path or :class:`~telethon.types.File` to send.
The rest of parameters behave the same as they do in :meth:`send_file`.
""" """
return await send_video( return await send_video(
self, self,
@ -508,17 +1145,89 @@ class Client:
handler: Callable[[Event], Awaitable[Any]], handler: Callable[[Event], Awaitable[Any]],
filter: Optional[Filter] = None, filter: Optional[Filter] = None,
) -> None: ) -> None:
"""
Set the filter to use for the given event handler.
:param handler:
The callable that was previously added as an event handler.
:param filter:
The filter to use for *handler*, or :data:`None` to remove the old filter.
.. rubric:: Example
.. code-block:: python
from telethon.events import filters
# Change the filter to handle '/stop'
client.set_handler_filter(my_handler, filters.Command('/stop'))
# Remove the filter
client.set_handler_filter(my_handler, None)
"""
set_handler_filter(self, handler, filter) set_handler_filter(self, handler, filter)
async def sign_in(self, token: LoginToken, code: str) -> Union[User, PasswordToken]: async def sign_in(self, token: LoginToken, code: str) -> Union[User, PasswordToken]:
"""
Sign in to a user account.
:param token:
The login token returned from :meth:`request_login_code`.
:return:
The user corresponding to :term:`yourself`, or a password token if the account has 2FA enabled.
.. rubric:: Example
.. code-block:: python
from telethon.types import PasswordToken
login_token = await client.request_login_code('+1 23 456')
user_or_token = await client.sign_in(login_token, input('code: '))
if isinstance(password_token, PasswordToken):
user = await client.check_password(password_token, '1-L0V3+T3l3th0n')
.. seealso::
:meth:`check_password`, the next step if the account has 2FA enabled.
"""
return await sign_in(self, token, code) return await sign_in(self, token, code)
async def sign_out(self) -> None: async def sign_out(self) -> None:
"""
Sign out, revoking the authorization of the current :term:`session`.
.. rubric:: Example
.. code-block:: python
await client.sign_out() # turn off the lights
await client.disconnect() # shut the door
"""
await sign_out(self) await sign_out(self)
async def unpin_message( async def unpin_message(
self, chat: ChatLike, message_id: Union[int, Literal["all"]] self, chat: ChatLike, message_id: Union[int, Literal["all"]]
) -> None: ) -> None:
"""
Unpin one or all messages from the top.
:param chat:
The :term:`chat` where the message pinned message is.
:param message_id:
The identifier of the message to unpin, or ``'all'`` to unpin them all.
.. rubric:: Example
.. code-block:: python
# Unpin all messages
await client.unpin_message(chat, 'all')
"""
await unpin_message(self, chat, message_id) await unpin_message(self, chat, message_id)
# --- # ---

View File

@ -39,6 +39,12 @@ def default_system_version() -> str:
@dataclass @dataclass
class Config: class Config:
"""
Configuration used by the :class:`telethon.Client`.
See the parameters of :class:`~telethon.Client` for an explanation of the fields.
"""
session: Session session: Session
api_id: int api_id: int
api_hash: str api_hash: str

View File

@ -10,7 +10,7 @@ if TYPE_CHECKING:
from .client import Client from .client import Client
async def get_me(self: Client) -> User: async def get_me(self: Client) -> Optional[User]:
self self
raise NotImplementedError raise NotImplementedError

View File

@ -11,6 +11,10 @@ if TYPE_CHECKING:
class Event(metaclass=NoPublicConstructor): class Event(metaclass=NoPublicConstructor):
"""
The base type of all events.
"""
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:

View File

@ -6,7 +6,7 @@ from .common import Filter
class Any: class Any:
""" """
Combine multiple filters, returning `True` if any of the filters pass. Combine multiple filters, returning :data:`True` if any of the filters pass.
""" """
__slots__ = ("_filters",) __slots__ = ("_filters",)
@ -27,7 +27,7 @@ class Any:
class All: class All:
""" """
Combine multiple filters, returning `True` if all of the filters pass. Combine multiple filters, returning :data:`True` if all of the filters pass.
""" """
__slots__ = ("_filters",) __slots__ = ("_filters",)
@ -48,8 +48,7 @@ class All:
class Not: class Not:
""" """
Negate the output of a single filter, returning `True` if the nested Negate the output of a single filter, returning :data:`True` if the nested filter does *not* pass.
filter does *not* pass.
""" """
__slots__ = ("_filter",) __slots__ = ("_filter",)

View File

@ -7,7 +7,7 @@ Filter = Callable[[Event], bool]
class Chats: class Chats:
""" """
Filter by `event.chat.id`. Filter by ``event.chat.id``, if the event has a chat.
""" """
__slots__ = ("_chats",) __slots__ = ("_chats",)
@ -31,7 +31,7 @@ class Chats:
class Senders: class Senders:
""" """
Filter by `event.sender.id`. Filter by ``event.sender.id``, if the event has a sender.
""" """
__slots__ = ("_senders",) __slots__ = ("_senders",)

View File

@ -6,10 +6,10 @@ from ..event import Event
class Text: class Text:
""" """
Filter by `event.text` using a *regular expression* pattern. Filter by ``event.text`` using a *regular expression* pattern.
The pattern is searched on the text anywhere, not matched at the start. The pattern is searched on the text anywhere, not matched at the start.
Use the `'^'` anchor if you want to match the text from the start. Use the ``'^'`` anchor if you want to match the text from the start.
The match, if any, is discarded. If you need to access captured groups, The match, if any, is discarded. If you need to access captured groups,
you need to manually perform the check inside the handler instead. you need to manually perform the check inside the handler instead.
@ -27,12 +27,12 @@ class Text:
class Command: class Command:
""" """
Filter by `event.text` to make sure the first word matches the command or Filter by ``event.text`` to make sure the first word matches the command or
the command + '@' + username, using the username of the logged-in account. the command + '@' + username, using the username of the logged-in account.
For example, if the logged-in account has an username of "bot", then the For example, if the logged-in account has an username of "bot", then the
filter `Command('/help')` will match both "/help" and "/help@bot", but not filter ``Command('/help')`` will match both ``"/help"`` and ``"/help@bot"``, but not
"/list" or "/help@other". ``"/list"`` or ``"/help@other"``.
Note that the leading forward-slash is not automatically added, Note that the leading forward-slash is not automatically added,
which allows for using a different prefix or no prefix at all. which allows for using a different prefix or no prefix at all.
@ -49,11 +49,11 @@ class Command:
class Incoming: class Incoming:
""" """
Filter by `event.incoming`, that is, messages sent from others to the Filter by ``event.incoming``, that is, messages sent from others to the
logged-in account. logged-in account.
This is not a reliable way to check that the update was not produced by This is not a reliable way to check that the update was not produced by
the logged-in account. the logged-in account in broadcast channels.
""" """
__slots__ = () __slots__ = ()
@ -64,11 +64,11 @@ class Incoming:
class Outgoing: class Outgoing:
""" """
Filter by `event.outgoing`, that is, messages sent from others to the Filter by ``event.outgoing``, that is, messages sent from others to the
logged-in account. logged-in account.
This is not a reliable way to check that the update was not produced by This is not a reliable way to check that the update was not produced by
the logged-in account. the logged-in account in broadcast channels.
""" """
__slots__ = () __slots__ = ()
@ -79,7 +79,7 @@ class Outgoing:
class Forward: class Forward:
""" """
Filter by `event.forward`. Filter by ``event.forward``.
""" """
__slots__ = () __slots__ = ()
@ -90,7 +90,7 @@ class Forward:
class Reply: class Reply:
""" """
Filter by `event.reply`. Filter by ``event.reply``.
""" """
__slots__ = () __slots__ = ()

View File

@ -15,6 +15,16 @@ if TYPE_CHECKING:
class NewMessage(Event, Message): class NewMessage(Event, Message):
"""
Occurs when a new message is sent or received.
.. warning::
Messages sent with the :class:`~telethon.Client` are also caught,
so be careful not to enter infinite loops!
This is true for all event types, including edits.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
if isinstance(update, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): if isinstance(update, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
@ -29,18 +39,30 @@ class NewMessage(Event, Message):
class MessageEdited(Event): class MessageEdited(Event):
"""
Occurs when a new message is sent or received.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
raise NotImplementedError() raise NotImplementedError()
class MessageDeleted(Event): class MessageDeleted(Event):
"""
Occurs when one or more messages are deleted.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
raise NotImplementedError() raise NotImplementedError()
class MessageRead(Event): class MessageRead(Event):
"""
Occurs both when your messages are read by others, and when you read messages.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
raise NotImplementedError() raise NotImplementedError()

View File

@ -10,12 +10,24 @@ if TYPE_CHECKING:
class CallbackQuery(Event): class CallbackQuery(Event):
"""
Occurs when an inline button was pressed.
Only bot accounts can receive this event.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
raise NotImplementedError() raise NotImplementedError()
class InlineQuery(Event): class InlineQuery(Event):
"""
Occurs when users type ``@bot query`` in their chat box.
Only bot accounts can receive this event.
"""
@classmethod @classmethod
def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]: def _try_from_update(cls, client: Client, update: abcs.Update) -> Optional[Self]:
raise NotImplementedError() raise NotImplementedError()

View File

@ -21,6 +21,7 @@ class AsyncList(abc.ABC, Generic[T]):
The `len()` of the asynchronous list will be the "total count" reported The `len()` of the asynchronous list will be the "total count" reported
by the server. It does not necessarily reflect how many items will by the server. It does not necessarily reflect how many items will
actually be returned. This count can change as more items are fetched. actually be returned. This count can change as more items are fetched.
Note that this method cannot be awaited.
""" """
def __init__(self) -> None: def __init__(self) -> None:

View File

@ -6,6 +6,10 @@ from ..meta import NoPublicConstructor
class Channel(metaclass=NoPublicConstructor): class Channel(metaclass=NoPublicConstructor):
"""
A broadcast channel.
"""
__slots__ = ("_raw",) __slots__ = ("_raw",)
def __init__( def __init__(
@ -45,6 +49,10 @@ class Channel(metaclass=NoPublicConstructor):
def title(self) -> str: def title(self) -> str:
return getattr(self._raw, "title", None) or "" return getattr(self._raw, "title", None) or ""
@property
def full_name(self) -> str:
return self.title
@property @property
def username(self) -> Optional[str]: def username(self) -> Optional[str]:
return getattr(self._raw, "username", None) return getattr(self._raw, "username", None)

View File

@ -6,6 +6,10 @@ from ..meta import NoPublicConstructor
class Group(metaclass=NoPublicConstructor): class Group(metaclass=NoPublicConstructor):
"""
A small group or supergroup.
"""
__slots__ = ("_raw",) __slots__ = ("_raw",)
def __init__( def __init__(
@ -49,6 +53,10 @@ class Group(metaclass=NoPublicConstructor):
def title(self) -> str: def title(self) -> str:
return getattr(self._raw, "title", None) or "" return getattr(self._raw, "title", None) or ""
@property
def full_name(self) -> str:
return self.title
@property @property
def username(self) -> Optional[str]: def username(self) -> Optional[str]:
return getattr(self._raw, "username", None) return getattr(self._raw, "username", None)

View File

@ -6,6 +6,10 @@ from ..meta import NoPublicConstructor
class RestrictionReason(metaclass=NoPublicConstructor): class RestrictionReason(metaclass=NoPublicConstructor):
"""
A restriction reason for :class:`telethon.types.User`.
"""
__slots__ = ("_raw",) __slots__ = ("_raw",)
def __init__(self, raw: types.RestrictionReason) -> None: def __init__(self, raw: types.RestrictionReason) -> None:
@ -30,6 +34,10 @@ class RestrictionReason(metaclass=NoPublicConstructor):
class User(metaclass=NoPublicConstructor): class User(metaclass=NoPublicConstructor):
"""
A user, representing either a bot account or an account created with a phone number.
"""
__slots__ = ("_raw",) __slots__ = ("_raw",)
def __init__(self, raw: types.User) -> None: def __init__(self, raw: types.User) -> None:

View File

@ -34,8 +34,8 @@ MediaLike = object
class InFileLike(Protocol): class InFileLike(Protocol):
""" """
[File-like object](https://docs.python.org/3/glossary.html#term-file-like-object) A :term:`file-like object` used for input only.
used for input only, where the `read` method can be `async`. The :meth:`read` method can be :keyword:`async`.
""" """
def read(self, n: int) -> Union[bytes, Coroutine[Any, Any, bytes]]: def read(self, n: int) -> Union[bytes, Coroutine[Any, Any, bytes]]:
@ -44,8 +44,8 @@ class InFileLike(Protocol):
class OutFileLike(Protocol): class OutFileLike(Protocol):
""" """
[File-like object](https://docs.python.org/3/glossary.html#term-file-like-object) A :term:`file-like object` used for output only.
used for output only, where the `write` method can be `async`. The :meth:`write` method can be :keyword:`async`.
""" """
def write(self, data: bytes) -> Union[Any, Coroutine[Any, Any, Any]]: def write(self, data: bytes) -> Union[Any, Coroutine[Any, Any, Any]]:

View File

@ -1,10 +1,14 @@
from typing import Self from typing import Optional, Self
from ...tl import types from ...tl import types
from .meta import NoPublicConstructor from .meta import NoPublicConstructor
class LoginToken(metaclass=NoPublicConstructor): class LoginToken(metaclass=NoPublicConstructor):
"""
Result of requesting a login code via :meth:`telethon.Client.request_login_code`.
"""
__slots__ = ("_code", "_phone") __slots__ = ("_code", "_phone")
def __init__(self, code: types.auth.SentCode, phone: str) -> None: def __init__(self, code: types.auth.SentCode, phone: str) -> None:
@ -14,3 +18,7 @@ class LoginToken(metaclass=NoPublicConstructor):
@classmethod @classmethod
def _new(cls, code: types.auth.SentCode, phone: str) -> Self: def _new(cls, code: types.auth.SentCode, phone: str) -> Self:
return cls._create(code, phone) return cls._create(code, phone)
@property
def timeout(self) -> Optional[int]:
return self._code.timeout

View File

@ -8,6 +8,10 @@ from .meta import NoPublicConstructor
class Message(metaclass=NoPublicConstructor): class Message(metaclass=NoPublicConstructor):
"""
A sent message.
"""
__slots__ = ("_raw",) __slots__ = ("_raw",)
def __init__(self, message: abcs.Message) -> None: def __init__(self, message: abcs.Message) -> None:
@ -28,6 +32,14 @@ class Message(metaclass=NoPublicConstructor):
def text(self) -> Optional[str]: def text(self) -> Optional[str]:
return getattr(self._raw, "message", None) return getattr(self._raw, "message", None)
@property
def html_text(self) -> Optional[str]:
raise NotImplementedError
@property
def markdown_text(self) -> Optional[str]:
raise NotImplementedError
@property @property
def date(self) -> Optional[datetime.datetime]: def date(self) -> Optional[datetime.datetime]:
date = getattr(self._raw, "date", None) date = getattr(self._raw, "date", None)
@ -80,3 +92,16 @@ class Message(metaclass=NoPublicConstructor):
) )
else None else None
) )
@property
def file(self) -> Optional[File]:
return self._file()
async def delete(self) -> None:
raise NotImplementedError
async def edit(self) -> None:
raise NotImplementedError
async def forward_to(self) -> None:
raise NotImplementedError

View File

@ -5,6 +5,10 @@ from .meta import NoPublicConstructor
class PasswordToken(metaclass=NoPublicConstructor): class PasswordToken(metaclass=NoPublicConstructor):
"""
Result of attempting to :meth:`~telethon.Client.sign_in` to a 2FA-protected account.
"""
__slots__ = ("_password",) __slots__ = ("_password",)
def __init__(self, password: types.account.Password) -> None: def __init__(self, password: types.account.Password) -> None:

View File

@ -9,6 +9,17 @@ MsgId = NewType("MsgId", int)
class RpcError(ValueError): class RpcError(ValueError):
"""
A Remote Procedure Call Error.
Only occurs when the answer to a request sent to Telegram is not the expected result.
The library will never construct instances of this error by itself.
.. seealso::
:doc:`/concepts/errors`
"""
def __init__( def __init__(
self, self,
*, *,

View File

@ -6,6 +6,10 @@ from ...tl import abcs, types
class PackedType(Enum): class PackedType(Enum):
"""
The type of a :class:`PackedChat`.
"""
# bits: zero, has-access-hash, channel, broadcast, group, chat, user, bot # bits: zero, has-access-hash, channel, broadcast, group, chat, user, bot
USER = 0b0000_0010 USER = 0b0000_0010
BOT = 0b0000_0011 BOT = 0b0000_0011
@ -16,6 +20,16 @@ class PackedType(Enum):
class PackedChat: class PackedChat:
"""
A compact representation of a :term:`chat`.
You can reuse it as many times as you want.
.. seealso::
:doc:`/concepts/chats`
"""
__slots__ = ("ty", "id", "access_hash") __slots__ = ("ty", "id", "access_hash")
def __init__(self, ty: PackedType, id: int, access_hash: Optional[int]) -> None: def __init__(self, ty: PackedType, id: int, access_hash: Optional[int]) -> None:

View File

@ -3,6 +3,14 @@ from typing import Any, Dict, List, Optional, Self
class DataCenter: class DataCenter:
"""
Data-center information.
:var id: The DC identifier.
:var addr: The server address of the DC, in ``'ip:port'`` format.
:var auth: Authentication key to encrypt communication with.
"""
__slots__ = ("id", "addr", "auth") __slots__ = ("id", "addr", "auth")
def __init__(self, *, id: int, addr: str, auth: Optional[bytes]) -> None: def __init__(self, *, id: int, addr: str, auth: Optional[bytes]) -> None:
@ -12,6 +20,14 @@ class DataCenter:
class User: class User:
"""
Information about the logged-in user.
:var id: User identifier.
:var dc: Data-center identifier of the user's "home" DC.
:var bot: :data:`True` if the user is from a bot account.
"""
__slots__ = ("id", "dc", "bot") __slots__ = ("id", "dc", "bot")
def __init__(self, *, id: int, dc: int, bot: bool) -> None: def __init__(self, *, id: int, dc: int, bot: bool) -> None:
@ -21,6 +37,13 @@ class User:
class ChannelState: class ChannelState:
"""
Update state for a channel.
:var id: The channel identifier.
:var pts: The channel's partial sequence number.
"""
__slots__ = ("id", "pts") __slots__ = ("id", "pts")
def __init__(self, *, id: int, pts: int) -> None: def __init__(self, *, id: int, pts: int) -> None:
@ -29,6 +52,16 @@ class ChannelState:
class UpdateState: class UpdateState:
"""
Update state for an account.
:var pts: The primary partial sequence number.
:var qts: The secondary partial sequence number.
:var date: Date of the latest update sequence.
:var seq: The sequence number.
:var channels: Update state for channels.
"""
__slots__ = ( __slots__ = (
"pts", "pts",
"qts", "qts",
@ -55,9 +88,9 @@ class UpdateState:
class Session: class Session:
""" """
A Telethon session. A Telethon :term:`session`.
A `Session` instance contains the required information to login into your A session instance contains the required information to login into your
Telegram account. **Never** give the saved session file to anyone else or Telegram account. **Never** give the saved session file to anyone else or
make it public. make it public.

View File

@ -15,9 +15,9 @@ class SqliteSession(Storage):
SQLite is a reliable way to persist data to disk and offers file locking. SQLite is a reliable way to persist data to disk and offers file locking.
Paths without extension will have '.session' appended to them. Paths without extension will have ``'.session'`` appended to them.
This is by convention, and to make it harder to commit session files to This is by convention, and to make it harder to commit session files to
an VCS by accident (adding `*.session` to `.gitignore` will catch them). an VCS by accident (adding ``*.session`` to ``.gitignore`` will catch them).
""" """
def __init__(self, file: Union[str, Path]): def __init__(self, file: Union[str, Path]):

View File

@ -5,6 +5,10 @@ from ..session import Session
class Storage(abc.ABC): class Storage(abc.ABC):
"""
Interface declaring the required methods of a :term:`session` storage.
"""
@abc.abstractmethod @abc.abstractmethod
async def load(self) -> Optional[Session]: async def load(self) -> Optional[Session]:
""" """
@ -27,4 +31,6 @@ class Storage(abc.ABC):
Delete the saved `Session`. Delete the saved `Session`.
This method is called by the library post `log_out`. This method is called by the library post `log_out`.
Note that both :meth:`load` and :meth:`save` may still be called after.
""" """

View File

@ -1,3 +1,12 @@
"""
Filters are functions that accept a single parameter, an :class:`~telethon.events.Event` instance, and return a :class:`bool`.
When the return value is :data:`True`, the associated :mod:`~telethon.events` handler will be invoked.
.. seealso::
The :doc:`/concepts/updates` concept to learn to combine filters or define your own.
"""
from .._impl.client.events.filters import ( from .._impl.client.events.filters import (
All, All,
Any, Any,

View File

@ -1,4 +1,4 @@
from ._impl.client.client import Config, InlineResult, InlineResults from ._impl.client.client import Config, InlineResult
from ._impl.client.types import ( from ._impl.client.types import (
AsyncList, AsyncList,
Channel, Channel,
@ -20,7 +20,6 @@ from ._impl.session import PackedChat, PackedType
__all__ = [ __all__ = [
"Config", "Config",
"InlineResult", "InlineResult",
"InlineResults",
"AsyncList", "AsyncList",
"Channel", "Channel",
"Chat", "Chat",

View File

@ -18,7 +18,7 @@ def main() -> None:
run("isort", ".", "-c", "--profile", "black", "--gitignore") run("isort", ".", "-c", "--profile", "black", "--gitignore")
or run("black", ".", "--check", "--extend-exclude", BLACK_IGNORE) or run("black", ".", "--check", "--extend-exclude", BLACK_IGNORE)
or run("mypy", "--strict", ".") or run("mypy", "--strict", ".")
or run("sphinx", "-nW", "client/doc", tmp_dir) or run("sphinx", "-M", "dummy", "client/doc", tmp_dir, "-n", "-W")
or run("pytest", ".", "-m", "not net") or run("pytest", ".", "-m", "not net")
) )