mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-12-01 22:03:46 +03:00
Begin unification of event builders and events
This commit is contained in:
parent
f2ef0bfceb
commit
9726169a8c
|
@ -779,3 +779,12 @@ input_peer removed from get_me; input peers should remain mostly an impl detail
|
||||||
raw api types and fns are now immutable. this can enable optimizations in the future.
|
raw api types and fns are now immutable. this can enable optimizations in the future.
|
||||||
|
|
||||||
upload_file has been removed from the public methods. it's a low-level method users should not need to use.
|
upload_file has been removed from the public methods. it's a low-level method users should not need to use.
|
||||||
|
|
||||||
|
events have changed. rather than differentiating between "event builder" and "event instance", instead there is only the instance, and you register the class.
|
||||||
|
where you had
|
||||||
|
@client.on(events.NewMessage(chats=...))
|
||||||
|
it's now
|
||||||
|
@client.on(events.NewMessage, chats=...)
|
||||||
|
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)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from . import (
|
||||||
)
|
)
|
||||||
from .. import version, _tl
|
from .. import version, _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
from .._events.common import EventBuilder, EventCommon
|
from .._events.base import EventBuilder
|
||||||
from .._misc import enums
|
from .._misc import enums
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import logging
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from ..errors._rpcbase import RpcError
|
from ..errors._rpcbase import RpcError
|
||||||
from .._events.common import EventBuilder, EventCommon
|
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, _get_handlers
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
|
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
import time
|
import time
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
@ -64,13 +64,16 @@ class AlbumHack:
|
||||||
await asyncio.sleep(diff)
|
await asyncio.sleep(diff)
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class Album(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||||
class Album(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever you receive an album. This event only exists
|
Occurs whenever you receive an album. This event only exists
|
||||||
to ease dealing with an unknown amount of messages that belong
|
to ease dealing with an unknown amount of messages that belong
|
||||||
to the same album.
|
to the same album.
|
||||||
|
|
||||||
|
Members:
|
||||||
|
messages (Sequence[`Message <telethon.tl._custom.message.Message>`]):
|
||||||
|
The list of messages belonging to the same album.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -91,12 +94,20 @@ class Album(EventBuilder):
|
||||||
await event.messages[4].reply('Cool!')
|
await event.messages[4].reply('Cool!')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, messages):
|
||||||
self, chats=None, *, blacklist_chats=False, func=None):
|
message = messages[0]
|
||||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
if not message.out and isinstance(message.peer_id, _tl.PeerUser):
|
||||||
|
# Incoming message (e.g. from a bot) has peer_id=us, and
|
||||||
|
# from_id=bot (the actual "chat" from a user's perspective).
|
||||||
|
chat_peer = message.from_id
|
||||||
|
else:
|
||||||
|
chat_peer = message.peer_id
|
||||||
|
|
||||||
@classmethod
|
_custom.chatgetter.ChatGetter.__init__(self, chat_peer=chat_peer, broadcast=bool(message.post))
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
_custom.sendergetter.SenderGetter.__init__(self, message.sender_id)
|
||||||
|
self.messages = messages
|
||||||
|
|
||||||
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
if not others:
|
if not others:
|
||||||
return # We only care about albums which come inside the same Updates
|
return # We only care about albums which come inside the same Updates
|
||||||
|
|
||||||
|
@ -135,34 +146,6 @@ class Album(EventBuilder):
|
||||||
and u.message.grouped_id == group)
|
and u.message.grouped_id == group)
|
||||||
])
|
])
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
# Albums with less than two messages require a few hacks to work.
|
|
||||||
if len(event.messages) > 1:
|
|
||||||
return super().filter(event)
|
|
||||||
|
|
||||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
|
||||||
"""
|
|
||||||
Represents the event of a new album.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
messages (Sequence[`Message <telethon.tl._custom.message.Message>`]):
|
|
||||||
The list of messages belonging to the same album.
|
|
||||||
"""
|
|
||||||
def __init__(self, messages):
|
|
||||||
message = messages[0]
|
|
||||||
if not message.out and isinstance(message.peer_id, _tl.PeerUser):
|
|
||||||
# Incoming message (e.g. from a bot) has peer_id=us, and
|
|
||||||
# from_id=bot (the actual "chat" from a user's perspective).
|
|
||||||
chat_peer = message.from_id
|
|
||||||
else:
|
|
||||||
chat_peer = message.peer_id
|
|
||||||
|
|
||||||
super().__init__(chat_peer=chat_peer,
|
|
||||||
msg_id=message.id, broadcast=bool(message.post))
|
|
||||||
|
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, message.sender_id)
|
|
||||||
self.messages = messages
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
def _set_client(self, client):
|
||||||
super()._set_client(client)
|
super()._set_client(client)
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
from .raw import Raw
|
import abc
|
||||||
|
|
||||||
|
|
||||||
_HANDLERS_ATTRIBUTE = '__tl.handlers'
|
|
||||||
|
|
||||||
|
|
||||||
class StopPropagation(Exception):
|
class StopPropagation(Exception):
|
||||||
|
@ -31,101 +28,16 @@ class StopPropagation(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def register(event=None):
|
class EventBuilder(abc.ABC):
|
||||||
|
@classmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _build(cls, update, others, self_id, entities, client):
|
||||||
"""
|
"""
|
||||||
Decorator method to *register* event handlers. This is the client-less
|
Builds an event for the given update if possible, or returns None.
|
||||||
`add_event_handler()
|
|
||||||
<telethon.client.updates.UpdateMethods.add_event_handler>` variant.
|
|
||||||
|
|
||||||
Note that this method only registers callbacks as handlers,
|
`others` are the rest of updates that came in the same container
|
||||||
and does not attach them to any client. This is useful for
|
as the current `update`.
|
||||||
external modules that don't have access to the client, but
|
|
||||||
still want to define themselves as a handler. Example:
|
|
||||||
|
|
||||||
>>> from telethon import events
|
`self_id` should be the current user's ID, since it is required
|
||||||
>>> @events.register(events.NewMessage)
|
for some events which lack this information but still need it.
|
||||||
... async def handler(event):
|
|
||||||
... ...
|
|
||||||
...
|
|
||||||
>>> # (somewhere else)
|
|
||||||
...
|
|
||||||
>>> from telethon import TelegramClient
|
|
||||||
>>> client = TelegramClient(...)
|
|
||||||
>>> client.add_event_handler(handler)
|
|
||||||
|
|
||||||
Remember that you can use this as a non-decorator
|
|
||||||
through ``register(event)(callback)``.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event (`_EventBuilder` | `type`):
|
|
||||||
The event builder class or instance to be used,
|
|
||||||
for instance ``events.NewMessage``.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(event, type):
|
|
||||||
event = event()
|
|
||||||
elif not event:
|
|
||||||
event = Raw()
|
|
||||||
|
|
||||||
def decorator(callback):
|
|
||||||
handlers = getattr(callback, _HANDLERS_ATTRIBUTE, [])
|
|
||||||
handlers.append(event)
|
|
||||||
setattr(callback, _HANDLERS_ATTRIBUTE, handlers)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def unregister(callback, event=None):
|
|
||||||
"""
|
|
||||||
Inverse operation of `register` (though not a decorator). Client-less
|
|
||||||
`remove_event_handler
|
|
||||||
<telethon.client.updates.UpdateMethods.remove_event_handler>`
|
|
||||||
variant. **Note that this won't remove handlers from the client**,
|
|
||||||
because it simply can't, so you would generally use this before
|
|
||||||
adding the handlers to the client.
|
|
||||||
|
|
||||||
This method is here for symmetry. You will rarely need to
|
|
||||||
unregister events, since you can simply just not add them
|
|
||||||
to any client.
|
|
||||||
|
|
||||||
If no event is given, all events for this callback are removed.
|
|
||||||
Returns how many callbacks were removed.
|
|
||||||
"""
|
|
||||||
found = 0
|
|
||||||
if event and not isinstance(event, type):
|
|
||||||
event = type(event)
|
|
||||||
|
|
||||||
handlers = getattr(callback, _HANDLERS_ATTRIBUTE, [])
|
|
||||||
handlers.append((event, callback))
|
|
||||||
i = len(handlers)
|
|
||||||
while i:
|
|
||||||
i -= 1
|
|
||||||
ev = handlers[i]
|
|
||||||
if not event or isinstance(ev, event):
|
|
||||||
del handlers[i]
|
|
||||||
found += 1
|
|
||||||
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
def is_handler(callback):
|
|
||||||
"""
|
|
||||||
Returns `True` if the given callback is an
|
|
||||||
event handler (i.e. you used `register` on it).
|
|
||||||
"""
|
|
||||||
return hasattr(callback, _HANDLERS_ATTRIBUTE)
|
|
||||||
|
|
||||||
|
|
||||||
def list(callback):
|
|
||||||
"""
|
|
||||||
Returns a list containing the registered event
|
|
||||||
builders inside the specified callback handler.
|
|
||||||
"""
|
|
||||||
return getattr(callback, _HANDLERS_ATTRIBUTE, [])[:]
|
|
||||||
|
|
||||||
|
|
||||||
def _get_handlers(callback):
|
|
||||||
"""
|
|
||||||
Like ``list`` but returns `None` if the callback was never registered.
|
|
||||||
"""
|
|
||||||
return getattr(callback, _HANDLERS_ATTRIBUTE, None)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import struct
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
@ -23,8 +23,7 @@ def auto_answer(func):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class CallbackQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||||
class CallbackQuery(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever you sign in as a bot and a user
|
Occurs whenever you sign in as a bot and a user
|
||||||
clicks one of the inline buttons on your messages.
|
clicks one of the inline buttons on your messages.
|
||||||
|
@ -34,18 +33,17 @@ class CallbackQuery(EventBuilder):
|
||||||
message. The `chats` parameter also supports checking against the
|
message. The `chats` parameter also supports checking against the
|
||||||
`chat_instance` which should be used for inline callbacks.
|
`chat_instance` which should be used for inline callbacks.
|
||||||
|
|
||||||
Args:
|
Members:
|
||||||
data (`bytes`, `str`, `callable`, optional):
|
query (:tl:`UpdateBotCallbackQuery`):
|
||||||
If set, the inline button payload data must match this data.
|
The original :tl:`UpdateBotCallbackQuery`.
|
||||||
A UTF-8 string can also be given, a regex or a callable. For
|
|
||||||
instance, to check against ``'data_1'`` and ``'data_2'`` you
|
|
||||||
can use ``re.compile(b'data_')``.
|
|
||||||
|
|
||||||
pattern (`bytes`, `str`, `callable`, `Pattern`, optional):
|
data_match (`obj`, optional):
|
||||||
If set, only buttons with payload matching this pattern will be handled.
|
The object returned by the ``data=`` parameter
|
||||||
You can specify a regex-like string which will be matched
|
when creating the event builder, if any. Similar
|
||||||
against the payload data, a callable function that returns `True`
|
to ``pattern_match`` for the new message event.
|
||||||
if a the payload data is acceptable, or a compiled regex pattern.
|
|
||||||
|
pattern_match (`obj`, optional):
|
||||||
|
Alias for ``data_match``.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -71,39 +69,17 @@ class CallbackQuery(EventBuilder):
|
||||||
Button.inline('Nope', b'no')
|
Button.inline('Nope', b'no')
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(self, query, peer, msg_id):
|
||||||
self, chats=None, *, blacklist_chats=False, func=None, data=None, pattern=None):
|
_custom.chatgetter.ChatGetter.__init__(self, peer)
|
||||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
||||||
|
self.query = query
|
||||||
if data and pattern:
|
self.data_match = None
|
||||||
raise ValueError("Only pass either data or pattern not both.")
|
self.pattern_match = None
|
||||||
|
self._message = None
|
||||||
if isinstance(data, str):
|
self._answered = False
|
||||||
data = data.encode('utf-8')
|
|
||||||
if isinstance(pattern, str):
|
|
||||||
pattern = pattern.encode('utf-8')
|
|
||||||
|
|
||||||
match = data if data else pattern
|
|
||||||
|
|
||||||
if isinstance(match, bytes):
|
|
||||||
self.match = data if data else re.compile(pattern).match
|
|
||||||
elif not match or callable(match):
|
|
||||||
self.match = match
|
|
||||||
elif hasattr(match, 'match') and callable(match.match):
|
|
||||||
if not isinstance(getattr(match, 'pattern', b''), bytes):
|
|
||||||
match = re.compile(match.pattern.encode('utf-8'),
|
|
||||||
match.flags & (~re.UNICODE))
|
|
||||||
|
|
||||||
self.match = match.match
|
|
||||||
else:
|
|
||||||
raise TypeError('Invalid data or pattern type given')
|
|
||||||
|
|
||||||
self._no_check = all(x is None for x in (
|
|
||||||
self.chats, self.func, self.match,
|
|
||||||
))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
if isinstance(update, _tl.UpdateBotCallbackQuery):
|
if isinstance(update, _tl.UpdateBotCallbackQuery):
|
||||||
return cls.Event(update, update.peer, update.msg_id)
|
return cls.Event(update, update.peer, update.msg_id)
|
||||||
elif isinstance(update, _tl.UpdateInlineBotCallbackQuery):
|
elif isinstance(update, _tl.UpdateInlineBotCallbackQuery):
|
||||||
|
@ -113,57 +89,6 @@ class CallbackQuery(EventBuilder):
|
||||||
peer = _tl.PeerChannel(-pid) if pid < 0 else _tl.PeerUser(pid)
|
peer = _tl.PeerChannel(-pid) if pid < 0 else _tl.PeerUser(pid)
|
||||||
return cls.Event(update, peer, mid)
|
return cls.Event(update, peer, mid)
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
# We can't call super().filter(...) because it ignores chat_instance
|
|
||||||
if self._no_check:
|
|
||||||
return event
|
|
||||||
|
|
||||||
if self.chats is not None:
|
|
||||||
inside = event.query.chat_instance in self.chats
|
|
||||||
if event.chat_id:
|
|
||||||
inside |= event.chat_id in self.chats
|
|
||||||
|
|
||||||
if inside == self.blacklist_chats:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.match:
|
|
||||||
if callable(self.match):
|
|
||||||
event.data_match = event.pattern_match = self.match(event.query.data)
|
|
||||||
if not event.data_match:
|
|
||||||
return
|
|
||||||
elif event.query.data != self.match:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.func:
|
|
||||||
# Return the result of func directly as it may need to be awaited
|
|
||||||
return self.func(event)
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
|
||||||
"""
|
|
||||||
Represents the event of a new callback query.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
query (:tl:`UpdateBotCallbackQuery`):
|
|
||||||
The original :tl:`UpdateBotCallbackQuery`.
|
|
||||||
|
|
||||||
data_match (`obj`, optional):
|
|
||||||
The object returned by the ``data=`` parameter
|
|
||||||
when creating the event builder, if any. Similar
|
|
||||||
to ``pattern_match`` for the new message event.
|
|
||||||
|
|
||||||
pattern_match (`obj`, optional):
|
|
||||||
Alias for ``data_match``.
|
|
||||||
"""
|
|
||||||
def __init__(self, query, peer, msg_id):
|
|
||||||
super().__init__(peer, msg_id=msg_id)
|
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
|
||||||
self.query = query
|
|
||||||
self.data_match = None
|
|
||||||
self.pattern_match = None
|
|
||||||
self._message = None
|
|
||||||
self._answered = False
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
def _set_client(self, client):
|
||||||
super()._set_client(client)
|
super()._set_client(client)
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
|
||||||
class ChatAction(EventBuilder):
|
class ChatAction(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Occurs on certain chat actions:
|
Occurs on certain chat actions:
|
||||||
|
@ -20,6 +19,47 @@ class ChatAction(EventBuilder):
|
||||||
Note that "chat" refers to "small group, megagroup and broadcast
|
Note that "chat" refers to "small group, megagroup and broadcast
|
||||||
channel", whereas "group" refers to "small group and megagroup" only.
|
channel", whereas "group" refers to "small group and megagroup" only.
|
||||||
|
|
||||||
|
Members:
|
||||||
|
action_message (`MessageAction <https://tl.telethon.dev/types/message_action.html>`_):
|
||||||
|
The message invoked by this Chat Action.
|
||||||
|
|
||||||
|
new_pin (`bool`):
|
||||||
|
`True` if there is a new pin.
|
||||||
|
|
||||||
|
new_photo (`bool`):
|
||||||
|
`True` if there's a new chat photo (or it was removed).
|
||||||
|
|
||||||
|
photo (:tl:`Photo`, optional):
|
||||||
|
The new photo (or `None` if it was removed).
|
||||||
|
|
||||||
|
user_added (`bool`):
|
||||||
|
`True` if the user was added by some other.
|
||||||
|
|
||||||
|
user_joined (`bool`):
|
||||||
|
`True` if the user joined on their own.
|
||||||
|
|
||||||
|
user_left (`bool`):
|
||||||
|
`True` if the user left on their own.
|
||||||
|
|
||||||
|
user_kicked (`bool`):
|
||||||
|
`True` if the user was kicked by some other.
|
||||||
|
|
||||||
|
user_approved (`bool`):
|
||||||
|
`True` if the user's join request was approved.
|
||||||
|
along with `user_joined` will be also True.
|
||||||
|
|
||||||
|
created (`bool`, optional):
|
||||||
|
`True` if this chat was just created.
|
||||||
|
|
||||||
|
new_title (`str`, optional):
|
||||||
|
The new title string for the chat, if applicable.
|
||||||
|
|
||||||
|
new_score (`str`, optional):
|
||||||
|
The new score string for the game, if applicable.
|
||||||
|
|
||||||
|
unpin (`bool`):
|
||||||
|
`True` if the existing pin gets unpinned.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -32,8 +72,64 @@ class ChatAction(EventBuilder):
|
||||||
await event.reply('Welcome to the group!')
|
await event.reply('Welcome to the group!')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, where, new_photo=None,
|
||||||
|
added_by=None, kicked_by=None, created=None, from_approval=None,
|
||||||
|
users=None, new_title=None, pin_ids=None, pin=None, new_score=None):
|
||||||
|
if isinstance(where, _tl.MessageService):
|
||||||
|
self.action_message = where
|
||||||
|
where = where.peer_id
|
||||||
|
else:
|
||||||
|
self.action_message = None
|
||||||
|
|
||||||
|
# TODO needs some testing (can there be more than one id, and do they follow pin order?)
|
||||||
|
# same in get_pinned_message
|
||||||
|
super().__init__(chat_peer=where, msg_id=pin_ids[0] if pin_ids else None)
|
||||||
|
|
||||||
|
self.new_pin = pin_ids is not None
|
||||||
|
self._pin_ids = pin_ids
|
||||||
|
self._pinned_messages = None
|
||||||
|
|
||||||
|
self.new_photo = new_photo is not None
|
||||||
|
self.photo = \
|
||||||
|
new_photo if isinstance(new_photo, _tl.Photo) else None
|
||||||
|
|
||||||
|
self._added_by = None
|
||||||
|
self._kicked_by = None
|
||||||
|
self.user_added = self.user_joined = self.user_left = \
|
||||||
|
self.user_kicked = self.unpin = False
|
||||||
|
|
||||||
|
if added_by is True or from_approval is True:
|
||||||
|
self.user_joined = True
|
||||||
|
elif added_by:
|
||||||
|
self.user_added = True
|
||||||
|
self._added_by = added_by
|
||||||
|
self.user_approved = from_approval
|
||||||
|
|
||||||
|
# If `from_id` was not present (it's `True`) or the affected
|
||||||
|
# user was "kicked by itself", then it left. Else it was kicked.
|
||||||
|
if kicked_by is True or (users is not None and kicked_by == users):
|
||||||
|
self.user_left = True
|
||||||
|
elif kicked_by:
|
||||||
|
self.user_kicked = True
|
||||||
|
self._kicked_by = kicked_by
|
||||||
|
|
||||||
|
self.created = bool(created)
|
||||||
|
|
||||||
|
if isinstance(users, list):
|
||||||
|
self._user_ids = [utils.get_peer_id(u) for u in users]
|
||||||
|
elif users:
|
||||||
|
self._user_ids = [utils.get_peer_id(users)]
|
||||||
|
else:
|
||||||
|
self._user_ids = []
|
||||||
|
|
||||||
|
self._users = None
|
||||||
|
self._input_users = None
|
||||||
|
self.new_title = new_title
|
||||||
|
self.new_score = new_score
|
||||||
|
self.unpin = not pin
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
# Rely on specific pin updates for unpins, but otherwise ignore them
|
# Rely on specific pin updates for unpins, but otherwise ignore them
|
||||||
# for new pins (we'd rather handle the new service message with pin,
|
# for new pins (we'd rather handle the new service message with pin,
|
||||||
# so that we can act on that message').
|
# so that we can act on that message').
|
||||||
|
@ -114,108 +210,6 @@ class ChatAction(EventBuilder):
|
||||||
return cls.Event(msg,
|
return cls.Event(msg,
|
||||||
new_score=action.score)
|
new_score=action.score)
|
||||||
|
|
||||||
class Event(EventCommon):
|
|
||||||
"""
|
|
||||||
Represents the event of a new chat action.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
action_message (`MessageAction <https://tl.telethon.dev/types/message_action.html>`_):
|
|
||||||
The message invoked by this Chat Action.
|
|
||||||
|
|
||||||
new_pin (`bool`):
|
|
||||||
`True` if there is a new pin.
|
|
||||||
|
|
||||||
new_photo (`bool`):
|
|
||||||
`True` if there's a new chat photo (or it was removed).
|
|
||||||
|
|
||||||
photo (:tl:`Photo`, optional):
|
|
||||||
The new photo (or `None` if it was removed).
|
|
||||||
|
|
||||||
user_added (`bool`):
|
|
||||||
`True` if the user was added by some other.
|
|
||||||
|
|
||||||
user_joined (`bool`):
|
|
||||||
`True` if the user joined on their own.
|
|
||||||
|
|
||||||
user_left (`bool`):
|
|
||||||
`True` if the user left on their own.
|
|
||||||
|
|
||||||
user_kicked (`bool`):
|
|
||||||
`True` if the user was kicked by some other.
|
|
||||||
|
|
||||||
user_approved (`bool`):
|
|
||||||
`True` if the user's join request was approved.
|
|
||||||
along with `user_joined` will be also True.
|
|
||||||
|
|
||||||
created (`bool`, optional):
|
|
||||||
`True` if this chat was just created.
|
|
||||||
|
|
||||||
new_title (`str`, optional):
|
|
||||||
The new title string for the chat, if applicable.
|
|
||||||
|
|
||||||
new_score (`str`, optional):
|
|
||||||
The new score string for the game, if applicable.
|
|
||||||
|
|
||||||
unpin (`bool`):
|
|
||||||
`True` if the existing pin gets unpinned.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, where, new_photo=None,
|
|
||||||
added_by=None, kicked_by=None, created=None, from_approval=None,
|
|
||||||
users=None, new_title=None, pin_ids=None, pin=None, new_score=None):
|
|
||||||
if isinstance(where, _tl.MessageService):
|
|
||||||
self.action_message = where
|
|
||||||
where = where.peer_id
|
|
||||||
else:
|
|
||||||
self.action_message = None
|
|
||||||
|
|
||||||
# TODO needs some testing (can there be more than one id, and do they follow pin order?)
|
|
||||||
# same in get_pinned_message
|
|
||||||
super().__init__(chat_peer=where, msg_id=pin_ids[0] if pin_ids else None)
|
|
||||||
|
|
||||||
self.new_pin = pin_ids is not None
|
|
||||||
self._pin_ids = pin_ids
|
|
||||||
self._pinned_messages = None
|
|
||||||
|
|
||||||
self.new_photo = new_photo is not None
|
|
||||||
self.photo = \
|
|
||||||
new_photo if isinstance(new_photo, _tl.Photo) else None
|
|
||||||
|
|
||||||
self._added_by = None
|
|
||||||
self._kicked_by = None
|
|
||||||
self.user_added = self.user_joined = self.user_left = \
|
|
||||||
self.user_kicked = self.unpin = False
|
|
||||||
|
|
||||||
if added_by is True or from_approval is True:
|
|
||||||
self.user_joined = True
|
|
||||||
elif added_by:
|
|
||||||
self.user_added = True
|
|
||||||
self._added_by = added_by
|
|
||||||
self.user_approved = from_approval
|
|
||||||
|
|
||||||
# If `from_id` was not present (it's `True`) or the affected
|
|
||||||
# user was "kicked by itself", then it left. Else it was kicked.
|
|
||||||
if kicked_by is True or (users is not None and kicked_by == users):
|
|
||||||
self.user_left = True
|
|
||||||
elif kicked_by:
|
|
||||||
self.user_kicked = True
|
|
||||||
self._kicked_by = kicked_by
|
|
||||||
|
|
||||||
self.created = bool(created)
|
|
||||||
|
|
||||||
if isinstance(users, list):
|
|
||||||
self._user_ids = [utils.get_peer_id(u) for u in users]
|
|
||||||
elif users:
|
|
||||||
self._user_ids = [utils.get_peer_id(users)]
|
|
||||||
else:
|
|
||||||
self._user_ids = []
|
|
||||||
|
|
||||||
self._users = None
|
|
||||||
self._input_users = None
|
|
||||||
self.new_title = new_title
|
|
||||||
self.new_score = new_score
|
|
||||||
self.unpin = not pin
|
|
||||||
|
|
||||||
async def respond(self, *args, **kwargs):
|
async def respond(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Responds to the chat action message (not as a reply). Shorthand for
|
Responds to the chat action message (not as a reply). Shorthand for
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
import abc
|
|
||||||
import asyncio
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from .. import _tl
|
|
||||||
from .._misc import utils, tlobject
|
|
||||||
from ..types._custom.chatgetter import ChatGetter
|
|
||||||
|
|
||||||
|
|
||||||
async def _into_id_set(client, chats):
|
|
||||||
"""Helper util to turn the input chat or chats into a set of IDs."""
|
|
||||||
if chats is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not utils.is_list_like(chats):
|
|
||||||
chats = (chats,)
|
|
||||||
|
|
||||||
result = set()
|
|
||||||
for chat in chats:
|
|
||||||
if isinstance(chat, int):
|
|
||||||
result.add(chat)
|
|
||||||
elif isinstance(chat, tlobject.TLObject) and chat.SUBCLASS_OF_ID == 0x2d45687:
|
|
||||||
# 0x2d45687 == crc32(b'Peer')
|
|
||||||
result.add(utils.get_peer_id(chat))
|
|
||||||
else:
|
|
||||||
chat = await client.get_input_entity(chat)
|
|
||||||
if isinstance(chat, _tl.InputPeerSelf):
|
|
||||||
chat = _tl.PeerUser(self._session_state.user_id)
|
|
||||||
result.add(utils.get_peer_id(chat))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class EventBuilder(abc.ABC):
|
|
||||||
"""
|
|
||||||
The common event builder, with builtin support to filter per chat.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
chats (`entity`, optional):
|
|
||||||
May be one or more entities (username/peer/etc.), preferably IDs.
|
|
||||||
By default, only matching chats will be handled.
|
|
||||||
|
|
||||||
blacklist_chats (`bool`, optional):
|
|
||||||
Whether to treat the chats as a blacklist instead of
|
|
||||||
as a whitelist (default). This means that every chat
|
|
||||||
will be handled *except* those specified in ``chats``
|
|
||||||
which will be ignored if ``blacklist_chats=True``.
|
|
||||||
|
|
||||||
func (`callable`, optional):
|
|
||||||
A callable (async or not) function that should accept the event as input
|
|
||||||
parameter, and return a value indicating whether the event
|
|
||||||
should be dispatched or not (any truthy value will do, it
|
|
||||||
does not need to be a `bool`). It works like a custom filter:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@client.on(events.NewMessage(func=lambda e: e.is_private))
|
|
||||||
async def handler(event):
|
|
||||||
pass # code here
|
|
||||||
"""
|
|
||||||
def __init__(self, chats=None, *, blacklist_chats=False, func=None):
|
|
||||||
self.chats = chats
|
|
||||||
self.blacklist_chats = bool(blacklist_chats)
|
|
||||||
self.resolved = False
|
|
||||||
self.func = func
|
|
||||||
self._resolve_lock = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abc.abstractmethod
|
|
||||||
def build(cls, update, others, self_id, entities, client):
|
|
||||||
"""
|
|
||||||
Builds an event for the given update if possible, or returns None.
|
|
||||||
|
|
||||||
`others` are the rest of updates that came in the same container
|
|
||||||
as the current `update`.
|
|
||||||
|
|
||||||
`self_id` should be the current user's ID, since it is required
|
|
||||||
for some events which lack this information but still need it.
|
|
||||||
"""
|
|
||||||
# TODO So many parameters specific to only some update types seems dirty
|
|
||||||
|
|
||||||
async def resolve(self, client):
|
|
||||||
"""Helper method to allow event builders to be resolved before usage"""
|
|
||||||
if self.resolved:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self._resolve_lock:
|
|
||||||
self._resolve_lock = asyncio.Lock()
|
|
||||||
|
|
||||||
async with self._resolve_lock:
|
|
||||||
if not self.resolved:
|
|
||||||
await self._resolve(client)
|
|
||||||
self.resolved = True
|
|
||||||
|
|
||||||
async def _resolve(self, client):
|
|
||||||
self.chats = await _into_id_set(client, self.chats)
|
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
"""
|
|
||||||
Returns a truthy value if the event passed the filter and should be
|
|
||||||
used, or falsy otherwise. The return value may need to be awaited.
|
|
||||||
|
|
||||||
The events must have been resolved before this can be called.
|
|
||||||
"""
|
|
||||||
if not self.resolved:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.chats is not None:
|
|
||||||
# Note: the `event.chat_id` property checks if it's `None` for us
|
|
||||||
inside = event.chat_id in self.chats
|
|
||||||
if inside == self.blacklist_chats:
|
|
||||||
# If this chat matches but it's a blacklist ignore.
|
|
||||||
# If it doesn't match but it's a whitelist ignore.
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.func:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Return the result of func directly as it may need to be awaited
|
|
||||||
return self.func(event)
|
|
||||||
|
|
||||||
|
|
||||||
class EventCommon(ChatGetter, abc.ABC):
|
|
||||||
"""
|
|
||||||
Intermediate class with common things to all events.
|
|
||||||
|
|
||||||
Remember that this class implements `ChatGetter
|
|
||||||
<telethon.tl.custom.chatgetter.ChatGetter>` which
|
|
||||||
means you have access to all chat properties and methods.
|
|
||||||
|
|
||||||
In addition, you can access the `original_update`
|
|
||||||
field which contains the original :tl:`Update`.
|
|
||||||
"""
|
|
||||||
_event_name = 'Event'
|
|
||||||
|
|
||||||
def __init__(self, chat_peer=None, msg_id=None, broadcast=None):
|
|
||||||
super().__init__(chat_peer, broadcast=broadcast)
|
|
||||||
self._entities = {}
|
|
||||||
self._client = None
|
|
||||||
self._message_id = msg_id
|
|
||||||
self.original_update = None
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
|
||||||
"""
|
|
||||||
Setter so subclasses can act accordingly when the client is set.
|
|
||||||
"""
|
|
||||||
# TODO Nuke
|
|
||||||
self._client = client
|
|
||||||
if self._chat_peer:
|
|
||||||
self._chat, self._input_chat = utils._get_entity_pair(self.chat_id, self._entities)
|
|
||||||
else:
|
|
||||||
self._chat = self._input_chat = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def client(self):
|
|
||||||
"""
|
|
||||||
The `telethon.TelegramClient` that created this event.
|
|
||||||
"""
|
|
||||||
return self._client
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _tl.TLObject.pretty_format(self.to_dict())
|
|
||||||
|
|
||||||
def stringify(self):
|
|
||||||
return _tl.TLObject.pretty_format(self.to_dict(), indent=0)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
d = {k: v for k, v in self.__dict__.items() if k[0] != '_'}
|
|
||||||
d['_'] = self._event_name
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def name_inner_event(cls):
|
|
||||||
"""Decorator to rename cls.Event 'Event' as 'cls.Event'"""
|
|
||||||
if hasattr(cls, 'Event'):
|
|
||||||
cls.Event._event_name = '{}.Event'.format(cls.__name__)
|
|
||||||
else:
|
|
||||||
warnings.warn('Class {} does not have a inner Event'.format(cls))
|
|
||||||
return cls
|
|
|
@ -3,34 +3,27 @@ import re
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class InlineQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||||
class InlineQuery(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever you sign in as a bot and a user
|
Occurs whenever you sign in as a bot and a user
|
||||||
sends an inline query such as ``@bot query``.
|
sends an inline query such as ``@bot query``.
|
||||||
|
|
||||||
Args:
|
Members:
|
||||||
users (`entity`, optional):
|
query (:tl:`UpdateBotInlineQuery`):
|
||||||
May be one or more entities (username/peer/etc.), preferably IDs.
|
The original :tl:`UpdateBotInlineQuery`.
|
||||||
By default, only inline queries from these users will be handled.
|
|
||||||
|
|
||||||
blacklist_users (`bool`, optional):
|
Make sure to access the `text` property of the query if
|
||||||
Whether to treat the users as a blacklist instead of
|
you want the text rather than the actual query object.
|
||||||
as a whitelist (default). This means that every chat
|
|
||||||
will be handled *except* those specified in ``users``
|
|
||||||
which will be ignored if ``blacklist_users=True``.
|
|
||||||
|
|
||||||
pattern (`str`, `callable`, `Pattern`, optional):
|
pattern_match (`obj`, optional):
|
||||||
If set, only queries matching this pattern will be handled.
|
The resulting object from calling the passed ``pattern``
|
||||||
You can specify a regex-like string which will be matched
|
function, which is ``re.compile(...).match`` by default.
|
||||||
against the message, a callable function that returns `True`
|
|
||||||
if a message is acceptable, or a compiled regex pattern.
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -47,55 +40,18 @@ class InlineQuery(EventBuilder):
|
||||||
builder.article('lowercase', text=event.text.lower()),
|
builder.article('lowercase', text=event.text.lower()),
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
def __init__(
|
|
||||||
self, users=None, *, blacklist_users=False, func=None, pattern=None):
|
|
||||||
super().__init__(users, blacklist_chats=blacklist_users, func=func)
|
|
||||||
|
|
||||||
if isinstance(pattern, str):
|
|
||||||
self.pattern = re.compile(pattern).match
|
|
||||||
elif not pattern or callable(pattern):
|
|
||||||
self.pattern = pattern
|
|
||||||
elif hasattr(pattern, 'match') and callable(pattern.match):
|
|
||||||
self.pattern = pattern.match
|
|
||||||
else:
|
|
||||||
raise TypeError('Invalid pattern type given')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
if isinstance(update, _tl.UpdateBotInlineQuery):
|
|
||||||
return cls.Event(update)
|
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
if self.pattern:
|
|
||||||
match = self.pattern(event.text)
|
|
||||||
if not match:
|
|
||||||
return
|
|
||||||
event.pattern_match = match
|
|
||||||
|
|
||||||
return super().filter(event)
|
|
||||||
|
|
||||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
|
||||||
"""
|
|
||||||
Represents the event of a new callback query.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
query (:tl:`UpdateBotInlineQuery`):
|
|
||||||
The original :tl:`UpdateBotInlineQuery`.
|
|
||||||
|
|
||||||
Make sure to access the `text` property of the query if
|
|
||||||
you want the text rather than the actual query object.
|
|
||||||
|
|
||||||
pattern_match (`obj`, optional):
|
|
||||||
The resulting object from calling the passed ``pattern``
|
|
||||||
function, which is ``re.compile(...).match`` by default.
|
|
||||||
"""
|
|
||||||
def __init__(self, query):
|
def __init__(self, query):
|
||||||
super().__init__(chat_peer=_tl.PeerUser(query.user_id))
|
_custom.chatgetter.ChatGetter.__init__(self, _tl.PeerUser(query.user_id))
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
||||||
self.query = query
|
self.query = query
|
||||||
self.pattern_match = None
|
self.pattern_match = None
|
||||||
self._answered = False
|
self._answered = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
|
if isinstance(update, _tl.UpdateBotInlineQuery):
|
||||||
|
return cls.Event(update)
|
||||||
|
|
||||||
def _set_client(self, client):
|
def _set_client(self, client):
|
||||||
super()._set_client(client)
|
super()._set_client(client)
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||||
|
@ -136,6 +92,9 @@ class InlineQuery(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Returns a new `InlineBuilder
|
Returns a new `InlineBuilder
|
||||||
<telethon.tl.custom.inlinebuilder.InlineBuilder>` instance.
|
<telethon.tl.custom.inlinebuilder.InlineBuilder>` instance.
|
||||||
|
|
||||||
|
See the documentation for `builder` to know what kind of answers
|
||||||
|
can be given.
|
||||||
"""
|
"""
|
||||||
return _custom.InlineBuilder(self._client)
|
return _custom.InlineBuilder(self._client)
|
||||||
|
|
||||||
|
@ -146,9 +105,6 @@ class InlineQuery(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Answers the inline query with the given results.
|
Answers the inline query with the given results.
|
||||||
|
|
||||||
See the documentation for `builder` to know what kind of answers
|
|
||||||
can be given.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
results (`list`, optional):
|
results (`list`, optional):
|
||||||
A list of :tl:`InputBotInlineResult` to use.
|
A list of :tl:`InputBotInlineResult` to use.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
|
from ..types import _custom
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class MessageDeleted(EventBuilder, _custom.chatgetter.ChatGetter):
|
||||||
class MessageDeleted(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever a message is deleted. Note that this event isn't 100%
|
Occurs whenever a message is deleted. Note that this event isn't 100%
|
||||||
reliable, since Telegram doesn't always notify the clients that a message
|
reliable, since Telegram doesn't always notify the clients that a message
|
||||||
|
@ -35,8 +35,13 @@ class MessageDeleted(EventBuilder):
|
||||||
for msg_id in event.deleted_ids:
|
for msg_id in event.deleted_ids:
|
||||||
print('Message', msg_id, 'was deleted in', event.chat_id)
|
print('Message', msg_id, 'was deleted in', event.chat_id)
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, deleted_ids, peer):
|
||||||
|
_custom.chatgetter.ChatGetter.__init__(self, chat_peer=peer)
|
||||||
|
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
||||||
|
self.deleted_ids = deleted_ids
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
if isinstance(update, _tl.UpdateDeleteMessages):
|
if isinstance(update, _tl.UpdateDeleteMessages):
|
||||||
return cls.Event(
|
return cls.Event(
|
||||||
deleted_ids=update.messages,
|
deleted_ids=update.messages,
|
||||||
|
@ -47,11 +52,3 @@ class MessageDeleted(EventBuilder):
|
||||||
deleted_ids=update.messages,
|
deleted_ids=update.messages,
|
||||||
peer=_tl.PeerChannel(update.channel_id)
|
peer=_tl.PeerChannel(update.channel_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Event(EventCommon):
|
|
||||||
def __init__(self, deleted_ids, peer):
|
|
||||||
super().__init__(
|
|
||||||
chat_peer=peer, msg_id=(deleted_ids or [0])[0]
|
|
||||||
)
|
|
||||||
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
|
||||||
self.deleted_ids = deleted_ids
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from .common import name_inner_event
|
from .base import EventBuilder
|
||||||
from .newmessage import NewMessage
|
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class MessageEdited(EventBuilder):
|
||||||
class MessageEdited(NewMessage):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever a message is edited. Just like `NewMessage
|
Occurs whenever a message is edited. Just like `NewMessage
|
||||||
<telethon.events.newmessage.NewMessage>`, you should treat
|
<telethon.events.newmessage.NewMessage>`, you should treat
|
||||||
|
@ -43,10 +41,7 @@ class MessageEdited(NewMessage):
|
||||||
print('Message', event.id, 'changed at', event.date)
|
print('Message', event.id, 'changed at', event.date)
|
||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
if isinstance(update, (_tl.UpdateEditMessage,
|
if isinstance(update, (_tl.UpdateEditMessage,
|
||||||
_tl.UpdateEditChannelMessage)):
|
_tl.UpdateEditChannelMessage)):
|
||||||
return cls.Event(update.message)
|
return cls.Event(update.message)
|
||||||
|
|
||||||
class Event(NewMessage.Event):
|
|
||||||
pass # Required if we want a different name for it
|
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
|
||||||
class MessageRead(EventBuilder):
|
class MessageRead(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Occurs whenever one or more messages are read in a chat.
|
Occurs whenever one or more messages are read in a chat.
|
||||||
|
|
||||||
Args:
|
Members:
|
||||||
inbox (`bool`, optional):
|
max_id (`int`):
|
||||||
If this argument is `True`, then when you read someone else's
|
Up to which message ID has been read. Every message
|
||||||
messages the event will be fired. By default (`False`) only
|
with an ID equal or lower to it have been read.
|
||||||
when messages you sent are read by someone else will fire it.
|
|
||||||
|
outbox (`bool`):
|
||||||
|
`True` if someone else has read your messages.
|
||||||
|
|
||||||
|
contents (`bool`):
|
||||||
|
`True` if what was read were the contents of a message.
|
||||||
|
This will be the case when e.g. you play a voice note.
|
||||||
|
It may only be set on ``inbox`` events.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -29,13 +35,17 @@ class MessageRead(EventBuilder):
|
||||||
# Log when you read message in a chat (from your "inbox")
|
# Log when you read message in a chat (from your "inbox")
|
||||||
print('You have read messages until', event.max_id)
|
print('You have read messages until', event.max_id)
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(self, peer=None, max_id=None, out=False, contents=False,
|
||||||
self, chats=None, *, blacklist_chats=False, func=None, inbox=False):
|
message_ids=None):
|
||||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
self.outbox = out
|
||||||
self.inbox = inbox
|
self.contents = contents
|
||||||
|
self._message_ids = message_ids or []
|
||||||
|
self._messages = None
|
||||||
|
self.max_id = max_id or max(message_ids or [], default=None)
|
||||||
|
super().__init__(peer, self.max_id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
if isinstance(update, _tl.UpdateReadHistoryInbox):
|
if isinstance(update, _tl.UpdateReadHistoryInbox):
|
||||||
return cls.Event(update.peer, update.max_id, False)
|
return cls.Event(update.peer, update.max_id, False)
|
||||||
elif isinstance(update, _tl.UpdateReadHistoryOutbox):
|
elif isinstance(update, _tl.UpdateReadHistoryOutbox):
|
||||||
|
@ -54,38 +64,6 @@ class MessageRead(EventBuilder):
|
||||||
message_ids=update.messages,
|
message_ids=update.messages,
|
||||||
contents=True)
|
contents=True)
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
if self.inbox == event.outbox:
|
|
||||||
return
|
|
||||||
|
|
||||||
return super().filter(event)
|
|
||||||
|
|
||||||
class Event(EventCommon):
|
|
||||||
"""
|
|
||||||
Represents the event of one or more messages being read.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
max_id (`int`):
|
|
||||||
Up to which message ID has been read. Every message
|
|
||||||
with an ID equal or lower to it have been read.
|
|
||||||
|
|
||||||
outbox (`bool`):
|
|
||||||
`True` if someone else has read your messages.
|
|
||||||
|
|
||||||
contents (`bool`):
|
|
||||||
`True` if what was read were the contents of a message.
|
|
||||||
This will be the case when e.g. you play a voice note.
|
|
||||||
It may only be set on ``inbox`` events.
|
|
||||||
"""
|
|
||||||
def __init__(self, peer=None, max_id=None, out=False, contents=False,
|
|
||||||
message_ids=None):
|
|
||||||
self.outbox = out
|
|
||||||
self.contents = contents
|
|
||||||
self._message_ids = message_ids or []
|
|
||||||
self._messages = None
|
|
||||||
self.max_id = max_id or max(message_ids or [], default=None)
|
|
||||||
super().__init__(peer, self.max_id)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inbox(self):
|
def inbox(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,43 +1,43 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class NewMessageEvent(EventBuilder, Message):
|
||||||
class NewMessage(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever a new text message or a message with media arrives.
|
Represents the event of a new message. This event can be treated
|
||||||
|
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
||||||
|
so please **refer to its documentation** to know what you can do
|
||||||
|
with this event.
|
||||||
|
|
||||||
Args:
|
Members:
|
||||||
incoming (`bool`, optional):
|
message (`Message <telethon.tl.custom.message.Message>`):
|
||||||
If set to `True`, only **incoming** messages will be handled.
|
This is the only difference with the received
|
||||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
`Message <telethon.tl.custom.message.Message>`, and will
|
||||||
|
return the `telethon.tl.custom.message.Message` itself,
|
||||||
|
not the text.
|
||||||
|
|
||||||
outgoing (`bool`, optional):
|
See `Message <telethon.tl.custom.message.Message>` for
|
||||||
If set to `True`, only **outgoing** messages will be handled.
|
the rest of available members and methods.
|
||||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
|
||||||
|
|
||||||
from_users (`entity`, optional):
|
pattern_match (`obj`):
|
||||||
Unlike `chats`, this parameter filters the *senders* of the
|
The resulting object from calling the passed ``pattern`` function.
|
||||||
message. That is, only messages *sent by these users* will be
|
Here's an example using a string (defaults to regex match):
|
||||||
handled. Use `chats` if you want private messages with this/these
|
|
||||||
users. `from_users` lets you filter by messages sent by *one or
|
|
||||||
more* users across the desired chats (doesn't need a list).
|
|
||||||
|
|
||||||
forwards (`bool`, optional):
|
>>> from telethon import TelegramClient, events
|
||||||
Whether forwarded messages should be handled or not. By default,
|
>>> client = TelegramClient(...)
|
||||||
both forwarded and normal messages are included. If it's `True`
|
>>>
|
||||||
*only* forwards will be handled. If it's `False` only messages
|
>>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!'))
|
||||||
that are *not* forwards will be handled.
|
... async def handler(event):
|
||||||
|
... # In this case, the result is a ``Match`` object
|
||||||
pattern (`str`, `callable`, `Pattern`, optional):
|
... # since the `str` pattern was converted into
|
||||||
If set, only messages matching this pattern will be handled.
|
... # the ``re.compile(pattern).match`` function.
|
||||||
You can specify a regex-like string which will be matched
|
... print('Welcomed', event.pattern_match.group(1))
|
||||||
against the message, a callable function that returns `True`
|
...
|
||||||
if a message is acceptable, or a compiled regex pattern.
|
>>>
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -57,45 +57,16 @@ class NewMessage(EventBuilder):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
await client.delete_messages(event.chat_id, [event.id, m.id])
|
await client.delete_messages(event.chat_id, [event.id, m.id])
|
||||||
"""
|
"""
|
||||||
def __init__(self, chats=None, *, blacklist_chats=False, func=None,
|
def __init__(self, message):
|
||||||
incoming=None, outgoing=None,
|
self.__dict__['_init'] = False
|
||||||
from_users=None, forwards=None, pattern=None):
|
super().__init__(chat_peer=message.peer_id,
|
||||||
if incoming and outgoing:
|
msg_id=message.id, broadcast=bool(message.post))
|
||||||
incoming = outgoing = None # Same as no filter
|
|
||||||
elif incoming is not None and outgoing is None:
|
|
||||||
outgoing = not incoming
|
|
||||||
elif outgoing is not None and incoming is None:
|
|
||||||
incoming = not outgoing
|
|
||||||
elif all(x is not None and not x for x in (incoming, outgoing)):
|
|
||||||
raise ValueError("Don't create an event handler if you "
|
|
||||||
"don't want neither incoming nor outgoing!")
|
|
||||||
|
|
||||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
self.pattern_match = None
|
||||||
self.incoming = incoming
|
self.message = message
|
||||||
self.outgoing = outgoing
|
|
||||||
self.from_users = from_users
|
|
||||||
self.forwards = forwards
|
|
||||||
if isinstance(pattern, str):
|
|
||||||
self.pattern = re.compile(pattern).match
|
|
||||||
elif not pattern or callable(pattern):
|
|
||||||
self.pattern = pattern
|
|
||||||
elif hasattr(pattern, 'match') and callable(pattern.match):
|
|
||||||
self.pattern = pattern.match
|
|
||||||
else:
|
|
||||||
raise TypeError('Invalid pattern type given')
|
|
||||||
|
|
||||||
# Should we short-circuit? E.g. perform no check at all
|
|
||||||
self._no_check = all(x is None for x in (
|
|
||||||
self.chats, self.incoming, self.outgoing, self.pattern,
|
|
||||||
self.from_users, self.forwards, self.from_users, self.func
|
|
||||||
))
|
|
||||||
|
|
||||||
async def _resolve(self, client):
|
|
||||||
await super()._resolve(client)
|
|
||||||
self.from_users = await _into_id_set(client, self.from_users)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others, self_id, entities, client):
|
def _build(cls, update, others, self_id, entities, client):
|
||||||
if isinstance(update,
|
if isinstance(update,
|
||||||
(_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage)):
|
(_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage)):
|
||||||
if not isinstance(update.message, _tl.Message):
|
if not isinstance(update.message, _tl.Message):
|
||||||
|
@ -139,85 +110,3 @@ class NewMessage(EventBuilder):
|
||||||
return
|
return
|
||||||
|
|
||||||
return cls.Event(_custom.Message._new(client, msg, entities, None))
|
return cls.Event(_custom.Message._new(client, msg, entities, None))
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
if self._no_check:
|
|
||||||
return event
|
|
||||||
|
|
||||||
if self.incoming and event.message.out:
|
|
||||||
return
|
|
||||||
if self.outgoing and not event.message.out:
|
|
||||||
return
|
|
||||||
if self.forwards is not None:
|
|
||||||
if bool(self.forwards) != bool(event.message.fwd_from):
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.from_users is not None:
|
|
||||||
if event.message.sender_id not in self.from_users:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.pattern:
|
|
||||||
match = self.pattern(event.message.message or '')
|
|
||||||
if not match:
|
|
||||||
return
|
|
||||||
event.pattern_match = match
|
|
||||||
|
|
||||||
return super().filter(event)
|
|
||||||
|
|
||||||
class Event(EventCommon):
|
|
||||||
"""
|
|
||||||
Represents the event of a new message. This event can be treated
|
|
||||||
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
|
||||||
so please **refer to its documentation** to know what you can do
|
|
||||||
with this event.
|
|
||||||
|
|
||||||
Members:
|
|
||||||
message (`Message <telethon.tl.custom.message.Message>`):
|
|
||||||
This is the only difference with the received
|
|
||||||
`Message <telethon.tl.custom.message.Message>`, and will
|
|
||||||
return the `telethon.tl.custom.message.Message` itself,
|
|
||||||
not the text.
|
|
||||||
|
|
||||||
See `Message <telethon.tl.custom.message.Message>` for
|
|
||||||
the rest of available members and methods.
|
|
||||||
|
|
||||||
pattern_match (`obj`):
|
|
||||||
The resulting object from calling the passed ``pattern`` function.
|
|
||||||
Here's an example using a string (defaults to regex match):
|
|
||||||
|
|
||||||
>>> from telethon import TelegramClient, events
|
|
||||||
>>> client = TelegramClient(...)
|
|
||||||
>>>
|
|
||||||
>>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!'))
|
|
||||||
... async def handler(event):
|
|
||||||
... # In this case, the result is a ``Match`` object
|
|
||||||
... # since the `str` pattern was converted into
|
|
||||||
... # the ``re.compile(pattern).match`` function.
|
|
||||||
... print('Welcomed', event.pattern_match.group(1))
|
|
||||||
...
|
|
||||||
>>>
|
|
||||||
"""
|
|
||||||
def __init__(self, message):
|
|
||||||
self.__dict__['_init'] = False
|
|
||||||
super().__init__(chat_peer=message.peer_id,
|
|
||||||
msg_id=message.id, broadcast=bool(message.post))
|
|
||||||
|
|
||||||
self.pattern_match = None
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
|
||||||
super()._set_client(client)
|
|
||||||
m = self.message
|
|
||||||
self.__dict__['_init'] = True # No new attributes can be set
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
if item in self.__dict__:
|
|
||||||
return self.__dict__[item]
|
|
||||||
else:
|
|
||||||
return getattr(self.message, item)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
if not self.__dict__['_init'] or name in self.__dict__:
|
|
||||||
self.__dict__[name] = value
|
|
||||||
else:
|
|
||||||
setattr(self.message, name, value)
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .common import EventBuilder
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,11 +8,6 @@ class Raw(EventBuilder):
|
||||||
:tl:`Update` object that Telegram sends. You normally shouldn't
|
:tl:`Update` object that Telegram sends. You normally shouldn't
|
||||||
need these.
|
need these.
|
||||||
|
|
||||||
Args:
|
|
||||||
types (`list` | `tuple` | `type`, optional):
|
|
||||||
The type or types that the :tl:`Update` instance must be.
|
|
||||||
Equivalent to ``if not isinstance(update, types): return``.
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -23,31 +18,6 @@ class Raw(EventBuilder):
|
||||||
# Print all incoming updates
|
# Print all incoming updates
|
||||||
print(update.stringify())
|
print(update.stringify())
|
||||||
"""
|
"""
|
||||||
def __init__(self, types=None, *, func=None):
|
|
||||||
super().__init__(func=func)
|
|
||||||
if not types:
|
|
||||||
self.types = None
|
|
||||||
elif not utils.is_list_like(types):
|
|
||||||
if not isinstance(types, type):
|
|
||||||
raise TypeError('Invalid input type given: {}'.format(types))
|
|
||||||
|
|
||||||
self.types = types
|
|
||||||
else:
|
|
||||||
if not all(isinstance(x, type) for x in types):
|
|
||||||
raise TypeError('Invalid input types given: {}'.format(types))
|
|
||||||
|
|
||||||
self.types = tuple(types)
|
|
||||||
|
|
||||||
async def resolve(self, client):
|
|
||||||
self.resolved = True
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
return update
|
return update
|
||||||
|
|
||||||
def filter(self, event):
|
|
||||||
if not self.types or isinstance(event, self.types):
|
|
||||||
if self.func:
|
|
||||||
# Return the result of func directly as it may need to be awaited
|
|
||||||
return self.func(event)
|
|
||||||
return event
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from .common import EventBuilder, EventCommon, name_inner_event
|
from .base import EventBuilder
|
||||||
from .._misc import utils
|
from .._misc import utils
|
||||||
from .. import _tl
|
from .. import _tl
|
||||||
from ..types import _custom
|
from ..types import _custom
|
||||||
|
@ -32,44 +32,10 @@ def _requires_status(function):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@name_inner_event
|
class UserUpdateEvent(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||||
class UserUpdate(EventBuilder):
|
|
||||||
"""
|
"""
|
||||||
Occurs whenever a user goes online, starts typing, etc.
|
Occurs whenever a user goes online, starts typing, etc.
|
||||||
|
|
||||||
Example
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from telethon import events
|
|
||||||
|
|
||||||
@client.on(events.UserUpdate)
|
|
||||||
async def handler(event):
|
|
||||||
# If someone is uploading, say something
|
|
||||||
if event.uploading:
|
|
||||||
await client.send_message(event.user_id, 'What are you sending?')
|
|
||||||
"""
|
|
||||||
@classmethod
|
|
||||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
if isinstance(update, _tl.UpdateUserStatus):
|
|
||||||
return cls.Event(_tl.PeerUser(update.user_id),
|
|
||||||
status=update.status)
|
|
||||||
elif isinstance(update, _tl.UpdateChannelUserTyping):
|
|
||||||
return cls.Event(update.from_id,
|
|
||||||
chat_peer=_tl.PeerChannel(update.channel_id),
|
|
||||||
typing=update.action)
|
|
||||||
elif isinstance(update, _tl.UpdateChatUserTyping):
|
|
||||||
return cls.Event(update.from_id,
|
|
||||||
chat_peer=_tl.PeerChat(update.chat_id),
|
|
||||||
typing=update.action)
|
|
||||||
elif isinstance(update, _tl.UpdateUserTyping):
|
|
||||||
return cls.Event(update.user_id,
|
|
||||||
typing=update.action)
|
|
||||||
|
|
||||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
|
||||||
"""
|
|
||||||
Represents the event of a user update
|
|
||||||
such as gone online, started typing, etc.
|
|
||||||
|
|
||||||
Members:
|
Members:
|
||||||
status (:tl:`UserStatus`, optional):
|
status (:tl:`UserStatus`, optional):
|
||||||
The user status if the update is about going online or offline.
|
The user status if the update is about going online or offline.
|
||||||
|
@ -84,14 +50,42 @@ class UserUpdate(EventBuilder):
|
||||||
You should check this attribute first before checking any
|
You should check this attribute first before checking any
|
||||||
of the typing properties, since they will all be `None`
|
of the typing properties, since they will all be `None`
|
||||||
if the action is not set.
|
if the action is not set.
|
||||||
|
|
||||||
|
Example
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import events
|
||||||
|
|
||||||
|
@client.on(events.UserUpdate)
|
||||||
|
async def handler(event):
|
||||||
|
# If someone is uploading, say something
|
||||||
|
if event.uploading:
|
||||||
|
await client.send_message(event.user_id, 'What are you sending?')
|
||||||
"""
|
"""
|
||||||
def __init__(self, peer, *, status=None, chat_peer=None, typing=None):
|
def __init__(self, peer, *, status=None, chat_peer=None, typing=None):
|
||||||
super().__init__(chat_peer or peer)
|
_custom.chatgetter.ChatGetter.__init__(self, chat_peer or peer)
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, utils.get_peer_id(peer))
|
_custom.sendergetter.SenderGetter.__init__(self, utils.get_peer_id(peer))
|
||||||
|
|
||||||
self.status = status
|
self.status = status
|
||||||
self.action = typing
|
self.action = typing
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||||
|
if isinstance(update, _tl.UpdateUserStatus):
|
||||||
|
return UserUpdateEvent(_tl.PeerUser(update.user_id),
|
||||||
|
status=update.status)
|
||||||
|
elif isinstance(update, _tl.UpdateChannelUserTyping):
|
||||||
|
return UserUpdateEvent(update.from_id,
|
||||||
|
chat_peer=_tl.PeerChannel(update.channel_id),
|
||||||
|
typing=update.action)
|
||||||
|
elif isinstance(update, _tl.UpdateChatUserTyping):
|
||||||
|
return UserUpdateEvent(update.from_id,
|
||||||
|
chat_peer=_tl.PeerChat(update.chat_id),
|
||||||
|
typing=update.action)
|
||||||
|
elif isinstance(update, _tl.UpdateUserTyping):
|
||||||
|
return UserUpdateEvent(update.user_id,
|
||||||
|
typing=update.action)
|
||||||
|
|
||||||
def _set_client(self, client):
|
def _set_client(self, client):
|
||||||
super()._set_client(client)
|
super()._set_client(client)
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user