mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 01:16:35 +03:00
Continue implementation
This commit is contained in:
parent
4cc6ecc39b
commit
f9435aa1f6
|
@ -5,6 +5,8 @@ build:
|
|||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
apt_packages:
|
||||
- graphviz
|
||||
|
||||
sphinx:
|
||||
configuration: client/doc/conf.py
|
||||
|
|
|
@ -318,7 +318,7 @@ In Telethon:
|
|||
.. code-block:: python
|
||||
|
||||
from telethon import Client, events
|
||||
from telethon.events.filters import Any, Command, TextOnly
|
||||
from telethon.events.filters import Any, Command, Media
|
||||
bot = Client('bot', api_id, api_hash)
|
||||
|
||||
# Handle '/start' and '/help'
|
||||
|
@ -329,8 +329,8 @@ In Telethon:
|
|||
I am here to echo your kind words back to you. Just say anything nice and I'll say the exact same thing to you!\
|
||||
""")
|
||||
|
||||
# Handle all other messages with only 'text'
|
||||
@bot.on(events.NewMessage, TextOnly())
|
||||
# Handle all other messages without media (negating the filter using ~)
|
||||
@bot.on(events.NewMessage, ~Media())
|
||||
async def echo_message(message: NewMessage):
|
||||
await message.reply(message.text)
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ Note that `CommonMark's markdown <https://commonmark.org/>`_ is not fully compat
|
|||
```
|
||||
|
||||
HTML is also not fully compatible with :term:`HTTP Bot API`'s
|
||||
`MarkdownV2 style <https://core.telegram.org/bots/api#markdownv2-style>`_,
|
||||
`HTML style <https://core.telegram.org/bots/api#html-style>`_,
|
||||
and instead favours more standard `HTML elements <https://developer.mozilla.org/en-US/docs/Web/HTML/Element>`_:
|
||||
|
||||
* ``strong`` and ``b`` for **bold**.
|
||||
|
|
|
@ -118,6 +118,7 @@ from .updates import (
|
|||
set_handler_filter,
|
||||
)
|
||||
from .users import (
|
||||
get_chats,
|
||||
get_contacts,
|
||||
get_me,
|
||||
input_to_peer,
|
||||
|
@ -683,6 +684,40 @@ class Client:
|
|||
"""
|
||||
return get_admin_log(self, chat)
|
||||
|
||||
async def get_chats(self, chats: Sequence[ChatLike]) -> List[Chat]:
|
||||
"""
|
||||
Get the latest basic information about the given chats.
|
||||
|
||||
This method is most commonly used to turn one or more :class:`~types.PackedChat` into the original :class:`~types.Chat`.
|
||||
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]:
|
||||
"""
|
||||
Get the users in your contact list.
|
||||
|
@ -1200,28 +1235,6 @@ class Client:
|
|||
"""
|
||||
return await request_login_code(self, phone)
|
||||
|
||||
async def resolve_to_packed(self, chat: ChatLike) -> PackedChat:
|
||||
"""
|
||||
Resolve a :term:`chat` and return a compact, reusable reference to it.
|
||||
|
||||
:param chat:
|
||||
The :term:`chat` to resolve.
|
||||
|
||||
:return: An efficient, reusable version of the input.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
friend = await client.resolve_to_packed('@cat')
|
||||
# Now you can use `friend` to get or send messages, files...
|
||||
|
||||
.. seealso::
|
||||
|
||||
In-depth explanation for :doc:`/concepts/chats`.
|
||||
"""
|
||||
return await resolve_to_packed(self, chat)
|
||||
|
||||
async def resolve_username(self, username: str) -> Chat:
|
||||
"""
|
||||
Resolve a username into a :term:`chat`.
|
||||
|
|
|
@ -154,5 +154,5 @@ async def dispatch_next(client: Client) -> None:
|
|||
for handler, filter in handlers:
|
||||
if not filter or filter(event):
|
||||
ret = await handler(event)
|
||||
if ret is Continue or client._shortcircuit_handlers:
|
||||
if ret is not Continue or client._shortcircuit_handlers:
|
||||
return
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, List, Optional, Sequence
|
||||
|
||||
from ...mtproto import RpcError
|
||||
from ...session import PackedChat, PackedType
|
||||
|
@ -13,6 +13,7 @@ from ..types import (
|
|||
Group,
|
||||
User,
|
||||
build_chat_map,
|
||||
expand_peer,
|
||||
peer_id,
|
||||
)
|
||||
|
||||
|
@ -73,12 +74,51 @@ async def resolve_username(self: Client, username: str) -> Chat:
|
|||
)
|
||||
|
||||
|
||||
async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
||||
async def get_chats(self: Client, chats: Sequence[ChatLike]) -> List[Chat]:
|
||||
packed_chats: List[PackedChat] = []
|
||||
input_users: List[types.InputUser] = []
|
||||
input_chats: List[int] = []
|
||||
input_channels: List[types.InputChannel] = []
|
||||
|
||||
for chat in chats:
|
||||
packed = await resolve_to_packed(self, chat)
|
||||
if packed.is_user():
|
||||
input_users.append(packed._to_input_user())
|
||||
elif packed.is_chat():
|
||||
input_chats.append(packed.id)
|
||||
else:
|
||||
input_channels.append(packed._to_input_channel())
|
||||
|
||||
users = (
|
||||
(await self(functions.users.get_users(id=input_users))) if input_users else []
|
||||
)
|
||||
groups = (
|
||||
(await self(functions.messages.get_chats(id=input_chats)))
|
||||
if input_chats
|
||||
else []
|
||||
)
|
||||
assert isinstance(groups, types.messages.Chats)
|
||||
channels = (
|
||||
(await self(functions.channels.get_channels(id=input_channels)))
|
||||
if input_channels
|
||||
else []
|
||||
)
|
||||
assert isinstance(channels, types.messages.Chats)
|
||||
|
||||
chat_map = build_chat_map(self, users, groups.chats + channels.chats)
|
||||
return [
|
||||
chat_map.get(chat.id)
|
||||
or expand_peer(self, chat._to_peer(), broadcast=chat.ty == PackedType.BROADCAST)
|
||||
for chat in packed_chats
|
||||
]
|
||||
|
||||
|
||||
async def resolve_to_packed(client: Client, chat: ChatLike) -> PackedChat:
|
||||
if isinstance(chat, PackedChat):
|
||||
return chat
|
||||
|
||||
if isinstance(chat, (User, Group, Channel)):
|
||||
packed = chat.pack() or self._chat_hashes.get(chat.id)
|
||||
packed = chat.pack() or client._chat_hashes.get(chat.id)
|
||||
if packed is not None:
|
||||
return packed
|
||||
|
||||
|
@ -96,11 +136,11 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
|||
if isinstance(chat, types.InputPeerEmpty):
|
||||
raise ValueError("Cannot resolve chat")
|
||||
elif isinstance(chat, types.InputPeerSelf):
|
||||
if not self._session.user:
|
||||
if not client._session.user:
|
||||
raise ValueError("Cannot resolve chat")
|
||||
return PackedChat(
|
||||
ty=PackedType.BOT if self._session.user.bot else PackedType.USER,
|
||||
id=self._chat_hashes.self_id,
|
||||
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):
|
||||
|
@ -130,9 +170,9 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
|||
|
||||
if isinstance(chat, str):
|
||||
if chat.startswith("+"):
|
||||
resolved = await resolve_phone(self, chat)
|
||||
resolved = await resolve_phone(client, chat)
|
||||
elif chat == "me":
|
||||
if me := self._session.user:
|
||||
if me := client._session.user:
|
||||
return PackedChat(
|
||||
ty=PackedType.BOT if me.bot else PackedType.USER,
|
||||
id=me.id,
|
||||
|
@ -141,13 +181,13 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
|||
else:
|
||||
resolved = None
|
||||
else:
|
||||
resolved = await resolve_username(self, username=chat)
|
||||
resolved = await resolve_username(client, username=chat)
|
||||
|
||||
if resolved and (packed := resolved.pack()) is not None:
|
||||
return packed
|
||||
|
||||
if isinstance(chat, int):
|
||||
packed = self._chat_hashes.get(chat)
|
||||
packed = client._chat_hashes.get(chat)
|
||||
if packed is None:
|
||||
raise ValueError("Cannot resolve chat")
|
||||
return packed
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from .combinators import All, Any, Filter, Not
|
||||
from .common import Chats, Senders
|
||||
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text, TextOnly
|
||||
from .common import Chats, ChatType, Senders
|
||||
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text
|
||||
|
||||
__all__ = [
|
||||
"All",
|
||||
|
@ -8,6 +8,7 @@ __all__ = [
|
|||
"Filter",
|
||||
"Not",
|
||||
"Chats",
|
||||
"ChatType",
|
||||
"Senders",
|
||||
"Command",
|
||||
"Forward",
|
||||
|
@ -16,5 +17,4 @@ __all__ = [
|
|||
"Outgoing",
|
||||
"Reply",
|
||||
"Text",
|
||||
"TextOnly",
|
||||
]
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import abc
|
||||
import typing
|
||||
from typing import Callable, Tuple
|
||||
from typing import Callable, Tuple, TypeAlias
|
||||
|
||||
from ..event import Event
|
||||
|
||||
Filter = Callable[[Event], bool]
|
||||
Filter: TypeAlias = Callable[[Event], bool]
|
||||
|
||||
|
||||
class Combinable(abc.ABC):
|
||||
|
@ -48,11 +48,12 @@ class Any(Combinable):
|
|||
"""
|
||||
Combine multiple filters, returning :data:`True` if any of the filters pass.
|
||||
|
||||
When either filter is *combinable*, you can use the ``|`` operator instead.
|
||||
When either filter is :class:`~telethon._impl.client.events.filters.combinators.Combinable`,
|
||||
you can use the ``|`` operator instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import Any, Command
|
||||
from telethon.events.filters import Any, Command
|
||||
|
||||
@bot.on(events.NewMessage, Any(Command('/start'), Command('/help')))
|
||||
async def handler(event): ...
|
||||
|
@ -87,11 +88,12 @@ class All(Combinable):
|
|||
"""
|
||||
Combine multiple filters, returning :data:`True` if all of the filters pass.
|
||||
|
||||
When either filter is *combinable*, you can use the ``&`` operator instead.
|
||||
When either filter is :class:`~telethon._impl.client.events.filters.combinators.Combinable`,
|
||||
you can use the ``&`` operator instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import All, Command, Text
|
||||
from telethon.events.filters import All, Command, Text
|
||||
|
||||
@bot.on(events.NewMessage, All(Command('/start'), Text(r'\bdata:\w+')))
|
||||
async def handler(event): ...
|
||||
|
@ -126,11 +128,12 @@ class Not(Combinable):
|
|||
"""
|
||||
Negate the output of a single filter, returning :data:`True` if the nested filter does *not* pass.
|
||||
|
||||
When the filter is *combinable*, you can use the ``~`` operator instead.
|
||||
When the filter is :class:`~telethon._impl.client.events.filters.combinators.Combinable`,
|
||||
you can use the ``~`` operator instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import All, Command
|
||||
from telethon.events.filters import All, Command
|
||||
|
||||
@bot.on(events.NewMessage, Not(Command('/start'))
|
||||
async def handler(event): ...
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Literal, Sequence, Tuple, Type, Union
|
||||
from typing import Sequence, Set, Type, Union
|
||||
|
||||
from ...types import Channel, Group, User
|
||||
from ..event import Event
|
||||
|
@ -8,20 +8,21 @@ from .combinators import Combinable
|
|||
class Chats(Combinable):
|
||||
"""
|
||||
Filter by ``event.chat.id``, if the event has a chat.
|
||||
|
||||
:param chat_ids: The chat identifiers to filter on.
|
||||
"""
|
||||
|
||||
__slots__ = ("_chats",)
|
||||
|
||||
def __init__(self, chat_id: Union[int, Sequence[int]], *chat_ids: int) -> None:
|
||||
self._chats = {chat_id} if isinstance(chat_id, int) else set(chat_id)
|
||||
self._chats.update(chat_ids)
|
||||
def __init__(self, chat_ids: Sequence[int]) -> None:
|
||||
self._chats = set(chat_ids)
|
||||
|
||||
@property
|
||||
def chat_ids(self) -> Tuple[int, ...]:
|
||||
def chat_ids(self) -> Set[int]:
|
||||
"""
|
||||
The chat identifiers this filter is filtering on.
|
||||
A copy of the set of chat identifiers this filter is filtering on.
|
||||
"""
|
||||
return tuple(self._chats)
|
||||
return set(self._chats)
|
||||
|
||||
def __call__(self, event: Event) -> bool:
|
||||
chat = getattr(event, "chat", None)
|
||||
|
@ -32,20 +33,21 @@ class Chats(Combinable):
|
|||
class Senders(Combinable):
|
||||
"""
|
||||
Filter by ``event.sender.id``, if the event has a sender.
|
||||
|
||||
:param sender_ids: The sender identifiers to filter on.
|
||||
"""
|
||||
|
||||
__slots__ = ("_senders",)
|
||||
|
||||
def __init__(self, sender_id: Union[int, Sequence[int]], *sender_ids: int) -> None:
|
||||
self._senders = {sender_id} if isinstance(sender_id, int) else set(sender_id)
|
||||
self._senders.update(sender_ids)
|
||||
def __init__(self, sender_ids: Sequence[int]) -> None:
|
||||
self._senders = set(sender_ids)
|
||||
|
||||
@property
|
||||
def sender_ids(self) -> Tuple[int, ...]:
|
||||
def sender_ids(self) -> Set[int]:
|
||||
"""
|
||||
The sender identifiers this filter is filtering on.
|
||||
A copy of the set of sender identifiers this filter is filtering on.
|
||||
"""
|
||||
return tuple(self._senders)
|
||||
return set(self._senders)
|
||||
|
||||
def __call__(self, event: Event) -> bool:
|
||||
sender = getattr(event, "sender", None)
|
||||
|
@ -55,37 +57,38 @@ class Senders(Combinable):
|
|||
|
||||
class ChatType(Combinable):
|
||||
"""
|
||||
Filter by chat type, either ``'user'``, ``'group'`` or ``'broadcast'``.
|
||||
Filter by chat type using :func:`isinstance`.
|
||||
|
||||
:param type: The chat type to filter on.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
from telethon.events import filters
|
||||
from telethon.types import Channel
|
||||
|
||||
# Handle only messages from broadcast channels
|
||||
@client.on(events.NewMessage, filters.ChatType(Channel))
|
||||
async def handler(event):
|
||||
print(event.text)
|
||||
"""
|
||||
|
||||
__slots__ = ("_type",)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type: Union[Literal["user"], Literal["group"], Literal["broadcast"]],
|
||||
type: Type[Union[User, Group, Channel]],
|
||||
) -> None:
|
||||
if type == "user":
|
||||
self._type: Union[Type[User], Type[Group], Type[Channel]] = User
|
||||
elif type == "group":
|
||||
self._type = Group
|
||||
elif type == "broadcast":
|
||||
self._type = Channel
|
||||
else:
|
||||
raise TypeError(f"unrecognised chat type: {type}")
|
||||
self._type = type
|
||||
|
||||
@property
|
||||
def type(self) -> Union[Literal["user"], Literal["group"], Literal["broadcast"]]:
|
||||
def type(self) -> Type[Union[User, Group, Channel]]:
|
||||
"""
|
||||
The chat type this filter is filtering on.
|
||||
"""
|
||||
if self._type == User:
|
||||
return "user"
|
||||
elif self._type == Group:
|
||||
return "group"
|
||||
elif self._type == Channel:
|
||||
return "broadcast"
|
||||
else:
|
||||
raise RuntimeError("unexpected case")
|
||||
return self._type
|
||||
|
||||
def __call__(self, event: Event) -> bool:
|
||||
sender = getattr(event, "chat", None)
|
||||
|
|
|
@ -21,7 +21,9 @@ class Text(Combinable):
|
|||
you need to manually perform the check inside the handler instead.
|
||||
|
||||
Note that the caption text in messages with media is also searched.
|
||||
If you want to filter based on media, use :class:`TextOnly` or :class:`Media`.
|
||||
If you want to filter based on media, use :class:`Media`.
|
||||
|
||||
:param regexp: The regular expression to :func:`re.search` with on the text.
|
||||
"""
|
||||
|
||||
__slots__ = ("_pattern",)
|
||||
|
@ -43,6 +45,8 @@ class Command(Combinable):
|
|||
filter ``Command('/help')`` will match both ``"/help"`` and ``"/help@bot"``, but not
|
||||
``"/list"`` or ``"/help@other"``.
|
||||
|
||||
:param command: The command to match on.
|
||||
|
||||
.. note::
|
||||
|
||||
The leading forward-slash is not automatically added!
|
||||
|
@ -58,7 +62,7 @@ class Command(Combinable):
|
|||
__slots__ = ("_cmd", "_username")
|
||||
|
||||
def __init__(self, command: str) -> None:
|
||||
if re.match(r"\s", command):
|
||||
if re.search(r"\s", command):
|
||||
raise ValueError(f"command cannot contain spaces: {command}")
|
||||
|
||||
self._cmd = command
|
||||
|
@ -87,11 +91,7 @@ class Command(Combinable):
|
|||
|
||||
class Incoming(Combinable):
|
||||
"""
|
||||
Filter by ``event.incoming``, that is, messages sent from others to the
|
||||
logged-in account.
|
||||
|
||||
This is not a reliable way to check that the update was not produced by
|
||||
the logged-in account in broadcast channels.
|
||||
Filter by ``event.incoming``, that is, messages sent from others to the logged-in account.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
@ -102,11 +102,9 @@ class Incoming(Combinable):
|
|||
|
||||
class Outgoing(Combinable):
|
||||
"""
|
||||
Filter by ``event.outgoing``, that is, messages sent from others to the
|
||||
logged-in account.
|
||||
Filter by ``event.outgoing``, that is, messages sent from the logged-in account.
|
||||
|
||||
This is not a reliable way to check that the update was not produced by
|
||||
the logged-in account in broadcast channels.
|
||||
This is not a reliable way to check that the update was not produced by the logged-in account in broadcast channels.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
@ -117,32 +115,24 @@ class Outgoing(Combinable):
|
|||
|
||||
class Forward(Combinable):
|
||||
"""
|
||||
Filter by ``event.forward``.
|
||||
Filter by ``event.forward_info``, that is, messages that have been forwarded from elsewhere.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, event: Event) -> bool:
|
||||
return getattr(event, "forward", None) is not None
|
||||
return getattr(event, "forward_info", None) is not None
|
||||
|
||||
|
||||
class Reply(Combinable):
|
||||
"""
|
||||
Filter by ``event.reply``.
|
||||
Filter by ``event.replied_message_id``, that is, messages which are a reply to another message.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, event: Event) -> bool:
|
||||
return getattr(event, "reply", None) is not None
|
||||
|
||||
|
||||
class TextOnly(Combinable):
|
||||
"""
|
||||
Filter by messages with some text and no media.
|
||||
|
||||
Note that link previews are only considered media if they have a photo or document.
|
||||
"""
|
||||
return getattr(event, "replied_message_id", None) is not None
|
||||
|
||||
|
||||
class Media(Combinable):
|
||||
|
@ -156,6 +146,10 @@ class Media(Combinable):
|
|||
When you specify one or more media types, *only* those types will be considered.
|
||||
|
||||
You can use literal strings or the constants defined by the filter.
|
||||
|
||||
:param types:
|
||||
The media types to filter on.
|
||||
This is all of them if none are specified.
|
||||
"""
|
||||
|
||||
PHOTO = "photo"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Self
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Self, Union
|
||||
|
||||
from ...tl import abcs, types
|
||||
from ..types import Chat, Message
|
||||
from ..types import Chat, Message, expand_peer, peer_id
|
||||
from .event import Event
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -14,6 +14,8 @@ class NewMessage(Event, Message):
|
|||
"""
|
||||
Occurs when a new message is sent or received.
|
||||
|
||||
This event can be treated as the :class:`~telethon.types.Message` itself.
|
||||
|
||||
.. caution::
|
||||
|
||||
Messages sent with the :class:`~telethon.Client` are also caught,
|
||||
|
@ -39,6 +41,8 @@ class NewMessage(Event, Message):
|
|||
class MessageEdited(Event, Message):
|
||||
"""
|
||||
Occurs when a new message is sent or received.
|
||||
|
||||
This event can be treated as the :class:`~telethon.types.Message` itself.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
@ -80,32 +84,96 @@ class MessageDeleted(Event):
|
|||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def message_ids(self) -> List[int]:
|
||||
"""
|
||||
The message identifiers of the messages that were deleted.
|
||||
"""
|
||||
return self._msg_ids
|
||||
|
||||
@property
|
||||
def channel_id(self) -> Optional[int]:
|
||||
"""
|
||||
The channel identifier of the supergroup or broadcast channel where the messages were deleted.
|
||||
|
||||
This will be :data:`None` if the messages were deleted anywhere else.
|
||||
"""
|
||||
return self._channel_id
|
||||
|
||||
|
||||
class MessageRead(Event):
|
||||
"""
|
||||
Occurs both when your messages are read by others, and when you read messages.
|
||||
"""
|
||||
|
||||
def __init__(self, peer: abcs.Peer, max_id: int, out: bool) -> None:
|
||||
self._peer = peer
|
||||
self._max_id = max_id
|
||||
self._out = out
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
update: Union[
|
||||
types.UpdateReadHistoryInbox,
|
||||
types.UpdateReadHistoryOutbox,
|
||||
types.UpdateReadChannelInbox,
|
||||
types.UpdateReadChannelOutbox,
|
||||
],
|
||||
chat_map: Dict[int, Chat],
|
||||
) -> None:
|
||||
self._client = client
|
||||
self._raw = update
|
||||
self._chat_map = chat_map
|
||||
|
||||
@classmethod
|
||||
def _try_from_update(
|
||||
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
||||
) -> Optional[Self]:
|
||||
if isinstance(update, types.UpdateReadHistoryInbox):
|
||||
return cls._create(update.peer, update.max_id, False)
|
||||
elif isinstance(update, types.UpdateReadHistoryOutbox):
|
||||
return cls._create(update.peer, update.max_id, True)
|
||||
elif isinstance(update, types.UpdateReadChannelInbox):
|
||||
return cls._create(
|
||||
types.PeerChannel(channel_id=update.channel_id), update.max_id, False
|
||||
)
|
||||
elif isinstance(update, types.UpdateReadChannelOutbox):
|
||||
return cls._create(
|
||||
types.PeerChannel(channel_id=update.channel_id), update.max_id, True
|
||||
)
|
||||
if isinstance(
|
||||
update,
|
||||
(
|
||||
types.UpdateReadHistoryInbox,
|
||||
types.UpdateReadHistoryOutbox,
|
||||
types.UpdateReadChannelInbox,
|
||||
types.UpdateReadChannelOutbox,
|
||||
),
|
||||
):
|
||||
return cls._create(client, update, chat_map)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _peer(self) -> abcs.Peer:
|
||||
if isinstance(
|
||||
self._raw, (types.UpdateReadHistoryInbox, types.UpdateReadHistoryOutbox)
|
||||
):
|
||||
return self._raw.peer
|
||||
else:
|
||||
return types.PeerChannel(channel_id=self._raw.channel_id)
|
||||
|
||||
@property
|
||||
def chat(self) -> Chat:
|
||||
"""
|
||||
The :term:`chat` when the messages were read.
|
||||
"""
|
||||
peer = self._peer()
|
||||
pid = peer_id(peer)
|
||||
if pid not in self._chat_map:
|
||||
self._chat_map[pid] = expand_peer(
|
||||
self._client, peer, broadcast=getattr(self._raw, "post", None)
|
||||
)
|
||||
return self._chat_map[pid]
|
||||
|
||||
@property
|
||||
def max_message_id_read(self) -> int:
|
||||
"""
|
||||
The highest message identifier of the messages that have been marked as read.
|
||||
|
||||
In other words, messages with an identifier below or equal (``<=``) to this value are considered read.
|
||||
Messages with an identifier higher (``>``) to this value are considered unread.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if message.id <= event.max_message_id_read:
|
||||
print('message is marked as read')
|
||||
else:
|
||||
print('message is not yet marked as read')
|
||||
"""
|
||||
return self._raw.max_id
|
||||
|
|
|
@ -163,7 +163,7 @@ class Draft(metaclass=NoPublicConstructor):
|
|||
if chat := self._chat_map.get(peer_id(self._peer)):
|
||||
packed = chat.pack()
|
||||
if packed is None:
|
||||
packed = await self._client.resolve_to_packed(peer_id(self._peer))
|
||||
packed = await self._client._resolve_to_packed(peer_id(self._peer))
|
||||
return packed
|
||||
|
||||
async def send(self) -> Message:
|
||||
|
|
|
@ -281,6 +281,26 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
return None
|
||||
|
||||
@property
|
||||
def incoming(self) -> bool:
|
||||
"""
|
||||
:data:`True` if the message is incoming.
|
||||
This would mean another user sent it, and the currently logged-in user received it.
|
||||
|
||||
This is usually the opposite of :attr:`outgoing`, although some messages can be neither.
|
||||
"""
|
||||
return getattr(self._raw, "out", None) is False
|
||||
|
||||
@property
|
||||
def outgoing(self) -> bool:
|
||||
"""
|
||||
:data:`True` if the message is outgoing.
|
||||
This would mean the currently logged-in user sent it.
|
||||
|
||||
This is usually the opposite of :attr:`incoming`, although some messages can be neither.
|
||||
"""
|
||||
return getattr(self._raw, "out", None) is True
|
||||
|
||||
async def get_replied_message(self) -> Optional[Message]:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.get_messages_with_ids`.
|
||||
|
|
|
@ -11,6 +11,7 @@ from .._impl.client.events.filters import (
|
|||
All,
|
||||
Any,
|
||||
Chats,
|
||||
ChatType,
|
||||
Command,
|
||||
Filter,
|
||||
Forward,
|
||||
|
@ -21,13 +22,13 @@ from .._impl.client.events.filters import (
|
|||
Reply,
|
||||
Senders,
|
||||
Text,
|
||||
TextOnly,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"All",
|
||||
"Any",
|
||||
"Chats",
|
||||
"ChatType",
|
||||
"Command",
|
||||
"Filter",
|
||||
"Forward",
|
||||
|
@ -38,5 +39,4 @@ __all__ = [
|
|||
"Reply",
|
||||
"Senders",
|
||||
"Text",
|
||||
"TextOnly",
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue
Block a user