mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 09:26:37 +03:00
Continue implementation
This commit is contained in:
parent
4cc6ecc39b
commit
f9435aa1f6
|
@ -5,6 +5,8 @@ build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.11"
|
python: "3.11"
|
||||||
|
apt_packages:
|
||||||
|
- graphviz
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: client/doc/conf.py
|
configuration: client/doc/conf.py
|
||||||
|
|
|
@ -318,7 +318,7 @@ In Telethon:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon import Client, events
|
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)
|
bot = Client('bot', api_id, api_hash)
|
||||||
|
|
||||||
# Handle '/start' and '/help'
|
# 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!\
|
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'
|
# Handle all other messages without media (negating the filter using ~)
|
||||||
@bot.on(events.NewMessage, TextOnly())
|
@bot.on(events.NewMessage, ~Media())
|
||||||
async def echo_message(message: NewMessage):
|
async def echo_message(message: NewMessage):
|
||||||
await message.reply(message.text)
|
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
|
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>`_:
|
and instead favours more standard `HTML elements <https://developer.mozilla.org/en-US/docs/Web/HTML/Element>`_:
|
||||||
|
|
||||||
* ``strong`` and ``b`` for **bold**.
|
* ``strong`` and ``b`` for **bold**.
|
||||||
|
|
|
@ -118,6 +118,7 @@ from .updates import (
|
||||||
set_handler_filter,
|
set_handler_filter,
|
||||||
)
|
)
|
||||||
from .users import (
|
from .users import (
|
||||||
|
get_chats,
|
||||||
get_contacts,
|
get_contacts,
|
||||||
get_me,
|
get_me,
|
||||||
input_to_peer,
|
input_to_peer,
|
||||||
|
@ -683,6 +684,40 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return get_admin_log(self, chat)
|
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]:
|
def get_contacts(self) -> AsyncList[User]:
|
||||||
"""
|
"""
|
||||||
Get the users in your contact list.
|
Get the users in your contact list.
|
||||||
|
@ -1200,28 +1235,6 @@ class Client:
|
||||||
"""
|
"""
|
||||||
return await request_login_code(self, phone)
|
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:
|
async def resolve_username(self, username: str) -> Chat:
|
||||||
"""
|
"""
|
||||||
Resolve a username into a :term:`chat`.
|
Resolve a username into a :term:`chat`.
|
||||||
|
|
|
@ -154,5 +154,5 @@ async def dispatch_next(client: Client) -> None:
|
||||||
for handler, filter in handlers:
|
for handler, filter in handlers:
|
||||||
if not filter or filter(event):
|
if not filter or filter(event):
|
||||||
ret = await handler(event)
|
ret = await handler(event)
|
||||||
if ret is Continue or client._shortcircuit_handlers:
|
if ret is not Continue or client._shortcircuit_handlers:
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional, Sequence
|
||||||
|
|
||||||
from ...mtproto import RpcError
|
from ...mtproto import RpcError
|
||||||
from ...session import PackedChat, PackedType
|
from ...session import PackedChat, PackedType
|
||||||
|
@ -13,6 +13,7 @@ from ..types import (
|
||||||
Group,
|
Group,
|
||||||
User,
|
User,
|
||||||
build_chat_map,
|
build_chat_map,
|
||||||
|
expand_peer,
|
||||||
peer_id,
|
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):
|
if isinstance(chat, PackedChat):
|
||||||
return chat
|
return chat
|
||||||
|
|
||||||
if isinstance(chat, (User, Group, Channel)):
|
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:
|
if packed is not None:
|
||||||
return packed
|
return packed
|
||||||
|
|
||||||
|
@ -96,11 +136,11 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
||||||
if isinstance(chat, types.InputPeerEmpty):
|
if isinstance(chat, types.InputPeerEmpty):
|
||||||
raise ValueError("Cannot resolve chat")
|
raise ValueError("Cannot resolve chat")
|
||||||
elif isinstance(chat, types.InputPeerSelf):
|
elif isinstance(chat, types.InputPeerSelf):
|
||||||
if not self._session.user:
|
if not client._session.user:
|
||||||
raise ValueError("Cannot resolve chat")
|
raise ValueError("Cannot resolve chat")
|
||||||
return PackedChat(
|
return PackedChat(
|
||||||
ty=PackedType.BOT if self._session.user.bot else PackedType.USER,
|
ty=PackedType.BOT if client._session.user.bot else PackedType.USER,
|
||||||
id=self._chat_hashes.self_id,
|
id=client._chat_hashes.self_id,
|
||||||
access_hash=0,
|
access_hash=0,
|
||||||
)
|
)
|
||||||
elif isinstance(chat, types.InputPeerChat):
|
elif isinstance(chat, types.InputPeerChat):
|
||||||
|
@ -130,9 +170,9 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
||||||
|
|
||||||
if isinstance(chat, str):
|
if isinstance(chat, str):
|
||||||
if chat.startswith("+"):
|
if chat.startswith("+"):
|
||||||
resolved = await resolve_phone(self, chat)
|
resolved = await resolve_phone(client, chat)
|
||||||
elif chat == "me":
|
elif chat == "me":
|
||||||
if me := self._session.user:
|
if me := client._session.user:
|
||||||
return PackedChat(
|
return PackedChat(
|
||||||
ty=PackedType.BOT if me.bot else PackedType.USER,
|
ty=PackedType.BOT if me.bot else PackedType.USER,
|
||||||
id=me.id,
|
id=me.id,
|
||||||
|
@ -141,13 +181,13 @@ async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat:
|
||||||
else:
|
else:
|
||||||
resolved = None
|
resolved = None
|
||||||
else:
|
else:
|
||||||
resolved = await resolve_username(self, username=chat)
|
resolved = await resolve_username(client, username=chat)
|
||||||
|
|
||||||
if resolved and (packed := resolved.pack()) is not None:
|
if resolved and (packed := resolved.pack()) is not None:
|
||||||
return packed
|
return packed
|
||||||
|
|
||||||
if isinstance(chat, int):
|
if isinstance(chat, int):
|
||||||
packed = self._chat_hashes.get(chat)
|
packed = client._chat_hashes.get(chat)
|
||||||
if packed is None:
|
if packed is None:
|
||||||
raise ValueError("Cannot resolve chat")
|
raise ValueError("Cannot resolve chat")
|
||||||
return packed
|
return packed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from .combinators import All, Any, Filter, Not
|
from .combinators import All, Any, Filter, Not
|
||||||
from .common import Chats, Senders
|
from .common import Chats, ChatType, Senders
|
||||||
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text, TextOnly
|
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"All",
|
"All",
|
||||||
|
@ -8,6 +8,7 @@ __all__ = [
|
||||||
"Filter",
|
"Filter",
|
||||||
"Not",
|
"Not",
|
||||||
"Chats",
|
"Chats",
|
||||||
|
"ChatType",
|
||||||
"Senders",
|
"Senders",
|
||||||
"Command",
|
"Command",
|
||||||
"Forward",
|
"Forward",
|
||||||
|
@ -16,5 +17,4 @@ __all__ = [
|
||||||
"Outgoing",
|
"Outgoing",
|
||||||
"Reply",
|
"Reply",
|
||||||
"Text",
|
"Text",
|
||||||
"TextOnly",
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import abc
|
import abc
|
||||||
import typing
|
import typing
|
||||||
from typing import Callable, Tuple
|
from typing import Callable, Tuple, TypeAlias
|
||||||
|
|
||||||
from ..event import Event
|
from ..event import Event
|
||||||
|
|
||||||
Filter = Callable[[Event], bool]
|
Filter: TypeAlias = Callable[[Event], bool]
|
||||||
|
|
||||||
|
|
||||||
class Combinable(abc.ABC):
|
class Combinable(abc.ABC):
|
||||||
|
@ -48,11 +48,12 @@ class Any(Combinable):
|
||||||
"""
|
"""
|
||||||
Combine multiple filters, returning :data:`True` if any of the filters pass.
|
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
|
.. 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')))
|
@bot.on(events.NewMessage, Any(Command('/start'), Command('/help')))
|
||||||
async def handler(event): ...
|
async def handler(event): ...
|
||||||
|
@ -87,11 +88,12 @@ class All(Combinable):
|
||||||
"""
|
"""
|
||||||
Combine multiple filters, returning :data:`True` if all of the filters pass.
|
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
|
.. 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+')))
|
@bot.on(events.NewMessage, All(Command('/start'), Text(r'\bdata:\w+')))
|
||||||
async def handler(event): ...
|
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.
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon.filters import All, Command
|
from telethon.events.filters import All, Command
|
||||||
|
|
||||||
@bot.on(events.NewMessage, Not(Command('/start'))
|
@bot.on(events.NewMessage, Not(Command('/start'))
|
||||||
async def handler(event): ...
|
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 ...types import Channel, Group, User
|
||||||
from ..event import Event
|
from ..event import Event
|
||||||
|
@ -8,20 +8,21 @@ from .combinators import Combinable
|
||||||
class Chats(Combinable):
|
class Chats(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.chat.id``, if the event has a chat.
|
Filter by ``event.chat.id``, if the event has a chat.
|
||||||
|
|
||||||
|
:param chat_ids: The chat identifiers to filter on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_chats",)
|
__slots__ = ("_chats",)
|
||||||
|
|
||||||
def __init__(self, chat_id: Union[int, Sequence[int]], *chat_ids: int) -> None:
|
def __init__(self, chat_ids: Sequence[int]) -> None:
|
||||||
self._chats = {chat_id} if isinstance(chat_id, int) else set(chat_id)
|
self._chats = set(chat_ids)
|
||||||
self._chats.update(chat_ids)
|
|
||||||
|
|
||||||
@property
|
@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:
|
def __call__(self, event: Event) -> bool:
|
||||||
chat = getattr(event, "chat", None)
|
chat = getattr(event, "chat", None)
|
||||||
|
@ -32,20 +33,21 @@ class Chats(Combinable):
|
||||||
class Senders(Combinable):
|
class Senders(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.sender.id``, if the event has a sender.
|
Filter by ``event.sender.id``, if the event has a sender.
|
||||||
|
|
||||||
|
:param sender_ids: The sender identifiers to filter on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_senders",)
|
__slots__ = ("_senders",)
|
||||||
|
|
||||||
def __init__(self, sender_id: Union[int, Sequence[int]], *sender_ids: int) -> None:
|
def __init__(self, sender_ids: Sequence[int]) -> None:
|
||||||
self._senders = {sender_id} if isinstance(sender_id, int) else set(sender_id)
|
self._senders = set(sender_ids)
|
||||||
self._senders.update(sender_ids)
|
|
||||||
|
|
||||||
@property
|
@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:
|
def __call__(self, event: Event) -> bool:
|
||||||
sender = getattr(event, "sender", None)
|
sender = getattr(event, "sender", None)
|
||||||
|
@ -55,37 +57,38 @@ class Senders(Combinable):
|
||||||
|
|
||||||
class ChatType(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",)
|
__slots__ = ("_type",)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
type: Union[Literal["user"], Literal["group"], Literal["broadcast"]],
|
type: Type[Union[User, Group, Channel]],
|
||||||
) -> None:
|
) -> None:
|
||||||
if type == "user":
|
self._type = type
|
||||||
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}")
|
|
||||||
|
|
||||||
@property
|
@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.
|
The chat type this filter is filtering on.
|
||||||
"""
|
"""
|
||||||
if self._type == User:
|
return self._type
|
||||||
return "user"
|
|
||||||
elif self._type == Group:
|
|
||||||
return "group"
|
|
||||||
elif self._type == Channel:
|
|
||||||
return "broadcast"
|
|
||||||
else:
|
|
||||||
raise RuntimeError("unexpected case")
|
|
||||||
|
|
||||||
def __call__(self, event: Event) -> bool:
|
def __call__(self, event: Event) -> bool:
|
||||||
sender = getattr(event, "chat", None)
|
sender = getattr(event, "chat", None)
|
||||||
|
|
|
@ -21,7 +21,9 @@ class Text(Combinable):
|
||||||
you need to manually perform the check inside the handler instead.
|
you need to manually perform the check inside the handler instead.
|
||||||
|
|
||||||
Note that the caption text in messages with media is also searched.
|
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",)
|
__slots__ = ("_pattern",)
|
||||||
|
@ -43,6 +45,8 @@ class Command(Combinable):
|
||||||
filter ``Command('/help')`` will match both ``"/help"`` and ``"/help@bot"``, but not
|
filter ``Command('/help')`` will match both ``"/help"`` and ``"/help@bot"``, but not
|
||||||
``"/list"`` or ``"/help@other"``.
|
``"/list"`` or ``"/help@other"``.
|
||||||
|
|
||||||
|
:param command: The command to match on.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The leading forward-slash is not automatically added!
|
The leading forward-slash is not automatically added!
|
||||||
|
@ -58,7 +62,7 @@ class Command(Combinable):
|
||||||
__slots__ = ("_cmd", "_username")
|
__slots__ = ("_cmd", "_username")
|
||||||
|
|
||||||
def __init__(self, command: str) -> None:
|
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}")
|
raise ValueError(f"command cannot contain spaces: {command}")
|
||||||
|
|
||||||
self._cmd = command
|
self._cmd = command
|
||||||
|
@ -87,11 +91,7 @@ class Command(Combinable):
|
||||||
|
|
||||||
class Incoming(Combinable):
|
class Incoming(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.incoming``, that is, messages sent from others to the
|
Filter by ``event.incoming``, that is, messages sent from others to the logged-in account.
|
||||||
logged-in account.
|
|
||||||
|
|
||||||
This is not a reliable way to check that the update was not produced by
|
|
||||||
the logged-in account in broadcast channels.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
@ -102,11 +102,9 @@ class Incoming(Combinable):
|
||||||
|
|
||||||
class Outgoing(Combinable):
|
class Outgoing(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.outgoing``, that is, messages sent from others to the
|
Filter by ``event.outgoing``, that is, messages sent from the logged-in account.
|
||||||
logged-in account.
|
|
||||||
|
|
||||||
This is not a reliable way to check that the update was not produced by
|
This is not a reliable way to check that the update was not produced by the logged-in account in broadcast channels.
|
||||||
the logged-in account in broadcast channels.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
@ -117,32 +115,24 @@ class Outgoing(Combinable):
|
||||||
|
|
||||||
class Forward(Combinable):
|
class Forward(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.forward``.
|
Filter by ``event.forward_info``, that is, messages that have been forwarded from elsewhere.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __call__(self, event: Event) -> bool:
|
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):
|
class Reply(Combinable):
|
||||||
"""
|
"""
|
||||||
Filter by ``event.reply``.
|
Filter by ``event.replied_message_id``, that is, messages which are a reply to another message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __call__(self, event: Event) -> bool:
|
def __call__(self, event: Event) -> bool:
|
||||||
return getattr(event, "reply", None) is not None
|
return getattr(event, "replied_message_id", 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Media(Combinable):
|
class Media(Combinable):
|
||||||
|
@ -156,6 +146,10 @@ class Media(Combinable):
|
||||||
When you specify one or more media types, *only* those types will be considered.
|
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.
|
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"
|
PHOTO = "photo"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from __future__ import annotations
|
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 ...tl import abcs, types
|
||||||
from ..types import Chat, Message
|
from ..types import Chat, Message, expand_peer, peer_id
|
||||||
from .event import Event
|
from .event import Event
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -14,6 +14,8 @@ class NewMessage(Event, Message):
|
||||||
"""
|
"""
|
||||||
Occurs when a new message is sent or received.
|
Occurs when a new message is sent or received.
|
||||||
|
|
||||||
|
This event can be treated as the :class:`~telethon.types.Message` itself.
|
||||||
|
|
||||||
.. caution::
|
.. caution::
|
||||||
|
|
||||||
Messages sent with the :class:`~telethon.Client` are also caught,
|
Messages sent with the :class:`~telethon.Client` are also caught,
|
||||||
|
@ -39,6 +41,8 @@ class NewMessage(Event, Message):
|
||||||
class MessageEdited(Event, Message):
|
class MessageEdited(Event, Message):
|
||||||
"""
|
"""
|
||||||
Occurs when a new message is sent or received.
|
Occurs when a new message is sent or received.
|
||||||
|
|
||||||
|
This event can be treated as the :class:`~telethon.types.Message` itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -80,32 +84,96 @@ class MessageDeleted(Event):
|
||||||
else:
|
else:
|
||||||
return None
|
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):
|
class MessageRead(Event):
|
||||||
"""
|
"""
|
||||||
Occurs both when your messages are read by others, and when you read messages.
|
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:
|
def __init__(
|
||||||
self._peer = peer
|
self,
|
||||||
self._max_id = max_id
|
client: Client,
|
||||||
self._out = out
|
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
|
@classmethod
|
||||||
def _try_from_update(
|
def _try_from_update(
|
||||||
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
if isinstance(update, types.UpdateReadHistoryInbox):
|
if isinstance(
|
||||||
return cls._create(update.peer, update.max_id, False)
|
update,
|
||||||
elif isinstance(update, types.UpdateReadHistoryOutbox):
|
(
|
||||||
return cls._create(update.peer, update.max_id, True)
|
types.UpdateReadHistoryInbox,
|
||||||
elif isinstance(update, types.UpdateReadChannelInbox):
|
types.UpdateReadHistoryOutbox,
|
||||||
return cls._create(
|
types.UpdateReadChannelInbox,
|
||||||
types.PeerChannel(channel_id=update.channel_id), update.max_id, False
|
types.UpdateReadChannelOutbox,
|
||||||
)
|
),
|
||||||
elif isinstance(update, types.UpdateReadChannelOutbox):
|
):
|
||||||
return cls._create(
|
return cls._create(client, update, chat_map)
|
||||||
types.PeerChannel(channel_id=update.channel_id), update.max_id, True
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return None
|
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)):
|
if chat := self._chat_map.get(peer_id(self._peer)):
|
||||||
packed = chat.pack()
|
packed = chat.pack()
|
||||||
if packed is None:
|
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
|
return packed
|
||||||
|
|
||||||
async def send(self) -> Message:
|
async def send(self) -> Message:
|
||||||
|
|
|
@ -281,6 +281,26 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
return None
|
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]:
|
async def get_replied_message(self) -> Optional[Message]:
|
||||||
"""
|
"""
|
||||||
Alias for :meth:`telethon.Client.get_messages_with_ids`.
|
Alias for :meth:`telethon.Client.get_messages_with_ids`.
|
||||||
|
|
|
@ -11,6 +11,7 @@ from .._impl.client.events.filters import (
|
||||||
All,
|
All,
|
||||||
Any,
|
Any,
|
||||||
Chats,
|
Chats,
|
||||||
|
ChatType,
|
||||||
Command,
|
Command,
|
||||||
Filter,
|
Filter,
|
||||||
Forward,
|
Forward,
|
||||||
|
@ -21,13 +22,13 @@ from .._impl.client.events.filters import (
|
||||||
Reply,
|
Reply,
|
||||||
Senders,
|
Senders,
|
||||||
Text,
|
Text,
|
||||||
TextOnly,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"All",
|
"All",
|
||||||
"Any",
|
"Any",
|
||||||
"Chats",
|
"Chats",
|
||||||
|
"ChatType",
|
||||||
"Command",
|
"Command",
|
||||||
"Filter",
|
"Filter",
|
||||||
"Forward",
|
"Forward",
|
||||||
|
@ -38,5 +39,4 @@ __all__ = [
|
||||||
"Reply",
|
"Reply",
|
||||||
"Senders",
|
"Senders",
|
||||||
"Text",
|
"Text",
|
||||||
"TextOnly",
|
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user