mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-01-24 08:14:14 +03:00
Make filters combinable
This commit is contained in:
parent
2def0a169c
commit
2992a8e212
|
@ -37,7 +37,7 @@ The most common way is using the :meth:`Client.on` decorator to register your ca
|
|||
|
||||
bot = Client(...)
|
||||
|
||||
@bot.on(events.NewMessage, filters.Command('/start'))
|
||||
@bot.on(events.NewMessage, filters.Command('/start') | filters.Command('/help'))
|
||||
async def handler(event: events.NewMessage):
|
||||
await event.respond('Beep boop!')
|
||||
|
||||
|
@ -46,7 +46,11 @@ The first parameter is the :class:`type` of one of the :mod:`telethon.events`, n
|
|||
The second parameter is optional.
|
||||
If provided, it must be a callable function that returns :data:`True` if the handler should run.
|
||||
Built-in filter functions are available in the :mod:`~telethon.events.filters` module.
|
||||
In this example, :class:`~events.filters.Command` means the handler will be called when the user sends */start* to the bot.
|
||||
In this example, :class:`~events.filters.Command` means the handler will be called when the user sends */start* or */help* to the bot.
|
||||
|
||||
Built-in filter functions are also :class:`~telethon._impl.client.events.filters.combinators.Combinable`.
|
||||
This means you can use ``|``, ``&`` and the unary ``~`` to combine filters with *or*, *and*, and negate them, respectively.
|
||||
These operators correspond to :class:`events.filters.Any`, :class:`events.filters.All` and :class:`events.filters.Not`.
|
||||
|
||||
When your ``handler`` function is called, it will receive a single parameter, the event.
|
||||
The event type is the same as the one you defined in the decorator when registering your handler.
|
||||
|
@ -140,7 +144,6 @@ This makes it very convenient to write custom filters using the :keyword:`lambda
|
|||
...
|
||||
|
||||
|
||||
|
||||
Setting priority on handlers
|
||||
----------------------------
|
||||
|
||||
|
|
|
@ -345,6 +345,7 @@ There is no longer nested ``class Event`` inside them either.
|
|||
|
||||
Instead, the event type itself is what the handler will actually be called with.
|
||||
Because filters are separate, there is no longer a need for v1 ``@events.register`` either.
|
||||
It also means you can combine filters with ``&``, ``|`` and ``~``.
|
||||
|
||||
Filters are now normal functions that work with any event.
|
||||
Of course, this doesn't mean all filters make sense for all events.
|
||||
|
|
|
@ -102,6 +102,10 @@ Private definitions
|
|||
|
||||
Generic parameter used by :class:`AsyncList`.
|
||||
|
||||
.. currentmodule:: telethon._impl.client.events.filters.combinators
|
||||
|
||||
.. autoclass:: Combinable
|
||||
|
||||
.. currentmodule:: telethon._impl.client.types.file
|
||||
|
||||
.. autoclass:: InFileLike
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from .combinators import All, Any, Not
|
||||
from .common import Chats, Filter, Senders
|
||||
from .combinators import All, Any, Filter, Not
|
||||
from .common import Chats, Senders
|
||||
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text, TextOnly
|
||||
|
||||
__all__ = [
|
||||
"All",
|
||||
"Any",
|
||||
"Filter",
|
||||
"Not",
|
||||
"Chats",
|
||||
"Filter",
|
||||
"Senders",
|
||||
"Command",
|
||||
"Forward",
|
||||
|
|
|
@ -1,12 +1,70 @@
|
|||
from typing import Tuple
|
||||
import abc
|
||||
import typing
|
||||
from typing import Callable, Tuple
|
||||
|
||||
from ..event import Event
|
||||
from .common import Filter
|
||||
|
||||
Filter = Callable[[Event], bool]
|
||||
|
||||
|
||||
class Any:
|
||||
class Combinable(abc.ABC):
|
||||
"""
|
||||
Subclass that enables filters to be combined.
|
||||
|
||||
* The :func:`bitwise or <operator.or_>` operator ``|`` can be used to combine filters with :class:`Any`.
|
||||
* The :func:`bitwise and <operator.and_>` operator ``&`` can be used to combine filters with :class:`All`.
|
||||
* The :func:`bitwise invert <operator.invert>` operator ``~`` can be used to negate a filter with :class:`Not`.
|
||||
|
||||
Filters combined this way will be merged.
|
||||
This means multiple ``|`` or ``&`` will lead to a single :class:`Any` or :class:`All` being used.
|
||||
Multiple ``~`` will toggle between using :class:`Not` and not using it.
|
||||
"""
|
||||
|
||||
def __or__(self, other: typing.Any) -> Filter:
|
||||
if not callable(other):
|
||||
return NotImplemented
|
||||
|
||||
lhs = self.filters if isinstance(self, Any) else (self,)
|
||||
rhs = other.filters if isinstance(other, Any) else (other,)
|
||||
return Any(*lhs, *rhs) # type: ignore [arg-type]
|
||||
|
||||
def __and__(self, other: typing.Any) -> Filter:
|
||||
if not callable(other):
|
||||
return NotImplemented
|
||||
|
||||
lhs = self.filters if isinstance(self, All) else (self,)
|
||||
rhs = other.filters if isinstance(other, All) else (other,)
|
||||
return All(*lhs, *rhs) # type: ignore [arg-type]
|
||||
|
||||
def __invert__(self) -> Filter:
|
||||
return self.filter if isinstance(self, Not) else Not(self) # type: ignore [return-value]
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(self, event: Event) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import Any, Command
|
||||
|
||||
@bot.on(events.NewMessage, Any(Command('/start'), Command('/help')))
|
||||
async def handler(event): ...
|
||||
|
||||
# equivalent to:
|
||||
|
||||
@bot.on(events.NewMessage, Command('/start') | Command('/help'))
|
||||
async def handler(event): ...
|
||||
|
||||
:param filter1: The first filter to check.
|
||||
:param filter2: The second filter to check if the first one failed.
|
||||
:param filters: The rest of filters to check if the first and second one failed.
|
||||
"""
|
||||
|
||||
__slots__ = ("_filters",)
|
||||
|
@ -25,9 +83,27 @@ class Any:
|
|||
return any(f(event) for f in self._filters)
|
||||
|
||||
|
||||
class All:
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import All, Command, Text
|
||||
|
||||
@bot.on(events.NewMessage, All(Command('/start'), Text(r'\bdata:\w+')))
|
||||
async def handler(event): ...
|
||||
|
||||
# equivalent to:
|
||||
|
||||
@bot.on(events.NewMessage, Command('/start') & Text(r'\bdata:\w+'))
|
||||
async def handler(event): ...
|
||||
|
||||
:param filter1: The first filter to check.
|
||||
:param filter2: The second filter to check.
|
||||
:param filters: The rest of filters to check.
|
||||
"""
|
||||
|
||||
__slots__ = ("_filters",)
|
||||
|
@ -46,9 +122,25 @@ class All:
|
|||
return all(f(event) for f in self._filters)
|
||||
|
||||
|
||||
class Not:
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.filters import All, Command
|
||||
|
||||
@bot.on(events.NewMessage, Not(Command('/start'))
|
||||
async def handler(event): ...
|
||||
|
||||
# equivalent to:
|
||||
|
||||
@bot.on(events.NewMessage, ~Command('/start'))
|
||||
async def handler(event): ...
|
||||
|
||||
:param filter: The filter to negate.
|
||||
"""
|
||||
|
||||
__slots__ = ("_filter",)
|
||||
|
@ -59,7 +151,7 @@ class Not:
|
|||
@property
|
||||
def filter(self) -> Filter:
|
||||
"""
|
||||
The filters being negated.
|
||||
The filter being negated.
|
||||
"""
|
||||
return self._filter
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from typing import Callable, Literal, Sequence, Tuple, Type, Union
|
||||
from typing import Literal, Sequence, Tuple, Type, Union
|
||||
|
||||
from ...types import Channel, Group, User
|
||||
from ..event import Event
|
||||
|
||||
Filter = Callable[[Event], bool]
|
||||
from .combinators import Combinable
|
||||
|
||||
|
||||
class Chats:
|
||||
class Chats(Combinable):
|
||||
"""
|
||||
Filter by ``event.chat.id``, if the event has a chat.
|
||||
"""
|
||||
|
@ -30,7 +29,7 @@ class Chats:
|
|||
return id in self._chats
|
||||
|
||||
|
||||
class Senders:
|
||||
class Senders(Combinable):
|
||||
"""
|
||||
Filter by ``event.sender.id``, if the event has a sender.
|
||||
"""
|
||||
|
@ -54,7 +53,7 @@ class Senders:
|
|||
return id in self._senders
|
||||
|
||||
|
||||
class ChatType:
|
||||
class ChatType(Combinable):
|
||||
"""
|
||||
Filter by chat type, either ``'user'``, ``'group'`` or ``'broadcast'``.
|
||||
"""
|
||||
|
|
|
@ -4,12 +4,13 @@ import re
|
|||
from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union
|
||||
|
||||
from ..event import Event
|
||||
from .combinators import Combinable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client.client import Client
|
||||
|
||||
|
||||
class Text:
|
||||
class Text(Combinable):
|
||||
"""
|
||||
Filter by ``event.text`` using a *regular expression* pattern.
|
||||
|
||||
|
@ -33,7 +34,7 @@ class Text:
|
|||
return re.search(self._pattern, text) is not None if text is not None else False
|
||||
|
||||
|
||||
class Command:
|
||||
class Command(Combinable):
|
||||
"""
|
||||
Filter by ``event.text`` to make sure the first word matches the command or
|
||||
the command + '@' + username, using the username of the logged-in account.
|
||||
|
@ -84,7 +85,7 @@ class Command:
|
|||
return False
|
||||
|
||||
|
||||
class Incoming:
|
||||
class Incoming(Combinable):
|
||||
"""
|
||||
Filter by ``event.incoming``, that is, messages sent from others to the
|
||||
logged-in account.
|
||||
|
@ -99,7 +100,7 @@ class Incoming:
|
|||
return getattr(event, "incoming", False)
|
||||
|
||||
|
||||
class Outgoing:
|
||||
class Outgoing(Combinable):
|
||||
"""
|
||||
Filter by ``event.outgoing``, that is, messages sent from others to the
|
||||
logged-in account.
|
||||
|
@ -114,7 +115,7 @@ class Outgoing:
|
|||
return getattr(event, "outgoing", False)
|
||||
|
||||
|
||||
class Forward:
|
||||
class Forward(Combinable):
|
||||
"""
|
||||
Filter by ``event.forward``.
|
||||
"""
|
||||
|
@ -125,7 +126,7 @@ class Forward:
|
|||
return getattr(event, "forward", None) is not None
|
||||
|
||||
|
||||
class Reply:
|
||||
class Reply(Combinable):
|
||||
"""
|
||||
Filter by ``event.reply``.
|
||||
"""
|
||||
|
@ -136,7 +137,7 @@ class Reply:
|
|||
return getattr(event, "reply", None) is not None
|
||||
|
||||
|
||||
class TextOnly:
|
||||
class TextOnly(Combinable):
|
||||
"""
|
||||
Filter by messages with some text and no media.
|
||||
|
||||
|
@ -144,7 +145,7 @@ class TextOnly:
|
|||
"""
|
||||
|
||||
|
||||
class Media:
|
||||
class Media(Combinable):
|
||||
"""
|
||||
Filter by the media type in the message.
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user