mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-11 08:32:22 +03:00
Migrate from chat to peer
This commit is contained in:
parent
1dba3ae6d0
commit
38241dffd2
|
@ -6,10 +6,10 @@ Glossary
|
||||||
.. glossary::
|
.. glossary::
|
||||||
:sorted:
|
:sorted:
|
||||||
|
|
||||||
chat
|
peer
|
||||||
A :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
|
A :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
|
||||||
|
|
||||||
.. seealso:: The :doc:`../concepts/chats` concept.
|
.. seealso:: The :doc:`../concepts/peers` concept.
|
||||||
|
|
||||||
yourself
|
yourself
|
||||||
The logged-in account, whether that represents a bot or a user with a phone number.
|
The logged-in account, whether that represents a bot or a user with a phone number.
|
||||||
|
|
|
@ -95,9 +95,9 @@ and instead favours more standard `HTML elements <https://developer.mozilla.org/
|
||||||
|
|
||||||
Both markdown and HTML recognise the following special URLs using the ``tg:`` protocol:
|
Both markdown and HTML recognise the following special URLs using the ``tg:`` protocol:
|
||||||
|
|
||||||
* ``tg://user?id=ab1234cd6789`` for inline mentions.
|
* ``tg://user?ref=u.123.A4B5`` for inline mentions.
|
||||||
To make sure the mention works, use :attr:`types.PeerRef.hex`.
|
You can obtain the reference using :attr:`types.Peer.ref` (as in ``f'tg://user?ref={user.ref}'``).
|
||||||
You can also use :attr:`types.User.id`, but the mention will fail if the user is not in cache.
|
You can also the ``?id=`` query parameter with :attr:`types.User.id` instead, but the mention may fail.
|
||||||
* ``tg://emoji?id=1234567890`` for custom emoji.
|
* ``tg://emoji?id=1234567890`` for custom emoji.
|
||||||
You must use the document identifier as the value.
|
You must use the document identifier as the value.
|
||||||
The alt-text of the image **must** be a emoji such as 👍.
|
The alt-text of the image **must** be a emoji such as 👍.
|
||||||
|
|
|
@ -1,43 +1,47 @@
|
||||||
Chats
|
Peers, users and chats
|
||||||
=====
|
======================
|
||||||
|
|
||||||
.. currentmodule:: telethon
|
.. currentmodule:: telethon
|
||||||
|
|
||||||
The term :term:`chat` is extremely overloaded, so it's no surprise many are confused by what it means.
|
The term :term:`peer` may sound strange at first, but it's the best we have after much consideration.
|
||||||
This section should hopefully clear that up.
|
This section aims to explain what peers are, and how they relate to users, group chats, and broadcast channels.
|
||||||
|
|
||||||
|
|
||||||
Telethon Chat
|
Telethon Peer
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The word :term:`chat` in Telethon is used to refer a place where messages are sent to.
|
The :class:`~types.Peer` type in Telethon is the base class for :class:`~types.User`, :class:`~types.Group` and :class:`~types.Channel`.
|
||||||
Therefore, a Telethon :term:`chat` can be another user, a bot, a group, or a broadcast channel.
|
Therefore, a Telethon ":term:`peer`" represents an entity with various attributes: identifier, username, photo, title, and other information depending on its type.
|
||||||
All of those are places where messages can be sent.
|
|
||||||
|
|
||||||
Of course, chats do more things than contain messages.
|
The :class:`~types.PeerRef` type represents a reference to a :class:`~types.Peer`, and can be obtained from its :attr:`~types.Peer.ref` attribute.
|
||||||
They often have a name, username, photo, description, and other information.
|
Each peer type has its own reference type, namely :class:`~types.UserRef`, :class:`~types.GroupRef` and :class:`~types.ChannelRef`.
|
||||||
|
|
||||||
When a :term:`chat` appears in a parameter or as a property,
|
| Most methods accept either the :class:`~types.Peer` or :class:`~types.PeerRef` (and their subclasses) as input.
|
||||||
it means that it will be either a :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
|
You do not need to fetch the full :class:`~types.Peer` to :meth:`~Client.get_messages` or :meth:`~Client.send_file`\ s— a :class:`~types.PeerRef` is enough.
|
||||||
|
| Some methods will only work on groups and channels (like :meth:`~Client.get_participants`), or users (like :meth:`~Client.inline_query`).
|
||||||
|
|
||||||
When a parameter must be "chat-like", it means Telethon will accept anything that can be "converted" to a :term:`chat`.
|
A Telethon "chat" refers to either groups and channels, or the place where messages are sent to.
|
||||||
The following types are chat-like:
|
In the latter case, the chat could also belong to a user, so it would be represented by a :class:`~types.Peer`.
|
||||||
|
|
||||||
* The ``'me'`` literal string. This represents the account that is logged in ("yourself").
|
A Telethon "group" is used to refer to either small group chats or supergroups.
|
||||||
* An ``'@username'``. The at-sign ``@`` is optional. Note that links are not supported.
|
This matches what the interface of official applications call these entities.
|
||||||
* An ``'+1 23'`` phone number string. It must be an :class:`str` and start with the plus-sign ``+`` character.
|
|
||||||
* An ``123`` integer identifier. It must be an :class:`int` and cannot be negative.
|
|
||||||
* An existing :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`.
|
|
||||||
* A :class:`~types.PeerRef`.
|
|
||||||
|
|
||||||
Previous versions of Telethon referred to this term as "entity" or "entities" instead.
|
A Telethon "user" is used to refer to either user accounts or bot accounts.
|
||||||
|
This matches Telegram's API, as both are represented by the same user object.
|
||||||
|
|
||||||
|
|
||||||
Telegram Chat
|
Telegram Peer
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The Telegram API is very confusing when it comes to the word "chat".
|
.. note::
|
||||||
You only need to know about this if you plan to use the :term:`Raw API`.
|
|
||||||
|
This section is mainly of interest if you plan to use the :term:`Raw API`.
|
||||||
|
|
||||||
|
Telegram uses :tl:`Peer`\ s to categorize users, groups and channels, much like how Telethon does.
|
||||||
|
It also has the concept of :tl:`InputPeer`\ s, which are commonly used as input parameters when sending requests.
|
||||||
|
These match the concept of Telethon's peer references.
|
||||||
|
|
||||||
|
The main confusion in Telegram's API comes from the word "chat".
|
||||||
|
|
||||||
In the :term:`TL` 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`.
|
||||||
|
@ -63,33 +67,34 @@ In Telethon:
|
||||||
Telethon classes aim to map to similar concepts in official applications.
|
Telethon classes aim to map to similar concepts in official applications.
|
||||||
|
|
||||||
|
|
||||||
Bot API chat
|
Bot API Peer
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The Bot API follows a certain convention when it comes to identifiers:
|
The Bot API does not use the word "peer", but instead opts to use "chat" and "user" only, despite chats also being able to reference users.
|
||||||
|
The Bot API follows a certain convention when it comes to chat and user identifiers:
|
||||||
|
|
||||||
* User IDs are positive.
|
* User IDs are positive.
|
||||||
* Chat IDs are negative.
|
* Chat IDs are negative.
|
||||||
* Channel IDs are *also* negative, but are prefixed by ``-100``.
|
* Channel IDs are *also* negative, but are prefixed by ``-100``.
|
||||||
|
|
||||||
Telethon encourages the use of :class:`~types.PeerRef` instead of naked identifiers.
|
Telethon does not support Bot API's formatted identifiers, and instead expects you to create the appropriated :class:`~types.PeerRef`:
|
||||||
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
|
.. code-block:: python
|
||||||
|
|
||||||
# If -1001234 is your ID...
|
from telethon.types import UserRef, GroupRef, ChannelRef
|
||||||
from telethon.types import PackedChat, PackedType
|
|
||||||
chat = PackedChat(PackedType.BROADCAST, 1234, None)
|
user = UserRef(123) # user_id 123 from bot API becomes 123
|
||||||
# ...you need to explicitly create a PackedChat with id=1234 and set the corresponding type (a channel).
|
group = GroupRef(456) # chat_id -456 from bot API becomes 456
|
||||||
# The access hash (see below) will be None, which may or may not work.
|
channel = ChannelRef(789) # chat_id -100789 from bot API becomes 789
|
||||||
|
|
||||||
|
While using a Telethon Client logged in to a bot account, the above may work for certain methods.
|
||||||
|
However, user accounts often require what's known as an "access hash", obtained by encountering the peer first.
|
||||||
|
|
||||||
|
|
||||||
Encountering chats
|
Encountering peers
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The way you encounter chats in Telethon is no different from official clients.
|
The way you encounter peers in Telethon is no different from official clients.
|
||||||
If you:
|
If you:
|
||||||
|
|
||||||
* …have joined a group or channel, or have sent private messages to some user, you can :meth:`~Client.get_dialogs`.
|
* …have joined a group or channel, or have sent private messages to some user, you can :meth:`~Client.get_dialogs`.
|
||||||
|
@ -99,21 +104,19 @@ If you:
|
||||||
* …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
|
Access hashes and authorizations
|
||||||
-----------------
|
--------------------------------
|
||||||
|
|
||||||
Users, supergroups and channels all need an :term:`access hash`.
|
Users, supergroups and channels all need an :term:`access hash`.
|
||||||
|
This value is proof that you're authorized to access the peer in question.
|
||||||
|
This value is also account-bound.
|
||||||
|
You cannot obtain an :term:`access hash` in Account-A and use it in Account-B.
|
||||||
|
|
||||||
In Telethon, the :class:`~types.PeerRef` is the recommended way to deal with the identifier-hash pairs.
|
In Telethon, the :class:`~types.PeerRef` is the recommended way to deal with the identifier-authorization pairs.
|
||||||
This compact type can be used anywhere a chat is expected.
|
This compact type can be used anywhere a peer is expected.
|
||||||
It's designed to be easy to store and cache in any way your application chooses.
|
It's designed to be easy to store and cache in any way your application chooses.
|
||||||
|
You can easily serialize it to a string and back via ``str(ref)`` and :meth:`types.PeerRef.from_str`.
|
||||||
|
|
||||||
Bot accounts can get away with an invalid :term:`access hash` for certain operations under certain conditions.
|
Bot accounts can get away with an invalid :term:`access hash` for certain operations under certain conditions.
|
||||||
The same is true for user accounts, although to a lesser extent.
|
The same is true for user accounts, although to a lesser extent.
|
||||||
|
When you create a :class:`~types.PeerRef` without specifying an authorization, a bogus :term:`access hash` will be used.
|
||||||
When using just the identifier to refer to a chat, Telethon will attempt to retrieve its hash from its in-memory cache.
|
|
||||||
If this fails, an invalid hash will be used. This may or may not make the API call succeed.
|
|
||||||
For this reason, it is recommended that you always use :class:`~types.PeerRef` instead.
|
|
||||||
|
|
||||||
Remember that an :term:`access hash` is account-bound.
|
|
||||||
You cannot obtain an :term:`access hash` in Account-A and use it in Account-B.
|
|
||||||
|
|
|
@ -444,20 +444,21 @@ Telegram may still choose to send their ``min`` version with only basic details.
|
||||||
But it means you don't have to remember 5 different ways of using chats.
|
But it means you don't have to remember 5 different ways of using chats.
|
||||||
|
|
||||||
To replace the concept of "input chats", v2 introduces :class:`types.PeerRef`.
|
To replace the concept of "input chats", v2 introduces :class:`types.PeerRef`.
|
||||||
A "packed chat" is a chat with *just* enough information that you can use it without relying on Telethon's cache.
|
A "peer" represents either a :class:`~types.User`, :class:`~types.Group` or :class:`~types.Channel`, much like Telegram's :tl:`Peer`.
|
||||||
|
A "peer reference" represents *just* enough information to reference that peer without relying on Telethon's cache.
|
||||||
This is the most efficient way to call methods like :meth:`Client.send_message` too.
|
This is the most efficient way to call methods like :meth:`Client.send_message` too.
|
||||||
|
|
||||||
The concept of "marked IDs" also no longer exists.
|
The concept of "marked IDs" also no longer exists.
|
||||||
This means v2 no longer supports the ``-`` or ``-100`` prefixes on identifiers.
|
This means v2 no longer supports the ``-`` or ``-100`` prefixes on identifiers.
|
||||||
:tl:`Peer`-wrapping is gone, too.
|
Using the raw :tl:`Peer` to wrap the identifiers is gone, too.
|
||||||
Instead, you're strongly encouraged to use :class:`types.PeerRef` instances.
|
Instead, you're strongly encouraged to use :class:`types.PeerRef` instances.
|
||||||
|
|
||||||
The concepts of of "entity" or "peer" are unified to simply :term:`chat`.
|
The concepts of of "entity" or "peer" are unified to :term:`peer`.
|
||||||
Overall, dealing with users, groups and channels should feel a lot more natural.
|
Overall, dealing with users, groups and channels should feel a lot more natural.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
In-depth explanation for :doc:`/concepts/chats`.
|
In-depth explanation for :doc:`/concepts/peers`.
|
||||||
|
|
||||||
|
|
||||||
Other methods like ``client.get_peer_id``, ``client.get_input_entity`` and ``client.get_entity`` are gone too.
|
Other methods like ``client.get_peer_id``, ``client.get_input_entity`` and ``client.get_entity`` are gone too.
|
||||||
|
|
|
@ -78,13 +78,13 @@ Concepts
|
||||||
|
|
||||||
A more in-depth explanation of some of the concepts and words used in Telethon.
|
A more in-depth explanation of some of the concepts and words used in Telethon.
|
||||||
|
|
||||||
:doc:`‣ Start reading Chat concept <concepts/chats>`
|
:doc:`‣ Start reading Chat concept <concepts/peers>`
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:hidden:
|
:hidden:
|
||||||
:caption: Concepts
|
:caption: Concepts
|
||||||
|
|
||||||
concepts/chats
|
concepts/peers
|
||||||
concepts/updates
|
concepts/updates
|
||||||
concepts/messages
|
concepts/messages
|
||||||
concepts/sessions
|
concepts/sessions
|
||||||
|
|
|
@ -118,6 +118,16 @@ Private definitions
|
||||||
|
|
||||||
New-type wrapper around :class:`int` used as a message identifier.
|
New-type wrapper around :class:`int` used as a message identifier.
|
||||||
|
|
||||||
|
.. currentmodule:: telethon._impl.session.chat.peer_ref
|
||||||
|
|
||||||
|
.. class:: PeerIdentifier
|
||||||
|
|
||||||
|
New-type wrapper around :class:`int` used as a message identifier.
|
||||||
|
|
||||||
|
.. class:: PeerAuth
|
||||||
|
|
||||||
|
New-type wrapper around :class:`int` used as a message identifier.
|
||||||
|
|
||||||
.. currentmodule:: telethon._impl.mtsender.sender
|
.. currentmodule:: telethon._impl.mtsender.sender
|
||||||
|
|
||||||
.. autoclass:: AsyncReader
|
.. autoclass:: AsyncReader
|
||||||
|
|
|
@ -35,9 +35,7 @@ async def complete_login(client: Client, auth: abcs.auth.Authorization) -> User:
|
||||||
id=user.id, dc=client._sender.dc_id, bot=user.bot, username=user.username
|
id=user.id, dc=client._sender.dc_id, bot=user.bot, username=user.username
|
||||||
)
|
)
|
||||||
|
|
||||||
packed = user.pack()
|
client._chat_hashes.set_self_user(user.id, user.bot)
|
||||||
assert packed is not None
|
|
||||||
client._chat_hashes.set_self_user(packed)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = await client(functions.updates.get_state())
|
state = await client(functions.updates.get_state())
|
||||||
|
|
|
@ -3,8 +3,9 @@ from __future__ import annotations
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from typing import TYPE_CHECKING, Optional, Self
|
from typing import TYPE_CHECKING, Optional, Self
|
||||||
|
|
||||||
from ...tl import abcs, functions, types
|
from ...session import PeerRef, UserRef
|
||||||
from ..types import ChatLike, InlineResult, NoPublicConstructor
|
from ...tl import functions, types
|
||||||
|
from ..types import InlineResult, NoPublicConstructor, Peer, User
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .client import Client
|
from .client import Client
|
||||||
|
@ -16,12 +17,12 @@ class InlineResults(metaclass=NoPublicConstructor):
|
||||||
client: Client,
|
client: Client,
|
||||||
bot: types.InputUser,
|
bot: types.InputUser,
|
||||||
query: str,
|
query: str,
|
||||||
chat: abcs.InputPeer,
|
peer: Optional[PeerRef],
|
||||||
):
|
):
|
||||||
self._client = client
|
self._client = client
|
||||||
self._bot = bot
|
self._bot = bot
|
||||||
self._query = query
|
self._query = query
|
||||||
self._peer = chat or types.InputPeerEmpty()
|
self._peer = peer
|
||||||
self._offset: Optional[str] = ""
|
self._offset: Optional[str] = ""
|
||||||
self._buffer: list[InlineResult] = []
|
self._buffer: list[InlineResult] = []
|
||||||
self._done = False
|
self._done = False
|
||||||
|
@ -37,7 +38,11 @@ class InlineResults(metaclass=NoPublicConstructor):
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.messages.get_inline_bot_results(
|
functions.messages.get_inline_bot_results(
|
||||||
bot=self._bot,
|
bot=self._bot,
|
||||||
peer=self._peer,
|
peer=(
|
||||||
|
self._peer._to_input_peer()
|
||||||
|
if self._peer
|
||||||
|
else types.InputPeerEmpty()
|
||||||
|
),
|
||||||
geo_point=None,
|
geo_point=None,
|
||||||
query=self._query,
|
query=self._query,
|
||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
|
@ -61,13 +66,16 @@ class InlineResults(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
|
|
||||||
async def inline_query(
|
async def inline_query(
|
||||||
self: Client, bot: ChatLike, query: str = "", *, chat: Optional[ChatLike] = None
|
self: Client,
|
||||||
|
bot: User | UserRef,
|
||||||
|
/,
|
||||||
|
query: str = "",
|
||||||
|
*,
|
||||||
|
peer: Optional[Peer | PeerRef] = None,
|
||||||
) -> AsyncIterator[InlineResult]:
|
) -> AsyncIterator[InlineResult]:
|
||||||
packed_bot = await self._resolve_to_packed(bot)
|
|
||||||
packed_chat = await self._resolve_to_packed(chat) if chat else None
|
|
||||||
return InlineResults._create(
|
return InlineResults._create(
|
||||||
self,
|
self,
|
||||||
packed_bot._to_input_user(),
|
bot._ref._to_input_user(),
|
||||||
query,
|
query,
|
||||||
packed_chat._to_input_peer() if packed_chat else types.InputPeerEmpty(),
|
peer._ref if peer else None,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,16 +3,19 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
from typing import TYPE_CHECKING, Optional, Sequence
|
from typing import TYPE_CHECKING, Optional, Sequence
|
||||||
|
|
||||||
from ...session import PeerRef
|
from ...session import ChannelRef, GroupRef, PeerRef, UserRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import functions, types
|
||||||
from ..types import (
|
from ..types import (
|
||||||
AdminRight,
|
AdminRight,
|
||||||
AsyncList,
|
AsyncList,
|
||||||
ChatLike,
|
Channel,
|
||||||
ChatRestriction,
|
ChatRestriction,
|
||||||
File,
|
File,
|
||||||
|
Group,
|
||||||
Participant,
|
Participant,
|
||||||
|
Peer,
|
||||||
RecentAction,
|
RecentAction,
|
||||||
|
User,
|
||||||
build_chat_map,
|
build_chat_map,
|
||||||
)
|
)
|
||||||
from .messages import SearchList
|
from .messages import SearchList
|
||||||
|
@ -25,23 +28,19 @@ class ParticipantList(AsyncList[Participant]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: ChannelRef | GroupRef,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._packed: Optional[PeerRef] = None
|
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
self._seen: set[int] = set()
|
self._seen: set[int] = set()
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if self._packed is None:
|
if isinstance(self._peer, ChannelRef):
|
||||||
self._packed = await self._client._resolve_to_packed(self._chat)
|
|
||||||
|
|
||||||
if self._packed.is_channel():
|
|
||||||
chanp = await self._client(
|
chanp = await self._client(
|
||||||
functions.channels.get_participants(
|
functions.channels.get_participants(
|
||||||
channel=self._packed._to_input_channel(),
|
channel=self._peer._to_input_channel(),
|
||||||
filter=types.ChannelParticipantsRecent(),
|
filter=types.ChannelParticipantsRecent(),
|
||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
limit=200,
|
limit=200,
|
||||||
|
@ -55,7 +54,7 @@ class ParticipantList(AsyncList[Participant]):
|
||||||
seen_count = len(self._seen)
|
seen_count = len(self._seen)
|
||||||
for p in chanp.participants:
|
for p in chanp.participants:
|
||||||
part = Participant._from_raw_channel(
|
part = Participant._from_raw_channel(
|
||||||
self._client, self._packed, p, chat_map
|
self._client, self._peer, p, chat_map
|
||||||
)
|
)
|
||||||
pid = part._peer_id()
|
pid = part._peer_id()
|
||||||
if pid not in self._seen:
|
if pid not in self._seen:
|
||||||
|
@ -66,9 +65,9 @@ class ParticipantList(AsyncList[Participant]):
|
||||||
self._offset += len(chanp.participants)
|
self._offset += len(chanp.participants)
|
||||||
self._done = len(self._seen) == seen_count
|
self._done = len(self._seen) == seen_count
|
||||||
|
|
||||||
elif self._packed.is_chat():
|
else:
|
||||||
chatp = await self._client(
|
chatp = await self._client(
|
||||||
functions.messages.get_full_chat(chat_id=self._packed.id)
|
functions.messages.get_full_chat(chat_id=self._peer._to_input_chat())
|
||||||
)
|
)
|
||||||
assert isinstance(chatp, types.messages.ChatFull)
|
assert isinstance(chatp, types.messages.ChatFull)
|
||||||
assert isinstance(chatp.full_chat, types.ChatFull)
|
assert isinstance(chatp.full_chat, types.ChatFull)
|
||||||
|
@ -81,48 +80,45 @@ class ParticipantList(AsyncList[Participant]):
|
||||||
self._buffer.append(
|
self._buffer.append(
|
||||||
Participant._from_raw_chat(
|
Participant._from_raw_chat(
|
||||||
self._client,
|
self._client,
|
||||||
self._packed,
|
self._peer,
|
||||||
participants.self_participant,
|
participants.self_participant,
|
||||||
chat_map,
|
chat_map,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif isinstance(participants, types.ChatParticipants):
|
elif isinstance(participants, types.ChatParticipants):
|
||||||
self._buffer.extend(
|
self._buffer.extend(
|
||||||
Participant._from_raw_chat(self._client, self._packed, p, chat_map)
|
Participant._from_raw_chat(self._client, self._peer, p, chat_map)
|
||||||
for p in participants.participants
|
for p in participants.participants
|
||||||
)
|
)
|
||||||
|
|
||||||
self._total = len(self._buffer)
|
self._total = len(self._buffer)
|
||||||
self._done = True
|
self._done = True
|
||||||
else:
|
|
||||||
raise TypeError("can only get participants from channels and groups")
|
|
||||||
|
|
||||||
|
|
||||||
def get_participants(self: Client, chat: ChatLike) -> AsyncList[Participant]:
|
def get_participants(
|
||||||
return ParticipantList(self, chat)
|
self: Client, chat: Group | Channel | GroupRef | ChannelRef, /
|
||||||
|
) -> AsyncList[Participant]:
|
||||||
|
return ParticipantList(self, chat._ref)
|
||||||
|
|
||||||
|
|
||||||
class RecentActionList(AsyncList[RecentAction]):
|
class RecentActionList(AsyncList[RecentAction]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: ChannelRef | GroupRef,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._peer: Optional[types.InputChannel] = None
|
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if self._peer is None:
|
if not isinstance(self._peer, ChannelRef):
|
||||||
self._peer = (
|
return # small group chats have no recent actions
|
||||||
await self._client._resolve_to_packed(self._chat)
|
|
||||||
)._to_input_channel()
|
|
||||||
|
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.channels.get_admin_log(
|
functions.channels.get_admin_log(
|
||||||
channel=self._peer,
|
channel=self._peer._to_input_channel(),
|
||||||
q="",
|
q="",
|
||||||
min_id=0,
|
min_id=0,
|
||||||
max_id=self._offset,
|
max_id=self._offset,
|
||||||
|
@ -141,34 +137,28 @@ class RecentActionList(AsyncList[RecentAction]):
|
||||||
self._offset = min(e.id for e in self._buffer)
|
self._offset = min(e.id for e in self._buffer)
|
||||||
|
|
||||||
|
|
||||||
def get_admin_log(self: Client, chat: ChatLike) -> AsyncList[RecentAction]:
|
def get_admin_log(
|
||||||
return RecentActionList(self, chat)
|
self: Client, chat: Group | Channel | GroupRef | ChannelRef, /
|
||||||
|
) -> AsyncList[RecentAction]:
|
||||||
|
return RecentActionList(self, chat._ref)
|
||||||
|
|
||||||
|
|
||||||
class ProfilePhotoList(AsyncList[File]):
|
class ProfilePhotoList(AsyncList[File]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: PeerRef,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._peer: Optional[abcs.InputPeer] = None
|
|
||||||
self._search_iter: Optional[SearchList] = None
|
self._search_iter: Optional[SearchList] = None
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if self._peer is None:
|
if isinstance(self._peer, UserRef):
|
||||||
self._peer = (
|
|
||||||
await self._client._resolve_to_packed(self._chat)
|
|
||||||
)._to_input_peer()
|
|
||||||
|
|
||||||
if isinstance(self._peer, types.InputPeerUser):
|
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.photos.get_user_photos(
|
functions.photos.get_user_photos(
|
||||||
user_id=types.InputUser(
|
user_id=self._peer._to_input_user(),
|
||||||
user_id=self._peer.user_id, access_hash=self._peer.access_hash
|
|
||||||
),
|
|
||||||
offset=0,
|
offset=0,
|
||||||
max_id=0,
|
max_id=0,
|
||||||
limit=0,
|
limit=0,
|
||||||
|
@ -191,86 +181,86 @@ class ProfilePhotoList(AsyncList[File]):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_profile_photos(self: Client, chat: ChatLike) -> AsyncList[File]:
|
def get_profile_photos(self: Client, peer: Peer | PeerRef, /) -> AsyncList[File]:
|
||||||
return ProfilePhotoList(self, chat)
|
return ProfilePhotoList(self, peer._ref)
|
||||||
|
|
||||||
|
|
||||||
async def set_participant_admin_rights(
|
async def set_participant_admin_rights(
|
||||||
self: Client, chat: ChatLike, user: ChatLike, rights: Sequence[AdminRight]
|
self: Client,
|
||||||
|
chat: Group | Channel | GroupRef | ChannelRef,
|
||||||
|
/,
|
||||||
|
participant: User | UserRef,
|
||||||
|
rights: Sequence[AdminRight],
|
||||||
) -> None:
|
) -> None:
|
||||||
packed = await self._resolve_to_packed(chat)
|
chat = chat._ref
|
||||||
participant = await self._resolve_to_packed(user)
|
user = participant._ref
|
||||||
|
if isinstance(chat, ChannelRef):
|
||||||
if packed.is_channel():
|
|
||||||
admin_rights = AdminRight._set_to_raw(set(rights))
|
admin_rights = AdminRight._set_to_raw(set(rights))
|
||||||
await self(
|
await self(
|
||||||
functions.channels.edit_admin(
|
functions.channels.edit_admin(
|
||||||
channel=packed._to_input_channel(),
|
channel=chat._to_input_channel(),
|
||||||
user_id=participant._to_input_user(),
|
user_id=user._to_input_user(),
|
||||||
admin_rights=admin_rights,
|
admin_rights=admin_rights,
|
||||||
rank="",
|
rank="",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif packed.is_chat():
|
else:
|
||||||
await self(
|
await self(
|
||||||
functions.messages.edit_chat_admin(
|
functions.messages.edit_chat_admin(
|
||||||
chat_id=packed.id,
|
chat_id=chat._to_input_chat(),
|
||||||
user_id=participant._to_input_user(),
|
user_id=user._to_input_user(),
|
||||||
is_admin=bool(rights),
|
is_admin=bool(rights),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
raise TypeError(f"Cannot set admin rights in {packed.ty}")
|
|
||||||
|
|
||||||
|
|
||||||
async def set_participant_restrictions(
|
async def set_participant_restrictions(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Group | Channel | GroupRef | ChannelRef,
|
||||||
user: ChatLike,
|
/,
|
||||||
|
participant: Peer | PeerRef,
|
||||||
restrictions: Sequence[ChatRestriction],
|
restrictions: Sequence[ChatRestriction],
|
||||||
*,
|
*,
|
||||||
until: Optional[datetime.datetime] = None,
|
until: Optional[datetime.datetime] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
packed = await self._resolve_to_packed(chat)
|
chat = chat._ref
|
||||||
participant = await self._resolve_to_packed(user)
|
peer = participant._ref
|
||||||
if packed.is_channel():
|
if isinstance(chat, ChannelRef):
|
||||||
banned_rights = ChatRestriction._set_to_raw(
|
banned_rights = ChatRestriction._set_to_raw(
|
||||||
set(restrictions),
|
set(restrictions),
|
||||||
until_date=int(until.timestamp()) if until else 0x7FFFFFFF,
|
until_date=int(until.timestamp()) if until else 0x7FFFFFFF,
|
||||||
)
|
)
|
||||||
await self(
|
await self(
|
||||||
functions.channels.edit_banned(
|
functions.channels.edit_banned(
|
||||||
channel=packed._to_input_channel(),
|
channel=chat._to_input_channel(),
|
||||||
participant=participant._to_input_peer(),
|
participant=peer._to_input_peer(),
|
||||||
banned_rights=banned_rights,
|
banned_rights=banned_rights,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif packed.is_chat():
|
elif isinstance(peer, UserRef):
|
||||||
if restrictions:
|
if restrictions:
|
||||||
await self(
|
await self(
|
||||||
functions.messages.delete_chat_user(
|
functions.messages.delete_chat_user(
|
||||||
revoke_history=ChatRestriction.VIEW_MESSAGES in restrictions,
|
revoke_history=ChatRestriction.VIEW_MESSAGES in restrictions,
|
||||||
chat_id=packed.id,
|
chat_id=chat._to_input_chat(),
|
||||||
user_id=participant._to_input_user(),
|
user_id=peer._to_input_user(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
raise TypeError(f"Cannot set banned rights in {packed.ty}")
|
|
||||||
|
|
||||||
|
|
||||||
async def set_chat_default_restrictions(
|
async def set_chat_default_restrictions(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
restrictions: Sequence[ChatRestriction],
|
restrictions: Sequence[ChatRestriction],
|
||||||
*,
|
*,
|
||||||
until: Optional[datetime.datetime] = None,
|
until: Optional[datetime.datetime] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
banned_rights = ChatRestriction._set_to_raw(
|
banned_rights = ChatRestriction._set_to_raw(
|
||||||
set(restrictions), int(until.timestamp()) if until else 0x7FFFFFFF
|
set(restrictions), int(until.timestamp()) if until else 0x7FFFFFFF
|
||||||
)
|
)
|
||||||
await self(
|
await self(
|
||||||
functions.messages.edit_chat_default_banned_rights(
|
functions.messages.edit_chat_default_banned_rights(
|
||||||
peer=peer, banned_rights=banned_rights
|
peer=chat._ref._to_input_peer(), banned_rights=banned_rights
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,14 +9,17 @@ from typing import Any, Literal, Optional, Self, Sequence, Type, TypeVar
|
||||||
from ....version import __version__ as default_version
|
from ....version import __version__ as default_version
|
||||||
from ...mtsender import Connector, Sender
|
from ...mtsender import Connector, Sender
|
||||||
from ...session import (
|
from ...session import (
|
||||||
|
ChannelRef,
|
||||||
ChatHashCache,
|
ChatHashCache,
|
||||||
DataCenter,
|
DataCenter,
|
||||||
|
GroupRef,
|
||||||
MemorySession,
|
MemorySession,
|
||||||
MessageBox,
|
MessageBox,
|
||||||
PeerRef,
|
PeerRef,
|
||||||
Session,
|
Session,
|
||||||
SqliteSession,
|
SqliteSession,
|
||||||
Storage,
|
Storage,
|
||||||
|
UserRef,
|
||||||
)
|
)
|
||||||
from ...tl import Request, abcs
|
from ...tl import Request, abcs
|
||||||
from ..events import Event
|
from ..events import Event
|
||||||
|
@ -25,11 +28,12 @@ from ..types import (
|
||||||
AdminRight,
|
AdminRight,
|
||||||
AlbumBuilder,
|
AlbumBuilder,
|
||||||
AsyncList,
|
AsyncList,
|
||||||
ChatLike,
|
Channel,
|
||||||
ChatRestriction,
|
ChatRestriction,
|
||||||
Dialog,
|
Dialog,
|
||||||
Draft,
|
Draft,
|
||||||
File,
|
File,
|
||||||
|
Group,
|
||||||
InFileLike,
|
InFileLike,
|
||||||
InlineResult,
|
InlineResult,
|
||||||
LoginToken,
|
LoginToken,
|
||||||
|
@ -103,14 +107,7 @@ from .updates import (
|
||||||
remove_event_handler,
|
remove_event_handler,
|
||||||
set_handler_filter,
|
set_handler_filter,
|
||||||
)
|
)
|
||||||
from .users import (
|
from .users import get_contacts, get_me, resolve_peers, resolve_phone, resolve_username
|
||||||
get_chats,
|
|
||||||
get_contacts,
|
|
||||||
get_me,
|
|
||||||
input_to_peer,
|
|
||||||
resolve_to_packed,
|
|
||||||
resolve_username,
|
|
||||||
)
|
|
||||||
|
|
||||||
Return = TypeVar("Return")
|
Return = TypeVar("Return")
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
@ -252,9 +249,9 @@ class Client:
|
||||||
self._message_box = MessageBox(base_logger=base_logger)
|
self._message_box = MessageBox(base_logger=base_logger)
|
||||||
self._chat_hashes = ChatHashCache(None)
|
self._chat_hashes = ChatHashCache(None)
|
||||||
self._last_update_limit_warn: Optional[float] = None
|
self._last_update_limit_warn: Optional[float] = None
|
||||||
self._updates: asyncio.Queue[
|
self._updates: asyncio.Queue[tuple[abcs.Update, dict[int, Peer]]] = (
|
||||||
tuple[abcs.Update, dict[int, Peer]]
|
asyncio.Queue(maxsize=self._config.update_queue_limit or 0)
|
||||||
] = asyncio.Queue(maxsize=self._config.update_queue_limit or 0)
|
)
|
||||||
self._dispatcher: Optional[asyncio.Task[None]] = None
|
self._dispatcher: Optional[asyncio.Task[None]] = None
|
||||||
self._handlers: dict[
|
self._handlers: dict[
|
||||||
Type[Event], list[tuple[Callable[[Any], Awaitable[Any]], Optional[Filter]]]
|
Type[Event], list[tuple[Callable[[Any], Awaitable[Any]], Optional[Filter]]]
|
||||||
|
@ -269,6 +266,7 @@ class Client:
|
||||||
def add_event_handler(
|
def add_event_handler(
|
||||||
self,
|
self,
|
||||||
handler: Callable[[Event], Awaitable[Any]],
|
handler: Callable[[Event], Awaitable[Any]],
|
||||||
|
/,
|
||||||
event_cls: Type[Event],
|
event_cls: Type[Event],
|
||||||
filter: Optional[Filter] = None,
|
filter: Optional[Filter] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -387,7 +385,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
await connect(self)
|
await connect(self)
|
||||||
|
|
||||||
async def delete_dialog(self, chat: ChatLike) -> None:
|
async def delete_dialog(self, dialog: Peer | PeerRef, /) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a dialog.
|
Delete a dialog.
|
||||||
|
|
||||||
|
@ -397,8 +395,8 @@ class Client:
|
||||||
|
|
||||||
Note that bot accounts do not have dialogs, so this method will fail when used in a bot account.
|
Note that bot accounts do not have dialogs, so this method will fail when used in a bot account.
|
||||||
|
|
||||||
:param chat:
|
:param dialog:
|
||||||
The :term:`chat` representing the dialog to delete.
|
The :term:`peer` representing the dialog to delete.
|
||||||
|
|
||||||
.. rubric:: Example
|
.. rubric:: Example
|
||||||
|
|
||||||
|
@ -409,16 +407,16 @@ class Client:
|
||||||
# You've realized you're more of a cat person
|
# You've realized you're more of a cat person
|
||||||
await client.delete_dialog(dialog.chat)
|
await client.delete_dialog(dialog.chat)
|
||||||
"""
|
"""
|
||||||
await delete_dialog(self, chat)
|
await delete_dialog(self, dialog)
|
||||||
|
|
||||||
async def delete_messages(
|
async def delete_messages(
|
||||||
self, chat: ChatLike, message_ids: list[int], *, revoke: bool = True
|
self, chat: Peer | PeerRef, /, message_ids: list[int], *, revoke: bool = True
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Delete messages.
|
Delete messages.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the messages are.
|
The :term:`peer` where the messages are.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
@ -468,7 +466,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
await disconnect(self)
|
await disconnect(self)
|
||||||
|
|
||||||
async def download(self, media: File, file: str | Path | OutFileLike) -> None:
|
async def download(self, media: File, /, file: str | Path | OutFileLike) -> None:
|
||||||
"""
|
"""
|
||||||
Download a file.
|
Download a file.
|
||||||
|
|
||||||
|
@ -504,7 +502,8 @@ class Client:
|
||||||
|
|
||||||
async def edit_draft(
|
async def edit_draft(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
peer: Peer | PeerRef,
|
||||||
|
/,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
|
@ -517,8 +516,8 @@ class Client:
|
||||||
|
|
||||||
This can also be used to clear the draft by setting the text to an empty string ``""``.
|
This can also be used to clear the draft by setting the text to an empty string ``""``.
|
||||||
|
|
||||||
:param chat:
|
:param peer:
|
||||||
The :term:`chat` where the draft will be saved to.
|
The :term:`peer` where the draft will be saved to.
|
||||||
|
|
||||||
:param text: See :ref:`formatting`.
|
:param text: See :ref:`formatting`.
|
||||||
:param markdown: See :ref:`formatting`.
|
:param markdown: See :ref:`formatting`.
|
||||||
|
@ -552,7 +551,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return await edit_draft(
|
return await edit_draft(
|
||||||
self,
|
self,
|
||||||
chat,
|
peer,
|
||||||
text,
|
text,
|
||||||
markdown=markdown,
|
markdown=markdown,
|
||||||
html=html,
|
html=html,
|
||||||
|
@ -562,7 +561,8 @@ class Client:
|
||||||
|
|
||||||
async def edit_message(
|
async def edit_message(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
message_id: int,
|
message_id: int,
|
||||||
*,
|
*,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
@ -575,7 +575,7 @@ class Client:
|
||||||
Edit a message.
|
Edit a message.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message to edit is.
|
The :term:`peer` where the message to edit is.
|
||||||
|
|
||||||
:param message_id:
|
:param message_id:
|
||||||
The identifier of the message to edit.
|
The identifier of the message to edit.
|
||||||
|
@ -617,19 +617,19 @@ class Client:
|
||||||
)
|
)
|
||||||
|
|
||||||
async def forward_messages(
|
async def forward_messages(
|
||||||
self, target: ChatLike, message_ids: list[int], source: ChatLike
|
self, target: Peer | PeerRef, message_ids: list[int], source: Peer | PeerRef
|
||||||
) -> list[Message]:
|
) -> list[Message]:
|
||||||
"""
|
"""
|
||||||
Forward messages from one :term:`chat` to another.
|
Forward messages from one :term:`peer` to another.
|
||||||
|
|
||||||
:param target:
|
:param target:
|
||||||
The :term:`chat` where the messages will be forwarded to.
|
The :term:`peer` where the messages will be forwarded to.
|
||||||
|
|
||||||
:param message_ids:
|
:param message_ids:
|
||||||
The list of message identifiers to forward.
|
The list of message identifiers to forward.
|
||||||
|
|
||||||
:param source:
|
:param source:
|
||||||
The source :term:`chat` where the messages to forward exist.
|
The source :term:`peer` where the messages to forward exist.
|
||||||
|
|
||||||
:return: The forwarded messages.
|
:return: The forwarded messages.
|
||||||
|
|
||||||
|
@ -651,16 +651,18 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return await forward_messages(self, target, message_ids, source)
|
return await forward_messages(self, target, message_ids, source)
|
||||||
|
|
||||||
def get_admin_log(self, chat: ChatLike) -> AsyncList[RecentAction]:
|
def get_admin_log(
|
||||||
|
self, chat: Group | Channel | GroupRef | ChannelRef, /
|
||||||
|
) -> AsyncList[RecentAction]:
|
||||||
"""
|
"""
|
||||||
Get the recent actions from the administrator's log.
|
Get the recent actions from the administrator's log.
|
||||||
|
|
||||||
This method requires you to be an administrator in the :term:`chat`.
|
This method requires you to be an administrator in the :term:`peer`.
|
||||||
|
|
||||||
The returned actions are also known as "admin log events".
|
The returned actions are also known as "admin log events".
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` to fetch recent actions from.
|
The :term:`peer` to fetch recent actions from.
|
||||||
|
|
||||||
:return: The recent actions.
|
:return: The recent actions.
|
||||||
|
|
||||||
|
@ -674,42 +676,6 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return get_admin_log(self, chat)
|
return get_admin_log(self, chat)
|
||||||
|
|
||||||
async def get_chats(
|
|
||||||
self, chats: list[ChatLike] | tuple[ChatLike, ...]
|
|
||||||
) -> list[Peer]:
|
|
||||||
"""
|
|
||||||
Get the latest basic information about the given chats.
|
|
||||||
|
|
||||||
This method is most commonly used to turn one or more :class:`~types.PeerRef` into the original :class:`~types.Peer`.
|
|
||||||
This includes users, groups and broadcast channels.
|
|
||||||
|
|
||||||
:param chats:
|
|
||||||
The users, groups or channels to fetch.
|
|
||||||
|
|
||||||
:return: The fetched chats.
|
|
||||||
|
|
||||||
.. rubric:: Example
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# Retrieve a PackedChat from somewhere
|
|
||||||
packed_user = my_database.get_packed_winner()
|
|
||||||
|
|
||||||
# Fetch it
|
|
||||||
users = await client.get_chats([packed_user])
|
|
||||||
user = users[0] # user will be a User if our packed_user was a user
|
|
||||||
|
|
||||||
# Notify the user they won, using their current full name in the message
|
|
||||||
await client.send_message(packed_user, f'Congratulations {user.name}, you won!')
|
|
||||||
|
|
||||||
.. caution::
|
|
||||||
|
|
||||||
This method supports being called with anything that looks like a chat, like every other method.
|
|
||||||
However, calling it with usernames or phone numbers will fetch the chats twice.
|
|
||||||
If that's the case, consider using :meth:`resolve_username` or :meth:`get_contacts` instead.
|
|
||||||
"""
|
|
||||||
return await get_chats(self, chats)
|
|
||||||
|
|
||||||
def get_contacts(self) -> AsyncList[User]:
|
def get_contacts(self) -> AsyncList[User]:
|
||||||
"""
|
"""
|
||||||
Get the users in your contact list.
|
Get the users in your contact list.
|
||||||
|
@ -763,7 +729,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return get_drafts(self)
|
return get_drafts(self)
|
||||||
|
|
||||||
def get_file_bytes(self, media: File) -> AsyncList[bytes]:
|
def get_file_bytes(self, media: File, /) -> AsyncList[bytes]:
|
||||||
"""
|
"""
|
||||||
Get the contents of an uploaded media file as chunks of :class:`bytes`.
|
Get the contents of an uploaded media file as chunks of :class:`bytes`.
|
||||||
|
|
||||||
|
@ -790,7 +756,7 @@ class Client:
|
||||||
return get_file_bytes(self, media)
|
return get_file_bytes(self, media)
|
||||||
|
|
||||||
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.
|
Get the filter associated to the given event handler.
|
||||||
|
@ -841,19 +807,20 @@ class Client:
|
||||||
|
|
||||||
def get_messages(
|
def get_messages(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
*,
|
*,
|
||||||
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`, from the newest message to the oldest.
|
Get the message history from a :term:`peer`, from the newest message to the oldest.
|
||||||
|
|
||||||
The returned iterator can be :func:`reversed` to fetch from the first to the last instead.
|
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 messages should be fetched from.
|
The :term:`peer` 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.
|
||||||
|
@ -891,13 +858,13 @@ class Client:
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_messages_with_ids(
|
def get_messages_with_ids(
|
||||||
self, chat: ChatLike, message_ids: list[int]
|
self, chat: Peer | PeerRef, /, message_ids: list[int]
|
||||||
) -> AsyncList[Message]:
|
) -> AsyncList[Message]:
|
||||||
"""
|
"""
|
||||||
Get the full message objects from the corresponding message identifiers.
|
Get the full message objects from the corresponding message identifiers.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message to fetch is.
|
The :term:`peer` where the message to fetch is.
|
||||||
|
|
||||||
:param message_ids:
|
:param message_ids:
|
||||||
The message identifiers of the messages to fetch.
|
The message identifiers of the messages to fetch.
|
||||||
|
@ -916,7 +883,9 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return get_messages_with_ids(self, chat, message_ids)
|
return get_messages_with_ids(self, chat, message_ids)
|
||||||
|
|
||||||
def get_participants(self, chat: ChatLike) -> AsyncList[Participant]:
|
def get_participants(
|
||||||
|
self, chat: Group | Channel | GroupRef | ChannelRef, /
|
||||||
|
) -> AsyncList[Participant]:
|
||||||
"""
|
"""
|
||||||
Get the participants in a group or channel, along with their permissions.
|
Get the participants in a group or channel, along with their permissions.
|
||||||
|
|
||||||
|
@ -927,7 +896,7 @@ class Client:
|
||||||
There is no way to bypass this.
|
There is no way to bypass this.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` to fetch participants from.
|
The :term:`peer` to fetch participants from.
|
||||||
|
|
||||||
:return: The participants.
|
:return: The participants.
|
||||||
|
|
||||||
|
@ -940,12 +909,12 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return get_participants(self, chat)
|
return get_participants(self, chat)
|
||||||
|
|
||||||
def get_profile_photos(self, chat: ChatLike) -> AsyncList[File]:
|
def get_profile_photos(self, peer: Peer | PeerRef, /) -> AsyncList[File]:
|
||||||
"""
|
"""
|
||||||
Get the profile pictures set in a chat, or user avatars.
|
Get the profile pictures set in a chat, or user avatars.
|
||||||
|
|
||||||
:param chat:
|
:param peer:
|
||||||
The :term:`chat` to fetch the profile photo files from.
|
The :term:`peer` to fetch the profile photo files from.
|
||||||
|
|
||||||
:return: The photo files.
|
:return: The photo files.
|
||||||
|
|
||||||
|
@ -958,10 +927,15 @@ class Client:
|
||||||
await client.download(photo, f'{i}.jpg')
|
await client.download(photo, f'{i}.jpg')
|
||||||
i += 1
|
i += 1
|
||||||
"""
|
"""
|
||||||
return get_profile_photos(self, chat)
|
return get_profile_photos(self, peer)
|
||||||
|
|
||||||
async def inline_query(
|
async def inline_query(
|
||||||
self, bot: ChatLike, query: str = "", *, chat: Optional[ChatLike] = None
|
self,
|
||||||
|
bot: User | UserRef,
|
||||||
|
/,
|
||||||
|
query: str = "",
|
||||||
|
*,
|
||||||
|
peer: Optional[Peer | PeerRef] = None,
|
||||||
) -> AsyncIterator[InlineResult]:
|
) -> AsyncIterator[InlineResult]:
|
||||||
"""
|
"""
|
||||||
Perform a *@bot inline query*.
|
Perform a *@bot inline query*.
|
||||||
|
@ -975,7 +949,7 @@ class Client:
|
||||||
:param query:
|
:param query:
|
||||||
The query string to send to the bot.
|
The query string to send to the bot.
|
||||||
|
|
||||||
:param chat:
|
:param peer:
|
||||||
Where the query is being made and will be sent.
|
Where the query is being made and will be sent.
|
||||||
Some bots display different results based on the type of chat.
|
Some bots display different results based on the type of chat.
|
||||||
|
|
||||||
|
@ -997,7 +971,7 @@ class Client:
|
||||||
if i == 10:
|
if i == 10:
|
||||||
break # did not find 'keyword' in the first few results
|
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, peer=peer)
|
||||||
|
|
||||||
async def interactive_login(
|
async def interactive_login(
|
||||||
self, phone_or_token: Optional[str] = None, *, password: Optional[str] = None
|
self, phone_or_token: Optional[str] = None, *, password: Optional[str] = None
|
||||||
|
@ -1052,7 +1026,7 @@ class Client:
|
||||||
return await is_authorized(self)
|
return await is_authorized(self)
|
||||||
|
|
||||||
def on(
|
def on(
|
||||||
self, event_cls: Type[Event], filter: Optional[Filter] = None
|
self, event_cls: Type[Event], /, filter: Optional[Filter] = None
|
||||||
) -> Callable[
|
) -> Callable[
|
||||||
[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]
|
[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]
|
||||||
]:
|
]:
|
||||||
|
@ -1093,12 +1067,12 @@ class Client:
|
||||||
"""
|
"""
|
||||||
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: Peer | PeerRef, /, message_id: int) -> Message:
|
||||||
"""
|
"""
|
||||||
Pin a message to be at the top.
|
Pin a message to be at the top.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message to pin is.
|
The :term:`peer` where the message to pin is.
|
||||||
|
|
||||||
:param message_id:
|
:param message_id:
|
||||||
The identifier of the message to pin.
|
The identifier of the message to pin.
|
||||||
|
@ -1144,7 +1118,7 @@ class Client:
|
||||||
return prepare_album(self)
|
return prepare_album(self)
|
||||||
|
|
||||||
async def read_message(
|
async def read_message(
|
||||||
self, chat: ChatLike, message_id: int | Literal["all"]
|
self, chat: Peer | PeerRef, /, message_id: int | Literal["all"]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Mark messages as read.
|
Mark messages as read.
|
||||||
|
@ -1176,7 +1150,9 @@ class Client:
|
||||||
"""
|
"""
|
||||||
await read_message(self, chat, message_id)
|
await read_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.
|
Remove the handler as a function to be called when events occur.
|
||||||
This is simply the opposite of :meth:`add_event_handler`.
|
This is simply the opposite of :meth:`add_event_handler`.
|
||||||
|
@ -1227,16 +1203,60 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return await request_login_code(self, phone)
|
return await request_login_code(self, phone)
|
||||||
|
|
||||||
async def resolve_username(self, username: str) -> Peer:
|
async def resolve_peers(self, peers: Sequence[Peer | PeerRef], /) -> list[Peer]:
|
||||||
"""
|
"""
|
||||||
Resolve a username into a :term:`chat`.
|
Resolve one or more peer references into peer objects.
|
||||||
|
|
||||||
|
This methods also accepts peer objects as input, which will be refetched but not mutated in-place.
|
||||||
|
|
||||||
|
:param peers:
|
||||||
|
The peers to fetch.
|
||||||
|
|
||||||
|
:return: The fetched peers, in the same order as the input.
|
||||||
|
|
||||||
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
[user, group, channel] = await client.resolve_peers([
|
||||||
|
user_ref, group_ref, channel_ref
|
||||||
|
])
|
||||||
|
"""
|
||||||
|
return await resolve_peers(self, peers)
|
||||||
|
|
||||||
|
async def resolve_phone(self, phone: str, /) -> Peer:
|
||||||
|
"""
|
||||||
|
Resolve a phone number into a :term:`peer`.
|
||||||
|
|
||||||
This method is rather expensive to call.
|
This method is rather expensive to call.
|
||||||
It is recommended to use it once and then :meth:`types.Peer.pack` the result.
|
It is recommended to use it once and then store the :attr:`types.Peer.ref`.
|
||||||
The packed chat can then be used (and re-fetched) more cheaply.
|
|
||||||
|
:param phone:
|
||||||
|
The phone number "+1 23 456" to resolve.
|
||||||
|
The phone number must contain the `International Calling Code <https://en.wikipedia.org/wiki/List_of_mobile_telephone_prefixes_by_country>`_.
|
||||||
|
You do not need to use include the ``'+'`` prefix, but the parameter must be a :class:`str`, not :class:`int`.
|
||||||
|
|
||||||
|
:return: The matching chat.
|
||||||
|
|
||||||
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
print(await client.resolve_phone('+1 23 456'))
|
||||||
|
"""
|
||||||
|
return await resolve_phone(self, phone)
|
||||||
|
|
||||||
|
async def resolve_username(self, username: str, /) -> Peer:
|
||||||
|
"""
|
||||||
|
Resolve a username into a :term:`peer`.
|
||||||
|
|
||||||
|
This method is rather expensive to call.
|
||||||
|
It is recommended to use it once and then store the :attr:`types.Peer.ref`.
|
||||||
|
|
||||||
:param username:
|
:param username:
|
||||||
The public "@username" to resolve.
|
The public "@username" to resolve.
|
||||||
|
You do not need to use include the ``'@'`` prefix.
|
||||||
|
Links cannot be used.
|
||||||
|
|
||||||
:return: The matching chat.
|
:return: The matching chat.
|
||||||
|
|
||||||
|
@ -1268,9 +1288,6 @@ class Client:
|
||||||
Perform a global message search.
|
Perform a global message search.
|
||||||
This is used to search messages in no particular chat (i.e. everywhere possible).
|
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:
|
:param limit:
|
||||||
How many messages to fetch at most.
|
How many messages to fetch at most.
|
||||||
|
|
||||||
|
@ -1301,7 +1318,8 @@ class Client:
|
||||||
|
|
||||||
def search_messages(
|
def search_messages(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
*,
|
*,
|
||||||
query: Optional[str] = None,
|
query: Optional[str] = None,
|
||||||
|
@ -1312,7 +1330,7 @@ class Client:
|
||||||
Search messages in a chat.
|
Search messages in a chat.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where messages will be searched.
|
The :term:`peer` where messages will be searched.
|
||||||
|
|
||||||
:param limit:
|
:param limit:
|
||||||
How many messages to fetch at most.
|
How many messages to fetch at most.
|
||||||
|
@ -1344,12 +1362,13 @@ class Client:
|
||||||
|
|
||||||
async def send_audio(
|
async def send_audio(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
mime_type: Optional[str] = None,
|
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
mime_type: Optional[str] = None,
|
||||||
duration: Optional[float] = None,
|
duration: Optional[float] = None,
|
||||||
voice: bool = False,
|
voice: bool = False,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
|
@ -1367,7 +1386,7 @@ class Client:
|
||||||
duration, title and performer if they are not provided.
|
duration, title and performer if they are not provided.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the audio media will be sent to.
|
The :term:`peer` where the audio media will be sent to.
|
||||||
|
|
||||||
:param file: See :meth:`send_file`.
|
:param file: See :meth:`send_file`.
|
||||||
:param size: See :meth:`send_file`.
|
:param size: See :meth:`send_file`.
|
||||||
|
@ -1391,9 +1410,9 @@ class Client:
|
||||||
self,
|
self,
|
||||||
chat,
|
chat,
|
||||||
file,
|
file,
|
||||||
mime_type,
|
|
||||||
size=size,
|
size=size,
|
||||||
name=name,
|
name=name,
|
||||||
|
mime_type=mime_type,
|
||||||
duration=duration,
|
duration=duration,
|
||||||
voice=voice,
|
voice=voice,
|
||||||
title=title,
|
title=title,
|
||||||
|
@ -1407,7 +1426,8 @@ class Client:
|
||||||
|
|
||||||
async def send_file(
|
async def send_file(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -1430,7 +1450,7 @@ class Client:
|
||||||
caption_markdown: Optional[str] = None,
|
caption_markdown: Optional[str] = None,
|
||||||
caption_html: Optional[str] = None,
|
caption_html: Optional[str] = None,
|
||||||
reply_to: Optional[int] = None,
|
reply_to: Optional[int] = None,
|
||||||
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
buttons: Optional[list[btns.Button] | list[list[btns.Button]]],
|
||||||
) -> Message:
|
) -> Message:
|
||||||
"""
|
"""
|
||||||
Send any type of file with any amount of attributes.
|
Send any type of file with any amount of attributes.
|
||||||
|
@ -1442,7 +1462,7 @@ class Client:
|
||||||
Unlike :meth:`send_photo`, image files will be sent as documents by default.
|
Unlike :meth:`send_photo`, image files will be sent as documents by default.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message will be sent to.
|
The :term:`peer` where the message will be sent to.
|
||||||
|
|
||||||
:param path:
|
:param path:
|
||||||
A local file path or :class:`~telethon.types.File` to send.
|
A local file path or :class:`~telethon.types.File` to send.
|
||||||
|
@ -1589,7 +1609,8 @@ class Client:
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
text: Optional[str | Message] = None,
|
text: Optional[str | Message] = None,
|
||||||
*,
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
|
@ -1602,7 +1623,7 @@ class Client:
|
||||||
Send a message.
|
Send a message.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message will be sent to.
|
The :term:`peer` where the message will be sent to.
|
||||||
|
|
||||||
:param text: See :ref:`formatting`.
|
:param text: See :ref:`formatting`.
|
||||||
:param markdown: See :ref:`formatting`.
|
:param markdown: See :ref:`formatting`.
|
||||||
|
@ -1636,7 +1657,8 @@ class Client:
|
||||||
|
|
||||||
async def send_photo(
|
async def send_photo(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -1662,7 +1684,7 @@ class Client:
|
||||||
width and height if they are not provided.
|
width and height if they are not provided.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the photo media will be sent to.
|
The :term:`peer` where the photo media will be sent to.
|
||||||
|
|
||||||
:param file: See :meth:`send_file`.
|
:param file: See :meth:`send_file`.
|
||||||
:param size: See :meth:`send_file`.
|
:param size: See :meth:`send_file`.
|
||||||
|
@ -1700,7 +1722,8 @@ class Client:
|
||||||
|
|
||||||
async def send_video(
|
async def send_video(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -1716,7 +1739,7 @@ class Client:
|
||||||
caption_markdown: Optional[str] = None,
|
caption_markdown: Optional[str] = None,
|
||||||
caption_html: Optional[str] = None,
|
caption_html: Optional[str] = None,
|
||||||
reply_to: Optional[int] = None,
|
reply_to: Optional[int] = None,
|
||||||
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
buttons: Optional[list[btns.Button] | list[list[btns.Button]]],
|
||||||
) -> Message:
|
) -> Message:
|
||||||
"""
|
"""
|
||||||
Send a video file.
|
Send a video file.
|
||||||
|
@ -1725,7 +1748,7 @@ class Client:
|
||||||
duration, width and height if they are not provided.
|
duration, width and height if they are not provided.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message will be sent to.
|
The :term:`peer` where the message will be sent to.
|
||||||
|
|
||||||
:param file: See :meth:`send_file`.
|
:param file: See :meth:`send_file`.
|
||||||
:param size: See :meth:`send_file`.
|
:param size: See :meth:`send_file`.
|
||||||
|
@ -1768,7 +1791,8 @@ class Client:
|
||||||
|
|
||||||
async def set_chat_default_restrictions(
|
async def set_chat_default_restrictions(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
restrictions: Sequence[ChatRestriction],
|
restrictions: Sequence[ChatRestriction],
|
||||||
*,
|
*,
|
||||||
until: Optional[datetime.datetime] = None,
|
until: Optional[datetime.datetime] = None,
|
||||||
|
@ -1777,7 +1801,7 @@ class Client:
|
||||||
Set the default restrictions to apply to all participant in a chat.
|
Set the default restrictions to apply to all participant in a chat.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the restrictions will be applied.
|
The :term:`peer` where the restrictions will be applied.
|
||||||
|
|
||||||
:param restrictions:
|
:param restrictions:
|
||||||
The sequence of restrictions to apply.
|
The sequence of restrictions to apply.
|
||||||
|
@ -1810,6 +1834,7 @@ class Client:
|
||||||
def set_handler_filter(
|
def set_handler_filter(
|
||||||
self,
|
self,
|
||||||
handler: Callable[[Event], Awaitable[Any]],
|
handler: Callable[[Event], Awaitable[Any]],
|
||||||
|
/,
|
||||||
filter: Optional[Filter] = None,
|
filter: Optional[Filter] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1836,7 +1861,11 @@ class Client:
|
||||||
set_handler_filter(self, handler, filter)
|
set_handler_filter(self, handler, filter)
|
||||||
|
|
||||||
async def set_participant_admin_rights(
|
async def set_participant_admin_rights(
|
||||||
self, chat: ChatLike, user: ChatLike, rights: Sequence[AdminRight]
|
self,
|
||||||
|
chat: Group | Channel | GroupRef | ChannelRef,
|
||||||
|
/,
|
||||||
|
participant: User | UserRef,
|
||||||
|
rights: Sequence[AdminRight],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Set the administrator rights granted to the participant in the chat.
|
Set the administrator rights granted to the participant in the chat.
|
||||||
|
@ -1847,7 +1876,7 @@ class Client:
|
||||||
In this case, granting any right will make the user an administrator with all rights.
|
In this case, granting any right will make the user an administrator with all rights.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the rights will be granted.
|
The :term:`peer` where the rights will be granted.
|
||||||
|
|
||||||
:param participant:
|
:param participant:
|
||||||
The participant to promote to administrator, usually a :class:`types.User`.
|
The participant to promote to administrator, usually a :class:`types.User`.
|
||||||
|
@ -1873,12 +1902,13 @@ class Client:
|
||||||
|
|
||||||
:meth:`telethon.types.Participant.set_admin_rights`
|
:meth:`telethon.types.Participant.set_admin_rights`
|
||||||
"""
|
"""
|
||||||
await set_participant_admin_rights(self, chat, user, rights)
|
await set_participant_admin_rights(self, chat, participant, rights)
|
||||||
|
|
||||||
async def set_participant_restrictions(
|
async def set_participant_restrictions(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: Group | Channel | GroupRef | ChannelRef,
|
||||||
user: ChatLike,
|
/,
|
||||||
|
participant: Peer | PeerRef,
|
||||||
restrictions: Sequence[ChatRestriction],
|
restrictions: Sequence[ChatRestriction],
|
||||||
*,
|
*,
|
||||||
until: Optional[datetime.datetime] = None,
|
until: Optional[datetime.datetime] = None,
|
||||||
|
@ -1893,7 +1923,7 @@ class Client:
|
||||||
The participant's history will be revoked if the restriction to :attr:`~types.ChatRestriction.VIEW_MESSAGES` is applied.
|
The participant's history will be revoked if the restriction to :attr:`~types.ChatRestriction.VIEW_MESSAGES` is applied.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the restrictions will be applied.
|
The :term:`peer` where the restrictions will be applied.
|
||||||
|
|
||||||
:param participant:
|
:param participant:
|
||||||
The participant to restrict or ban, usually a :class:`types.User`.
|
The participant to restrict or ban, usually a :class:`types.User`.
|
||||||
|
@ -1929,7 +1959,9 @@ class Client:
|
||||||
|
|
||||||
:meth:`telethon.types.Participant.set_restrictions`
|
:meth:`telethon.types.Participant.set_restrictions`
|
||||||
"""
|
"""
|
||||||
await set_participant_restrictions(self, chat, user, restrictions, until=until)
|
await set_participant_restrictions(
|
||||||
|
self, chat, participant, restrictions, until=until
|
||||||
|
)
|
||||||
|
|
||||||
async def sign_in(self, token: LoginToken, code: str) -> User | PasswordToken:
|
async def sign_in(self, token: LoginToken, code: str) -> User | PasswordToken:
|
||||||
"""
|
"""
|
||||||
|
@ -1977,13 +2009,13 @@ class Client:
|
||||||
await sign_out(self)
|
await sign_out(self)
|
||||||
|
|
||||||
async def unpin_message(
|
async def unpin_message(
|
||||||
self, chat: ChatLike, message_id: int | Literal["all"]
|
self, chat: Peer | PeerRef, /, message_id: int | Literal["all"]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Unpin one or all messages from the top.
|
Unpin one or all messages from the top.
|
||||||
|
|
||||||
:param chat:
|
:param chat:
|
||||||
The :term:`chat` where the message pinned message is.
|
The :term:`peer` where the message pinned message is.
|
||||||
|
|
||||||
:param message_id:
|
:param message_id:
|
||||||
The identifier of the message to unpin, or ``'all'`` to unpin them all.
|
The identifier of the message to unpin, or ``'all'`` to unpin them all.
|
||||||
|
@ -2014,16 +2046,10 @@ class Client:
|
||||||
def _build_message_map(
|
def _build_message_map(
|
||||||
self,
|
self,
|
||||||
result: abcs.Updates,
|
result: abcs.Updates,
|
||||||
peer: Optional[abcs.InputPeer],
|
peer: Optional[PeerRef],
|
||||||
) -> MessageMap:
|
) -> MessageMap:
|
||||||
return build_message_map(self, result, peer)
|
return build_message_map(self, result, peer)
|
||||||
|
|
||||||
async def _resolve_to_packed(self, chat: ChatLike) -> PeerRef:
|
|
||||||
return await resolve_to_packed(self, chat)
|
|
||||||
|
|
||||||
def _input_to_peer(self, input: Optional[abcs.InputPeer]) -> Optional[abcs.Peer]:
|
|
||||||
return input_to_peer(self, input)
|
|
||||||
|
|
||||||
async def _upload(
|
async def _upload(
|
||||||
self, fd: str | Path | InFileLike, size: Optional[int], name: Optional[str]
|
self, fd: str | Path | InFileLike, size: Optional[int], name: Optional[str]
|
||||||
) -> tuple[abcs.InputFile, str]:
|
) -> tuple[abcs.InputFile, str]:
|
||||||
|
|
|
@ -3,12 +3,13 @@ from __future__ import annotations
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from ...session import PeerRef
|
||||||
from ...tl import functions, types
|
from ...tl import functions, types
|
||||||
from ..types import (
|
from ..types import (
|
||||||
AsyncList,
|
AsyncList,
|
||||||
ChatLike,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
Draft,
|
Draft,
|
||||||
|
Peer,
|
||||||
build_chat_map,
|
build_chat_map,
|
||||||
build_msg_map,
|
build_msg_map,
|
||||||
parse_message,
|
parse_message,
|
||||||
|
@ -59,8 +60,8 @@ def get_dialogs(self: Client) -> AsyncList[Dialog]:
|
||||||
return DialogList(self)
|
return DialogList(self)
|
||||||
|
|
||||||
|
|
||||||
async def delete_dialog(self: Client, chat: ChatLike) -> None:
|
async def delete_dialog(self: Client, dialog: Peer | PeerRef, /) -> None:
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
peer = dialog._ref
|
||||||
if isinstance(peer, types.InputPeerChannel):
|
if isinstance(peer, types.InputPeerChannel):
|
||||||
await self(
|
await self(
|
||||||
functions.channels.leave_channel(
|
functions.channels.leave_channel(
|
||||||
|
@ -119,7 +120,8 @@ def get_drafts(self: Client) -> AsyncList[Draft]:
|
||||||
|
|
||||||
async def edit_draft(
|
async def edit_draft(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
peer: Peer | PeerRef,
|
||||||
|
/,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
|
@ -127,8 +129,7 @@ async def edit_draft(
|
||||||
link_preview: bool = False,
|
link_preview: bool = False,
|
||||||
reply_to: Optional[int] = None,
|
reply_to: Optional[int] = None,
|
||||||
) -> Draft:
|
) -> Draft:
|
||||||
packed = await self._resolve_to_packed(chat)
|
peer = peer._ref
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
message, entities = parse_message(
|
message, entities = parse_message(
|
||||||
text=text, markdown=markdown, html=html, allow_empty=False
|
text=text, markdown=markdown, html=html, allow_empty=False
|
||||||
)
|
)
|
||||||
|
@ -138,7 +139,7 @@ async def edit_draft(
|
||||||
no_webpage=not link_preview,
|
no_webpage=not link_preview,
|
||||||
reply_to_msg_id=reply_to,
|
reply_to_msg_id=reply_to,
|
||||||
top_msg_id=None,
|
top_msg_id=None,
|
||||||
peer=peer,
|
peer=peer._to_input_peer(),
|
||||||
message=message,
|
message=message,
|
||||||
entities=entities,
|
entities=entities,
|
||||||
)
|
)
|
||||||
|
@ -147,7 +148,7 @@ async def edit_draft(
|
||||||
|
|
||||||
return Draft._from_raw(
|
return Draft._from_raw(
|
||||||
client=self,
|
client=self,
|
||||||
peer=packed._to_peer(),
|
peer=peer._to_peer(),
|
||||||
top_msg_id=0,
|
top_msg_id=0,
|
||||||
draft=types.DraftMessage(
|
draft=types.DraftMessage(
|
||||||
no_webpage=not link_preview,
|
no_webpage=not link_preview,
|
||||||
|
|
|
@ -6,16 +6,17 @@ from inspect import isawaitable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from ...session import PeerRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
from ..types import (
|
from ..types import (
|
||||||
AlbumBuilder,
|
AlbumBuilder,
|
||||||
AsyncList,
|
AsyncList,
|
||||||
ChatLike,
|
|
||||||
File,
|
File,
|
||||||
InFileLike,
|
InFileLike,
|
||||||
Message,
|
Message,
|
||||||
OutFileLike,
|
OutFileLike,
|
||||||
OutWrapper,
|
OutWrapper,
|
||||||
|
Peer,
|
||||||
)
|
)
|
||||||
from ..types import buttons as btns
|
from ..types import buttons as btns
|
||||||
from ..types import (
|
from ..types import (
|
||||||
|
@ -44,7 +45,8 @@ def prepare_album(self: Client) -> AlbumBuilder:
|
||||||
|
|
||||||
async def send_photo(
|
async def send_photo(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -83,12 +85,13 @@ async def send_photo(
|
||||||
|
|
||||||
async def send_audio(
|
async def send_audio(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
mime_type: Optional[str] = None,
|
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
mime_type: Optional[str] = None,
|
||||||
duration: Optional[float] = None,
|
duration: Optional[float] = None,
|
||||||
voice: bool = False,
|
voice: bool = False,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
|
@ -120,7 +123,8 @@ async def send_audio(
|
||||||
|
|
||||||
async def send_video(
|
async def send_video(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -161,7 +165,8 @@ async def send_video(
|
||||||
|
|
||||||
async def send_file(
|
async def send_file(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
file: str | Path | InFileLike | File,
|
file: str | Path | InFileLike | File,
|
||||||
*,
|
*,
|
||||||
size: Optional[int] = None,
|
size: Optional[int] = None,
|
||||||
|
@ -289,14 +294,13 @@ async def send_file(
|
||||||
|
|
||||||
async def do_send_file(
|
async def do_send_file(
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
input_media: abcs.InputMedia,
|
input_media: abcs.InputMedia,
|
||||||
message: str,
|
message: str,
|
||||||
entities: Optional[list[abcs.MessageEntity]],
|
entities: Optional[list[abcs.MessageEntity]],
|
||||||
reply_to: Optional[int],
|
reply_to: Optional[int],
|
||||||
buttons: Optional[list[btns.Button] | list[list[btns.Button]]],
|
buttons: Optional[list[btns.Button] | list[list[btns.Button]]],
|
||||||
) -> Message:
|
) -> Message:
|
||||||
peer = (await client._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
random_id = generate_random_id()
|
random_id = generate_random_id()
|
||||||
return client._build_message_map(
|
return client._build_message_map(
|
||||||
await client(
|
await client(
|
||||||
|
@ -306,7 +310,7 @@ async def do_send_file(
|
||||||
clear_draft=False,
|
clear_draft=False,
|
||||||
noforwards=False,
|
noforwards=False,
|
||||||
update_stickersets_order=False,
|
update_stickersets_order=False,
|
||||||
peer=peer,
|
peer=chat._ref._to_input_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
||||||
if reply_to
|
if reply_to
|
||||||
|
@ -321,7 +325,7 @@ async def do_send_file(
|
||||||
send_as=None,
|
send_as=None,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
peer,
|
chat._ref,
|
||||||
).with_random_id(random_id)
|
).with_random_id(random_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -452,11 +456,13 @@ class FileBytesList(AsyncList[bytes]):
|
||||||
self._done = len(result.bytes) < MAX_CHUNK_SIZE
|
self._done = len(result.bytes) < MAX_CHUNK_SIZE
|
||||||
|
|
||||||
|
|
||||||
def get_file_bytes(self: Client, media: File) -> AsyncList[bytes]:
|
def get_file_bytes(self: Client, media: File, /) -> AsyncList[bytes]:
|
||||||
return FileBytesList(self, media)
|
return FileBytesList(self, media)
|
||||||
|
|
||||||
|
|
||||||
async def download(self: Client, media: File, file: str | Path | OutFileLike) -> None:
|
async def download(
|
||||||
|
self: Client, media: File, /, file: str | Path | OutFileLike
|
||||||
|
) -> None:
|
||||||
fd = OutWrapper(file)
|
fd = OutWrapper(file)
|
||||||
try:
|
try:
|
||||||
async for chunk in get_file_bytes(self, media):
|
async for chunk in get_file_bytes(self, media):
|
||||||
|
|
|
@ -4,9 +4,9 @@ import datetime
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING, Literal, Optional, Self
|
from typing import TYPE_CHECKING, Literal, Optional, Self
|
||||||
|
|
||||||
from ...session import PeerRef
|
from ...session import ChannelRef, PeerRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
from ..types import AsyncList, ChatLike, Message, Peer, build_chat_map
|
from ..types import AsyncList, Message, Peer, build_chat_map
|
||||||
from ..types import buttons as btns
|
from ..types import buttons as btns
|
||||||
from ..types import generate_random_id, parse_message, peer_id
|
from ..types import generate_random_id, parse_message, peer_id
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
text: Optional[str | Message] = None,
|
text: Optional[str | Message] = None,
|
||||||
*,
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
|
@ -25,8 +26,6 @@ async def send_message(
|
||||||
reply_to: Optional[int] = None,
|
reply_to: Optional[int] = None,
|
||||||
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
packed = await self._resolve_to_packed(chat)
|
|
||||||
peer = packed._to_input_peer()
|
|
||||||
random_id = generate_random_id()
|
random_id = generate_random_id()
|
||||||
|
|
||||||
if isinstance(text, Message):
|
if isinstance(text, Message):
|
||||||
|
@ -38,7 +37,7 @@ async def send_message(
|
||||||
clear_draft=False,
|
clear_draft=False,
|
||||||
noforwards=not text.can_forward,
|
noforwards=not text.can_forward,
|
||||||
update_stickersets_order=False,
|
update_stickersets_order=False,
|
||||||
peer=peer,
|
peer=chat._ref._to_input_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.InputReplyToMessage(
|
types.InputReplyToMessage(
|
||||||
reply_to_msg_id=text.replied_message_id, top_msg_id=None
|
reply_to_msg_id=text.replied_message_id, top_msg_id=None
|
||||||
|
@ -64,7 +63,7 @@ async def send_message(
|
||||||
clear_draft=False,
|
clear_draft=False,
|
||||||
noforwards=False,
|
noforwards=False,
|
||||||
update_stickersets_order=False,
|
update_stickersets_order=False,
|
||||||
peer=peer,
|
peer=chat._ref._to_input_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
||||||
if reply_to
|
if reply_to
|
||||||
|
@ -90,7 +89,7 @@ async def send_message(
|
||||||
if self._session.user
|
if self._session.user
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
peer_id=packed._to_peer(),
|
peer_id=chat._ref._to_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.MessageReplyHeader(
|
types.MessageReplyHeader(
|
||||||
reply_to_scheduled=False,
|
reply_to_scheduled=False,
|
||||||
|
@ -109,12 +108,13 @@ async def send_message(
|
||||||
ttl_period=result.ttl_period,
|
ttl_period=result.ttl_period,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self._build_message_map(result, peer).with_random_id(random_id)
|
return self._build_message_map(result, chat._ref).with_random_id(random_id)
|
||||||
|
|
||||||
|
|
||||||
async def edit_message(
|
async def edit_message(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
message_id: int,
|
message_id: int,
|
||||||
*,
|
*,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
@ -123,7 +123,6 @@ async def edit_message(
|
||||||
link_preview: bool = False,
|
link_preview: bool = False,
|
||||||
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
message, entities = parse_message(
|
message, entities = parse_message(
|
||||||
text=text, markdown=markdown, html=html, allow_empty=False
|
text=text, markdown=markdown, html=html, allow_empty=False
|
||||||
)
|
)
|
||||||
|
@ -131,7 +130,7 @@ async def edit_message(
|
||||||
await self(
|
await self(
|
||||||
functions.messages.edit_message(
|
functions.messages.edit_message(
|
||||||
no_webpage=not link_preview,
|
no_webpage=not link_preview,
|
||||||
peer=peer,
|
peer=chat._ref._to_input_peer(),
|
||||||
id=message_id,
|
id=message_id,
|
||||||
message=message,
|
message=message,
|
||||||
media=None,
|
media=None,
|
||||||
|
@ -140,18 +139,23 @@ async def edit_message(
|
||||||
schedule_date=None,
|
schedule_date=None,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
peer,
|
chat._ref,
|
||||||
).with_id(message_id)
|
).with_id(message_id)
|
||||||
|
|
||||||
|
|
||||||
async def delete_messages(
|
async def delete_messages(
|
||||||
self: Client, chat: ChatLike, message_ids: list[int], *, revoke: bool = True
|
self: Client,
|
||||||
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
|
message_ids: list[int],
|
||||||
|
*,
|
||||||
|
revoke: bool = True,
|
||||||
) -> int:
|
) -> int:
|
||||||
packed_chat = await self._resolve_to_packed(chat)
|
peer = chat._ref
|
||||||
if packed_chat.is_channel():
|
if isinstance(peer, ChannelRef):
|
||||||
affected = await self(
|
affected = await self(
|
||||||
functions.channels.delete_messages(
|
functions.channels.delete_messages(
|
||||||
channel=packed_chat._to_input_channel(), id=message_ids
|
channel=peer._to_input_channel(), id=message_ids
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -163,10 +167,8 @@ async def delete_messages(
|
||||||
|
|
||||||
|
|
||||||
async def forward_messages(
|
async def forward_messages(
|
||||||
self: Client, target: ChatLike, message_ids: list[int], source: ChatLike
|
self: Client, target: Peer | PeerRef, message_ids: list[int], source: Peer | PeerRef
|
||||||
) -> list[Message]:
|
) -> list[Message]:
|
||||||
to_peer = (await self._resolve_to_packed(target))._to_input_peer()
|
|
||||||
from_peer = (await self._resolve_to_packed(source))._to_input_peer()
|
|
||||||
random_ids = [generate_random_id() for _ in message_ids]
|
random_ids = [generate_random_id() for _ in message_ids]
|
||||||
map = self._build_message_map(
|
map = self._build_message_map(
|
||||||
await self(
|
await self(
|
||||||
|
@ -177,16 +179,16 @@ async def forward_messages(
|
||||||
drop_author=False,
|
drop_author=False,
|
||||||
drop_media_captions=False,
|
drop_media_captions=False,
|
||||||
noforwards=False,
|
noforwards=False,
|
||||||
from_peer=from_peer,
|
from_peer=source._ref._to_input_peer(),
|
||||||
id=message_ids,
|
id=message_ids,
|
||||||
random_id=random_ids,
|
random_id=random_ids,
|
||||||
to_peer=to_peer,
|
to_peer=target._ref._to_input_peer(),
|
||||||
top_msg_id=None,
|
top_msg_id=None,
|
||||||
schedule_date=None,
|
schedule_date=None,
|
||||||
send_as=None,
|
send_as=None,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
to_peer,
|
target._ref,
|
||||||
)
|
)
|
||||||
return [map.with_random_id(id) for id in random_ids]
|
return [map.with_random_id(id) for id in random_ids]
|
||||||
|
|
||||||
|
@ -239,7 +241,7 @@ class HistoryList(MessageList):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: PeerRef,
|
||||||
limit: int,
|
limit: int,
|
||||||
*,
|
*,
|
||||||
offset_id: int,
|
offset_id: int,
|
||||||
|
@ -247,8 +249,7 @@ class HistoryList(MessageList):
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._peer: Optional[abcs.InputPeer] = None
|
|
||||||
self._limit = limit
|
self._limit = limit
|
||||||
self._offset_id = offset_id
|
self._offset_id = offset_id
|
||||||
self._offset_date = offset_date
|
self._offset_date = offset_date
|
||||||
|
@ -256,15 +257,10 @@ class HistoryList(MessageList):
|
||||||
self._done = limit <= 0
|
self._done = limit <= 0
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if self._peer is None:
|
|
||||||
self._peer = (
|
|
||||||
await self._client._resolve_to_packed(self._chat)
|
|
||||||
)._to_input_peer()
|
|
||||||
|
|
||||||
limit = min(max(self._limit, 1), 100)
|
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._to_input_peer(),
|
||||||
offset_id=self._offset_id,
|
offset_id=self._offset_id,
|
||||||
offset_date=self._offset_date,
|
offset_date=self._offset_date,
|
||||||
add_offset=-limit if self._reversed else 0,
|
add_offset=-limit if self._reversed else 0,
|
||||||
|
@ -286,7 +282,7 @@ class HistoryList(MessageList):
|
||||||
def __reversed__(self) -> Self:
|
def __reversed__(self) -> Self:
|
||||||
new = self.__class__(
|
new = self.__class__(
|
||||||
self._client,
|
self._client,
|
||||||
self._chat,
|
self._peer,
|
||||||
self._limit,
|
self._limit,
|
||||||
offset_id=1 if self._offset_id == 0 else self._offset_id,
|
offset_id=1 if self._offset_id == 0 else self._offset_id,
|
||||||
offset_date=self._offset_date,
|
offset_date=self._offset_date,
|
||||||
|
@ -298,7 +294,8 @@ class HistoryList(MessageList):
|
||||||
|
|
||||||
def get_messages(
|
def get_messages(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
*,
|
*,
|
||||||
offset_id: Optional[int] = None,
|
offset_id: Optional[int] = None,
|
||||||
|
@ -306,7 +303,7 @@ def get_messages(
|
||||||
) -> AsyncList[Message]:
|
) -> AsyncList[Message]:
|
||||||
return HistoryList(
|
return HistoryList(
|
||||||
self,
|
self,
|
||||||
chat,
|
chat._ref,
|
||||||
sys.maxsize if limit is None else limit,
|
sys.maxsize if limit is None else limit,
|
||||||
offset_id=offset_id or 0,
|
offset_id=offset_id or 0,
|
||||||
offset_date=int(offset_date.timestamp()) if offset_date is not None else 0,
|
offset_date=int(offset_date.timestamp()) if offset_date is not None else 0,
|
||||||
|
@ -317,25 +314,22 @@ class CherryPickedList(MessageList):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: PeerRef,
|
||||||
ids: list[int],
|
ids: list[int],
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._packed: Optional[PeerRef] = None
|
|
||||||
self._ids: list[abcs.InputMessage] = [types.InputMessageId(id=id) for id in ids]
|
self._ids: list[abcs.InputMessage] = [types.InputMessageId(id=id) for id in ids]
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if not self._ids:
|
if not self._ids:
|
||||||
return
|
return
|
||||||
if self._packed is None:
|
|
||||||
self._packed = await self._client._resolve_to_packed(self._chat)
|
|
||||||
|
|
||||||
if self._packed.is_channel():
|
if isinstance(self._peer, ChannelRef):
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.channels.get_messages(
|
functions.channels.get_messages(
|
||||||
channel=self._packed._to_input_channel(), id=self._ids[:100]
|
channel=self._peer._to_input_channel(), id=self._ids[:100]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -349,17 +343,18 @@ class CherryPickedList(MessageList):
|
||||||
|
|
||||||
def get_messages_with_ids(
|
def get_messages_with_ids(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
message_ids: list[int],
|
message_ids: list[int],
|
||||||
) -> AsyncList[Message]:
|
) -> AsyncList[Message]:
|
||||||
return CherryPickedList(self, chat, message_ids)
|
return CherryPickedList(self, chat._ref, message_ids)
|
||||||
|
|
||||||
|
|
||||||
class SearchList(MessageList):
|
class SearchList(MessageList):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: ChatLike,
|
peer: PeerRef,
|
||||||
limit: int,
|
limit: int,
|
||||||
*,
|
*,
|
||||||
query: str,
|
query: str,
|
||||||
|
@ -368,8 +363,7 @@ class SearchList(MessageList):
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = client
|
self._client = client
|
||||||
self._chat = chat
|
self._peer = peer
|
||||||
self._peer: Optional[abcs.InputPeer] = None
|
|
||||||
self._limit = limit
|
self._limit = limit
|
||||||
self._query = query
|
self._query = query
|
||||||
self._filter = types.InputMessagesFilterEmpty()
|
self._filter = types.InputMessagesFilterEmpty()
|
||||||
|
@ -377,14 +371,9 @@ class SearchList(MessageList):
|
||||||
self._offset_date = offset_date
|
self._offset_date = offset_date
|
||||||
|
|
||||||
async def _fetch_next(self) -> None:
|
async def _fetch_next(self) -> None:
|
||||||
if self._peer is None:
|
|
||||||
self._peer = (
|
|
||||||
await self._client._resolve_to_packed(self._chat)
|
|
||||||
)._to_input_peer()
|
|
||||||
|
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.messages.search(
|
functions.messages.search(
|
||||||
peer=self._peer,
|
peer=self._peer._to_input_peer(),
|
||||||
q=self._query,
|
q=self._query,
|
||||||
from_id=None,
|
from_id=None,
|
||||||
top_msg_id=None,
|
top_msg_id=None,
|
||||||
|
@ -411,7 +400,8 @@ class SearchList(MessageList):
|
||||||
|
|
||||||
def search_messages(
|
def search_messages(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: Peer | PeerRef,
|
||||||
|
/,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
*,
|
*,
|
||||||
query: Optional[str] = None,
|
query: Optional[str] = None,
|
||||||
|
@ -420,7 +410,7 @@ def search_messages(
|
||||||
) -> AsyncList[Message]:
|
) -> AsyncList[Message]:
|
||||||
return SearchList(
|
return SearchList(
|
||||||
self,
|
self,
|
||||||
chat,
|
chat._ref,
|
||||||
sys.maxsize if limit is None else limit,
|
sys.maxsize if limit is None else limit,
|
||||||
query=query or "",
|
query=query or "",
|
||||||
offset_id=offset_id or 0,
|
offset_id=offset_id or 0,
|
||||||
|
@ -475,8 +465,7 @@ class GlobalSearchList(MessageList):
|
||||||
|
|
||||||
self._offset_peer = types.InputPeerEmpty()
|
self._offset_peer = types.InputPeerEmpty()
|
||||||
if last.peer_id and (chat := chat_map.get(peer_id(last.peer_id))):
|
if last.peer_id and (chat := chat_map.get(peer_id(last.peer_id))):
|
||||||
if packed := chat.pack():
|
self._offset_peer = chat._ref._to_input_peer()
|
||||||
self._offset_peer = packed._to_input_peer()
|
|
||||||
|
|
||||||
|
|
||||||
def search_all_messages(
|
def search_all_messages(
|
||||||
|
@ -496,54 +485,62 @@ def search_all_messages(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def pin_message(self: Client, chat: ChatLike, message_id: int) -> Message:
|
async def pin_message(
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
self: Client, chat: Peer | PeerRef, /, message_id: int
|
||||||
|
) -> Message:
|
||||||
return self._build_message_map(
|
return self._build_message_map(
|
||||||
await self(
|
await self(
|
||||||
functions.messages.update_pinned_message(
|
functions.messages.update_pinned_message(
|
||||||
silent=True, unpin=False, pm_oneside=False, peer=peer, id=message_id
|
silent=True,
|
||||||
|
unpin=False,
|
||||||
|
pm_oneside=False,
|
||||||
|
peer=chat._ref._to_input_peer(),
|
||||||
|
id=message_id,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
peer,
|
chat._ref,
|
||||||
).get_single()
|
).get_single()
|
||||||
|
|
||||||
|
|
||||||
async def unpin_message(
|
async def unpin_message(
|
||||||
self: Client, chat: ChatLike, message_id: int | Literal["all"]
|
self: Client, chat: Peer | PeerRef, /, message_id: int | Literal["all"]
|
||||||
) -> None:
|
) -> None:
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
if message_id == "all":
|
if message_id == "all":
|
||||||
await self(
|
await self(
|
||||||
functions.messages.unpin_all_messages(
|
functions.messages.unpin_all_messages(
|
||||||
peer=peer,
|
peer=chat._ref._to_input_peer(),
|
||||||
top_msg_id=None,
|
top_msg_id=None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self(
|
await self(
|
||||||
functions.messages.update_pinned_message(
|
functions.messages.update_pinned_message(
|
||||||
silent=True, unpin=True, pm_oneside=False, peer=peer, id=message_id
|
silent=True,
|
||||||
|
unpin=True,
|
||||||
|
pm_oneside=False,
|
||||||
|
peer=chat._ref._to_input_peer(),
|
||||||
|
id=message_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def read_message(
|
async def read_message(
|
||||||
self: Client, chat: ChatLike, message_id: int | Literal["all"]
|
self: Client, chat: Peer | PeerRef, /, message_id: int | Literal["all"]
|
||||||
) -> None:
|
) -> None:
|
||||||
packed = await self._resolve_to_packed(chat)
|
|
||||||
if message_id == "all":
|
if message_id == "all":
|
||||||
message_id = 0
|
message_id = 0
|
||||||
|
|
||||||
if packed.is_channel():
|
peer = chat._ref
|
||||||
|
if isinstance(peer, ChannelRef):
|
||||||
await self(
|
await self(
|
||||||
functions.channels.read_history(
|
functions.channels.read_history(
|
||||||
channel=packed._to_input_channel(), max_id=message_id
|
channel=peer._to_input_channel(), max_id=message_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self(
|
await self(
|
||||||
functions.messages.read_history(
|
functions.messages.read_history(
|
||||||
peer=packed._to_input_peer(), max_id=message_id
|
peer=peer._ref._to_input_peer(), max_id=message_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -554,7 +551,7 @@ class MessageMap:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
peer: Optional[abcs.InputPeer],
|
peer: Optional[PeerRef],
|
||||||
random_id_to_id: dict[int, int],
|
random_id_to_id: dict[int, int],
|
||||||
id_to_message: dict[int, Message],
|
id_to_message: dict[int, Message],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -580,7 +577,9 @@ class MessageMap:
|
||||||
def _empty(self, id: int = 0) -> Message:
|
def _empty(self, id: int = 0) -> Message:
|
||||||
return Message._from_raw(
|
return Message._from_raw(
|
||||||
self._client,
|
self._client,
|
||||||
types.MessageEmpty(id=id, peer_id=self._client._input_to_peer(self._peer)),
|
types.MessageEmpty(
|
||||||
|
id=id, peer_id=self._peer._to_peer() if self._peer else None
|
||||||
|
),
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -588,7 +587,7 @@ class MessageMap:
|
||||||
def build_message_map(
|
def build_message_map(
|
||||||
client: Client,
|
client: Client,
|
||||||
result: abcs.Updates,
|
result: abcs.Updates,
|
||||||
peer: Optional[abcs.InputPeer],
|
peer: Optional[PeerRef],
|
||||||
) -> MessageMap:
|
) -> MessageMap:
|
||||||
if isinstance(result, (types.Updates, types.UpdatesCombined)):
|
if isinstance(result, (types.Updates, types.UpdatesCombined)):
|
||||||
updates = result.updates
|
updates = result.updates
|
||||||
|
|
|
@ -196,9 +196,7 @@ async def connect(self: Client) -> None:
|
||||||
self._session.user = SessionUser(
|
self._session.user = SessionUser(
|
||||||
id=me.id, dc=self._sender.dc_id, bot=me.bot, username=me.username
|
id=me.id, dc=self._sender.dc_id, bot=me.bot, username=me.username
|
||||||
)
|
)
|
||||||
packed = me.pack()
|
self._chat_hashes.set_self_user(me.id, me.bot)
|
||||||
assert packed is not None
|
|
||||||
self._chat_hashes.set_self_user(packed)
|
|
||||||
|
|
||||||
self._dispatcher = asyncio.create_task(dispatcher(self))
|
self._dispatcher = asyncio.create_task(dispatcher(self))
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ UPDATE_LIMIT_EXCEEDED_LOG_COOLDOWN = 300
|
||||||
|
|
||||||
|
|
||||||
def on(
|
def on(
|
||||||
self: Client, event_cls: Type[Event], filter: Optional[Filter] = None
|
self: Client, event_cls: Type[Event], /, filter: Optional[Filter] = None
|
||||||
) -> Callable[[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]]:
|
) -> Callable[[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]]:
|
||||||
def wrapper(
|
def wrapper(
|
||||||
handler: Callable[[Event], Awaitable[Any]]
|
handler: Callable[[Event], Awaitable[Any]]
|
||||||
|
@ -34,6 +34,7 @@ def on(
|
||||||
def add_event_handler(
|
def add_event_handler(
|
||||||
self: Client,
|
self: Client,
|
||||||
handler: Callable[[Event], Awaitable[Any]],
|
handler: Callable[[Event], Awaitable[Any]],
|
||||||
|
/,
|
||||||
event_cls: Type[Event],
|
event_cls: Type[Event],
|
||||||
filter: Optional[Filter] = None,
|
filter: Optional[Filter] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -41,7 +42,7 @@ def add_event_handler(
|
||||||
|
|
||||||
|
|
||||||
def remove_event_handler(
|
def remove_event_handler(
|
||||||
self: Client, handler: Callable[[Event], Awaitable[Any]]
|
self: Client, handler: Callable[[Event], Awaitable[Any]], /
|
||||||
) -> None:
|
) -> None:
|
||||||
for event_cls, handlers in tuple(self._handlers.items()):
|
for event_cls, handlers in tuple(self._handlers.items()):
|
||||||
for i in reversed(range(len(handlers))):
|
for i in reversed(range(len(handlers))):
|
||||||
|
@ -52,7 +53,7 @@ def remove_event_handler(
|
||||||
|
|
||||||
|
|
||||||
def get_handler_filter(
|
def get_handler_filter(
|
||||||
self: Client, handler: Callable[[Event], Awaitable[Any]]
|
self: Client, handler: Callable[[Event], Awaitable[Any]], /
|
||||||
) -> Optional[Filter]:
|
) -> Optional[Filter]:
|
||||||
for handlers in self._handlers.values():
|
for handlers in self._handlers.values():
|
||||||
for h, f in handlers:
|
for h, f in handlers:
|
||||||
|
@ -64,6 +65,7 @@ def get_handler_filter(
|
||||||
def set_handler_filter(
|
def set_handler_filter(
|
||||||
self: Client,
|
self: Client,
|
||||||
handler: Callable[[Event], Awaitable[Any]],
|
handler: Callable[[Event], Awaitable[Any]],
|
||||||
|
/,
|
||||||
filter: Optional[Filter] = None,
|
filter: Optional[Filter] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
for handlers in self._handlers.values():
|
for handlers in self._handlers.values():
|
||||||
|
|
|
@ -1,21 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Sequence
|
||||||
|
|
||||||
from ...mtproto import RpcError
|
from ...mtproto import RpcError
|
||||||
from ...session import PackedType, PeerRef
|
from ...session import GroupRef, PeerRef, UserRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
from ..types import (
|
from ..types import AsyncList, Peer, User, build_chat_map, expand_peer, peer_id
|
||||||
AsyncList,
|
|
||||||
Channel,
|
|
||||||
ChatLike,
|
|
||||||
Group,
|
|
||||||
Peer,
|
|
||||||
User,
|
|
||||||
build_chat_map,
|
|
||||||
expand_peer,
|
|
||||||
peer_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .client import Client
|
from .client import Client
|
||||||
|
@ -62,35 +52,33 @@ def resolved_peer_to_chat(client: Client, resolved: abcs.contacts.ResolvedPeer)
|
||||||
raise ValueError("no matching chat found in response")
|
raise ValueError("no matching chat found in response")
|
||||||
|
|
||||||
|
|
||||||
async def resolve_phone(client: Client, phone: str) -> Peer:
|
async def resolve_phone(self: Client, phone: str, /) -> Peer:
|
||||||
return resolved_peer_to_chat(
|
return resolved_peer_to_chat(
|
||||||
client, await client(functions.contacts.resolve_phone(phone=phone))
|
self, await self(functions.contacts.resolve_phone(phone=phone))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def resolve_username(self: Client, username: str) -> Peer:
|
async def resolve_username(self: Client, username: str, /) -> Peer:
|
||||||
return resolved_peer_to_chat(
|
return resolved_peer_to_chat(
|
||||||
self, await self(functions.contacts.resolve_username(username=username))
|
self, await self(functions.contacts.resolve_username(username=username))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_chats(
|
async def resolve_peers(self: Client, peers: Sequence[Peer | PeerRef], /) -> list[Peer]:
|
||||||
self: Client, chats: list[ChatLike] | tuple[ChatLike, ...]
|
refs: list[PeerRef] = []
|
||||||
) -> list[Peer]:
|
|
||||||
packed_chats: list[PeerRef] = []
|
|
||||||
input_users: list[types.InputUser] = []
|
input_users: list[types.InputUser] = []
|
||||||
input_chats: list[int] = []
|
input_chats: list[int] = []
|
||||||
input_channels: list[types.InputChannel] = []
|
input_channels: list[types.InputChannel] = []
|
||||||
|
|
||||||
for chat in chats:
|
for peer in peers:
|
||||||
packed = await resolve_to_packed(self, chat)
|
peer = peer._ref
|
||||||
packed_chats.append(packed)
|
refs.append(peer)
|
||||||
if packed.is_user():
|
if isinstance(peer, UserRef):
|
||||||
input_users.append(packed._to_input_user())
|
input_users.append(peer._to_input_user())
|
||||||
elif packed.is_chat():
|
elif isinstance(peer, GroupRef):
|
||||||
input_chats.append(packed.id)
|
input_chats.append(peer._to_input_chat())
|
||||||
else:
|
else:
|
||||||
input_channels.append(packed._to_input_channel())
|
input_channels.append(peer._to_input_channel())
|
||||||
|
|
||||||
if input_users:
|
if input_users:
|
||||||
ret_users = await self(functions.users.get_users(id=input_users))
|
ret_users = await self(functions.users.get_users(id=input_users))
|
||||||
|
@ -101,152 +89,18 @@ async def get_chats(
|
||||||
if input_chats:
|
if input_chats:
|
||||||
ret_chats = await self(functions.messages.get_chats(id=input_chats))
|
ret_chats = await self(functions.messages.get_chats(id=input_chats))
|
||||||
assert isinstance(ret_chats, types.messages.Chats)
|
assert isinstance(ret_chats, types.messages.Chats)
|
||||||
groups = list(ret_chats.chats)
|
chats = list(ret_chats.chats)
|
||||||
else:
|
else:
|
||||||
groups = []
|
chats = []
|
||||||
|
|
||||||
if input_channels:
|
if input_channels:
|
||||||
ret_chats = await self(functions.channels.get_channels(id=input_channels))
|
ret_chats = await self(functions.channels.get_channels(id=input_channels))
|
||||||
assert isinstance(ret_chats, types.messages.Chats)
|
assert isinstance(ret_chats, types.messages.Chats)
|
||||||
channels = list(ret_chats.chats)
|
chats.extend(ret_chats.chats)
|
||||||
else:
|
|
||||||
channels = []
|
|
||||||
|
|
||||||
chat_map = build_chat_map(self, users, groups + channels)
|
chat_map = build_chat_map(self, users, chats)
|
||||||
return [
|
return [
|
||||||
chat_map.get(chat.id)
|
chat_map.get(ref.identifier)
|
||||||
or expand_peer(self, chat._to_peer(), broadcast=chat.ty == PackedType.BROADCAST)
|
or expand_peer(self, ref._to_peer(), broadcast=None)
|
||||||
for chat in packed_chats
|
for ref in refs
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def resolve_to_packed(
|
|
||||||
client: Client, chat: ChatLike | abcs.InputPeer | abcs.Peer
|
|
||||||
) -> PeerRef:
|
|
||||||
if isinstance(chat, PeerRef):
|
|
||||||
return chat
|
|
||||||
|
|
||||||
if isinstance(chat, (User, Group, Channel)):
|
|
||||||
packed = chat.pack() or client._chat_hashes.get(chat.id)
|
|
||||||
if packed is not None:
|
|
||||||
return packed
|
|
||||||
|
|
||||||
# Try anyway (may work for contacts or bot users).
|
|
||||||
if isinstance(chat, User):
|
|
||||||
ty = PackedType.USER
|
|
||||||
elif isinstance(chat, Group):
|
|
||||||
ty = PackedType.MEGAGROUP if chat.is_megagroup else PackedType.CHAT
|
|
||||||
else:
|
|
||||||
ty = PackedType.BROADCAST
|
|
||||||
|
|
||||||
return PeerRef(ty=ty, id=chat.id, access_hash=0)
|
|
||||||
|
|
||||||
if isinstance(chat, abcs.InputPeer):
|
|
||||||
if isinstance(chat, types.InputPeerEmpty):
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
elif isinstance(chat, types.InputPeerSelf):
|
|
||||||
if not client._session.user:
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.BOT if client._session.user.bot else PackedType.USER,
|
|
||||||
id=client._chat_hashes.self_id,
|
|
||||||
access_hash=0,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.InputPeerChat):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.CHAT,
|
|
||||||
id=chat.chat_id,
|
|
||||||
access_hash=None,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.InputPeerUser):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.USER,
|
|
||||||
id=chat.user_id,
|
|
||||||
access_hash=chat.access_hash,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.InputPeerChannel):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.BROADCAST,
|
|
||||||
id=chat.channel_id,
|
|
||||||
access_hash=chat.access_hash,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.InputPeerUserFromMessage):
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
elif isinstance(chat, types.InputPeerChannelFromMessage):
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
||||||
if isinstance(chat, abcs.Peer):
|
|
||||||
packed = client._chat_hashes.get(peer_id(chat))
|
|
||||||
if packed is not None:
|
|
||||||
return packed
|
|
||||||
if isinstance(chat, types.PeerUser):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.USER,
|
|
||||||
id=chat.user_id,
|
|
||||||
access_hash=0,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.PeerChat):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.CHAT,
|
|
||||||
id=chat.chat_id,
|
|
||||||
access_hash=0,
|
|
||||||
)
|
|
||||||
elif isinstance(chat, types.PeerChannel):
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.BROADCAST,
|
|
||||||
id=chat.channel_id,
|
|
||||||
access_hash=0,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
||||||
if isinstance(chat, str):
|
|
||||||
if chat.startswith("+"):
|
|
||||||
resolved = await resolve_phone(client, chat)
|
|
||||||
elif chat == "me":
|
|
||||||
if me := client._session.user:
|
|
||||||
return PeerRef(
|
|
||||||
ty=PackedType.BOT if me.bot else PackedType.USER,
|
|
||||||
id=me.id,
|
|
||||||
access_hash=0,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
resolved = None
|
|
||||||
else:
|
|
||||||
resolved = await resolve_username(client, username=chat)
|
|
||||||
|
|
||||||
if resolved and (packed := resolved.pack()) is not None:
|
|
||||||
return packed
|
|
||||||
|
|
||||||
if isinstance(chat, int):
|
|
||||||
packed = client._chat_hashes.get(chat)
|
|
||||||
if packed is None:
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
return packed
|
|
||||||
|
|
||||||
raise ValueError("Cannot resolve chat")
|
|
||||||
|
|
||||||
|
|
||||||
def input_to_peer(
|
|
||||||
client: Client, input: Optional[abcs.InputPeer]
|
|
||||||
) -> Optional[abcs.Peer]:
|
|
||||||
if input is None:
|
|
||||||
return None
|
|
||||||
elif isinstance(input, types.InputPeerEmpty):
|
|
||||||
return None
|
|
||||||
elif isinstance(input, types.InputPeerSelf):
|
|
||||||
return types.PeerUser(user_id=client._chat_hashes.self_id)
|
|
||||||
elif isinstance(input, types.InputPeerChat):
|
|
||||||
return types.PeerChat(chat_id=input.chat_id)
|
|
||||||
elif isinstance(input, types.InputPeerUser):
|
|
||||||
return types.PeerUser(user_id=input.user_id)
|
|
||||||
elif isinstance(input, types.InputPeerChannel):
|
|
||||||
return types.PeerChannel(channel_id=input.channel_id)
|
|
||||||
elif isinstance(input, types.InputPeerUserFromMessage):
|
|
||||||
return None
|
|
||||||
elif isinstance(input, types.InputPeerChannelFromMessage):
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ class MessageRead(Event):
|
||||||
@property
|
@property
|
||||||
def chat(self) -> Peer:
|
def chat(self) -> Peer:
|
||||||
"""
|
"""
|
||||||
The :term:`chat` when the messages were read.
|
The :term:`peer` where the messages were read.
|
||||||
"""
|
"""
|
||||||
peer = self._peer()
|
peer = self._peer()
|
||||||
pid = peer_id(peer)
|
pid = peer_id(peer)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional, Self
|
from typing import TYPE_CHECKING, Optional, Self
|
||||||
|
|
||||||
|
from ...session import PeerRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
from ..client.messages import CherryPickedList
|
from ..client.messages import CherryPickedList
|
||||||
from ..types import Message, Peer
|
from ..types import Message, Peer
|
||||||
|
@ -79,9 +80,9 @@ class ButtonCallback(Event):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pid = peer_id(self._raw.peer)
|
pid = peer_id(self._raw.peer)
|
||||||
chat = self._chat_map.get(pid) or await self._client._resolve_to_packed(pid)
|
chat = self._chat_map.get(pid) or PeerRef._empty_from_peer(self._raw.peer)
|
||||||
|
|
||||||
lst = CherryPickedList(self._client, chat, [])
|
lst = CherryPickedList(self._client, chat._ref, [])
|
||||||
lst._ids.append(
|
lst._ids.append(
|
||||||
types.InputMessageCallbackQuery(
|
types.InputMessageCallbackQuery(
|
||||||
id=self._raw.msg_id, query_id=self._raw.query_id
|
id=self._raw.msg_id, query_id=self._raw.query_id
|
||||||
|
|
|
@ -25,16 +25,7 @@ from .message import (
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
from .participant import Participant
|
from .participant import Participant
|
||||||
from .password_token import PasswordToken
|
from .password_token import PasswordToken
|
||||||
from .peer import (
|
from .peer import Channel, Group, Peer, User, build_chat_map, expand_peer, peer_id
|
||||||
Channel,
|
|
||||||
ChatLike,
|
|
||||||
Group,
|
|
||||||
Peer,
|
|
||||||
User,
|
|
||||||
build_chat_map,
|
|
||||||
expand_peer,
|
|
||||||
peer_id,
|
|
||||||
)
|
|
||||||
from .recent_action import RecentAction
|
from .recent_action import RecentAction
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -45,7 +36,6 @@ __all__ = [
|
||||||
"CallbackAnswer",
|
"CallbackAnswer",
|
||||||
"Channel",
|
"Channel",
|
||||||
"Peer",
|
"Peer",
|
||||||
"ChatLike",
|
|
||||||
"Group",
|
"Group",
|
||||||
"User",
|
"User",
|
||||||
"build_chat_map",
|
"build_chat_map",
|
||||||
|
|
|
@ -4,11 +4,12 @@ import mimetypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from ...session import PeerRef
|
||||||
from ...tl import abcs, functions, types
|
from ...tl import abcs, functions, types
|
||||||
from .file import InFileLike, try_get_url_path
|
from .file import InFileLike, try_get_url_path
|
||||||
from .message import Message, generate_random_id, parse_message
|
from .message import Message, generate_random_id, parse_message
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
from .peer import ChatLike
|
from .peer import Peer
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..client.client import Client
|
from ..client.client import Client
|
||||||
|
@ -197,7 +198,7 @@ class AlbumBuilder(metaclass=NoPublicConstructor):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def send(
|
async def send(
|
||||||
self, chat: ChatLike, *, reply_to: Optional[int] = None
|
self, peer: Peer | PeerRef, *, reply_to: Optional[int] = None
|
||||||
) -> list[Message]:
|
) -> list[Message]:
|
||||||
"""
|
"""
|
||||||
Send the album.
|
Send the album.
|
||||||
|
@ -214,7 +215,6 @@ class AlbumBuilder(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
messages = await album.send(chat)
|
messages = await album.send(chat)
|
||||||
"""
|
"""
|
||||||
peer = (await self._client._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
msg_map = self._client._build_message_map(
|
msg_map = self._client._build_message_map(
|
||||||
await self._client(
|
await self._client(
|
||||||
functions.messages.send_multi_media(
|
functions.messages.send_multi_media(
|
||||||
|
@ -223,7 +223,7 @@ class AlbumBuilder(metaclass=NoPublicConstructor):
|
||||||
clear_draft=False,
|
clear_draft=False,
|
||||||
noforwards=False,
|
noforwards=False,
|
||||||
update_stickersets_order=False,
|
update_stickersets_order=False,
|
||||||
peer=peer,
|
peer=peer._ref._to_input_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.InputReplyToMessage(
|
types.InputReplyToMessage(
|
||||||
reply_to_msg_id=reply_to, top_msg_id=None
|
reply_to_msg_id=reply_to, top_msg_id=None
|
||||||
|
@ -236,6 +236,6 @@ class AlbumBuilder(metaclass=NoPublicConstructor):
|
||||||
send_as=None,
|
send_as=None,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
peer,
|
peer._ref,
|
||||||
)
|
)
|
||||||
return [msg_map.with_random_id(media.random_id) for media in self._medias]
|
return [msg_map.with_random_id(media.random_id) for media in self._medias]
|
||||||
|
|
|
@ -47,14 +47,12 @@ class Callback(InlineButton):
|
||||||
The bot's answer will be returned, or :data:`None` if they don't answer in time.
|
The bot's answer will be returned, or :data:`None` if they don't answer in time.
|
||||||
"""
|
"""
|
||||||
message = self._message()
|
message = self._message()
|
||||||
packed = message.chat.pack()
|
|
||||||
assert packed
|
|
||||||
|
|
||||||
return CallbackAnswer._create(
|
return CallbackAnswer._create(
|
||||||
await message._client(
|
await message._client(
|
||||||
functions.messages.get_bot_callback_answer(
|
functions.messages.get_bot_callback_answer(
|
||||||
game=False,
|
game=False,
|
||||||
peer=packed._to_input_peer(),
|
peer=message.chat._ref._to_input_peer(),
|
||||||
msg_id=message.id,
|
msg_id=message.id,
|
||||||
data=self.data,
|
data=self.data,
|
||||||
password=None,
|
password=None,
|
||||||
|
|
|
@ -150,7 +150,7 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
new_draft = await old_draft.edit('new text', link_preview=False)
|
new_draft = await old_draft.edit('new text', link_preview=False)
|
||||||
"""
|
"""
|
||||||
return await self._client.edit_draft(
|
return await self._client.edit_draft(
|
||||||
await self._packed_chat(),
|
self._peer_ref(),
|
||||||
text,
|
text,
|
||||||
markdown=markdown,
|
markdown=markdown,
|
||||||
html=html,
|
html=html,
|
||||||
|
@ -158,13 +158,11 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _packed_chat(self) -> PeerRef:
|
def _peer_ref(self) -> PeerRef:
|
||||||
packed = None
|
|
||||||
if chat := self._chat_map.get(peer_id(self._peer)):
|
if chat := self._chat_map.get(peer_id(self._peer)):
|
||||||
packed = chat.pack()
|
return chat._ref
|
||||||
if packed is None:
|
else:
|
||||||
packed = await self._client._resolve_to_packed(peer_id(self._peer))
|
return PeerRef._empty_from_peer(self._peer)
|
||||||
return packed
|
|
||||||
|
|
||||||
async def send(self) -> Message:
|
async def send(self) -> Message:
|
||||||
"""
|
"""
|
||||||
|
@ -180,9 +178,6 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
await draft.send(clear=False)
|
await draft.send(clear=False)
|
||||||
"""
|
"""
|
||||||
packed = await self._packed_chat()
|
|
||||||
peer = packed._to_input_peer()
|
|
||||||
|
|
||||||
reply_to = self.replied_message_id
|
reply_to = self.replied_message_id
|
||||||
message = getattr(self._raw, "message", "")
|
message = getattr(self._raw, "message", "")
|
||||||
entities = getattr(self._raw, "entities", None)
|
entities = getattr(self._raw, "entities", None)
|
||||||
|
@ -196,7 +191,7 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
clear_draft=True,
|
clear_draft=True,
|
||||||
noforwards=False,
|
noforwards=False,
|
||||||
update_stickersets_order=False,
|
update_stickersets_order=False,
|
||||||
peer=peer,
|
peer=self._peer_ref()._to_input_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
types.InputReplyToMessage(reply_to_msg_id=reply_to, top_msg_id=None)
|
||||||
if reply_to
|
if reply_to
|
||||||
|
@ -221,7 +216,7 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
if self._client._session.user
|
if self._client._session.user
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
peer_id=packed._to_peer(),
|
peer_id=self._peer_ref()._to_peer(),
|
||||||
reply_to=(
|
reply_to=(
|
||||||
types.MessageReplyHeader(
|
types.MessageReplyHeader(
|
||||||
reply_to_scheduled=False,
|
reply_to_scheduled=False,
|
||||||
|
@ -240,9 +235,9 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
ttl_period=result.ttl_period,
|
ttl_period=result.ttl_period,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self._client._build_message_map(result, peer).with_random_id(
|
return self._client._build_message_map(
|
||||||
random_id
|
result, self._peer_ref()
|
||||||
)
|
).with_random_id(random_id)
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,10 +2,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from ...tl import abcs, functions, types
|
from ...session import PeerRef
|
||||||
|
from ...tl import functions, types
|
||||||
from .message import Message, generate_random_id
|
from .message import Message, generate_random_id
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
from .peer import ChatLike
|
from .peer import Peer
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..client.client import Client
|
from ..client.client import Client
|
||||||
|
@ -23,7 +24,7 @@ class InlineResult(metaclass=NoPublicConstructor):
|
||||||
client: Client,
|
client: Client,
|
||||||
results: types.messages.BotResults,
|
results: types.messages.BotResults,
|
||||||
result: types.BotInlineMediaResult | types.BotInlineResult,
|
result: types.BotInlineMediaResult | types.BotInlineResult,
|
||||||
default_peer: abcs.InputPeer,
|
default_peer: Optional[PeerRef],
|
||||||
):
|
):
|
||||||
self._client = client
|
self._client = client
|
||||||
self._raw_results = results
|
self._raw_results = results
|
||||||
|
@ -50,7 +51,7 @@ class InlineResult(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
async def send(
|
async def send(
|
||||||
self,
|
self,
|
||||||
chat: Optional[ChatLike] = None,
|
peer: Optional[Peer | PeerRef] = None,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
"""
|
"""
|
||||||
Send the result to the desired chat.
|
Send the result to the desired chat.
|
||||||
|
@ -62,13 +63,12 @@ class InlineResult(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
:return: The sent message.
|
:return: The sent message.
|
||||||
"""
|
"""
|
||||||
if chat is None and isinstance(self._default_peer, types.InputPeerEmpty):
|
if peer is None:
|
||||||
raise ValueError("no target chat was specified")
|
if self._default_peer is None:
|
||||||
|
raise ValueError("no target chat was specified")
|
||||||
if chat is not None:
|
|
||||||
peer = (await self._client._resolve_to_packed(chat))._to_input_peer()
|
|
||||||
else:
|
|
||||||
peer = self._default_peer
|
peer = self._default_peer
|
||||||
|
else:
|
||||||
|
peer = peer._ref
|
||||||
|
|
||||||
random_id = generate_random_id()
|
random_id = generate_random_id()
|
||||||
return self._client._build_message_map(
|
return self._client._build_message_map(
|
||||||
|
@ -78,7 +78,7 @@ class InlineResult(metaclass=NoPublicConstructor):
|
||||||
background=False,
|
background=False,
|
||||||
clear_draft=False,
|
clear_draft=False,
|
||||||
hide_via=False,
|
hide_via=False,
|
||||||
peer=peer,
|
peer=peer._to_input_peer(),
|
||||||
reply_to=None,
|
reply_to=None,
|
||||||
random_id=random_id,
|
random_id=random_id,
|
||||||
query_id=self._raw_results.query_id,
|
query_id=self._raw_results.query_id,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import datetime
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any, Optional, Self, Sequence
|
from typing import TYPE_CHECKING, Any, Optional, Self, Sequence
|
||||||
|
|
||||||
|
from ...session import PeerRef
|
||||||
from ...tl import abcs, types
|
from ...tl import abcs, types
|
||||||
from ..parsers import (
|
from ..parsers import (
|
||||||
generate_html_message,
|
generate_html_message,
|
||||||
|
@ -14,7 +15,7 @@ from ..parsers import (
|
||||||
from .buttons import Button, as_concrete_row, create_button
|
from .buttons import Button, as_concrete_row, create_button
|
||||||
from .file import File
|
from .file import File
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
from .peer import ChatLike, Peer, expand_peer, peer_id
|
from .peer import Peer, expand_peer, peer_id
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..client.client import Client
|
from ..client.client import Client
|
||||||
|
@ -186,7 +187,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
@property
|
@property
|
||||||
def chat(self) -> Peer:
|
def chat(self) -> Peer:
|
||||||
"""
|
"""
|
||||||
The :term:`chat` when the message was sent.
|
The :term:`peer` where the message was sent.
|
||||||
"""
|
"""
|
||||||
peer = self._raw.peer_id or types.PeerUser(user_id=0)
|
peer = self._raw.peer_id or types.PeerUser(user_id=0)
|
||||||
pid = peer_id(peer)
|
pid = peer_id(peer)
|
||||||
|
@ -199,7 +200,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
@property
|
@property
|
||||||
def sender(self) -> Optional[Peer]:
|
def sender(self) -> Optional[Peer]:
|
||||||
"""
|
"""
|
||||||
The :term:`chat` that sent the message.
|
The :term:`peer` that sent the message.
|
||||||
|
|
||||||
This will usually be a :class:`User`, but can also be a :class:`Channel`.
|
This will usually be a :class:`User`, but can also be a :class:`Channel`.
|
||||||
|
|
||||||
|
@ -320,7 +321,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
if self.replied_message_id is not None:
|
if self.replied_message_id is not None:
|
||||||
from ..client.messages import CherryPickedList
|
from ..client.messages import CherryPickedList
|
||||||
|
|
||||||
lst = CherryPickedList(self._client, self.chat, [])
|
lst = CherryPickedList(self._client, self.chat._ref, [])
|
||||||
lst._ids.append(types.InputMessageReplyTo(id=self.id))
|
lst._ids.append(types.InputMessageReplyTo(id=self.id))
|
||||||
return (await lst)[0]
|
return (await lst)[0]
|
||||||
return None
|
return None
|
||||||
|
@ -415,7 +416,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
buttons=buttons,
|
buttons=buttons,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def forward(self, target: ChatLike) -> Message:
|
async def forward(self, target: Peer | PeerRef) -> Message:
|
||||||
"""
|
"""
|
||||||
Alias for :meth:`telethon.Client.forward_messages`.
|
Alias for :meth:`telethon.Client.forward_messages`.
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
from typing import TYPE_CHECKING, Optional, Self, Sequence
|
from typing import TYPE_CHECKING, Optional, Self, Sequence
|
||||||
|
|
||||||
from ...session import PeerRef
|
from ...session import ChannelRef, GroupRef
|
||||||
from ...tl import abcs, types
|
from ...tl import abcs, types
|
||||||
from .admin_right import AdminRight
|
from .admin_right import AdminRight
|
||||||
from .chat_restriction import ChatRestriction
|
from .chat_restriction import ChatRestriction
|
||||||
|
@ -24,7 +24,7 @@ class Participant(metaclass=NoPublicConstructor):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: PeerRef,
|
chat: GroupRef | ChannelRef,
|
||||||
participant: (
|
participant: (
|
||||||
types.ChannelParticipant
|
types.ChannelParticipant
|
||||||
| types.ChannelParticipantSelf
|
| types.ChannelParticipantSelf
|
||||||
|
@ -47,7 +47,7 @@ class Participant(metaclass=NoPublicConstructor):
|
||||||
def _from_raw_channel(
|
def _from_raw_channel(
|
||||||
cls,
|
cls,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: PeerRef,
|
chat: ChannelRef,
|
||||||
participant: abcs.ChannelParticipant,
|
participant: abcs.ChannelParticipant,
|
||||||
chat_map: dict[int, Peer],
|
chat_map: dict[int, Peer],
|
||||||
) -> Self:
|
) -> Self:
|
||||||
|
@ -70,7 +70,7 @@ class Participant(metaclass=NoPublicConstructor):
|
||||||
def _from_raw_chat(
|
def _from_raw_chat(
|
||||||
cls,
|
cls,
|
||||||
client: Client,
|
client: Client,
|
||||||
chat: PeerRef,
|
chat: GroupRef,
|
||||||
participant: abcs.ChatParticipant,
|
participant: abcs.ChatParticipant,
|
||||||
chat_map: dict[int, Peer],
|
chat_map: dict[int, Peer],
|
||||||
) -> Self:
|
) -> Self:
|
||||||
|
@ -193,7 +193,14 @@ class Participant(metaclass=NoPublicConstructor):
|
||||||
"""
|
"""
|
||||||
participant = self.user or self.banned or self.left
|
participant = self.user or self.banned or self.left
|
||||||
assert participant
|
assert participant
|
||||||
await self._client.set_participant_admin_rights(self._chat, participant, rights)
|
if isinstance(participant, User):
|
||||||
|
await self._client.set_participant_admin_rights(
|
||||||
|
self._chat, participant, rights
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"participant of type {participant.__class__.__name__} cannot be made admin"
|
||||||
|
)
|
||||||
|
|
||||||
async def set_restrictions(
|
async def set_restrictions(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING, Optional, Sequence
|
from typing import TYPE_CHECKING, Optional, Sequence
|
||||||
|
|
||||||
from ....session import PeerRef
|
|
||||||
from ....tl import abcs, types
|
from ....tl import abcs, types
|
||||||
from .channel import Channel
|
from .channel import Channel
|
||||||
from .group import Group
|
from .group import Group
|
||||||
|
@ -15,8 +14,6 @@ from .user import User
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...client.client import Client
|
from ...client.client import Client
|
||||||
|
|
||||||
ChatLike = Peer | PeerRef | int | str
|
|
||||||
|
|
||||||
|
|
||||||
def build_chat_map(
|
def build_chat_map(
|
||||||
client: Client, users: Sequence[abcs.User], chats: Sequence[abcs.Chat]
|
client: Client, users: Sequence[abcs.User], chats: Sequence[abcs.Chat]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Optional, Self
|
from typing import Optional, Self
|
||||||
|
|
||||||
from ....session import PackedType, PeerRef
|
from ....session import ChannelRef
|
||||||
from ....tl import abcs, types
|
from ....tl import abcs, types
|
||||||
from ..meta import NoPublicConstructor
|
from ..meta import NoPublicConstructor
|
||||||
from .peer import Peer
|
from .peer import Peer
|
||||||
|
@ -50,18 +50,12 @@ class Channel(Peer, metaclass=NoPublicConstructor):
|
||||||
def username(self) -> Optional[str]:
|
def username(self) -> Optional[str]:
|
||||||
return getattr(self._raw, "username", None)
|
return getattr(self._raw, "username", None)
|
||||||
|
|
||||||
def pack(self) -> Optional[PeerRef]:
|
@property
|
||||||
if self._raw.access_hash is None:
|
def ref(self) -> ChannelRef:
|
||||||
return None
|
return ChannelRef(self._raw.id, self._raw.access_hash)
|
||||||
else:
|
|
||||||
return PeerRef(
|
@property
|
||||||
ty=(
|
def _ref(self) -> ChannelRef:
|
||||||
PackedType.GIGAGROUP
|
return self.ref
|
||||||
if getattr(self._raw, "gigagroup", False)
|
|
||||||
else PackedType.BROADCAST
|
|
||||||
),
|
|
||||||
id=self._raw.id,
|
|
||||||
access_hash=self._raw.access_hash,
|
|
||||||
)
|
|
||||||
|
|
||||||
# endregion Overrides
|
# endregion Overrides
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
from typing import TYPE_CHECKING, Optional, Self, Sequence
|
from typing import TYPE_CHECKING, Optional, Self, Sequence
|
||||||
|
|
||||||
from ....session import PackedType, PeerRef
|
from ....session import ChannelRef, GroupRef
|
||||||
from ....tl import abcs, types
|
from ....tl import abcs, types
|
||||||
from ..chat_restriction import ChatRestriction
|
from ..chat_restriction import ChatRestriction
|
||||||
from ..meta import NoPublicConstructor
|
from ..meta import NoPublicConstructor
|
||||||
|
@ -65,17 +65,16 @@ class Group(Peer, metaclass=NoPublicConstructor):
|
||||||
def username(self) -> Optional[str]:
|
def username(self) -> Optional[str]:
|
||||||
return getattr(self._raw, "username", None)
|
return getattr(self._raw, "username", None)
|
||||||
|
|
||||||
def pack(self) -> Optional[PeerRef]:
|
@property
|
||||||
|
def ref(self) -> GroupRef | ChannelRef:
|
||||||
if isinstance(self._raw, (types.ChatEmpty, types.Chat, types.ChatForbidden)):
|
if isinstance(self._raw, (types.ChatEmpty, types.Chat, types.ChatForbidden)):
|
||||||
return PeerRef(ty=PackedType.CHAT, id=self._raw.id, access_hash=None)
|
return GroupRef(self._raw.id, None)
|
||||||
elif self._raw.access_hash is None:
|
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
return PeerRef(
|
return ChannelRef(self._raw.id, self._raw.access_hash)
|
||||||
ty=PackedType.MEGAGROUP,
|
|
||||||
id=self._raw.id,
|
@property
|
||||||
access_hash=self._raw.access_hash,
|
def _ref(self) -> GroupRef | ChannelRef:
|
||||||
)
|
return self.ref
|
||||||
|
|
||||||
# endregion Overrides
|
# endregion Overrides
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ....session import PeerRef
|
from ....session import ChannelRef, GroupRef, UserRef
|
||||||
|
|
||||||
|
|
||||||
class Peer(abc.ABC):
|
class Peer(abc.ABC):
|
||||||
|
@ -15,7 +15,7 @@ class Peer(abc.ABC):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
"""
|
"""
|
||||||
The chat's integer identifier.
|
The peer's integer identifier.
|
||||||
|
|
||||||
This identifier is always a positive number.
|
This identifier is always a positive number.
|
||||||
|
|
||||||
|
@ -39,20 +39,28 @@ class Peer(abc.ABC):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def username(self) -> Optional[str]:
|
def username(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
The primary *@username* of the chat.
|
The primary *@username* of the user, group or chat.
|
||||||
|
|
||||||
The returned string will *not* contain the at-sign ``@``.
|
The returned string will *not* contain the at-sign ``@``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def pack(self) -> Optional[PeerRef]:
|
def ref(self) -> UserRef | GroupRef | ChannelRef:
|
||||||
"""
|
"""
|
||||||
Pack the chat into a compact and reusable object.
|
The reusable reference to this user, group or channel.
|
||||||
|
|
||||||
This object can be easily serialized and saved to persistent storage.
|
This can be used to persist the reference to a database or disk,
|
||||||
Unlike resolving usernames, packed chats can be reused without costly calls.
|
or to create inline mentions.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:doc:`/concepts/chats`
|
:doc:`/concepts/peers`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ref(self) -> UserRef | GroupRef | ChannelRef:
|
||||||
|
"""
|
||||||
|
Private alias that also exists in refs to make conversion trivial.
|
||||||
|
"""
|
||||||
|
return self.ref
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Optional, Self
|
from typing import Optional, Self
|
||||||
|
|
||||||
from ....session import PackedType, PeerRef
|
from ....session import UserRef
|
||||||
from ....tl import abcs, types
|
from ....tl import abcs, types
|
||||||
from ..meta import NoPublicConstructor
|
from ..meta import NoPublicConstructor
|
||||||
from .peer import Peer
|
from .peer import Peer
|
||||||
|
@ -87,15 +87,13 @@ class User(Peer, metaclass=NoPublicConstructor):
|
||||||
def username(self) -> Optional[str]:
|
def username(self) -> Optional[str]:
|
||||||
return self._raw.username
|
return self._raw.username
|
||||||
|
|
||||||
def pack(self) -> Optional[PeerRef]:
|
@property
|
||||||
if self._raw.access_hash is None:
|
def ref(self) -> UserRef:
|
||||||
return None
|
return UserRef(self._raw.id, self._raw.access_hash)
|
||||||
else:
|
|
||||||
return PeerRef(
|
@property
|
||||||
ty=PackedType.BOT if self._raw.bot else PackedType.USER,
|
def _ref(self) -> UserRef:
|
||||||
id=self._raw.id,
|
return self.ref
|
||||||
access_hash=self._raw.access_hash,
|
|
||||||
)
|
|
||||||
|
|
||||||
# endregion Overrides
|
# endregion Overrides
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
from .chat import ChatHashCache, PackedType, PeerRef
|
from .chat import (
|
||||||
|
ChannelRef,
|
||||||
|
ChatHashCache,
|
||||||
|
GroupRef,
|
||||||
|
PeerAuth,
|
||||||
|
PeerIdentifier,
|
||||||
|
PeerRef,
|
||||||
|
UserRef,
|
||||||
|
)
|
||||||
from .message_box import (
|
from .message_box import (
|
||||||
BOT_CHANNEL_DIFF_LIMIT,
|
BOT_CHANNEL_DIFF_LIMIT,
|
||||||
NO_UPDATES_TIMEOUT,
|
NO_UPDATES_TIMEOUT,
|
||||||
|
@ -14,9 +22,13 @@ from .session import ChannelState, DataCenter, Session, UpdateState, User
|
||||||
from .storage import MemorySession, SqliteSession, Storage
|
from .storage import MemorySession, SqliteSession, Storage
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"ChannelRef",
|
||||||
"ChatHashCache",
|
"ChatHashCache",
|
||||||
|
"GroupRef",
|
||||||
|
"PeerAuth",
|
||||||
|
"PeerIdentifier",
|
||||||
"PeerRef",
|
"PeerRef",
|
||||||
"PackedType",
|
"UserRef",
|
||||||
"BOT_CHANNEL_DIFF_LIMIT",
|
"BOT_CHANNEL_DIFF_LIMIT",
|
||||||
"NO_UPDATES_TIMEOUT",
|
"NO_UPDATES_TIMEOUT",
|
||||||
"USER_CHANNEL_DIFF_LIMIT",
|
"USER_CHANNEL_DIFF_LIMIT",
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
from .hash_cache import ChatHashCache
|
from .hash_cache import ChatHashCache
|
||||||
from .peer_ref import PackedType, PeerRef
|
from .peer_ref import ChannelRef, GroupRef, PeerAuth, PeerIdentifier, PeerRef, UserRef
|
||||||
|
|
||||||
__all__ = ["ChatHashCache", "PeerRef", "PackedType"]
|
__all__ = [
|
||||||
|
"ChatHashCache",
|
||||||
|
"ChannelRef",
|
||||||
|
"GroupRef",
|
||||||
|
"PeerAuth",
|
||||||
|
"PeerIdentifier",
|
||||||
|
"PeerRef",
|
||||||
|
"UserRef",
|
||||||
|
]
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
from typing import Any, Optional, Sequence
|
from typing import Any, Optional, Sequence, Type, TypeAlias
|
||||||
|
|
||||||
from ...tl import abcs, types
|
from ...tl import abcs, types
|
||||||
from .peer_ref import PackedType, PeerRef
|
from .peer_ref import ChannelRef, GroupRef, PeerRef, UserRef
|
||||||
|
|
||||||
|
PeerRefType: TypeAlias = Type[UserRef] | Type[ChannelRef] | Type[GroupRef]
|
||||||
|
|
||||||
|
|
||||||
class ChatHashCache:
|
class ChatHashCache:
|
||||||
__slots__ = ("_hash_map", "_self_id", "_self_bot")
|
__slots__ = ("_hash_map", "_self_id", "_self_bot")
|
||||||
|
|
||||||
def __init__(self, self_user: Optional[tuple[int, bool]]):
|
def __init__(self, self_user: Optional[tuple[int, bool]]):
|
||||||
self._hash_map: dict[int, tuple[int, PackedType]] = {}
|
self._hash_map: dict[int, tuple[PeerRefType, int]] = {}
|
||||||
self._self_id = self_user[0] if self_user else None
|
self._self_id = self_user[0] if self_user else None
|
||||||
self._self_bot = self_user[1] if self_user else False
|
self._self_bot = self_user[1] if self_user else False
|
||||||
|
|
||||||
|
@ -21,15 +23,14 @@ class ChatHashCache:
|
||||||
def is_self_bot(self) -> bool:
|
def is_self_bot(self) -> bool:
|
||||||
return self._self_bot
|
return self._self_bot
|
||||||
|
|
||||||
def set_self_user(self, user: PeerRef) -> None:
|
def set_self_user(self, identifier: int, bot: bool) -> None:
|
||||||
assert user.ty in (PackedType.USER, PackedType.BOT)
|
self._self_id = identifier
|
||||||
self._self_bot = user.ty == PackedType.BOT
|
self._self_bot = bot
|
||||||
self._self_id = user.id
|
|
||||||
|
|
||||||
def get(self, id: int) -> Optional[PeerRef]:
|
def get(self, identifier: int) -> Optional[PeerRef]:
|
||||||
if (entry := self._hash_map.get(id)) is not None:
|
if (entry := self._hash_map.get(identifier)) is not None:
|
||||||
hash, ty = entry
|
cls, authorization = entry
|
||||||
return PeerRef(ty, id, hash)
|
return cls(identifier, authorization)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -38,8 +39,8 @@ class ChatHashCache:
|
||||||
self._self_id = None
|
self._self_id = None
|
||||||
self._self_bot = False
|
self._self_bot = False
|
||||||
|
|
||||||
def _has(self, id: int) -> bool:
|
def _has(self, identifier: int) -> bool:
|
||||||
return id in self._hash_map
|
return identifier in self._hash_map
|
||||||
|
|
||||||
def _has_peer(self, peer: abcs.Peer) -> bool:
|
def _has_peer(self, peer: abcs.Peer) -> bool:
|
||||||
if isinstance(peer, types.PeerUser):
|
if isinstance(peer, types.PeerUser):
|
||||||
|
@ -140,8 +141,7 @@ class ChatHashCache:
|
||||||
pass
|
pass
|
||||||
elif isinstance(user, types.User):
|
elif isinstance(user, types.User):
|
||||||
if not user.min and user.access_hash is not None:
|
if not user.min and user.access_hash is not None:
|
||||||
ty = PackedType.BOT if user.bot else PackedType.USER
|
self._hash_map[user.id] = (UserRef, user.access_hash)
|
||||||
self._hash_map[user.id] = (user.access_hash, ty)
|
|
||||||
else:
|
else:
|
||||||
success &= user.id in self._hash_map
|
success &= user.id in self._hash_map
|
||||||
else:
|
else:
|
||||||
|
@ -152,18 +152,11 @@ class ChatHashCache:
|
||||||
pass
|
pass
|
||||||
elif isinstance(chat, types.Channel):
|
elif isinstance(chat, types.Channel):
|
||||||
if not chat.min and chat.access_hash is not None:
|
if not chat.min and chat.access_hash is not None:
|
||||||
if chat.megagroup:
|
self._hash_map[chat.id] = (ChannelRef, chat.access_hash)
|
||||||
ty = PackedType.MEGAGROUP
|
|
||||||
elif chat.gigagroup:
|
|
||||||
ty = PackedType.GIGAGROUP
|
|
||||||
else:
|
|
||||||
ty = PackedType.BROADCAST
|
|
||||||
self._hash_map[chat.id] = (chat.access_hash, ty)
|
|
||||||
else:
|
else:
|
||||||
success &= chat.id in self._hash_map
|
success &= chat.id in self._hash_map
|
||||||
elif isinstance(chat, types.ChannelForbidden):
|
elif isinstance(chat, types.ChannelForbidden):
|
||||||
ty = PackedType.MEGAGROUP if chat.megagroup else PackedType.BROADCAST
|
self._hash_map[chat.id] = (ChannelRef, chat.access_hash)
|
||||||
self._hash_map[chat.id] = (chat.access_hash, ty)
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unexpected case")
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
|
|
@ -1,150 +1,254 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
import struct
|
import struct
|
||||||
from enum import IntFlag
|
from typing import Optional, Self, TypeAlias
|
||||||
from typing import Optional, Self
|
|
||||||
|
|
||||||
from ...tl import abcs, types
|
from ...tl import abcs, types
|
||||||
|
|
||||||
|
PeerIdentifier: TypeAlias = int
|
||||||
|
PeerAuth: TypeAlias = Optional[int]
|
||||||
|
|
||||||
class PackedType(IntFlag):
|
USER_PREFIX = "u."
|
||||||
"""
|
GROUP_PREFIX = "g."
|
||||||
The type of a :class:`PeerRef`.
|
CHANNEL_PREFIX = "c."
|
||||||
|
|
||||||
|
|
||||||
|
class PeerRef(abc.ABC):
|
||||||
"""
|
"""
|
||||||
|
A reference to a :term:`peer`.
|
||||||
|
|
||||||
# bits: zero, has-access-hash, channel, broadcast, group, chat, user, bot
|
References can be used to interact with any method that expects a peer,
|
||||||
USER = 0b0000_0010
|
without the need to fetch or resolve the entire peer beforehand.
|
||||||
BOT = 0b0000_0011
|
|
||||||
CHAT = 0b0000_0100
|
|
||||||
MEGAGROUP = 0b0010_1000
|
|
||||||
BROADCAST = 0b0011_0000
|
|
||||||
GIGAGROUP = 0b0011_1000
|
|
||||||
|
|
||||||
|
A reference consists of both an identifier and the authorization to access the peer.
|
||||||
|
The proof of authorization is represented by Telegram's access hash witness.
|
||||||
|
|
||||||
class PeerRef:
|
You can access the :attr:`telethon.types.Peer.ref` attribute on :class:`~telethon.types.User`,
|
||||||
"""
|
|
||||||
A compact representation of a :term:`chat`.
|
|
||||||
|
|
||||||
You can reuse it as many times as you want.
|
|
||||||
|
|
||||||
You can call ``chat.pack()`` on :class:`~telethon.types.User`,
|
|
||||||
:class:`~telethon.types.Group` or :class:`~telethon.types.Channel` to obtain it.
|
:class:`~telethon.types.Group` or :class:`~telethon.types.Channel` to obtain it.
|
||||||
|
|
||||||
|
Not all references are always valid in all contexts.
|
||||||
|
Under certain conditions, it is possible for a reference without an authorization to be usable,
|
||||||
|
and for a reference with an authorization to not be usable everywhere.
|
||||||
|
The exact rules are defined by Telegram and could change any time.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:doc:`/concepts/chats`
|
:doc:`/concepts/peers`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("ty", "id", "access_hash")
|
__slots__ = ("identifier", "authorization")
|
||||||
|
|
||||||
def __init__(self, ty: PackedType, id: int, access_hash: Optional[int]) -> None:
|
def __init__(
|
||||||
self.ty = ty
|
self, identifier: PeerIdentifier, authorization: PeerAuth = None
|
||||||
self.id = id
|
) -> None:
|
||||||
self.access_hash = access_hash
|
assert (
|
||||||
|
identifier >= 0
|
||||||
def __bytes__(self) -> bytes:
|
), "PeerRef identifiers must be positive; see the documentation for Peers"
|
||||||
return struct.pack(
|
self.identifier = identifier
|
||||||
"<Bqq",
|
self.authorization = authorization
|
||||||
self.ty.value | (0 if self.access_hash is None else 0b0100_0000),
|
|
||||||
self.id,
|
|
||||||
self.access_hash or 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, data: bytes) -> Self:
|
def from_str(cls, string: str, /) -> UserRef | GroupRef | ChannelRef:
|
||||||
ty_byte, id, access_hash = struct.unpack("<Bqq", data)
|
|
||||||
has_hash = (ty_byte & 0b0100_0000) != 0
|
|
||||||
ty = PackedType(ty_byte & 0b0011_1111)
|
|
||||||
return cls(ty, id, access_hash if has_hash else None)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hex(self) -> str:
|
|
||||||
"""
|
"""
|
||||||
Convenience property to convert to bytes and represent them as hexadecimal numbers:
|
Create a reference back from its string representation:
|
||||||
|
|
||||||
.. code-block::
|
:param string:
|
||||||
|
The :class:`str` representation of the :class:`PeerRef`.
|
||||||
|
|
||||||
assert packed.hex == bytes(packed).hex()
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ref: PeerRef = ...
|
||||||
|
assert PeerRef.from_str(str(ref)) == ref
|
||||||
"""
|
"""
|
||||||
return bytes(self).hex()
|
if match := re.match(r"(\w\.)(\d+)\.([^.]+)", string):
|
||||||
|
prefix, iden, auth = match.groups()
|
||||||
|
|
||||||
|
identifier = int(iden)
|
||||||
|
|
||||||
|
if auth == "0":
|
||||||
|
authorization: Optional[int] = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
(authorization,) = struct.unpack(
|
||||||
|
"!q", base64.urlsafe_b64decode(auth.encode("ascii") + b"=")
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(f"invalid PeerRef string: {string!r}")
|
||||||
|
|
||||||
|
if prefix == USER_PREFIX:
|
||||||
|
return UserRef(identifier, authorization)
|
||||||
|
elif prefix == GROUP_PREFIX:
|
||||||
|
return GroupRef(identifier, authorization)
|
||||||
|
elif prefix == CHANNEL_PREFIX:
|
||||||
|
return ChannelRef(identifier, authorization)
|
||||||
|
|
||||||
|
raise ValueError(f"invalid PeerRef string: {string!r}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hex(cls, hex: str) -> Self:
|
def _empty_from_peer(cls, peer: abcs.Peer) -> UserRef | GroupRef | ChannelRef:
|
||||||
"""
|
if isinstance(peer, types.PeerUser):
|
||||||
Convenience method to convert hexadecimal numbers into bytes then passed to :meth:`from_bytes`:
|
return UserRef(peer.user_id, None)
|
||||||
|
elif isinstance(peer, types.PeerChat):
|
||||||
:param hex:
|
return GroupRef(peer.chat_id, None)
|
||||||
Hexadecimal numbers to convert from.
|
elif isinstance(peer, types.PeerChannel):
|
||||||
|
return ChannelRef(peer.channel_id, None)
|
||||||
.. code-block::
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
assert PackedChat.from_hex(packed.hex) == packed
|
|
||||||
"""
|
|
||||||
return cls.from_bytes(bytes.fromhex(hex))
|
|
||||||
|
|
||||||
def is_user(self) -> bool:
|
|
||||||
return self.ty in (PackedType.USER, PackedType.BOT)
|
|
||||||
|
|
||||||
def is_chat(self) -> bool:
|
|
||||||
return self.ty in (PackedType.CHAT,)
|
|
||||||
|
|
||||||
def is_channel(self) -> bool:
|
|
||||||
return self.ty in (
|
|
||||||
PackedType.MEGAGROUP,
|
|
||||||
PackedType.BROADCAST,
|
|
||||||
PackedType.GIGAGROUP,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
def _to_peer(self) -> abcs.Peer:
|
def _to_peer(self) -> abcs.Peer:
|
||||||
if self.is_user():
|
pass
|
||||||
return types.PeerUser(user_id=self.id)
|
|
||||||
elif self.is_chat():
|
|
||||||
return types.PeerChat(chat_id=self.id)
|
|
||||||
elif self.is_channel():
|
|
||||||
return types.PeerChannel(channel_id=self.id)
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
def _to_input_peer(self) -> abcs.InputPeer:
|
def _to_input_peer(self) -> abcs.InputPeer:
|
||||||
if self.is_user():
|
pass
|
||||||
return types.InputPeerUser(
|
|
||||||
user_id=self.id, access_hash=self.access_hash or 0
|
|
||||||
)
|
|
||||||
elif self.is_chat():
|
|
||||||
return types.InputPeerChat(chat_id=self.id)
|
|
||||||
elif self.is_channel():
|
|
||||||
return types.InputPeerChannel(
|
|
||||||
channel_id=self.id, access_hash=self.access_hash or 0
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
||||||
def _to_input_user(self) -> types.InputUser:
|
@abc.abstractmethod
|
||||||
if self.is_user():
|
def __str__(self) -> str:
|
||||||
return types.InputUser(user_id=self.id, access_hash=self.access_hash or 0)
|
"""
|
||||||
else:
|
Format the reference into a :class:`str`.
|
||||||
raise TypeError("chat is not a user")
|
|
||||||
|
|
||||||
def _to_chat_id(self) -> int:
|
.. seealso::
|
||||||
if self.is_chat():
|
|
||||||
return self.id
|
|
||||||
else:
|
|
||||||
raise TypeError("chat is not a group")
|
|
||||||
|
|
||||||
def _to_input_channel(self) -> types.InputChannel:
|
:doc:`/concepts/messages`, to learn how this can be used to format inline mentions in messages.
|
||||||
if self.is_channel():
|
"""
|
||||||
return types.InputChannel(
|
|
||||||
channel_id=self.id, access_hash=self.access_hash or 0
|
def __repr__(self) -> str:
|
||||||
|
"""
|
||||||
|
Format the reference in a way that's easy to debug.
|
||||||
|
"""
|
||||||
|
return f"{self.__class__.__name__}({self.identifier}, {self.authorization})"
|
||||||
|
|
||||||
|
def _encode_str(self) -> str:
|
||||||
|
if self.authorization is None:
|
||||||
|
auth = "0"
|
||||||
|
else:
|
||||||
|
auth = (
|
||||||
|
base64.urlsafe_b64encode(struct.pack("!q", self.authorization))
|
||||||
|
.decode("ascii")
|
||||||
|
.rstrip("=")
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
raise TypeError("chat is not a channel")
|
return f"{self.identifier}.{auth}"
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, self.__class__):
|
if not isinstance(other, self.__class__):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return (
|
return (
|
||||||
self.ty == other.ty
|
self.identifier == other.identifier
|
||||||
and self.id == other.id
|
and self.authorization == other.authorization
|
||||||
and self.access_hash == other.access_hash
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ref(self) -> UserRef | GroupRef | ChannelRef:
|
||||||
|
assert isinstance(self, (UserRef, GroupRef, ChannelRef))
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class UserRef(PeerRef):
|
||||||
|
"""
|
||||||
|
A user reference.
|
||||||
|
|
||||||
|
This includes both user accounts and bot accounts, and corresponds to a bare Telegram :tl:`user`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, string: str, /) -> Self:
|
||||||
|
ref = super().from_str(string)
|
||||||
|
if not isinstance(ref, cls):
|
||||||
|
raise TypeError("PeerRef string does not belong to UserRef")
|
||||||
|
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def _to_peer(self) -> abcs.Peer:
|
||||||
|
return types.PeerUser(user_id=self.identifier)
|
||||||
|
|
||||||
|
def _to_input_peer(self) -> abcs.InputPeer:
|
||||||
|
return types.InputPeerUser(
|
||||||
|
user_id=self.identifier, access_hash=self.authorization or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def _to_input_user(self) -> types.InputUser:
|
||||||
|
return types.InputUser(
|
||||||
|
user_id=self.identifier, access_hash=self.authorization or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"PackedChat.{self.ty.name}({self.id})"
|
return f"{USER_PREFIX}{self._encode_str()}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ref(self) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class GroupRef(PeerRef):
|
||||||
|
"""
|
||||||
|
A group reference.
|
||||||
|
|
||||||
|
This only includes small group chats, and corresponds to a bare Telegram :tl:`chat`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, string: str, /) -> Self:
|
||||||
|
ref = super().from_str(string)
|
||||||
|
if not isinstance(ref, cls):
|
||||||
|
raise TypeError("PeerRef string does not belong to GroupRef")
|
||||||
|
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def _to_peer(self) -> abcs.Peer:
|
||||||
|
return types.PeerChat(chat_id=self.identifier)
|
||||||
|
|
||||||
|
def _to_input_peer(self) -> abcs.InputPeer:
|
||||||
|
return types.InputPeerChat(chat_id=self.identifier)
|
||||||
|
|
||||||
|
def _to_input_chat(self) -> int:
|
||||||
|
return self.identifier
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{GROUP_PREFIX}{self._encode_str()}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ref(self) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelRef(PeerRef):
|
||||||
|
"""
|
||||||
|
A channel reference.
|
||||||
|
|
||||||
|
This includes broadcast channels, megagroups and gigagroups, and corresponds to a bare Telegram :tl:`channel`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, string: str, /) -> Self:
|
||||||
|
ref = super().from_str(string)
|
||||||
|
if not isinstance(ref, cls):
|
||||||
|
raise TypeError("PeerRef string does not belong to ChannelRef")
|
||||||
|
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def _to_peer(self) -> abcs.Peer:
|
||||||
|
return types.PeerChannel(channel_id=self.identifier)
|
||||||
|
|
||||||
|
def _to_input_peer(self) -> abcs.InputPeer:
|
||||||
|
return types.InputPeerChannel(
|
||||||
|
channel_id=self.identifier, access_hash=self.authorization or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def _to_input_channel(self) -> types.InputChannel:
|
||||||
|
return types.InputChannel(
|
||||||
|
channel_id=self.identifier, access_hash=self.authorization or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{CHANNEL_PREFIX}{self._encode_str()}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ref(self) -> Self:
|
||||||
|
return self
|
||||||
|
|
|
@ -546,12 +546,12 @@ class MessageBox:
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
packed = chat_hashes.get(id)
|
ref = chat_hashes.get(id)
|
||||||
if packed:
|
if ref:
|
||||||
assert packed.access_hash is not None
|
assert ref.authorization is not None
|
||||||
channel = types.InputChannel(
|
channel = types.InputChannel(
|
||||||
channel_id=packed.id,
|
channel_id=ref.identifier,
|
||||||
access_hash=packed.access_hash,
|
access_hash=ref.authorization,
|
||||||
)
|
)
|
||||||
if state := self.map.get(entry):
|
if state := self.map.get(entry):
|
||||||
gd = functions.updates.get_channel_difference(
|
gd = functions.updates.get_channel_difference(
|
||||||
|
|
|
@ -23,7 +23,7 @@ from .._impl.client.types import (
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
from .._impl.client.types.buttons import Button, InlineButton
|
from .._impl.client.types.buttons import Button, InlineButton
|
||||||
from .._impl.session import PackedType, PeerRef
|
from .._impl.session import ChannelRef, GroupRef, PeerRef, UserRef
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AdminRight",
|
"AdminRight",
|
||||||
|
@ -46,6 +46,8 @@ __all__ = [
|
||||||
"User",
|
"User",
|
||||||
"Button",
|
"Button",
|
||||||
"InlineButton",
|
"InlineButton",
|
||||||
|
"ChannelRef",
|
||||||
|
"GroupRef",
|
||||||
"PeerRef",
|
"PeerRef",
|
||||||
"PackedType",
|
"UserRef",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from telethon._impl.session import PackedType, PeerRef
|
|
||||||
|
|
||||||
|
|
||||||
def test_hash_optional() -> None:
|
|
||||||
for ty in PackedType:
|
|
||||||
pc = PeerRef(ty, 123, 456789)
|
|
||||||
assert PeerRef.from_bytes(bytes(pc)) == pc
|
|
||||||
|
|
||||||
pc = PeerRef(ty, 987, None)
|
|
||||||
assert PeerRef.from_bytes(bytes(pc)) == pc
|
|
40
client/tests/peer_ref_test.py
Normal file
40
client/tests/peer_ref_test.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from pytest import raises
|
||||||
|
from telethon._impl.session import ChannelRef, GroupRef, PeerRef, UserRef
|
||||||
|
|
||||||
|
USER = UserRef(12, 34)
|
||||||
|
GROUP = GroupRef(5, None)
|
||||||
|
CHANNEL = ChannelRef(67, 89)
|
||||||
|
|
||||||
|
|
||||||
|
def test_peer_ref() -> None:
|
||||||
|
assert PeerRef.from_str(str(USER)) == USER
|
||||||
|
assert PeerRef.from_str(str(GROUP)) == GROUP
|
||||||
|
assert PeerRef.from_str(str(CHANNEL)) == CHANNEL
|
||||||
|
|
||||||
|
assert inspect.isabstract(PeerRef)
|
||||||
|
|
||||||
|
with raises(ValueError):
|
||||||
|
PeerRef.from_str("invalid")
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_ref() -> None:
|
||||||
|
assert UserRef.from_str(str(USER)) == USER
|
||||||
|
|
||||||
|
with raises(TypeError):
|
||||||
|
UserRef.from_str(str(GROUP))
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_ref() -> None:
|
||||||
|
assert GroupRef.from_str(str(GROUP)) == GROUP
|
||||||
|
|
||||||
|
with raises(TypeError):
|
||||||
|
GroupRef.from_str(str(CHANNEL))
|
||||||
|
|
||||||
|
|
||||||
|
def test_channel_ref() -> None:
|
||||||
|
assert ChannelRef.from_str(str(CHANNEL)) == CHANNEL
|
||||||
|
|
||||||
|
with raises(TypeError):
|
||||||
|
ChannelRef.from_str(str(USER))
|
|
@ -28,7 +28,7 @@ class FunctionMethodsVisitor(ast.NodeVisitor):
|
||||||
self._try_add_def(node)
|
self._try_add_def(node)
|
||||||
|
|
||||||
def _try_add_def(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
def _try_add_def(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
||||||
match node.args.args:
|
match node.args.posonlyargs + node.args.args:
|
||||||
case [ast.arg(arg="self", annotation=ast.Name(id="Client")), *_]:
|
case [ast.arg(arg="self", annotation=ast.Name(id="Client")), *_]:
|
||||||
self.methods.append(node)
|
self.methods.append(node)
|
||||||
case _:
|
case _:
|
||||||
|
@ -94,14 +94,20 @@ def main() -> None:
|
||||||
|
|
||||||
call: ast.AST = ast.Call(
|
call: ast.AST = ast.Call(
|
||||||
func=ast.Name(id=function.name, ctx=ast.Load()),
|
func=ast.Name(id=function.name, ctx=ast.Load()),
|
||||||
args=[ast.Name(id=a.arg, ctx=ast.Load()) for a in function.args.args],
|
args=[
|
||||||
|
ast.Name(id=a.arg, ctx=ast.Load())
|
||||||
|
for a in function.args.posonlyargs + function.args.args
|
||||||
|
],
|
||||||
keywords=[
|
keywords=[
|
||||||
ast.keyword(arg=a.arg, value=ast.Name(id=a.arg, ctx=ast.Load()))
|
ast.keyword(arg=a.arg, value=ast.Name(id=a.arg, ctx=ast.Load()))
|
||||||
for a in function.args.kwonlyargs
|
for a in function.args.kwonlyargs
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
function.args.args[0].annotation = None
|
if function.args.posonlyargs:
|
||||||
|
function.args.posonlyargs[0].annotation = None
|
||||||
|
else:
|
||||||
|
function.args.args[0].annotation = None
|
||||||
|
|
||||||
if isinstance(function, ast.AsyncFunctionDef):
|
if isinstance(function, ast.AsyncFunctionDef):
|
||||||
call = ast.Await(value=call)
|
call = ast.Await(value=call)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user