mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-29 12:53:44 +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.
|
||||
|
||||
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 ..types import _custom
|
||||
from .._events.common import EventBuilder, EventCommon
|
||||
from .._events.base import EventBuilder
|
||||
from .._misc import enums
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import logging
|
|||
from collections import deque
|
||||
|
||||
from ..errors._rpcbase import RpcError
|
||||
from .._events.common import EventBuilder, EventCommon
|
||||
from .._events.base import EventBuilder
|
||||
from .._events.raw import Raw
|
||||
from .._events.base import StopPropagation, _get_handlers
|
||||
from .._misc import utils
|
||||
|
|
|
@ -2,7 +2,7 @@ import asyncio
|
|||
import time
|
||||
import weakref
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .base import EventBuilder
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..types import _custom
|
||||
|
@ -64,13 +64,16 @@ class AlbumHack:
|
|||
await asyncio.sleep(diff)
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class Album(EventBuilder):
|
||||
class Album(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
Occurs whenever you receive an album. This event only exists
|
||||
to ease dealing with an unknown amount of messages that belong
|
||||
to the same album.
|
||||
|
||||
Members:
|
||||
messages (Sequence[`Message <telethon.tl._custom.message.Message>`]):
|
||||
The list of messages belonging to the same album.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -91,12 +94,20 @@ class Album(EventBuilder):
|
|||
await event.messages[4].reply('Cool!')
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, chats=None, *, blacklist_chats=False, func=None):
|
||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||
_custom.chatgetter.ChatGetter.__init__(self, chat_peer=chat_peer, broadcast=bool(message.post))
|
||||
_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:
|
||||
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)
|
||||
])
|
||||
|
||||
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):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
from .raw import Raw
|
||||
|
||||
|
||||
_HANDLERS_ATTRIBUTE = '__tl.handlers'
|
||||
import abc
|
||||
|
||||
|
||||
class StopPropagation(Exception):
|
||||
|
@ -31,101 +28,16 @@ class StopPropagation(Exception):
|
|||
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
|
||||
`add_event_handler()
|
||||
<telethon.client.updates.UpdateMethods.add_event_handler>` variant.
|
||||
Builds an event for the given update if possible, or returns None.
|
||||
|
||||
Note that this method only registers callbacks as handlers,
|
||||
and does not attach them to any client. This is useful for
|
||||
external modules that don't have access to the client, but
|
||||
still want to define themselves as a handler. Example:
|
||||
`others` are the rest of updates that came in the same container
|
||||
as the current `update`.
|
||||
|
||||
>>> from telethon import events
|
||||
>>> @events.register(events.NewMessage)
|
||||
... 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``.
|
||||
`self_id` should be the current user's ID, since it is required
|
||||
for some events which lack this information but still need it.
|
||||
"""
|
||||
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 functools
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .base import EventBuilder
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..types import _custom
|
||||
|
@ -23,8 +23,7 @@ def auto_answer(func):
|
|||
return wrapped
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class CallbackQuery(EventBuilder):
|
||||
class CallbackQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
Occurs whenever you sign in as a bot and a user
|
||||
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
|
||||
`chat_instance` which should be used for inline callbacks.
|
||||
|
||||
Args:
|
||||
data (`bytes`, `str`, `callable`, optional):
|
||||
If set, the inline button payload data must match this data.
|
||||
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_')``.
|
||||
Members:
|
||||
query (:tl:`UpdateBotCallbackQuery`):
|
||||
The original :tl:`UpdateBotCallbackQuery`.
|
||||
|
||||
pattern (`bytes`, `str`, `callable`, `Pattern`, optional):
|
||||
If set, only buttons with payload matching this pattern will be handled.
|
||||
You can specify a regex-like string which will be matched
|
||||
against the payload data, a callable function that returns `True`
|
||||
if a the payload data is acceptable, or a compiled regex pattern.
|
||||
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``.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -71,39 +69,17 @@ class CallbackQuery(EventBuilder):
|
|||
Button.inline('Nope', b'no')
|
||||
])
|
||||
"""
|
||||
def __init__(
|
||||
self, chats=None, *, blacklist_chats=False, func=None, data=None, pattern=None):
|
||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
||||
|
||||
if data and pattern:
|
||||
raise ValueError("Only pass either data or pattern not both.")
|
||||
|
||||
if isinstance(data, str):
|
||||
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,
|
||||
))
|
||||
def __init__(self, query, peer, msg_id):
|
||||
_custom.chatgetter.ChatGetter.__init__(self, peer)
|
||||
_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
|
||||
|
||||
@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):
|
||||
return cls.Event(update, update.peer, update.msg_id)
|
||||
elif isinstance(update, _tl.UpdateInlineBotCallbackQuery):
|
||||
|
@ -113,57 +89,6 @@ class CallbackQuery(EventBuilder):
|
|||
peer = _tl.PeerChannel(-pid) if pid < 0 else _tl.PeerUser(pid)
|
||||
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):
|
||||
super()._set_client(client)
|
||||
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 .. import _tl
|
||||
from ..types import _custom
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class ChatAction(EventBuilder):
|
||||
"""
|
||||
Occurs on certain chat actions:
|
||||
|
@ -20,6 +19,47 @@ class ChatAction(EventBuilder):
|
|||
Note that "chat" refers to "small group, megagroup and broadcast
|
||||
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
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -32,8 +72,64 @@ class ChatAction(EventBuilder):
|
|||
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
|
||||
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
|
||||
# for new pins (we'd rather handle the new service message with pin,
|
||||
# so that we can act on that message').
|
||||
|
@ -114,108 +210,6 @@ class ChatAction(EventBuilder):
|
|||
return cls.Event(msg,
|
||||
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):
|
||||
"""
|
||||
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
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .base import EventBuilder
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..types import _custom
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class InlineQuery(EventBuilder):
|
||||
class InlineQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
Occurs whenever you sign in as a bot and a user
|
||||
sends an inline query such as ``@bot query``.
|
||||
|
||||
Args:
|
||||
users (`entity`, optional):
|
||||
May be one or more entities (username/peer/etc.), preferably IDs.
|
||||
By default, only inline queries from these users will be handled.
|
||||
Members:
|
||||
query (:tl:`UpdateBotInlineQuery`):
|
||||
The original :tl:`UpdateBotInlineQuery`.
|
||||
|
||||
blacklist_users (`bool`, optional):
|
||||
Whether to treat the users as a blacklist instead of
|
||||
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``.
|
||||
Make sure to access the `text` property of the query if
|
||||
you want the text rather than the actual query object.
|
||||
|
||||
pattern (`str`, `callable`, `Pattern`, optional):
|
||||
If set, only queries matching this pattern will be handled.
|
||||
You can specify a regex-like string which will be matched
|
||||
against the message, a callable function that returns `True`
|
||||
if a message is acceptable, or a compiled regex pattern.
|
||||
pattern_match (`obj`, optional):
|
||||
The resulting object from calling the passed ``pattern``
|
||||
function, which is ``re.compile(...).match`` by default.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -47,55 +40,18 @@ class InlineQuery(EventBuilder):
|
|||
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):
|
||||
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)
|
||||
self.query = query
|
||||
self.pattern_match = None
|
||||
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):
|
||||
super()._set_client(client)
|
||||
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
|
||||
<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)
|
||||
|
||||
|
@ -146,9 +105,6 @@ class InlineQuery(EventBuilder):
|
|||
"""
|
||||
Answers the inline query with the given results.
|
||||
|
||||
See the documentation for `builder` to know what kind of answers
|
||||
can be given.
|
||||
|
||||
Args:
|
||||
results (`list`, optional):
|
||||
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 ..types import _custom
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageDeleted(EventBuilder):
|
||||
class MessageDeleted(EventBuilder, _custom.chatgetter.ChatGetter):
|
||||
"""
|
||||
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
|
||||
|
@ -35,8 +35,13 @@ class MessageDeleted(EventBuilder):
|
|||
for msg_id in event.deleted_ids:
|
||||
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
|
||||
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):
|
||||
return cls.Event(
|
||||
deleted_ids=update.messages,
|
||||
|
@ -47,11 +52,3 @@ class MessageDeleted(EventBuilder):
|
|||
deleted_ids=update.messages,
|
||||
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 .newmessage import NewMessage
|
||||
from .base import EventBuilder
|
||||
from .. import _tl
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageEdited(NewMessage):
|
||||
class MessageEdited(EventBuilder):
|
||||
"""
|
||||
Occurs whenever a message is edited. Just like `NewMessage
|
||||
<telethon.events.newmessage.NewMessage>`, you should treat
|
||||
|
@ -43,10 +41,7 @@ class MessageEdited(NewMessage):
|
|||
print('Message', event.id, 'changed at', event.date)
|
||||
"""
|
||||
@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,
|
||||
_tl.UpdateEditChannelMessage)):
|
||||
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 .. import _tl
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageRead(EventBuilder):
|
||||
"""
|
||||
Occurs whenever one or more messages are read in a chat.
|
||||
|
||||
Args:
|
||||
inbox (`bool`, optional):
|
||||
If this argument is `True`, then when you read someone else's
|
||||
messages the event will be fired. By default (`False`) only
|
||||
when messages you sent are read by someone else will fire it.
|
||||
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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -29,13 +35,17 @@ class MessageRead(EventBuilder):
|
|||
# Log when you read message in a chat (from your "inbox")
|
||||
print('You have read messages until', event.max_id)
|
||||
"""
|
||||
def __init__(
|
||||
self, chats=None, *, blacklist_chats=False, func=None, inbox=False):
|
||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
||||
self.inbox = inbox
|
||||
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)
|
||||
|
||||
@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):
|
||||
return cls.Event(update.peer, update.max_id, False)
|
||||
elif isinstance(update, _tl.UpdateReadHistoryOutbox):
|
||||
|
@ -54,38 +64,6 @@ class MessageRead(EventBuilder):
|
|||
message_ids=update.messages,
|
||||
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
|
||||
def inbox(self):
|
||||
"""
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
import re
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set
|
||||
from .base import EventBuilder
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..types import _custom
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class NewMessage(EventBuilder):
|
||||
class NewMessageEvent(EventBuilder, Message):
|
||||
"""
|
||||
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:
|
||||
incoming (`bool`, optional):
|
||||
If set to `True`, only **incoming** messages will be handled.
|
||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
||||
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.
|
||||
|
||||
outgoing (`bool`, optional):
|
||||
If set to `True`, only **outgoing** messages will be handled.
|
||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||
See `Message <telethon.tl.custom.message.Message>` for
|
||||
the rest of available members and methods.
|
||||
|
||||
from_users (`entity`, optional):
|
||||
Unlike `chats`, this parameter filters the *senders* of the
|
||||
message. That is, only messages *sent by these users* will be
|
||||
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).
|
||||
pattern_match (`obj`):
|
||||
The resulting object from calling the passed ``pattern`` function.
|
||||
Here's an example using a string (defaults to regex match):
|
||||
|
||||
forwards (`bool`, optional):
|
||||
Whether forwarded messages should be handled or not. By default,
|
||||
both forwarded and normal messages are included. If it's `True`
|
||||
*only* forwards will be handled. If it's `False` only messages
|
||||
that are *not* forwards will be handled.
|
||||
|
||||
pattern (`str`, `callable`, `Pattern`, optional):
|
||||
If set, only messages matching this pattern will be handled.
|
||||
You can specify a regex-like string which will be matched
|
||||
against the message, a callable function that returns `True`
|
||||
if a message is acceptable, or a compiled regex pattern.
|
||||
>>> 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))
|
||||
...
|
||||
>>>
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
@ -57,45 +57,16 @@ class NewMessage(EventBuilder):
|
|||
await asyncio.sleep(5)
|
||||
await client.delete_messages(event.chat_id, [event.id, m.id])
|
||||
"""
|
||||
def __init__(self, chats=None, *, blacklist_chats=False, func=None,
|
||||
incoming=None, outgoing=None,
|
||||
from_users=None, forwards=None, pattern=None):
|
||||
if incoming and outgoing:
|
||||
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!")
|
||||
def __init__(self, message):
|
||||
self.__dict__['_init'] = False
|
||||
super().__init__(chat_peer=message.peer_id,
|
||||
msg_id=message.id, broadcast=bool(message.post))
|
||||
|
||||
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
||||
self.incoming = incoming
|
||||
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)
|
||||
self.pattern_match = None
|
||||
self.message = message
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others, self_id, entities, client):
|
||||
def _build(cls, update, others, self_id, entities, client):
|
||||
if isinstance(update,
|
||||
(_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage)):
|
||||
if not isinstance(update.message, _tl.Message):
|
||||
|
@ -139,85 +110,3 @@ class NewMessage(EventBuilder):
|
|||
return
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -8,11 +8,6 @@ class Raw(EventBuilder):
|
|||
:tl:`Update` object that Telegram sends. You normally shouldn't
|
||||
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
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -23,31 +18,6 @@ class Raw(EventBuilder):
|
|||
# Print all incoming updates
|
||||
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
|
||||
def build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||
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 functools
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .base import EventBuilder
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..types import _custom
|
||||
|
@ -32,44 +32,10 @@ def _requires_status(function):
|
|||
return wrapped
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class UserUpdate(EventBuilder):
|
||||
class UserUpdateEvent(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
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:
|
||||
status (:tl:`UserStatus`, optional):
|
||||
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
|
||||
of the typing properties, since they will all be `None`
|
||||
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):
|
||||
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))
|
||||
|
||||
self.status = status
|
||||
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):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
|
Loading…
Reference in New Issue
Block a user