mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-26 03:13:45 +03:00
Rework methods to manage event handlers
This commit is contained in:
parent
9726169a8c
commit
0802f7e6b2
|
@ -788,3 +788,4 @@ it's now
|
|||
this also means filters are unified, although not all have an effect on all events. from_users renamed to senders. messageread inbox is gone in favor of outgoing/incoming.
|
||||
events.register, unregister, is_handler and list are gone. now you can typehint instead.
|
||||
def handler(event: events.NewMessage)
|
||||
client.on, add, and remove have changed parameters/retval
|
|
@ -148,6 +148,7 @@ def init(
|
|||
self._no_updates = not receive_updates
|
||||
self._updates_queue = asyncio.Queue(maxsize=max_queued_updates)
|
||||
self._updates_handle = None
|
||||
self._update_handlers = [] # sorted list
|
||||
self._message_box = MessageBox()
|
||||
self._entity_cache = EntityCache() # required for proper update handling (to know when to getDifference)
|
||||
|
||||
|
|
|
@ -2791,15 +2791,24 @@ class TelegramClient:
|
|||
"""
|
||||
|
||||
@forward_call(updates.on)
|
||||
def on(self: 'TelegramClient', event: EventBuilder):
|
||||
def on(self: 'TelegramClient', *events, priority=0, **filters):
|
||||
"""
|
||||
Decorator used to `add_event_handler` more conveniently.
|
||||
|
||||
This decorator should be above other decorators which modify the function.
|
||||
|
||||
Arguments
|
||||
event (`_EventBuilder` | `type`):
|
||||
The event builder class or instance to be used,
|
||||
for instance ``events.NewMessage``.
|
||||
event (`type` | `tuple`):
|
||||
The event type(s) you wish to receive, for instance ``events.NewMessage``.
|
||||
This may also be raw update types.
|
||||
The same handler is registered multiple times, one per type.
|
||||
|
||||
priority (`int`):
|
||||
The event priority. Events with higher priority are dispatched first.
|
||||
The order between events with the same priority is arbitrary.
|
||||
|
||||
filters (any):
|
||||
Filters passed to `make_filter`.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -2808,7 +2817,12 @@ class TelegramClient:
|
|||
client = TelegramClient(...)
|
||||
|
||||
# Here we use client.on
|
||||
@client.on(events.NewMessage)
|
||||
@client.on(events.NewMessage, priority=100)
|
||||
async def handler(event):
|
||||
...
|
||||
|
||||
# Both new incoming messages and incoming edits
|
||||
@client.on(events.NewMessage, events.MessageEdited, incoming=True)
|
||||
async def handler(event):
|
||||
...
|
||||
"""
|
||||
|
@ -2816,8 +2830,11 @@ class TelegramClient:
|
|||
@forward_call(updates.add_event_handler)
|
||||
def add_event_handler(
|
||||
self: 'TelegramClient',
|
||||
callback: updates.Callback,
|
||||
event: EventBuilder = None):
|
||||
callback: updates.Callback = None,
|
||||
event: EventBuilder = None,
|
||||
priority=0,
|
||||
**filters
|
||||
):
|
||||
"""
|
||||
Registers a new event handler callback.
|
||||
|
||||
|
@ -2827,17 +2844,29 @@ class TelegramClient:
|
|||
callback (`callable`):
|
||||
The callable function accepting one parameter to be used.
|
||||
|
||||
Note that if you have used `telethon.events.register` in
|
||||
the callback, ``event`` will be ignored, and instead the
|
||||
events you previously registered will be used.
|
||||
If `None`, the method can be used as a decorator. Note that the handler function
|
||||
will be replaced with the `EventHandler` instance in this case, but it will still
|
||||
be callable.
|
||||
|
||||
event (`_EventBuilder` | `type`, optional):
|
||||
The event builder class or instance to be used,
|
||||
for instance ``events.NewMessage``.
|
||||
|
||||
If left unspecified, `telethon.events.raw.Raw` (the
|
||||
:tl:`Update` objects with no further processing) will
|
||||
be passed instead.
|
||||
If left unspecified, it will be inferred from the type hint
|
||||
used in the handler, or be `telethon.events.raw.Raw` (the
|
||||
:tl:`Update` objects with no further processing) if there is
|
||||
none. Note that the type hint must be the desired type. It
|
||||
cannot be a string, an union, or anything more complex.
|
||||
|
||||
priority (`int`):
|
||||
The event priority. Events with higher priority are dispatched first.
|
||||
The order between events with the same priority is arbitrary.
|
||||
|
||||
filters (any):
|
||||
Filters passed to `make_filter`.
|
||||
|
||||
Returns
|
||||
An `EventHandler` instance, which can be used
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -2845,22 +2874,47 @@ class TelegramClient:
|
|||
from telethon import TelegramClient, events
|
||||
client = TelegramClient(...)
|
||||
|
||||
# Adding a handler, the "boring" way
|
||||
async def handler(event):
|
||||
...
|
||||
|
||||
client.add_event_handler(handler, events.NewMessage)
|
||||
client.add_event_handler(handler, events.NewMessage, priority=50)
|
||||
|
||||
# Automatic type
|
||||
async def handler(event: events.MessageEdited)
|
||||
...
|
||||
|
||||
client.add_event_handler(handler, outgoing=False)
|
||||
|
||||
# Streamlined adding
|
||||
@client.add_event_handler
|
||||
async def handler(event: events.MessageDeleted):
|
||||
...
|
||||
"""
|
||||
|
||||
@forward_call(updates.remove_event_handler)
|
||||
def remove_event_handler(
|
||||
self: 'TelegramClient',
|
||||
callback: updates.Callback,
|
||||
event: EventBuilder = None) -> int:
|
||||
callback: updates.Callback = None,
|
||||
event: EventBuilder = None,
|
||||
priority=None,
|
||||
) -> int:
|
||||
"""
|
||||
Inverse operation of `add_event_handler()`.
|
||||
|
||||
If no event is given, all events for this callback are removed.
|
||||
Returns how many callbacks were removed.
|
||||
Returns a list in arbitrary order with all removed `EventHandler` instances.
|
||||
|
||||
Arguments
|
||||
callback (`callable`):
|
||||
The callable function accepting one parameter to be used.
|
||||
If passed an `EventHandler` instance, both `event` and `priority` are ignored.
|
||||
|
||||
event (`_EventBuilder` | `type`, optional):
|
||||
The event builder class or instance to be used when searching.
|
||||
|
||||
priority (`int`):
|
||||
The event priority to be used when searching.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -2876,6 +2930,12 @@ class TelegramClient:
|
|||
|
||||
# "handler" will stop receiving anything
|
||||
client.remove_event_handler(handler)
|
||||
|
||||
# Remove all handlers with priority 50
|
||||
client.remove_event_handler(priority=50)
|
||||
|
||||
# Remove all deleted-message handlers
|
||||
client.remove_event_handler(event=events.MessageDeleted)
|
||||
"""
|
||||
|
||||
@forward_call(updates.list_event_handlers)
|
||||
|
@ -2885,7 +2945,7 @@ class TelegramClient:
|
|||
Lists all registered event handlers.
|
||||
|
||||
Returns
|
||||
A list of pairs consisting of ``(callback, event)``.
|
||||
A list of all registered `EventHandler` in arbitrary order.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -2895,8 +2955,8 @@ class TelegramClient:
|
|||
'''Greets someone'''
|
||||
await event.reply('Hi')
|
||||
|
||||
for callback, event in client.list_event_handlers():
|
||||
print(id(callback), type(event))
|
||||
for handler in client.list_event_handlers():
|
||||
print(id(handler.callback), handler.event)
|
||||
"""
|
||||
|
||||
@forward_call(updates.catch_up)
|
||||
|
|
|
@ -7,12 +7,15 @@ import time
|
|||
import traceback
|
||||
import typing
|
||||
import logging
|
||||
import inspect
|
||||
import bisect
|
||||
import warnings
|
||||
from collections import deque
|
||||
|
||||
from ..errors._rpcbase import RpcError
|
||||
from .._events.base import EventBuilder
|
||||
from .._events.raw import Raw
|
||||
from .._events.base import StopPropagation, _get_handlers
|
||||
from .._events.base import StopPropagation, EventBuilder, EventHandler
|
||||
from .._events.filters import make_filter
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
|
||||
|
@ -33,51 +36,96 @@ async def run_until_disconnected(self: 'TelegramClient'):
|
|||
await self(_tl.fn.updates.GetState())
|
||||
await self._sender.wait_disconnected()
|
||||
|
||||
def on(self: 'TelegramClient', event: EventBuilder):
|
||||
def on(self: 'TelegramClient', *events, priority=0, **filters):
|
||||
def decorator(f):
|
||||
self.add_event_handler(f, event)
|
||||
for event in events:
|
||||
self.add_event_handler(f, event, priority=priority, **filters)
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
def add_event_handler(
|
||||
self: 'TelegramClient',
|
||||
callback: Callback,
|
||||
event: EventBuilder = None):
|
||||
builders = _get_handlers(callback)
|
||||
if builders is not None:
|
||||
for event in builders:
|
||||
self._event_builders.append((event, callback))
|
||||
return
|
||||
callback=None,
|
||||
event=None,
|
||||
priority=0,
|
||||
**filters
|
||||
):
|
||||
if callback is None:
|
||||
return functools.partial(add_event_handler, self, event=event, priority=priority, **filters)
|
||||
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
elif not event:
|
||||
event = Raw()
|
||||
if event is None:
|
||||
for param in inspect.signature(callback).parameters.values():
|
||||
if not issubclass(param.annotation, EventBuilder):
|
||||
raise TypeError(f'unrecognized event handler type: {param.annotation!r}')
|
||||
event = param.annotation
|
||||
break # only check the first parameter
|
||||
|
||||
self._event_builders.append((event, callback))
|
||||
if event is None:
|
||||
event = Raw
|
||||
|
||||
handler = EventHandler(event, callback, priority, make_filter(**filters))
|
||||
bisect.insort(self._update_handlers, handler)
|
||||
return handler
|
||||
|
||||
def remove_event_handler(
|
||||
self: 'TelegramClient',
|
||||
callback: Callback,
|
||||
event: EventBuilder = None) -> int:
|
||||
found = 0
|
||||
if event and not isinstance(event, type):
|
||||
event = type(event)
|
||||
callback,
|
||||
event,
|
||||
priority,
|
||||
):
|
||||
if callback is None and event is None and priority is None:
|
||||
raise ValueError('must specify at least one of callback, event or priority')
|
||||
|
||||
i = len(self._event_builders)
|
||||
while i:
|
||||
i -= 1
|
||||
ev, cb = self._event_builders[i]
|
||||
if cb == callback and (not event or isinstance(ev, event)):
|
||||
del self._event_builders[i]
|
||||
found += 1
|
||||
if not self._update_handlers:
|
||||
return [] # won't be removing anything (some code paths rely on non-empty lists)
|
||||
|
||||
return found
|
||||
if isinstance(callback, EventHandler):
|
||||
if event is not None or priority is not None:
|
||||
warnings.warn('event and priority are ignored when removing EventHandler instances')
|
||||
|
||||
index = bisect.bisect_left(self._update_handlers, callback)
|
||||
try:
|
||||
if self._update_handlers[index] == callback:
|
||||
return [self._update_handlers.pop(index)]
|
||||
except IndexError:
|
||||
pass
|
||||
return []
|
||||
|
||||
if priority is not None:
|
||||
# can binary-search (using a dummy EventHandler)
|
||||
index = bisect.bisect_right(self._update_handlers, EventHandler(None, None, priority, None))
|
||||
try:
|
||||
while self._update_handlers[index].priority == priority:
|
||||
index += 1
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
removed = []
|
||||
while index > 0 and self._update_handlers[index - 1].priority == priority:
|
||||
index -= 1
|
||||
if callback is not None and self._update_handlers[index].callback != callback:
|
||||
continue
|
||||
if event is not None and self._update_handlers[index].event != event:
|
||||
continue
|
||||
removed.append(self._update_handlers.pop(index))
|
||||
|
||||
return removed
|
||||
|
||||
# slow-path, remove all matching
|
||||
removed = []
|
||||
for index, handler in reversed(enumerate(self._update_handlers)):
|
||||
if callback is not None and handler.callback != callback:
|
||||
continue
|
||||
if event is not None and handler.event != event:
|
||||
continue
|
||||
removed.append(self._update_handlers.pop(index))
|
||||
|
||||
return removed
|
||||
|
||||
def list_event_handlers(self: 'TelegramClient')\
|
||||
-> 'typing.Sequence[typing.Tuple[Callback, EventBuilder]]':
|
||||
return [(callback, event) for event, callback in self._event_builders]
|
||||
return self._update_handlers[:]
|
||||
|
||||
async def catch_up(self: 'TelegramClient'):
|
||||
# The update loop is probably blocked on either timeout or an update to arrive.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import abc
|
||||
import functools
|
||||
|
||||
|
||||
class StopPropagation(Exception):
|
||||
|
@ -41,3 +42,23 @@ class EventBuilder(abc.ABC):
|
|||
`self_id` should be the current user's ID, since it is required
|
||||
for some events which lack this information but still need it.
|
||||
"""
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class EventHandler:
|
||||
__slots__ = ('_event', '_callback', '_priority', '_filter')
|
||||
|
||||
def __init__(self, event, callback, priority, filter):
|
||||
self._event = event
|
||||
self._callback = callback
|
||||
self._priority = priority
|
||||
self._filter = filter
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._priority < other._priority
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._callback(*args, **kwargs)
|
||||
|
|
|
@ -6,7 +6,7 @@ from .. import _tl
|
|||
from ..types import _custom
|
||||
|
||||
|
||||
class NewMessageEvent(EventBuilder, Message):
|
||||
class NewMessage(EventBuilder, _custom.Message):
|
||||
"""
|
||||
Represents the event of a new message. This event can be treated
|
||||
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
||||
|
|
|
@ -32,7 +32,7 @@ def _requires_status(function):
|
|||
return wrapped
|
||||
|
||||
|
||||
class UserUpdateEvent(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
class UserUpdate(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
Occurs whenever a user goes online, starts typing, etc.
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from ._events.base import StopPropagation, register, unregister, is_handler, list
|
||||
from ._events.base import StopPropagation
|
||||
from ._events.raw import Raw
|
||||
|
||||
from ._events.album import Album
|
||||
from ._events.chataction import ChatAction
|
||||
from ._events.messagedeleted import MessageDeleted
|
||||
|
|
Loading…
Reference in New Issue
Block a user