mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-18 12:30:59 +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.
|
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.
|
events.register, unregister, is_handler and list are gone. now you can typehint instead.
|
||||||
def handler(event: events.NewMessage)
|
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._no_updates = not receive_updates
|
||||||
self._updates_queue = asyncio.Queue(maxsize=max_queued_updates)
|
self._updates_queue = asyncio.Queue(maxsize=max_queued_updates)
|
||||||
self._updates_handle = None
|
self._updates_handle = None
|
||||||
|
self._update_handlers = [] # sorted list
|
||||||
self._message_box = MessageBox()
|
self._message_box = MessageBox()
|
||||||
self._entity_cache = EntityCache() # required for proper update handling (to know when to getDifference)
|
self._entity_cache = EntityCache() # required for proper update handling (to know when to getDifference)
|
||||||
|
|
||||||
|
|
|
@ -2791,15 +2791,24 @@ class TelegramClient:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@forward_call(updates.on)
|
@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.
|
Decorator used to `add_event_handler` more conveniently.
|
||||||
|
|
||||||
|
This decorator should be above other decorators which modify the function.
|
||||||
|
|
||||||
Arguments
|
Arguments
|
||||||
event (`_EventBuilder` | `type`):
|
event (`type` | `tuple`):
|
||||||
The event builder class or instance to be used,
|
The event type(s) you wish to receive, for instance ``events.NewMessage``.
|
||||||
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
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -2808,7 +2817,12 @@ class TelegramClient:
|
||||||
client = TelegramClient(...)
|
client = TelegramClient(...)
|
||||||
|
|
||||||
# Here we use client.on
|
# 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):
|
async def handler(event):
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
@ -2816,8 +2830,11 @@ class TelegramClient:
|
||||||
@forward_call(updates.add_event_handler)
|
@forward_call(updates.add_event_handler)
|
||||||
def add_event_handler(
|
def add_event_handler(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
callback: updates.Callback,
|
callback: updates.Callback = None,
|
||||||
event: EventBuilder = None):
|
event: EventBuilder = None,
|
||||||
|
priority=0,
|
||||||
|
**filters
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Registers a new event handler callback.
|
Registers a new event handler callback.
|
||||||
|
|
||||||
|
@ -2827,17 +2844,29 @@ class TelegramClient:
|
||||||
callback (`callable`):
|
callback (`callable`):
|
||||||
The callable function accepting one parameter to be used.
|
The callable function accepting one parameter to be used.
|
||||||
|
|
||||||
Note that if you have used `telethon.events.register` in
|
If `None`, the method can be used as a decorator. Note that the handler function
|
||||||
the callback, ``event`` will be ignored, and instead the
|
will be replaced with the `EventHandler` instance in this case, but it will still
|
||||||
events you previously registered will be used.
|
be callable.
|
||||||
|
|
||||||
event (`_EventBuilder` | `type`, optional):
|
event (`_EventBuilder` | `type`, optional):
|
||||||
The event builder class or instance to be used,
|
The event builder class or instance to be used,
|
||||||
for instance ``events.NewMessage``.
|
for instance ``events.NewMessage``.
|
||||||
|
|
||||||
If left unspecified, `telethon.events.raw.Raw` (the
|
If left unspecified, it will be inferred from the type hint
|
||||||
:tl:`Update` objects with no further processing) will
|
used in the handler, or be `telethon.events.raw.Raw` (the
|
||||||
be passed instead.
|
: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
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -2845,22 +2874,47 @@ class TelegramClient:
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
client = TelegramClient(...)
|
client = TelegramClient(...)
|
||||||
|
|
||||||
|
# Adding a handler, the "boring" way
|
||||||
async def handler(event):
|
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)
|
@forward_call(updates.remove_event_handler)
|
||||||
def remove_event_handler(
|
def remove_event_handler(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
callback: updates.Callback,
|
callback: updates.Callback = None,
|
||||||
event: EventBuilder = None) -> int:
|
event: EventBuilder = None,
|
||||||
|
priority=None,
|
||||||
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Inverse operation of `add_event_handler()`.
|
Inverse operation of `add_event_handler()`.
|
||||||
|
|
||||||
If no event is given, all events for this callback are removed.
|
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
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -2876,6 +2930,12 @@ class TelegramClient:
|
||||||
|
|
||||||
# "handler" will stop receiving anything
|
# "handler" will stop receiving anything
|
||||||
client.remove_event_handler(handler)
|
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)
|
@forward_call(updates.list_event_handlers)
|
||||||
|
@ -2885,7 +2945,7 @@ class TelegramClient:
|
||||||
Lists all registered event handlers.
|
Lists all registered event handlers.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
A list of pairs consisting of ``(callback, event)``.
|
A list of all registered `EventHandler` in arbitrary order.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -2895,8 +2955,8 @@ class TelegramClient:
|
||||||
'''Greets someone'''
|
'''Greets someone'''
|
||||||
await event.reply('Hi')
|
await event.reply('Hi')
|
||||||
|
|
||||||
for callback, event in client.list_event_handlers():
|
for handler in client.list_event_handlers():
|
||||||
print(id(callback), type(event))
|
print(id(handler.callback), handler.event)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@forward_call(updates.catch_up)
|
@forward_call(updates.catch_up)
|
||||||
|
|
|
@ -7,12 +7,15 @@ import time
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
import logging
|
import logging
|
||||||
|
import inspect
|
||||||
|
import bisect
|
||||||
|
import warnings
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from ..errors._rpcbase import RpcError
|
from ..errors._rpcbase import RpcError
|
||||||
from .._events.base import EventBuilder
|
|
||||||
from .._events.raw import Raw
|
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 .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
|
|
||||||
|
@ -33,51 +36,96 @@ async def run_until_disconnected(self: 'TelegramClient'):
|
||||||
await self(_tl.fn.updates.GetState())
|
await self(_tl.fn.updates.GetState())
|
||||||
await self._sender.wait_disconnected()
|
await self._sender.wait_disconnected()
|
||||||
|
|
||||||
def on(self: 'TelegramClient', event: EventBuilder):
|
def on(self: 'TelegramClient', *events, priority=0, **filters):
|
||||||
def decorator(f):
|
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 f
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def add_event_handler(
|
def add_event_handler(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
callback: Callback,
|
callback=None,
|
||||||
event: EventBuilder = None):
|
event=None,
|
||||||
builders = _get_handlers(callback)
|
priority=0,
|
||||||
if builders is not None:
|
**filters
|
||||||
for event in builders:
|
):
|
||||||
self._event_builders.append((event, callback))
|
if callback is None:
|
||||||
return
|
return functools.partial(add_event_handler, self, event=event, priority=priority, **filters)
|
||||||
|
|
||||||
if isinstance(event, type):
|
if event is None:
|
||||||
event = event()
|
for param in inspect.signature(callback).parameters.values():
|
||||||
elif not event:
|
if not issubclass(param.annotation, EventBuilder):
|
||||||
event = Raw()
|
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(
|
def remove_event_handler(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
callback: Callback,
|
callback,
|
||||||
event: EventBuilder = None) -> int:
|
event,
|
||||||
found = 0
|
priority,
|
||||||
if event and not isinstance(event, type):
|
):
|
||||||
event = type(event)
|
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)
|
if not self._update_handlers:
|
||||||
while i:
|
return [] # won't be removing anything (some code paths rely on non-empty lists)
|
||||||
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
|
|
||||||
|
|
||||||
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')\
|
def list_event_handlers(self: 'TelegramClient')\
|
||||||
-> 'typing.Sequence[typing.Tuple[Callback, EventBuilder]]':
|
-> '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'):
|
async def catch_up(self: 'TelegramClient'):
|
||||||
# The update loop is probably blocked on either timeout or an update to arrive.
|
# The update loop is probably blocked on either timeout or an update to arrive.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import abc
|
import abc
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
class StopPropagation(Exception):
|
class StopPropagation(Exception):
|
||||||
|
@ -41,3 +42,23 @@ class EventBuilder(abc.ABC):
|
||||||
`self_id` should be the current user's ID, since it is required
|
`self_id` should be the current user's ID, since it is required
|
||||||
for some events which lack this information but still need it.
|
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
|
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
|
Represents the event of a new message. This event can be treated
|
||||||
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
||||||
|
|
|
@ -32,7 +32,7 @@ def _requires_status(function):
|
||||||
return wrapped
|
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.
|
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.raw import Raw
|
||||||
|
|
||||||
from ._events.album import Album
|
from ._events.album import Album
|
||||||
from ._events.chataction import ChatAction
|
from ._events.chataction import ChatAction
|
||||||
from ._events.messagedeleted import MessageDeleted
|
from ._events.messagedeleted import MessageDeleted
|
||||||
|
|
Loading…
Reference in New Issue
Block a user