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,216 +146,188 @@ 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)
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
||||
"""
|
||||
Represents the event of a new album.
|
||||
self.messages = [
|
||||
_custom.Message._new(client, m, self._entities, None)
|
||||
for m in self.messages
|
||||
]
|
||||
|
||||
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
|
||||
if len(self.messages) == 1:
|
||||
# This will require hacks to be a proper album event
|
||||
hack = client._albums.get(self.grouped_id)
|
||||
if hack is None:
|
||||
client._albums[self.grouped_id] = AlbumHack(client, self)
|
||||
else:
|
||||
chat_peer = message.peer_id
|
||||
hack.extend(self.messages)
|
||||
|
||||
super().__init__(chat_peer=chat_peer,
|
||||
msg_id=message.id, broadcast=bool(message.post))
|
||||
@property
|
||||
def grouped_id(self):
|
||||
"""
|
||||
The shared ``grouped_id`` between all the messages.
|
||||
"""
|
||||
return self.messages[0].grouped_id
|
||||
|
||||
_custom.sendergetter.SenderGetter.__init__(self, message.sender_id)
|
||||
self.messages = messages
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The message text of the first photo with a caption,
|
||||
formatted using the client's default parse mode.
|
||||
"""
|
||||
return next((m.text for m in self.messages if m.text), '')
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
@property
|
||||
def raw_text(self):
|
||||
"""
|
||||
The raw message text of the first photo
|
||||
with a caption, ignoring any formatting.
|
||||
"""
|
||||
return next((m.raw_text for m in self.messages if m.raw_text), '')
|
||||
|
||||
self.messages = [
|
||||
_custom.Message._new(client, m, self._entities, None)
|
||||
for m in self.messages
|
||||
]
|
||||
@property
|
||||
def is_reply(self):
|
||||
"""
|
||||
`True` if the album is a reply to some other message.
|
||||
|
||||
if len(self.messages) == 1:
|
||||
# This will require hacks to be a proper album event
|
||||
hack = client._albums.get(self.grouped_id)
|
||||
if hack is None:
|
||||
client._albums[self.grouped_id] = AlbumHack(client, self)
|
||||
else:
|
||||
hack.extend(self.messages)
|
||||
Remember that you can access the ID of the message
|
||||
this one is replying to through `reply_to_msg_id`,
|
||||
and the `Message` object with `get_reply_message()`.
|
||||
"""
|
||||
# Each individual message in an album all reply to the same message
|
||||
return self.messages[0].is_reply
|
||||
|
||||
@property
|
||||
def grouped_id(self):
|
||||
"""
|
||||
The shared ``grouped_id`` between all the messages.
|
||||
"""
|
||||
return self.messages[0].grouped_id
|
||||
@property
|
||||
def forward(self):
|
||||
"""
|
||||
The `Forward <telethon.tl._custom.forward.Forward>`
|
||||
information for the first message in the album if it was forwarded.
|
||||
"""
|
||||
# Each individual message in an album all reply to the same message
|
||||
return self.messages[0].forward
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The message text of the first photo with a caption,
|
||||
formatted using the client's default parse mode.
|
||||
"""
|
||||
return next((m.text for m in self.messages if m.text), '')
|
||||
# endregion Public Properties
|
||||
|
||||
@property
|
||||
def raw_text(self):
|
||||
"""
|
||||
The raw message text of the first photo
|
||||
with a caption, ignoring any formatting.
|
||||
"""
|
||||
return next((m.raw_text for m in self.messages if m.raw_text), '')
|
||||
# region Public Methods
|
||||
|
||||
@property
|
||||
def is_reply(self):
|
||||
"""
|
||||
`True` if the album is a reply to some other message.
|
||||
async def get_reply_message(self):
|
||||
"""
|
||||
The `Message <telethon.tl._custom.message.Message>`
|
||||
that this album is replying to, or `None`.
|
||||
|
||||
Remember that you can access the ID of the message
|
||||
this one is replying to through `reply_to_msg_id`,
|
||||
and the `Message` object with `get_reply_message()`.
|
||||
"""
|
||||
# Each individual message in an album all reply to the same message
|
||||
return self.messages[0].is_reply
|
||||
The result will be cached after its first use.
|
||||
"""
|
||||
return await self.messages[0].get_reply_message()
|
||||
|
||||
@property
|
||||
def forward(self):
|
||||
"""
|
||||
The `Forward <telethon.tl._custom.forward.Forward>`
|
||||
information for the first message in the album if it was forwarded.
|
||||
"""
|
||||
# Each individual message in an album all reply to the same message
|
||||
return self.messages[0].forward
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the album (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message`
|
||||
with ``entity`` already set.
|
||||
"""
|
||||
return await self.messages[0].respond(*args, **kwargs)
|
||||
|
||||
# endregion Public Properties
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the first photo in the album (as a reply). Shorthand
|
||||
for `telethon.client.messages.MessageMethods.send_message`
|
||||
with both ``entity`` and ``reply_to`` already set.
|
||||
"""
|
||||
return await self.messages[0].reply(*args, **kwargs)
|
||||
|
||||
# region Public Methods
|
||||
async def forward_to(self, *args, **kwargs):
|
||||
"""
|
||||
Forwards the entire album. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.forward_messages`
|
||||
with both ``messages`` and ``from_peer`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
kwargs['messages'] = self.messages
|
||||
kwargs['from_peer'] = await self.get_input_chat()
|
||||
return await self._client.forward_messages(*args, **kwargs)
|
||||
|
||||
async def get_reply_message(self):
|
||||
"""
|
||||
The `Message <telethon.tl._custom.message.Message>`
|
||||
that this album is replying to, or `None`.
|
||||
async def edit(self, *args, **kwargs):
|
||||
"""
|
||||
Edits the first caption or the message, or the first messages'
|
||||
caption if no caption is set, iff it's outgoing. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.edit_message`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
|
||||
The result will be cached after its first use.
|
||||
"""
|
||||
return await self.messages[0].get_reply_message()
|
||||
Returns `None` if the message was incoming,
|
||||
or the edited `Message` otherwise.
|
||||
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the album (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message`
|
||||
with ``entity`` already set.
|
||||
"""
|
||||
return await self.messages[0].respond(*args, **kwargs)
|
||||
.. note::
|
||||
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the first photo in the album (as a reply). Shorthand
|
||||
for `telethon.client.messages.MessageMethods.send_message`
|
||||
with both ``entity`` and ``reply_to`` already set.
|
||||
"""
|
||||
return await self.messages[0].reply(*args, **kwargs)
|
||||
This is different from `client.edit_message
|
||||
<telethon.client.messages.MessageMethods.edit_message>`
|
||||
and **will respect** the previous state of the message.
|
||||
For example, if the message didn't have a link preview,
|
||||
the edit won't add one by default, and you should force
|
||||
it by setting it to `True` if you want it.
|
||||
|
||||
async def forward_to(self, *args, **kwargs):
|
||||
"""
|
||||
Forwards the entire album. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.forward_messages`
|
||||
with both ``messages`` and ``from_peer`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
kwargs['messages'] = self.messages
|
||||
kwargs['from_peer'] = await self.get_input_chat()
|
||||
return await self._client.forward_messages(*args, **kwargs)
|
||||
This is generally the most desired and convenient behaviour,
|
||||
and will work for link previews and message buttons.
|
||||
"""
|
||||
for msg in self.messages:
|
||||
if msg.raw_text:
|
||||
return await msg.edit(*args, **kwargs)
|
||||
|
||||
async def edit(self, *args, **kwargs):
|
||||
"""
|
||||
Edits the first caption or the message, or the first messages'
|
||||
caption if no caption is set, iff it's outgoing. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.edit_message`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
return await self.messages[0].edit(*args, **kwargs)
|
||||
|
||||
Returns `None` if the message was incoming,
|
||||
or the edited `Message` otherwise.
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the entire album. You're responsible for checking whether
|
||||
you have the permission to do so, or to except the error otherwise.
|
||||
Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), self.messages,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
.. note::
|
||||
async def mark_read(self):
|
||||
"""
|
||||
Marks the entire album as read. Shorthand for
|
||||
`client.mark_read()
|
||||
<telethon.client.messages.MessageMethods.mark_read>`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
await self._client.mark_read(
|
||||
await self.get_input_chat(), max_id=self.messages[-1].id)
|
||||
|
||||
This is different from `client.edit_message
|
||||
<telethon.client.messages.MessageMethods.edit_message>`
|
||||
and **will respect** the previous state of the message.
|
||||
For example, if the message didn't have a link preview,
|
||||
the edit won't add one by default, and you should force
|
||||
it by setting it to `True` if you want it.
|
||||
async def pin(self, *, notify=False):
|
||||
"""
|
||||
Pins the first photo in the album. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.pin_message`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
return await self.messages[0].pin(notify=notify)
|
||||
|
||||
This is generally the most desired and convenient behaviour,
|
||||
and will work for link previews and message buttons.
|
||||
"""
|
||||
for msg in self.messages:
|
||||
if msg.raw_text:
|
||||
return await msg.edit(*args, **kwargs)
|
||||
def __len__(self):
|
||||
"""
|
||||
Return the amount of messages in the album.
|
||||
|
||||
return await self.messages[0].edit(*args, **kwargs)
|
||||
Equivalent to ``len(self.messages)``.
|
||||
"""
|
||||
return len(self.messages)
|
||||
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the entire album. You're responsible for checking whether
|
||||
you have the permission to do so, or to except the error otherwise.
|
||||
Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), self.messages,
|
||||
*args, **kwargs
|
||||
)
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate over the messages in the album.
|
||||
|
||||
async def mark_read(self):
|
||||
"""
|
||||
Marks the entire album as read. Shorthand for
|
||||
`client.mark_read()
|
||||
<telethon.client.messages.MessageMethods.mark_read>`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
await self._client.mark_read(
|
||||
await self.get_input_chat(), max_id=self.messages[-1].id)
|
||||
Equivalent to ``iter(self.messages)``.
|
||||
"""
|
||||
return iter(self.messages)
|
||||
|
||||
async def pin(self, *, notify=False):
|
||||
"""
|
||||
Pins the first photo in the album. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.pin_message`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
return await self.messages[0].pin(notify=notify)
|
||||
def __getitem__(self, n):
|
||||
"""
|
||||
Access the n'th message in the album.
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Return the amount of messages in the album.
|
||||
|
||||
Equivalent to ``len(self.messages)``.
|
||||
"""
|
||||
return len(self.messages)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate over the messages in the album.
|
||||
|
||||
Equivalent to ``iter(self.messages)``.
|
||||
"""
|
||||
return iter(self.messages)
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""
|
||||
Access the n'th message in the album.
|
||||
|
||||
Equivalent to ``event.messages[n]``.
|
||||
"""
|
||||
return self.messages[n]
|
||||
Equivalent to ``event.messages[n]``.
|
||||
"""
|
||||
return self.messages[n]
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
Decorator method to *register* event handlers. This is the client-less
|
||||
`add_event_handler()
|
||||
<telethon.client.updates.UpdateMethods.add_event_handler>` variant.
|
||||
class EventBuilder(abc.ABC):
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _build(cls, update, others, self_id, entities, client):
|
||||
"""
|
||||
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``.
|
||||
"""
|
||||
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)
|
||||
`self_id` should be the current user's ID, since it is required
|
||||
for some events which lack this information but still need it.
|
||||
"""
|
||||
|
|
|
@ -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,242 +89,191 @@ 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
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
||||
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):
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
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``.
|
||||
Returns the query ID. The user clicking the inline
|
||||
button is the one who generated this random ID.
|
||||
"""
|
||||
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
|
||||
return self.query.query_id
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
@property
|
||||
def message_id(self):
|
||||
"""
|
||||
Returns the message ID to which the clicked inline button belongs.
|
||||
"""
|
||||
return self._message_id
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
Returns the query ID. The user clicking the inline
|
||||
button is the one who generated this random ID.
|
||||
"""
|
||||
return self.query.query_id
|
||||
@property
|
||||
def data(self):
|
||||
"""
|
||||
Returns the data payload from the original inline button.
|
||||
"""
|
||||
return self.query.data
|
||||
|
||||
@property
|
||||
def message_id(self):
|
||||
"""
|
||||
Returns the message ID to which the clicked inline button belongs.
|
||||
"""
|
||||
return self._message_id
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""
|
||||
Returns the data payload from the original inline button.
|
||||
"""
|
||||
return self.query.data
|
||||
|
||||
@property
|
||||
def chat_instance(self):
|
||||
"""
|
||||
Unique identifier for the chat where the callback occurred.
|
||||
Useful for high scores in games.
|
||||
"""
|
||||
return self.query.chat_instance
|
||||
|
||||
async def get_message(self):
|
||||
"""
|
||||
Returns the message to which the clicked inline button belongs.
|
||||
"""
|
||||
if self._message is not None:
|
||||
return self._message
|
||||
|
||||
try:
|
||||
chat = await self.get_input_chat() if self.is_channel else None
|
||||
self._message = await self._client.get_messages(
|
||||
chat, ids=self._message_id)
|
||||
except ValueError:
|
||||
return
|
||||
@property
|
||||
def chat_instance(self):
|
||||
"""
|
||||
Unique identifier for the chat where the callback occurred.
|
||||
Useful for high scores in games.
|
||||
"""
|
||||
return self.query.chat_instance
|
||||
|
||||
async def get_message(self):
|
||||
"""
|
||||
Returns the message to which the clicked inline button belongs.
|
||||
"""
|
||||
if self._message is not None:
|
||||
return self._message
|
||||
|
||||
async def _refetch_sender(self):
|
||||
self._sender = self._entities.get(self.sender_id)
|
||||
if not self._sender:
|
||||
return
|
||||
try:
|
||||
chat = await self.get_input_chat() if self.is_channel else None
|
||||
self._message = await self._client.get_messages(
|
||||
chat, ids=self._message_id)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
self._input_sender = utils.get_input_peer(self._chat)
|
||||
if not getattr(self._input_sender, 'access_hash', True):
|
||||
# getattr with True to handle the InputPeerSelf() case
|
||||
m = await self.get_message()
|
||||
if m:
|
||||
self._sender = m._sender
|
||||
self._input_sender = m._input_sender
|
||||
return self._message
|
||||
|
||||
async def answer(
|
||||
self, message=None, cache_time=0, *, url=None, alert=False):
|
||||
"""
|
||||
Answers the callback query (and stops the loading circle).
|
||||
async def _refetch_sender(self):
|
||||
self._sender = self._entities.get(self.sender_id)
|
||||
if not self._sender:
|
||||
return
|
||||
|
||||
Args:
|
||||
message (`str`, optional):
|
||||
The toast message to show feedback to the user.
|
||||
self._input_sender = utils.get_input_peer(self._chat)
|
||||
if not getattr(self._input_sender, 'access_hash', True):
|
||||
# getattr with True to handle the InputPeerSelf() case
|
||||
m = await self.get_message()
|
||||
if m:
|
||||
self._sender = m._sender
|
||||
self._input_sender = m._input_sender
|
||||
|
||||
cache_time (`int`, optional):
|
||||
For how long this result should be cached on
|
||||
the user's client. Defaults to 0 for no cache.
|
||||
async def answer(
|
||||
self, message=None, cache_time=0, *, url=None, alert=False):
|
||||
"""
|
||||
Answers the callback query (and stops the loading circle).
|
||||
|
||||
url (`str`, optional):
|
||||
The URL to be opened in the user's client. Note that
|
||||
the only valid URLs are those of games your bot has,
|
||||
or alternatively a 't.me/your_bot?start=xyz' parameter.
|
||||
Args:
|
||||
message (`str`, optional):
|
||||
The toast message to show feedback to the user.
|
||||
|
||||
alert (`bool`, optional):
|
||||
Whether an alert (a pop-up dialog) should be used
|
||||
instead of showing a toast. Defaults to `False`.
|
||||
"""
|
||||
if self._answered:
|
||||
return
|
||||
cache_time (`int`, optional):
|
||||
For how long this result should be cached on
|
||||
the user's client. Defaults to 0 for no cache.
|
||||
|
||||
res = await self._client(_tl.fn.messages.SetBotCallbackAnswer(
|
||||
query_id=self.query.query_id,
|
||||
cache_time=cache_time,
|
||||
alert=alert,
|
||||
message=message,
|
||||
url=url,
|
||||
))
|
||||
self._answered = True
|
||||
return res
|
||||
url (`str`, optional):
|
||||
The URL to be opened in the user's client. Note that
|
||||
the only valid URLs are those of games your bot has,
|
||||
or alternatively a 't.me/your_bot?start=xyz' parameter.
|
||||
|
||||
@property
|
||||
def via_inline(self):
|
||||
"""
|
||||
Whether this callback was generated from an inline button sent
|
||||
via an inline query or not. If the bot sent the message itself
|
||||
with buttons, and one of those is clicked, this will be `False`.
|
||||
If a user sent the message coming from an inline query to the
|
||||
bot, and one of those is clicked, this will be `True`.
|
||||
alert (`bool`, optional):
|
||||
Whether an alert (a pop-up dialog) should be used
|
||||
instead of showing a toast. Defaults to `False`.
|
||||
"""
|
||||
if self._answered:
|
||||
return
|
||||
|
||||
If it's `True`, it's likely that the bot is **not** in the
|
||||
chat, so methods like `respond` or `delete` won't work (but
|
||||
`edit` will always work).
|
||||
"""
|
||||
return isinstance(self.query, _tl.UpdateInlineBotCallbackQuery)
|
||||
res = await self._client(_tl.fn.messages.SetBotCallbackAnswer(
|
||||
query_id=self.query.query_id,
|
||||
cache_time=cache_time,
|
||||
alert=alert,
|
||||
message=message,
|
||||
url=url,
|
||||
))
|
||||
self._answered = True
|
||||
return res
|
||||
|
||||
@auto_answer
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the message (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
``entity`` already set.
|
||||
@property
|
||||
def via_inline(self):
|
||||
"""
|
||||
Whether this callback was generated from an inline button sent
|
||||
via an inline query or not. If the bot sent the message itself
|
||||
with buttons, and one of those is clicked, this will be `False`.
|
||||
If a user sent the message coming from an inline query to the
|
||||
bot, and one of those is clicked, this will be `True`.
|
||||
|
||||
This method will also `answer` the callback if necessary.
|
||||
If it's `True`, it's likely that the bot is **not** in the
|
||||
chat, so methods like `respond` or `delete` won't work (but
|
||||
`edit` will always work).
|
||||
"""
|
||||
return isinstance(self.query, _tl.UpdateInlineBotCallbackQuery)
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
@auto_answer
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the message (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
``entity`` already set.
|
||||
|
||||
@auto_answer
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the message (as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
both ``entity`` and ``reply_to`` already set.
|
||||
This method will also `answer` the callback if necessary.
|
||||
|
||||
This method will also `answer` the callback if necessary.
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
kwargs['reply_to'] = self.query.msg_id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
@auto_answer
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the message (as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
both ``entity`` and ``reply_to`` already set.
|
||||
|
||||
@auto_answer
|
||||
async def edit(self, *args, **kwargs):
|
||||
"""
|
||||
Edits the message. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.edit_message` with
|
||||
the ``entity`` set to the correct :tl:`InputBotInlineMessageID`.
|
||||
This method will also `answer` the callback if necessary.
|
||||
|
||||
Returns `True` if the edit was successful.
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
kwargs['reply_to'] = self.query.msg_id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
This method will also `answer` the callback if necessary.
|
||||
@auto_answer
|
||||
async def edit(self, *args, **kwargs):
|
||||
"""
|
||||
Edits the message. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.edit_message` with
|
||||
the ``entity`` set to the correct :tl:`InputBotInlineMessageID`.
|
||||
|
||||
.. note::
|
||||
Returns `True` if the edit was successful.
|
||||
|
||||
This method won't respect the previous message unlike
|
||||
`Message.edit <telethon.tl._custom.message.Message.edit>`,
|
||||
since the message object is normally not present.
|
||||
"""
|
||||
if isinstance(self.query.msg_id, _tl.InputBotInlineMessageID):
|
||||
return await self._client.edit_message(
|
||||
None, self.query.msg_id, *args, **kwargs
|
||||
)
|
||||
else:
|
||||
return await self._client.edit_message(
|
||||
await self.get_input_chat(), self.query.msg_id,
|
||||
*args, **kwargs
|
||||
)
|
||||
This method will also `answer` the callback if necessary.
|
||||
|
||||
@auto_answer
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the message. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
.. note::
|
||||
|
||||
If you need to delete more than one message at once, don't use
|
||||
this `delete` method. Use a
|
||||
`telethon.client.telegramclient.TelegramClient` instance directly.
|
||||
|
||||
This method will also `answer` the callback if necessary.
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.query.msg_id],
|
||||
This method won't respect the previous message unlike
|
||||
`Message.edit <telethon.tl._custom.message.Message.edit>`,
|
||||
since the message object is normally not present.
|
||||
"""
|
||||
if isinstance(self.query.msg_id, _tl.InputBotInlineMessageID):
|
||||
return await self._client.edit_message(
|
||||
None, self.query.msg_id, *args, **kwargs
|
||||
)
|
||||
else:
|
||||
return await self._client.edit_message(
|
||||
await self.get_input_chat(), self.query.msg_id,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
@auto_answer
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the message. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
|
||||
If you need to delete more than one message at once, don't use
|
||||
this `delete` method. Use a
|
||||
`telethon.client.telegramclient.TelegramClient` instance directly.
|
||||
|
||||
This method will also `answer` the callback if necessary.
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.query.msg_id],
|
||||
*args, **kwargs
|
||||
)
|
||||
|
|
|
@ -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,332 +210,230 @@ class ChatAction(EventBuilder):
|
|||
return cls.Event(msg,
|
||||
new_score=action.score)
|
||||
|
||||
class Event(EventCommon):
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
Responds to the chat action message (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
``entity`` already set.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
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
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the chat action message (as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
both ``entity`` and ``reply_to`` already set.
|
||||
|
||||
# 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)
|
||||
Has the same effect as `respond` if there is no message.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return await self.respond(*args, **kwargs)
|
||||
|
||||
self.new_pin = pin_ids is not None
|
||||
self._pin_ids = pin_ids
|
||||
self._pinned_messages = None
|
||||
kwargs['reply_to'] = self.action_message.id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
self.new_photo = new_photo is not None
|
||||
self.photo = \
|
||||
new_photo if isinstance(new_photo, _tl.Photo) else None
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the chat action message. You're responsible for checking
|
||||
whether you have the permission to do so, or to except the error
|
||||
otherwise. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
|
||||
self._added_by = None
|
||||
self._kicked_by = None
|
||||
self.user_added = self.user_joined = self.user_left = \
|
||||
self.user_kicked = self.unpin = False
|
||||
Does nothing if no message action triggered this event.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return
|
||||
|
||||
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
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.action_message],
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
# 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
|
||||
async def get_pinned_message(self):
|
||||
"""
|
||||
If ``new_pin`` is `True`, this returns the `Message
|
||||
<telethon.tl.custom.message.Message>` object that was pinned.
|
||||
"""
|
||||
if self._pinned_messages is None:
|
||||
await self.get_pinned_messages()
|
||||
|
||||
self.created = bool(created)
|
||||
if self._pinned_messages:
|
||||
return self._pinned_messages[0]
|
||||
|
||||
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 = []
|
||||
async def get_pinned_messages(self):
|
||||
"""
|
||||
If ``new_pin`` is `True`, this returns a `list` of `Message
|
||||
<telethon.tl.custom.message.Message>` objects that were pinned.
|
||||
"""
|
||||
if not self._pin_ids:
|
||||
return self._pin_ids # either None or empty list
|
||||
|
||||
self._users = None
|
||||
self._input_users = None
|
||||
self.new_title = new_title
|
||||
self.new_score = new_score
|
||||
self.unpin = not pin
|
||||
chat = await self.get_input_chat()
|
||||
if chat:
|
||||
self._pinned_messages = await self._client.get_messages(
|
||||
self._input_chat, ids=self._pin_ids)
|
||||
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the chat action message (not as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
``entity`` already set.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
return self._pinned_messages
|
||||
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the chat action message (as a reply). Shorthand for
|
||||
`telethon.client.messages.MessageMethods.send_message` with
|
||||
both ``entity`` and ``reply_to`` already set.
|
||||
@property
|
||||
def added_by(self):
|
||||
"""
|
||||
The user who added ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._added_by and not isinstance(self._added_by, _tl.User):
|
||||
aby = self._entities.get(utils.get_peer_id(self._added_by))
|
||||
if aby:
|
||||
self._added_by = aby
|
||||
|
||||
Has the same effect as `respond` if there is no message.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return await self.respond(*args, **kwargs)
|
||||
return self._added_by
|
||||
|
||||
kwargs['reply_to'] = self.action_message.id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
async def get_added_by(self):
|
||||
"""
|
||||
Returns `added_by` but will make an API call if necessary.
|
||||
"""
|
||||
if not self.added_by and self._added_by:
|
||||
self._added_by = await self._client.get_entity(self._added_by)
|
||||
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the chat action message. You're responsible for checking
|
||||
whether you have the permission to do so, or to except the error
|
||||
otherwise. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.delete_messages` with
|
||||
``entity`` and ``message_ids`` already set.
|
||||
return self._added_by
|
||||
|
||||
Does nothing if no message action triggered this event.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return
|
||||
@property
|
||||
def kicked_by(self):
|
||||
"""
|
||||
The user who kicked ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._kicked_by and not isinstance(self._kicked_by, _tl.User):
|
||||
kby = self._entities.get(utils.get_peer_id(self._kicked_by))
|
||||
if kby:
|
||||
self._kicked_by = kby
|
||||
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.action_message],
|
||||
*args, **kwargs
|
||||
)
|
||||
return self._kicked_by
|
||||
|
||||
async def get_pinned_message(self):
|
||||
"""
|
||||
If ``new_pin`` is `True`, this returns the `Message
|
||||
<telethon.tl.custom.message.Message>` object that was pinned.
|
||||
"""
|
||||
if self._pinned_messages is None:
|
||||
await self.get_pinned_messages()
|
||||
async def get_kicked_by(self):
|
||||
"""
|
||||
Returns `kicked_by` but will make an API call if necessary.
|
||||
"""
|
||||
if not self.kicked_by and self._kicked_by:
|
||||
self._kicked_by = await self._client.get_entity(self._kicked_by)
|
||||
|
||||
if self._pinned_messages:
|
||||
return self._pinned_messages[0]
|
||||
return self._kicked_by
|
||||
|
||||
async def get_pinned_messages(self):
|
||||
"""
|
||||
If ``new_pin`` is `True`, this returns a `list` of `Message
|
||||
<telethon.tl.custom.message.Message>` objects that were pinned.
|
||||
"""
|
||||
if not self._pin_ids:
|
||||
return self._pin_ids # either None or empty list
|
||||
@property
|
||||
def user(self):
|
||||
"""
|
||||
The first user that takes part in this action. For example, who joined.
|
||||
|
||||
chat = await self.get_input_chat()
|
||||
if chat:
|
||||
self._pinned_messages = await self._client.get_messages(
|
||||
self._input_chat, ids=self._pin_ids)
|
||||
Might be `None` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
if self.users:
|
||||
return self._users[0]
|
||||
|
||||
return self._pinned_messages
|
||||
async def get_user(self):
|
||||
"""
|
||||
Returns `user` but will make an API call if necessary.
|
||||
"""
|
||||
if self.users or await self.get_users():
|
||||
return self._users[0]
|
||||
|
||||
@property
|
||||
def added_by(self):
|
||||
"""
|
||||
The user who added ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._added_by and not isinstance(self._added_by, _tl.User):
|
||||
aby = self._entities.get(utils.get_peer_id(self._added_by))
|
||||
if aby:
|
||||
self._added_by = aby
|
||||
@property
|
||||
def input_user(self):
|
||||
"""
|
||||
Input version of the ``self.user`` property.
|
||||
"""
|
||||
if self.input_users:
|
||||
return self._input_users[0]
|
||||
|
||||
return self._added_by
|
||||
async def get_input_user(self):
|
||||
"""
|
||||
Returns `input_user` but will make an API call if necessary.
|
||||
"""
|
||||
if self.input_users or await self.get_input_users():
|
||||
return self._input_users[0]
|
||||
|
||||
async def get_added_by(self):
|
||||
"""
|
||||
Returns `added_by` but will make an API call if necessary.
|
||||
"""
|
||||
if not self.added_by and self._added_by:
|
||||
self._added_by = await self._client.get_entity(self._added_by)
|
||||
@property
|
||||
def user_id(self):
|
||||
"""
|
||||
Returns the marked signed ID of the first user, if any.
|
||||
"""
|
||||
if self._user_ids:
|
||||
return self._user_ids[0]
|
||||
|
||||
return self._added_by
|
||||
@property
|
||||
def users(self):
|
||||
"""
|
||||
A list of users that take part in this action. For example, who joined.
|
||||
|
||||
@property
|
||||
def kicked_by(self):
|
||||
"""
|
||||
The user who kicked ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._kicked_by and not isinstance(self._kicked_by, _tl.User):
|
||||
kby = self._entities.get(utils.get_peer_id(self._kicked_by))
|
||||
if kby:
|
||||
self._kicked_by = kby
|
||||
Might be empty if the information can't be retrieved or there
|
||||
are no users taking part.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
return self._kicked_by
|
||||
if self._users is None:
|
||||
self._users = [
|
||||
self._entities[user_id]
|
||||
for user_id in self._user_ids
|
||||
if user_id in self._entities
|
||||
]
|
||||
|
||||
async def get_kicked_by(self):
|
||||
"""
|
||||
Returns `kicked_by` but will make an API call if necessary.
|
||||
"""
|
||||
if not self.kicked_by and self._kicked_by:
|
||||
self._kicked_by = await self._client.get_entity(self._kicked_by)
|
||||
return self._users
|
||||
|
||||
return self._kicked_by
|
||||
async def get_users(self):
|
||||
"""
|
||||
Returns `users` but will make an API call if necessary.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""
|
||||
The first user that takes part in this action. For example, who joined.
|
||||
# Note: we access the property first so that it fills if needed
|
||||
if (self.users is None or len(self._users) != len(self._user_ids)) and self.action_message:
|
||||
await self.action_message._reload_message()
|
||||
self._users = [
|
||||
u for u in self.action_message.action_entities
|
||||
if isinstance(u, (_tl.User, _tl.UserEmpty))]
|
||||
|
||||
Might be `None` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
if self.users:
|
||||
return self._users[0]
|
||||
return self._users
|
||||
|
||||
async def get_user(self):
|
||||
"""
|
||||
Returns `user` but will make an API call if necessary.
|
||||
"""
|
||||
if self.users or await self.get_users():
|
||||
return self._users[0]
|
||||
@property
|
||||
def input_users(self):
|
||||
"""
|
||||
Input version of the ``self.users`` property.
|
||||
"""
|
||||
if self._input_users is None and self._user_ids:
|
||||
self._input_users = []
|
||||
for user_id in self._user_ids:
|
||||
# Try to get it from our entities
|
||||
try:
|
||||
self._input_users.append(utils.get_input_peer(self._entities[user_id]))
|
||||
continue
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
@property
|
||||
def input_user(self):
|
||||
"""
|
||||
Input version of the ``self.user`` property.
|
||||
"""
|
||||
if self.input_users:
|
||||
return self._input_users[0]
|
||||
return self._input_users or []
|
||||
|
||||
async def get_input_user(self):
|
||||
"""
|
||||
Returns `input_user` but will make an API call if necessary.
|
||||
"""
|
||||
if self.input_users or await self.get_input_users():
|
||||
return self._input_users[0]
|
||||
async def get_input_users(self):
|
||||
"""
|
||||
Returns `input_users` but will make an API call if necessary.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""
|
||||
Returns the marked signed ID of the first user, if any.
|
||||
"""
|
||||
if self._user_ids:
|
||||
return self._user_ids[0]
|
||||
# Note: we access the property first so that it fills if needed
|
||||
if (self.input_users is None or len(self._input_users) != len(self._user_ids)) and self.action_message:
|
||||
self._input_users = [
|
||||
utils.get_input_peer(u)
|
||||
for u in self.action_message.action_entities
|
||||
if isinstance(u, (_tl.User, _tl.UserEmpty))]
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
"""
|
||||
A list of users that take part in this action. For example, who joined.
|
||||
return self._input_users or []
|
||||
|
||||
Might be empty if the information can't be retrieved or there
|
||||
are no users taking part.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
if self._users is None:
|
||||
self._users = [
|
||||
self._entities[user_id]
|
||||
for user_id in self._user_ids
|
||||
if user_id in self._entities
|
||||
]
|
||||
|
||||
return self._users
|
||||
|
||||
async def get_users(self):
|
||||
"""
|
||||
Returns `users` but will make an API call if necessary.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
# Note: we access the property first so that it fills if needed
|
||||
if (self.users is None or len(self._users) != len(self._user_ids)) and self.action_message:
|
||||
await self.action_message._reload_message()
|
||||
self._users = [
|
||||
u for u in self.action_message.action_entities
|
||||
if isinstance(u, (_tl.User, _tl.UserEmpty))]
|
||||
|
||||
return self._users
|
||||
|
||||
@property
|
||||
def input_users(self):
|
||||
"""
|
||||
Input version of the ``self.users`` property.
|
||||
"""
|
||||
if self._input_users is None and self._user_ids:
|
||||
self._input_users = []
|
||||
for user_id in self._user_ids:
|
||||
# Try to get it from our entities
|
||||
try:
|
||||
self._input_users.append(utils.get_input_peer(self._entities[user_id]))
|
||||
continue
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
return self._input_users or []
|
||||
|
||||
async def get_input_users(self):
|
||||
"""
|
||||
Returns `input_users` but will make an API call if necessary.
|
||||
"""
|
||||
if not self._user_ids:
|
||||
return []
|
||||
|
||||
# Note: we access the property first so that it fills if needed
|
||||
if (self.input_users is None or len(self._input_users) != len(self._user_ids)) and self.action_message:
|
||||
self._input_users = [
|
||||
utils.get_input_peer(u)
|
||||
for u in self.action_message.action_entities
|
||||
if isinstance(u, (_tl.User, _tl.UserEmpty))]
|
||||
|
||||
return self._input_users or []
|
||||
|
||||
@property
|
||||
def user_ids(self):
|
||||
"""
|
||||
Returns the marked signed ID of the users, if any.
|
||||
"""
|
||||
if self._user_ids:
|
||||
return self._user_ids[:]
|
||||
@property
|
||||
def user_ids(self):
|
||||
"""
|
||||
Returns the marked signed ID of the users, if any.
|
||||
"""
|
||||
if self._user_ids:
|
||||
return self._user_ids[:]
|
||||
|
|
|
@ -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,200 +40,163 @@ 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')
|
||||
def __init__(self, query):
|
||||
_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):
|
||||
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
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
||||
return super().filter(event)
|
||||
|
||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
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.
|
||||
Returns the unique identifier for the query ID.
|
||||
"""
|
||||
def __init__(self, query):
|
||||
super().__init__(chat_peer=_tl.PeerUser(query.user_id))
|
||||
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
||||
self.query = query
|
||||
self.pattern_match = None
|
||||
self._answered = False
|
||||
return self.query.query_id
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
Returns the text the user used to make the inline query.
|
||||
"""
|
||||
return self.query.query
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
Returns the unique identifier for the query ID.
|
||||
"""
|
||||
return self.query.query_id
|
||||
@property
|
||||
def offset(self):
|
||||
"""
|
||||
The string the user's client used as an offset for the query.
|
||||
This will either be empty or equal to offsets passed to `answer`.
|
||||
"""
|
||||
return self.query.offset
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
Returns the text the user used to make the inline query.
|
||||
"""
|
||||
return self.query.query
|
||||
@property
|
||||
def geo(self):
|
||||
"""
|
||||
If the user location is requested when using inline mode
|
||||
and the user's device is able to send it, this will return
|
||||
the :tl:`GeoPoint` with the position of the user.
|
||||
"""
|
||||
return self.query.geo
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
"""
|
||||
The string the user's client used as an offset for the query.
|
||||
This will either be empty or equal to offsets passed to `answer`.
|
||||
"""
|
||||
return self.query.offset
|
||||
@property
|
||||
def builder(self):
|
||||
"""
|
||||
Returns a new `InlineBuilder
|
||||
<telethon.tl.custom.inlinebuilder.InlineBuilder>` instance.
|
||||
|
||||
@property
|
||||
def geo(self):
|
||||
"""
|
||||
If the user location is requested when using inline mode
|
||||
and the user's device is able to send it, this will return
|
||||
the :tl:`GeoPoint` with the position of the user.
|
||||
"""
|
||||
return self.query.geo
|
||||
See the documentation for `builder` to know what kind of answers
|
||||
can be given.
|
||||
"""
|
||||
return _custom.InlineBuilder(self._client)
|
||||
|
||||
@property
|
||||
def builder(self):
|
||||
"""
|
||||
Returns a new `InlineBuilder
|
||||
<telethon.tl.custom.inlinebuilder.InlineBuilder>` instance.
|
||||
"""
|
||||
return _custom.InlineBuilder(self._client)
|
||||
async def answer(
|
||||
self, results=None, cache_time=0, *,
|
||||
gallery=False, next_offset=None, private=False,
|
||||
switch_pm=None, switch_pm_param=''):
|
||||
"""
|
||||
Answers the inline query with the given results.
|
||||
|
||||
async def answer(
|
||||
self, results=None, cache_time=0, *,
|
||||
gallery=False, next_offset=None, private=False,
|
||||
switch_pm=None, switch_pm_param=''):
|
||||
"""
|
||||
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.
|
||||
You should use `builder` to create these:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
builder = inline.builder
|
||||
r1 = builder.article('Be nice', text='Have a nice day')
|
||||
r2 = builder.article('Be bad', text="I don't like you")
|
||||
await inline.answer([r1, r2])
|
||||
|
||||
You can send up to 50 results as documented in
|
||||
https://core.telegram.org/bots/api#answerinlinequery.
|
||||
Sending more will raise ``ResultsTooMuchError``,
|
||||
and you should consider using `next_offset` to
|
||||
paginate them.
|
||||
|
||||
cache_time (`int`, optional):
|
||||
For how long this result should be cached on
|
||||
the user's client. Defaults to 0 for no cache.
|
||||
|
||||
gallery (`bool`, optional):
|
||||
Whether the results should show as a gallery (grid) or not.
|
||||
|
||||
next_offset (`str`, optional):
|
||||
The offset the client will send when the user scrolls the
|
||||
results and it repeats the request.
|
||||
|
||||
private (`bool`, optional):
|
||||
Whether the results should be cached by Telegram
|
||||
(not private) or by the user's client (private).
|
||||
|
||||
switch_pm (`str`, optional):
|
||||
If set, this text will be shown in the results
|
||||
to allow the user to switch to private messages.
|
||||
|
||||
switch_pm_param (`str`, optional):
|
||||
Optional parameter to start the bot with if
|
||||
`switch_pm` was used.
|
||||
|
||||
Example:
|
||||
Args:
|
||||
results (`list`, optional):
|
||||
A list of :tl:`InputBotInlineResult` to use.
|
||||
You should use `builder` to create these:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bot.on(events.InlineQuery)
|
||||
async def handler(event):
|
||||
builder = event.builder
|
||||
builder = inline.builder
|
||||
r1 = builder.article('Be nice', text='Have a nice day')
|
||||
r2 = builder.article('Be bad', text="I don't like you")
|
||||
await inline.answer([r1, r2])
|
||||
|
||||
rev_text = event.text[::-1]
|
||||
await event.answer([
|
||||
builder.article('Reverse text', text=rev_text),
|
||||
builder.photo('/path/to/photo.jpg')
|
||||
])
|
||||
"""
|
||||
if self._answered:
|
||||
return
|
||||
You can send up to 50 results as documented in
|
||||
https://core.telegram.org/bots/api#answerinlinequery.
|
||||
Sending more will raise ``ResultsTooMuchError``,
|
||||
and you should consider using `next_offset` to
|
||||
paginate them.
|
||||
|
||||
if results:
|
||||
futures = [self._as_future(x) for x in results]
|
||||
cache_time (`int`, optional):
|
||||
For how long this result should be cached on
|
||||
the user's client. Defaults to 0 for no cache.
|
||||
|
||||
await asyncio.wait(futures)
|
||||
gallery (`bool`, optional):
|
||||
Whether the results should show as a gallery (grid) or not.
|
||||
|
||||
# All futures will be in the `done` *set* that `wait` returns.
|
||||
#
|
||||
# Precisely because it's a `set` and not a `list`, it
|
||||
# will not preserve the order, but since all futures
|
||||
# completed we can use our original, ordered `list`.
|
||||
results = [x.result() for x in futures]
|
||||
else:
|
||||
results = []
|
||||
next_offset (`str`, optional):
|
||||
The offset the client will send when the user scrolls the
|
||||
results and it repeats the request.
|
||||
|
||||
if switch_pm:
|
||||
switch_pm = _tl.InlineBotSwitchPM(switch_pm, switch_pm_param)
|
||||
private (`bool`, optional):
|
||||
Whether the results should be cached by Telegram
|
||||
(not private) or by the user's client (private).
|
||||
|
||||
return await self._client(
|
||||
_tl.fn.messages.SetInlineBotResults(
|
||||
query_id=self.query.query_id,
|
||||
results=results,
|
||||
cache_time=cache_time,
|
||||
gallery=gallery,
|
||||
next_offset=next_offset,
|
||||
private=private,
|
||||
switch_pm=switch_pm
|
||||
)
|
||||
switch_pm (`str`, optional):
|
||||
If set, this text will be shown in the results
|
||||
to allow the user to switch to private messages.
|
||||
|
||||
switch_pm_param (`str`, optional):
|
||||
Optional parameter to start the bot with if
|
||||
`switch_pm` was used.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bot.on(events.InlineQuery)
|
||||
async def handler(event):
|
||||
builder = event.builder
|
||||
|
||||
rev_text = event.text[::-1]
|
||||
await event.answer([
|
||||
builder.article('Reverse text', text=rev_text),
|
||||
builder.photo('/path/to/photo.jpg')
|
||||
])
|
||||
"""
|
||||
if self._answered:
|
||||
return
|
||||
|
||||
if results:
|
||||
futures = [self._as_future(x) for x in results]
|
||||
|
||||
await asyncio.wait(futures)
|
||||
|
||||
# All futures will be in the `done` *set* that `wait` returns.
|
||||
#
|
||||
# Precisely because it's a `set` and not a `list`, it
|
||||
# will not preserve the order, but since all futures
|
||||
# completed we can use our original, ordered `list`.
|
||||
results = [x.result() for x in futures]
|
||||
else:
|
||||
results = []
|
||||
|
||||
if switch_pm:
|
||||
switch_pm = _tl.InlineBotSwitchPM(switch_pm, switch_pm_param)
|
||||
|
||||
return await self._client(
|
||||
_tl.fn.messages.SetInlineBotResults(
|
||||
query_id=self.query.query_id,
|
||||
results=results,
|
||||
cache_time=cache_time,
|
||||
gallery=gallery,
|
||||
next_offset=next_offset,
|
||||
private=private,
|
||||
switch_pm=switch_pm
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _as_future(obj):
|
||||
if inspect.isawaitable(obj):
|
||||
return asyncio.ensure_future(obj)
|
||||
@staticmethod
|
||||
def _as_future(obj):
|
||||
if inspect.isawaitable(obj):
|
||||
return asyncio.ensure_future(obj)
|
||||
|
||||
f = asyncio.get_running_loop().create_future()
|
||||
f.set_result(obj)
|
||||
return f
|
||||
f = asyncio.get_running_loop().create_future()
|
||||
f.set_result(obj)
|
||||
return f
|
||||
|
|
|
@ -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,90 +64,58 @@ 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):
|
||||
@property
|
||||
def inbox(self):
|
||||
"""
|
||||
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.
|
||||
`True` if you have read someone else's messages.
|
||||
"""
|
||||
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)
|
||||
return not self.outbox
|
||||
|
||||
@property
|
||||
def inbox(self):
|
||||
"""
|
||||
`True` if you have read someone else's messages.
|
||||
"""
|
||||
return not self.outbox
|
||||
@property
|
||||
def message_ids(self):
|
||||
"""
|
||||
The IDs of the messages **which contents'** were read.
|
||||
|
||||
@property
|
||||
def message_ids(self):
|
||||
"""
|
||||
The IDs of the messages **which contents'** were read.
|
||||
Use :meth:`is_read` if you need to check whether a message
|
||||
was read instead checking if it's in here.
|
||||
"""
|
||||
return self._message_ids
|
||||
|
||||
Use :meth:`is_read` if you need to check whether a message
|
||||
was read instead checking if it's in here.
|
||||
"""
|
||||
return self._message_ids
|
||||
async def get_messages(self):
|
||||
"""
|
||||
Returns the list of `Message <telethon.tl.custom.message.Message>`
|
||||
**which contents'** were read.
|
||||
|
||||
async def get_messages(self):
|
||||
"""
|
||||
Returns the list of `Message <telethon.tl.custom.message.Message>`
|
||||
**which contents'** were read.
|
||||
|
||||
Use :meth:`is_read` if you need to check whether a message
|
||||
was read instead checking if it's in here.
|
||||
"""
|
||||
if self._messages is None:
|
||||
chat = await self.get_input_chat()
|
||||
if not chat:
|
||||
self._messages = []
|
||||
else:
|
||||
self._messages = await self._client.get_messages(
|
||||
chat, ids=self._message_ids)
|
||||
|
||||
return self._messages
|
||||
|
||||
def is_read(self, message):
|
||||
"""
|
||||
Returns `True` if the given message (or its ID) has been read.
|
||||
|
||||
If a list-like argument is provided, this method will return a
|
||||
list of booleans indicating which messages have been read.
|
||||
"""
|
||||
if utils.is_list_like(message):
|
||||
return [(m if isinstance(m, int) else m.id) <= self.max_id
|
||||
for m in message]
|
||||
Use :meth:`is_read` if you need to check whether a message
|
||||
was read instead checking if it's in here.
|
||||
"""
|
||||
if self._messages is None:
|
||||
chat = await self.get_input_chat()
|
||||
if not chat:
|
||||
self._messages = []
|
||||
else:
|
||||
return (message if isinstance(message, int)
|
||||
else message.id) <= self.max_id
|
||||
self._messages = await self._client.get_messages(
|
||||
chat, ids=self._message_ids)
|
||||
|
||||
def __contains__(self, message):
|
||||
"""`True` if the message(s) are read message."""
|
||||
if utils.is_list_like(message):
|
||||
return all(self.is_read(message))
|
||||
else:
|
||||
return self.is_read(message)
|
||||
return self._messages
|
||||
|
||||
def is_read(self, message):
|
||||
"""
|
||||
Returns `True` if the given message (or its ID) has been read.
|
||||
|
||||
If a list-like argument is provided, this method will return a
|
||||
list of booleans indicating which messages have been read.
|
||||
"""
|
||||
if utils.is_list_like(message):
|
||||
return [(m if isinstance(m, int) else m.id) <= self.max_id
|
||||
for m in message]
|
||||
else:
|
||||
return (message if isinstance(message, int)
|
||||
else message.id) <= self.max_id
|
||||
|
||||
def __contains__(self, message):
|
||||
"""`True` if the message(s) are read message."""
|
||||
if utils.is_list_like(message):
|
||||
return all(self.is_read(message))
|
||||
else:
|
||||
return self.is_read(message)
|
||||
|
|
|
@ -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,11 +32,25 @@ 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.
|
||||
|
||||
Members:
|
||||
status (:tl:`UserStatus`, optional):
|
||||
The user status if the update is about going online or offline.
|
||||
|
||||
You should check this attribute first before checking any
|
||||
of the seen within properties, since they will all be `None`
|
||||
if the status is not set.
|
||||
|
||||
action (:tl:`SendMessageAction`, optional):
|
||||
The "typing" action if any the user is performing if any.
|
||||
|
||||
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
|
||||
|
||||
|
@ -48,262 +62,242 @@ class UserUpdate(EventBuilder):
|
|||
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):
|
||||
_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):
|
||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
||||
if isinstance(update, _tl.UpdateUserStatus):
|
||||
return cls.Event(_tl.PeerUser(update.user_id),
|
||||
return UserUpdateEvent(_tl.PeerUser(update.user_id),
|
||||
status=update.status)
|
||||
elif isinstance(update, _tl.UpdateChannelUserTyping):
|
||||
return cls.Event(update.from_id,
|
||||
return UserUpdateEvent(update.from_id,
|
||||
chat_peer=_tl.PeerChannel(update.channel_id),
|
||||
typing=update.action)
|
||||
elif isinstance(update, _tl.UpdateChatUserTyping):
|
||||
return cls.Event(update.from_id,
|
||||
return UserUpdateEvent(update.from_id,
|
||||
chat_peer=_tl.PeerChat(update.chat_id),
|
||||
typing=update.action)
|
||||
elif isinstance(update, _tl.UpdateUserTyping):
|
||||
return cls.Event(update.user_id,
|
||||
return UserUpdateEvent(update.user_id,
|
||||
typing=update.action)
|
||||
|
||||
class Event(EventCommon, _custom.sendergetter.SenderGetter):
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""Alias for `sender <telethon.tl.custom.sendergetter.SenderGetter.sender>`."""
|
||||
return self.sender
|
||||
|
||||
async def get_user(self):
|
||||
"""Alias for `get_sender <telethon.tl.custom.sendergetter.SenderGetter.get_sender>`."""
|
||||
return await self.get_sender()
|
||||
|
||||
@property
|
||||
def input_user(self):
|
||||
"""Alias for `input_sender <telethon.tl.custom.sendergetter.SenderGetter.input_sender>`."""
|
||||
return self.input_sender
|
||||
|
||||
async def get_input_user(self):
|
||||
"""Alias for `get_input_sender <telethon.tl.custom.sendergetter.SenderGetter.get_input_sender>`."""
|
||||
return await self.get_input_sender()
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Alias for `sender_id <telethon.tl.custom.sendergetter.SenderGetter.sender_id>`."""
|
||||
return self.sender_id
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def typing(self):
|
||||
"""
|
||||
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.
|
||||
|
||||
You should check this attribute first before checking any
|
||||
of the seen within properties, since they will all be `None`
|
||||
if the status is not set.
|
||||
|
||||
action (:tl:`SendMessageAction`, optional):
|
||||
The "typing" action if any the user is performing if any.
|
||||
|
||||
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.
|
||||
`True` if the action is typing a message.
|
||||
"""
|
||||
def __init__(self, peer, *, status=None, chat_peer=None, typing=None):
|
||||
super().__init__(chat_peer or peer)
|
||||
_custom.sendergetter.SenderGetter.__init__(self, utils.get_peer_id(peer))
|
||||
return isinstance(self.action, _tl.SendMessageTypingAction)
|
||||
|
||||
self.status = status
|
||||
self.action = typing
|
||||
@property
|
||||
@_requires_action
|
||||
def uploading(self):
|
||||
"""
|
||||
`True` if the action is uploading something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageChooseContactAction,
|
||||
_tl.SendMessageChooseStickerAction,
|
||||
_tl.SendMessageUploadAudioAction,
|
||||
_tl.SendMessageUploadDocumentAction,
|
||||
_tl.SendMessageUploadPhotoAction,
|
||||
_tl.SendMessageUploadRoundAction,
|
||||
_tl.SendMessageUploadVideoAction
|
||||
))
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||
@property
|
||||
@_requires_action
|
||||
def recording(self):
|
||||
"""
|
||||
`True` if the action is recording something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordAudioAction,
|
||||
_tl.SendMessageRecordRoundAction,
|
||||
_tl.SendMessageRecordVideoAction
|
||||
))
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""Alias for `sender <telethon.tl.custom.sendergetter.SenderGetter.sender>`."""
|
||||
return self.sender
|
||||
@property
|
||||
@_requires_action
|
||||
def playing(self):
|
||||
"""
|
||||
`True` if the action is playing a game.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageGamePlayAction)
|
||||
|
||||
async def get_user(self):
|
||||
"""Alias for `get_sender <telethon.tl.custom.sendergetter.SenderGetter.get_sender>`."""
|
||||
return await self.get_sender()
|
||||
@property
|
||||
@_requires_action
|
||||
def cancel(self):
|
||||
"""
|
||||
`True` if the action was cancelling other actions.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageCancelAction)
|
||||
|
||||
@property
|
||||
def input_user(self):
|
||||
"""Alias for `input_sender <telethon.tl.custom.sendergetter.SenderGetter.input_sender>`."""
|
||||
return self.input_sender
|
||||
@property
|
||||
@_requires_action
|
||||
def geo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a geo.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageGeoLocationAction)
|
||||
|
||||
async def get_input_user(self):
|
||||
"""Alias for `get_input_sender <telethon.tl.custom.sendergetter.SenderGetter.get_input_sender>`."""
|
||||
return await self.get_input_sender()
|
||||
@property
|
||||
@_requires_action
|
||||
def audio(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an audio.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordAudioAction,
|
||||
_tl.SendMessageUploadAudioAction
|
||||
))
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Alias for `sender_id <telethon.tl.custom.sendergetter.SenderGetter.sender_id>`."""
|
||||
return self.sender_id
|
||||
@property
|
||||
@_requires_action
|
||||
def round(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is a round video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordRoundAction,
|
||||
_tl.SendMessageUploadRoundAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def typing(self):
|
||||
"""
|
||||
`True` if the action is typing a message.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageTypingAction)
|
||||
@property
|
||||
@_requires_action
|
||||
def video(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordVideoAction,
|
||||
_tl.SendMessageUploadVideoAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def uploading(self):
|
||||
"""
|
||||
`True` if the action is uploading something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageChooseContactAction,
|
||||
_tl.SendMessageChooseStickerAction,
|
||||
_tl.SendMessageUploadAudioAction,
|
||||
_tl.SendMessageUploadDocumentAction,
|
||||
_tl.SendMessageUploadPhotoAction,
|
||||
_tl.SendMessageUploadRoundAction,
|
||||
_tl.SendMessageUploadVideoAction
|
||||
))
|
||||
@property
|
||||
@_requires_action
|
||||
def contact(self):
|
||||
"""
|
||||
`True` if what's being uploaded (selected) is a contact.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageChooseContactAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def recording(self):
|
||||
"""
|
||||
`True` if the action is recording something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordAudioAction,
|
||||
_tl.SendMessageRecordRoundAction,
|
||||
_tl.SendMessageRecordVideoAction
|
||||
))
|
||||
@property
|
||||
@_requires_action
|
||||
def document(self):
|
||||
"""
|
||||
`True` if what's being uploaded is document.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageUploadDocumentAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def playing(self):
|
||||
"""
|
||||
`True` if the action is playing a game.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageGamePlayAction)
|
||||
@property
|
||||
@_requires_action
|
||||
def sticker(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a sticker.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageChooseStickerAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def cancel(self):
|
||||
"""
|
||||
`True` if the action was cancelling other actions.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageCancelAction)
|
||||
@property
|
||||
@_requires_action
|
||||
def photo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a photo.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageUploadPhotoAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def geo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a geo.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageGeoLocationAction)
|
||||
@property
|
||||
@_requires_action
|
||||
def last_seen(self):
|
||||
"""
|
||||
Exact `datetime.datetime` when the user was last seen if known.
|
||||
"""
|
||||
if isinstance(self.status, _tl.UserStatusOffline):
|
||||
return self.status.was_online
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def audio(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an audio.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordAudioAction,
|
||||
_tl.SendMessageUploadAudioAction
|
||||
))
|
||||
@property
|
||||
@_requires_status
|
||||
def until(self):
|
||||
"""
|
||||
The `datetime.datetime` until when the user should appear online.
|
||||
"""
|
||||
if isinstance(self.status, _tl.UserStatusOnline):
|
||||
return self.status.expires
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def round(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is a round video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordRoundAction,
|
||||
_tl.SendMessageUploadRoundAction
|
||||
))
|
||||
def _last_seen_delta(self):
|
||||
if isinstance(self.status, _tl.UserStatusOffline):
|
||||
return datetime.datetime.now(tz=datetime.timezone.utc) - self.status.was_online
|
||||
elif isinstance(self.status, _tl.UserStatusOnline):
|
||||
return datetime.timedelta(days=0)
|
||||
elif isinstance(self.status, _tl.UserStatusRecently):
|
||||
return datetime.timedelta(days=1)
|
||||
elif isinstance(self.status, _tl.UserStatusLastWeek):
|
||||
return datetime.timedelta(days=7)
|
||||
elif isinstance(self.status, _tl.UserStatusLastMonth):
|
||||
return datetime.timedelta(days=30)
|
||||
else:
|
||||
return datetime.timedelta(days=365)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def video(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
_tl.SendMessageRecordVideoAction,
|
||||
_tl.SendMessageUploadVideoAction
|
||||
))
|
||||
@property
|
||||
@_requires_status
|
||||
def online(self):
|
||||
"""
|
||||
`True` if the user is currently online,
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=0)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def contact(self):
|
||||
"""
|
||||
`True` if what's being uploaded (selected) is a contact.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageChooseContactAction)
|
||||
@property
|
||||
@_requires_status
|
||||
def recently(self):
|
||||
"""
|
||||
`True` if the user was seen within a day.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=1)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def document(self):
|
||||
"""
|
||||
`True` if what's being uploaded is document.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageUploadDocumentAction)
|
||||
@property
|
||||
@_requires_status
|
||||
def within_weeks(self):
|
||||
"""
|
||||
`True` if the user was seen within 7 days.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=7)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def sticker(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a sticker.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageChooseStickerAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def photo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a photo.
|
||||
"""
|
||||
return isinstance(self.action, _tl.SendMessageUploadPhotoAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def last_seen(self):
|
||||
"""
|
||||
Exact `datetime.datetime` when the user was last seen if known.
|
||||
"""
|
||||
if isinstance(self.status, _tl.UserStatusOffline):
|
||||
return self.status.was_online
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def until(self):
|
||||
"""
|
||||
The `datetime.datetime` until when the user should appear online.
|
||||
"""
|
||||
if isinstance(self.status, _tl.UserStatusOnline):
|
||||
return self.status.expires
|
||||
|
||||
def _last_seen_delta(self):
|
||||
if isinstance(self.status, _tl.UserStatusOffline):
|
||||
return datetime.datetime.now(tz=datetime.timezone.utc) - self.status.was_online
|
||||
elif isinstance(self.status, _tl.UserStatusOnline):
|
||||
return datetime.timedelta(days=0)
|
||||
elif isinstance(self.status, _tl.UserStatusRecently):
|
||||
return datetime.timedelta(days=1)
|
||||
elif isinstance(self.status, _tl.UserStatusLastWeek):
|
||||
return datetime.timedelta(days=7)
|
||||
elif isinstance(self.status, _tl.UserStatusLastMonth):
|
||||
return datetime.timedelta(days=30)
|
||||
else:
|
||||
return datetime.timedelta(days=365)
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def online(self):
|
||||
"""
|
||||
`True` if the user is currently online,
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=0)
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def recently(self):
|
||||
"""
|
||||
`True` if the user was seen within a day.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=1)
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def within_weeks(self):
|
||||
"""
|
||||
`True` if the user was seen within 7 days.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=7)
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def within_months(self):
|
||||
"""
|
||||
`True` if the user was seen within 30 days.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=30)
|
||||
@property
|
||||
@_requires_status
|
||||
def within_months(self):
|
||||
"""
|
||||
`True` if the user was seen within 30 days.
|
||||
"""
|
||||
return self._last_seen_delta() <= datetime.timedelta(days=30)
|
||||
|
|
Loading…
Reference in New Issue
Block a user