mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Все подряд
This commit is contained in:
140
.venv2/Lib/site-packages/telethon/events/__init__.py
Normal file
140
.venv2/Lib/site-packages/telethon/events/__init__.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from .raw import Raw
|
||||
from .album import Album
|
||||
from .chataction import ChatAction
|
||||
from .messagedeleted import MessageDeleted
|
||||
from .messageedited import MessageEdited
|
||||
from .messageread import MessageRead
|
||||
from .newmessage import NewMessage
|
||||
from .userupdate import UserUpdate
|
||||
from .callbackquery import CallbackQuery
|
||||
from .inlinequery import InlineQuery
|
||||
|
||||
|
||||
_HANDLERS_ATTRIBUTE = '__tl.handlers'
|
||||
|
||||
|
||||
class StopPropagation(Exception):
|
||||
"""
|
||||
If this exception is raised in any of the handlers for a given event,
|
||||
it will stop the execution of all other registered event handlers.
|
||||
It can be seen as the ``StopIteration`` in a for loop but for events.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> from telethon import TelegramClient, events
|
||||
>>> client = TelegramClient(...)
|
||||
>>>
|
||||
>>> @client.on(events.NewMessage)
|
||||
... async def delete(event):
|
||||
... await event.delete()
|
||||
... # No other event handler will have a chance to handle this event
|
||||
... raise StopPropagation
|
||||
...
|
||||
>>> @client.on(events.NewMessage)
|
||||
... async def _(event):
|
||||
... # Will never be reached, because it is the second handler
|
||||
... pass
|
||||
"""
|
||||
# For some reason Sphinx wants the silly >>> or
|
||||
# it will show warnings and look bad when generated.
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
>>> 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)
|
343
.venv2/Lib/site-packages/telethon/events/album.py
Normal file
343
.venv2/Lib/site-packages/telethon/events/album.py
Normal file
@@ -0,0 +1,343 @@
|
||||
import asyncio
|
||||
import time
|
||||
import weakref
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types
|
||||
from ..tl.custom.sendergetter import SenderGetter
|
||||
|
||||
_IGNORE_MAX_SIZE = 100 # len()
|
||||
_IGNORE_MAX_AGE = 5 # seconds
|
||||
|
||||
# IDs to ignore, and when they were added. If it grows too large, we will
|
||||
# remove old entries. Although it should generally not be bigger than 10,
|
||||
# it may be possible some updates are not processed and thus not removed.
|
||||
_IGNORE_DICT = {}
|
||||
|
||||
|
||||
_HACK_DELAY = 0.5
|
||||
|
||||
|
||||
class AlbumHack:
|
||||
"""
|
||||
When receiving an album from a different data-center, they will come in
|
||||
separate `Updates`, so we need to temporarily remember them for a while
|
||||
and only after produce the event.
|
||||
|
||||
Of course events are not designed for this kind of wizardy, so this is
|
||||
a dirty hack that gets the job done.
|
||||
|
||||
When cleaning up the code base we may want to figure out a better way
|
||||
to do this, or just leave the album problem to the users; the update
|
||||
handling code is bad enough as it is.
|
||||
"""
|
||||
def __init__(self, client, event):
|
||||
# It's probably silly to use a weakref here because this object is
|
||||
# very short-lived but might as well try to do "the right thing".
|
||||
self._client = weakref.ref(client)
|
||||
self._event = event # parent event
|
||||
self._due = client.loop.time() + _HACK_DELAY
|
||||
|
||||
client.loop.create_task(self.deliver_event())
|
||||
|
||||
def extend(self, messages):
|
||||
client = self._client()
|
||||
if client: # weakref may be dead
|
||||
self._event.messages.extend(messages)
|
||||
self._due = client.loop.time() + _HACK_DELAY
|
||||
|
||||
async def deliver_event(self):
|
||||
while True:
|
||||
client = self._client()
|
||||
if client is None:
|
||||
return # weakref is dead, nothing to deliver
|
||||
|
||||
diff = self._due - client.loop.time()
|
||||
if diff <= 0:
|
||||
# We've hit our due time, deliver event. It won't respect
|
||||
# sequential updates but fixing that would just worsen this.
|
||||
await client._dispatch_event(self._event)
|
||||
return
|
||||
|
||||
del client # Clear ref and sleep until our due time
|
||||
await asyncio.sleep(diff)
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class Album(EventBuilder):
|
||||
"""
|
||||
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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.Album)
|
||||
async def handler(event):
|
||||
# Counting how many photos or videos the album has
|
||||
print('Got an album with', len(event), 'items')
|
||||
|
||||
# Forwarding the album as a whole to some chat
|
||||
event.forward_to(chat)
|
||||
|
||||
# Printing the caption
|
||||
print(event.text)
|
||||
|
||||
# Replying to the fifth item in the album
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
# TODO normally we'd only check updates if they come with other updates
|
||||
# but MessageBox is not designed for this so others will always be None.
|
||||
# In essence we always rely on AlbumHack rather than returning early if not others.
|
||||
others = [update]
|
||||
|
||||
if isinstance(update,
|
||||
(types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||
if not isinstance(update.message, types.Message):
|
||||
return # We don't care about MessageService's here
|
||||
|
||||
group = update.message.grouped_id
|
||||
if group is None:
|
||||
return # It must be grouped
|
||||
|
||||
# Check whether we are supposed to skip this update, and
|
||||
# if we do also remove it from the ignore list since we
|
||||
# won't need to check against it again.
|
||||
if _IGNORE_DICT.pop(id(update), None):
|
||||
return
|
||||
|
||||
# Check if the ignore list is too big, and if it is clean it
|
||||
# TODO time could technically go backwards; time is not monotonic
|
||||
now = time.time()
|
||||
if len(_IGNORE_DICT) > _IGNORE_MAX_SIZE:
|
||||
for i in [i for i, t in _IGNORE_DICT.items() if now - t > _IGNORE_MAX_AGE]:
|
||||
del _IGNORE_DICT[i]
|
||||
|
||||
# Add the other updates to the ignore list
|
||||
for u in others:
|
||||
if u is not update:
|
||||
_IGNORE_DICT[id(u)] = now
|
||||
|
||||
# Figure out which updates share the same group and use those
|
||||
return cls.Event([
|
||||
u.message for u in others
|
||||
if (isinstance(u, (types.UpdateNewMessage, types.UpdateNewChannelMessage))
|
||||
and isinstance(u.message, types.Message)
|
||||
and u.message.grouped_id == group)
|
||||
])
|
||||
|
||||
def filter(self, event):
|
||||
# Albums with less than two messages require a few hacks to work.
|
||||
if len(event.messages) > 1:
|
||||
return super().filter(event)
|
||||
|
||||
class Event(EventCommon, SenderGetter):
|
||||
"""
|
||||
Represents the event of a new album.
|
||||
|
||||
Members:
|
||||
messages (Sequence[`Message <telethon.tl.custom.message.Message>`]):
|
||||
The list of messages belonging to the same album.
|
||||
"""
|
||||
def __init__(self, messages):
|
||||
message = messages[0]
|
||||
super().__init__(chat_peer=message.peer_id,
|
||||
msg_id=message.id, broadcast=bool(message.post))
|
||||
SenderGetter.__init__(self, message.sender_id)
|
||||
self.messages = messages
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(
|
||||
self.sender_id, self._entities, client._mb_entity_cache)
|
||||
|
||||
for msg in self.messages:
|
||||
msg._finish_init(client, self._entities, None)
|
||||
|
||||
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)
|
||||
|
||||
@property
|
||||
def grouped_id(self):
|
||||
"""
|
||||
The shared ``grouped_id`` between all the messages.
|
||||
"""
|
||||
return self.messages[0].grouped_id
|
||||
|
||||
@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), '')
|
||||
|
||||
@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), '')
|
||||
|
||||
@property
|
||||
def is_reply(self):
|
||||
"""
|
||||
`True` if the album is a reply to some other message.
|
||||
|
||||
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 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
|
||||
|
||||
# endregion Public Properties
|
||||
|
||||
# region Public Methods
|
||||
|
||||
async def get_reply_message(self):
|
||||
"""
|
||||
The `Message <telethon.tl.custom.message.Message>`
|
||||
that this album is replying to, or `None`.
|
||||
|
||||
The result will be cached after its first use.
|
||||
"""
|
||||
return await self.messages[0].get_reply_message()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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 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.
|
||||
|
||||
Returns `None` if the message was incoming,
|
||||
or the edited `Message` otherwise.
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
return await self.messages[0].edit(*args, **kwargs)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
async def mark_read(self):
|
||||
"""
|
||||
Marks the entire album as read. Shorthand for
|
||||
`client.send_read_acknowledge()
|
||||
<telethon.client.messages.MessageMethods.send_read_acknowledge>`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
await self._client.send_read_acknowledge(
|
||||
await self.get_input_chat(), max_id=self.messages[-1].id)
|
||||
|
||||
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 __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]
|
346
.venv2/Lib/site-packages/telethon/events/callbackquery.py
Normal file
346
.venv2/Lib/site-packages/telethon/events/callbackquery.py
Normal file
@@ -0,0 +1,346 @@
|
||||
import re
|
||||
import struct
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types, functions
|
||||
from ..tl.custom.sendergetter import SenderGetter
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class CallbackQuery(EventBuilder):
|
||||
"""
|
||||
Occurs whenever you sign in as a bot and a user
|
||||
clicks one of the inline buttons on your messages.
|
||||
|
||||
Note that the `chats` parameter will **not** work with normal
|
||||
IDs or peers if the clicked inline button comes from a "via bot"
|
||||
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_')``.
|
||||
|
||||
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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events, Button
|
||||
|
||||
# Handle all callback queries and check data inside the handler
|
||||
@client.on(events.CallbackQuery)
|
||||
async def handler(event):
|
||||
if event.data == b'yes':
|
||||
await event.answer('Correct answer!')
|
||||
|
||||
# Handle only callback queries with data being b'no'
|
||||
@client.on(events.CallbackQuery(data=b'no'))
|
||||
async def handler(event):
|
||||
# Pop-up message with alert
|
||||
await event.answer('Wrong answer!', alert=True)
|
||||
|
||||
# Send a message with buttons users can click
|
||||
async def main():
|
||||
await client.send_message(user, 'Yes or no?', buttons=[
|
||||
Button.inline('Yes!', b'yes'),
|
||||
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,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateBotCallbackQuery):
|
||||
return cls.Event(update, update.peer, update.msg_id)
|
||||
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
|
||||
# See https://github.com/LonamiWebs/Telethon/pull/1005
|
||||
# The long message ID is actually just msg_id + peer_id
|
||||
mid, pid = struct.unpack('<ii', struct.pack('<q', update.msg_id.id))
|
||||
peer = types.PeerChannel(-pid) if pid < 0 else types.PeerUser(pid)
|
||||
return cls.Event(update, peer, mid)
|
||||
|
||||
def filter(self, event):
|
||||
# We can't call super().filter(...) because it ignores chat_instance
|
||||
if self._no_check:
|
||||
return event
|
||||
|
||||
if self.chats is not None:
|
||||
inside = event.query.chat_instance in self.chats
|
||||
if event.chat_id:
|
||||
inside |= event.chat_id in self.chats
|
||||
|
||||
if inside == self.blacklist_chats:
|
||||
return
|
||||
|
||||
if self.match:
|
||||
if callable(self.match):
|
||||
event.data_match = event.pattern_match = self.match(event.query.data)
|
||||
if not event.data_match:
|
||||
return
|
||||
elif event.query.data != self.match:
|
||||
return
|
||||
|
||||
if self.func:
|
||||
# Return the result of func directly as it may need to be awaited
|
||||
return self.func(event)
|
||||
return True
|
||||
|
||||
class Event(EventCommon, SenderGetter):
|
||||
"""
|
||||
Represents the event of a new callback query.
|
||||
|
||||
Members:
|
||||
query (:tl:`UpdateBotCallbackQuery`):
|
||||
The original :tl:`UpdateBotCallbackQuery`.
|
||||
|
||||
data_match (`obj`, optional):
|
||||
The object returned by the ``data=`` parameter
|
||||
when creating the event builder, if any. Similar
|
||||
to ``pattern_match`` for the new message event.
|
||||
|
||||
pattern_match (`obj`, optional):
|
||||
Alias for ``data_match``.
|
||||
"""
|
||||
def __init__(self, query, peer, msg_id):
|
||||
super().__init__(peer, msg_id=msg_id)
|
||||
SenderGetter.__init__(self, query.user_id)
|
||||
self.query = query
|
||||
self.data_match = None
|
||||
self.pattern_match = None
|
||||
self._message = None
|
||||
self._answered = False
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(
|
||||
self.sender_id, self._entities, client._mb_entity_cache)
|
||||
|
||||
@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 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
|
||||
|
||||
return self._message
|
||||
|
||||
async def _refetch_sender(self):
|
||||
self._sender = self._entities.get(self.sender_id)
|
||||
if not self._sender:
|
||||
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
|
||||
try:
|
||||
self._input_sender = self._client._mb_entity_cache.get(
|
||||
utils.resolve_id(self._sender_id)[0])._as_input_peer()
|
||||
except AttributeError:
|
||||
m = await self.get_message()
|
||||
if m:
|
||||
self._sender = m._sender
|
||||
self._input_sender = m._input_sender
|
||||
|
||||
async def answer(
|
||||
self, message=None, cache_time=0, *, url=None, alert=False):
|
||||
"""
|
||||
Answers the callback query (and stops the loading circle).
|
||||
|
||||
Args:
|
||||
message (`str`, optional):
|
||||
The toast message to show feedback to the user.
|
||||
|
||||
cache_time (`int`, optional):
|
||||
For how long this result should be cached on
|
||||
the user's client. Defaults to 0 for no cache.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
self._answered = True
|
||||
return await self._client(
|
||||
functions.messages.SetBotCallbackAnswerRequest(
|
||||
query_id=self.query.query_id,
|
||||
cache_time=cache_time,
|
||||
alert=alert,
|
||||
message=message,
|
||||
url=url
|
||||
)
|
||||
)
|
||||
|
||||
@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`.
|
||||
|
||||
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, types.UpdateInlineBotCallbackQuery)
|
||||
|
||||
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.
|
||||
|
||||
This method also creates a task to `answer` the callback.
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
self._client.loop.create_task(self.answer())
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
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 also creates a task to `answer` the callback.
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
self._client.loop.create_task(self.answer())
|
||||
kwargs['reply_to'] = self.query.msg_id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
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` or :tl:`InputBotInlineMessageID64`.
|
||||
|
||||
Returns `True` if the edit was successful.
|
||||
|
||||
This method also creates a task to `answer` the callback.
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
"""
|
||||
self._client.loop.create_task(self.answer())
|
||||
if isinstance(self.query.msg_id, (types.InputBotInlineMessageID, types.InputBotInlineMessageID64)):
|
||||
return await self._client.edit_message(
|
||||
self.query.msg_id, *args, **kwargs
|
||||
)
|
||||
else:
|
||||
return await self._client.edit_message(
|
||||
await self.get_input_chat(), self.query.msg_id,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
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 also creates a task to `answer` the callback.
|
||||
|
||||
This method will likely fail if `via_inline` is `True`.
|
||||
"""
|
||||
self._client.loop.create_task(self.answer())
|
||||
if isinstance(self.query.msg_id, (types.InputBotInlineMessageID, types.InputBotInlineMessageID64)):
|
||||
raise TypeError('Inline messages cannot be deleted as there is no API request available to do so')
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.query.msg_id],
|
||||
*args, **kwargs
|
||||
)
|
458
.venv2/Lib/site-packages/telethon/events/chataction.py
Normal file
458
.venv2/Lib/site-packages/telethon/events/chataction.py
Normal file
@@ -0,0 +1,458 @@
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class ChatAction(EventBuilder):
|
||||
"""
|
||||
Occurs on certain chat actions:
|
||||
|
||||
* Whenever a new chat is created.
|
||||
* Whenever a chat's title or photo is changed or removed.
|
||||
* Whenever a new message is pinned.
|
||||
* Whenever a user scores in a game.
|
||||
* Whenever a user joins or is added to the group.
|
||||
* Whenever a user is removed or leaves a group if it has
|
||||
less than 50 members or the removed user was a bot.
|
||||
|
||||
Note that "chat" refers to "small group, megagroup and broadcast
|
||||
channel", whereas "group" refers to "small group and megagroup" only.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.ChatAction)
|
||||
async def handler(event):
|
||||
# Welcome every new user
|
||||
if event.user_joined:
|
||||
await event.reply('Welcome to the group!')
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
# 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').
|
||||
if isinstance(update, types.UpdatePinnedChannelMessages) and not update.pinned:
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
pin_ids=update.messages,
|
||||
pin=update.pinned)
|
||||
|
||||
elif isinstance(update, types.UpdatePinnedMessages) and not update.pinned:
|
||||
return cls.Event(update.peer,
|
||||
pin_ids=update.messages,
|
||||
pin=update.pinned)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantAdd):
|
||||
return cls.Event(types.PeerChat(update.chat_id),
|
||||
added_by=update.inviter_id or True,
|
||||
users=update.user_id)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantDelete):
|
||||
return cls.Event(types.PeerChat(update.chat_id),
|
||||
kicked_by=True,
|
||||
users=update.user_id)
|
||||
|
||||
# UpdateChannel is sent if we leave a channel, and the update._entities
|
||||
# set by _process_update would let us make some guesses. However it's
|
||||
# better not to rely on this. Rely only in MessageActionChatDeleteUser.
|
||||
|
||||
elif (isinstance(update, (
|
||||
types.UpdateNewMessage, types.UpdateNewChannelMessage))
|
||||
and isinstance(update.message, types.MessageService)):
|
||||
msg = update.message
|
||||
action = update.message.action
|
||||
if isinstance(action, types.MessageActionChatJoinedByLink):
|
||||
return cls.Event(msg,
|
||||
added_by=True,
|
||||
users=msg.from_id)
|
||||
elif isinstance(action, types.MessageActionChatAddUser):
|
||||
# If a user adds itself, it means they joined via the public chat username
|
||||
added_by = ([msg.sender_id] == action.users) or msg.from_id
|
||||
return cls.Event(msg,
|
||||
added_by=added_by,
|
||||
users=action.users)
|
||||
elif isinstance(action, types.MessageActionChatDeleteUser):
|
||||
return cls.Event(msg,
|
||||
kicked_by=utils.get_peer_id(msg.from_id) if msg.from_id else True,
|
||||
users=action.user_id)
|
||||
elif isinstance(action, types.MessageActionChatCreate):
|
||||
return cls.Event(msg,
|
||||
users=action.users,
|
||||
created=True,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChannelCreate):
|
||||
return cls.Event(msg,
|
||||
created=True,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||
return cls.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||
return cls.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_photo=action.photo)
|
||||
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||
return cls.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_photo=True)
|
||||
elif isinstance(action, types.MessageActionPinMessage) and msg.reply_to:
|
||||
return cls.Event(msg,
|
||||
pin_ids=[msg.reply_to_msg_id])
|
||||
elif isinstance(action, types.MessageActionGameScore):
|
||||
return cls.Event(msg,
|
||||
new_score=action.score)
|
||||
|
||||
elif isinstance(update, types.UpdateChannelParticipant) \
|
||||
and bool(update.new_participant) != bool(update.prev_participant):
|
||||
# If members are hidden, bots will receive this update instead,
|
||||
# as there won't be a service message. Promotions and demotions
|
||||
# seem to have both new and prev participant, which are ignored
|
||||
# by this event.
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
users=update.user_id,
|
||||
added_by=update.actor_id if update.new_participant else None,
|
||||
kicked_by=update.actor_id if update.prev_participant else None)
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
Represents the event of a new chat action.
|
||||
|
||||
Members:
|
||||
action_message (`MessageAction <https://tl.telethon.dev/types/message_action.html>`_):
|
||||
The message invoked by this Chat Action.
|
||||
|
||||
new_pin (`bool`):
|
||||
`True` if there is a new pin.
|
||||
|
||||
new_photo (`bool`):
|
||||
`True` if there's a new chat photo (or it was removed).
|
||||
|
||||
photo (:tl:`Photo`, optional):
|
||||
The new photo (or `None` if it was removed).
|
||||
|
||||
user_added (`bool`):
|
||||
`True` if the user was added by some other.
|
||||
|
||||
user_joined (`bool`):
|
||||
`True` if the user joined on their own.
|
||||
|
||||
user_left (`bool`):
|
||||
`True` if the user left on their own.
|
||||
|
||||
user_kicked (`bool`):
|
||||
`True` if the user was kicked by some other.
|
||||
|
||||
created (`bool`, optional):
|
||||
`True` if this chat was just created.
|
||||
|
||||
new_title (`str`, optional):
|
||||
The new title string for the chat, if applicable.
|
||||
|
||||
new_score (`str`, optional):
|
||||
The new score string for the game, if applicable.
|
||||
|
||||
unpin (`bool`):
|
||||
`True` if the existing pin gets unpinned.
|
||||
"""
|
||||
|
||||
def __init__(self, where, new_photo=None,
|
||||
added_by=None, kicked_by=None, created=None,
|
||||
users=None, new_title=None, pin_ids=None, pin=None, new_score=None):
|
||||
if isinstance(where, types.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, types.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:
|
||||
self.user_joined = True
|
||||
elif added_by:
|
||||
self.user_added = True
|
||||
self._added_by = added_by
|
||||
|
||||
# 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
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
if self.action_message:
|
||||
self.action_message._finish_init(client, self._entities, None)
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Has the same effect as `respond` if there is no message.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return await self.respond(*args, **kwargs)
|
||||
|
||||
kwargs['reply_to'] = self.action_message.id
|
||||
return await self._client.send_message(
|
||||
await self.get_input_chat(), *args, **kwargs)
|
||||
|
||||
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.
|
||||
|
||||
Does nothing if no message action triggered this event.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return
|
||||
|
||||
return await self._client.delete_messages(
|
||||
await self.get_input_chat(), [self.action_message],
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
if self._pinned_messages:
|
||||
return self._pinned_messages[0]
|
||||
|
||||
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
|
||||
|
||||
chat = await self.get_input_chat()
|
||||
if chat:
|
||||
self._pinned_messages = await self._client.get_messages(
|
||||
self._input_chat, ids=self._pin_ids)
|
||||
|
||||
return self._pinned_messages
|
||||
|
||||
@property
|
||||
def added_by(self):
|
||||
"""
|
||||
The user who added ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._added_by and not isinstance(self._added_by, types.User):
|
||||
aby = self._entities.get(utils.get_peer_id(self._added_by))
|
||||
if aby:
|
||||
self._added_by = aby
|
||||
|
||||
return self._added_by
|
||||
|
||||
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)
|
||||
|
||||
return self._added_by
|
||||
|
||||
@property
|
||||
def kicked_by(self):
|
||||
"""
|
||||
The user who kicked ``users``, if applicable (`None` otherwise).
|
||||
"""
|
||||
if self._kicked_by and not isinstance(self._kicked_by, types.User):
|
||||
kby = self._entities.get(utils.get_peer_id(self._kicked_by))
|
||||
if kby:
|
||||
self._kicked_by = kby
|
||||
|
||||
return self._kicked_by
|
||||
|
||||
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._kicked_by
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""
|
||||
The first user that takes part in this action. For example, who joined.
|
||||
|
||||
Might be `None` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
if self.users:
|
||||
return self._users[0]
|
||||
|
||||
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_user(self):
|
||||
"""
|
||||
Input version of the ``self.user`` property.
|
||||
"""
|
||||
if self.input_users:
|
||||
return self._input_users[0]
|
||||
|
||||
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]
|
||||
|
||||
@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]
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
"""
|
||||
A list of users that take part in this action. For example, who joined.
|
||||
|
||||
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, (types.User, types.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:
|
||||
# First 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
|
||||
|
||||
# If missing, try from the entity cache
|
||||
try:
|
||||
self._input_users.append(self._client._mb_entity_cache.get(
|
||||
utils.resolve_id(user_id)[0])._as_input_peer())
|
||||
continue
|
||||
except AttributeError:
|
||||
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, (types.User, types.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[:]
|
186
.venv2/Lib/site-packages/telethon/events/common.py
Normal file
186
.venv2/Lib/site-packages/telethon/events/common.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import abc
|
||||
import asyncio
|
||||
import warnings
|
||||
|
||||
from .. import utils
|
||||
from ..tl import TLObject, types
|
||||
from ..tl.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):
|
||||
if chat < 0:
|
||||
result.add(chat) # Explicitly marked IDs are negative
|
||||
else:
|
||||
result.update({ # Support all valid types of peers
|
||||
utils.get_peer_id(types.PeerUser(chat)),
|
||||
utils.get_peer_id(types.PeerChat(chat)),
|
||||
utils.get_peer_id(types.PeerChannel(chat)),
|
||||
})
|
||||
elif isinstance(chat, 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, types.InputPeerSelf):
|
||||
chat = await client.get_me(input_peer=True)
|
||||
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=None, self_id=None):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
self._client = client
|
||||
if self._chat_peer:
|
||||
self._chat, self._input_chat = utils._get_entity_pair(
|
||||
self.chat_id, self._entities, client._mb_entity_cache)
|
||||
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 TLObject.pretty_format(self.to_dict())
|
||||
|
||||
def stringify(self):
|
||||
return 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
|
247
.venv2/Lib/site-packages/telethon/events/inlinequery.py
Normal file
247
.venv2/Lib/site-packages/telethon/events/inlinequery.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import inspect
|
||||
import re
|
||||
|
||||
import asyncio
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils, helpers
|
||||
from ..tl import types, functions, custom
|
||||
from ..tl.custom.sendergetter import SenderGetter
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class InlineQuery(EventBuilder):
|
||||
"""
|
||||
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.
|
||||
|
||||
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``.
|
||||
|
||||
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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.InlineQuery)
|
||||
async def handler(event):
|
||||
builder = event.builder
|
||||
|
||||
# Two options (convert user text to UPPERCASE or lowercase)
|
||||
await event.answer([
|
||||
builder.article('UPPERCASE', text=event.text.upper()),
|
||||
builder.article('lowercase', text=event.text.lower()),
|
||||
])
|
||||
"""
|
||||
def __init__(
|
||||
self, users=None, *, blacklist_users=False, func=None, pattern=None):
|
||||
super().__init__(users, blacklist_chats=blacklist_users, func=func)
|
||||
|
||||
if isinstance(pattern, str):
|
||||
self.pattern = re.compile(pattern).match
|
||||
elif not pattern or callable(pattern):
|
||||
self.pattern = pattern
|
||||
elif hasattr(pattern, 'match') and callable(pattern.match):
|
||||
self.pattern = pattern.match
|
||||
else:
|
||||
raise TypeError('Invalid pattern type given')
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateBotInlineQuery):
|
||||
return cls.Event(update)
|
||||
|
||||
def filter(self, event):
|
||||
if self.pattern:
|
||||
match = self.pattern(event.text)
|
||||
if not match:
|
||||
return
|
||||
event.pattern_match = match
|
||||
|
||||
return super().filter(event)
|
||||
|
||||
class Event(EventCommon, SenderGetter):
|
||||
"""
|
||||
Represents the event of a new callback query.
|
||||
|
||||
Members:
|
||||
query (:tl:`UpdateBotInlineQuery`):
|
||||
The original :tl:`UpdateBotInlineQuery`.
|
||||
|
||||
Make sure to access the `text` property of the query if
|
||||
you want the text rather than the actual query object.
|
||||
|
||||
pattern_match (`obj`, optional):
|
||||
The resulting object from calling the passed ``pattern``
|
||||
function, which is ``re.compile(...).match`` by default.
|
||||
"""
|
||||
def __init__(self, query):
|
||||
super().__init__(chat_peer=types.PeerUser(query.user_id))
|
||||
SenderGetter.__init__(self, query.user_id)
|
||||
self.query = query
|
||||
self.pattern_match = None
|
||||
self._answered = False
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(
|
||||
self.sender_id, self._entities, client._mb_entity_cache)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
Returns the unique identifier for the query ID.
|
||||
"""
|
||||
return self.query.query_id
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
Returns the text the user used to make the inline query.
|
||||
"""
|
||||
return self.query.query
|
||||
|
||||
@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 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 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.
|
||||
|
||||
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:
|
||||
|
||||
.. 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 = types.InlineBotSwitchPM(switch_pm, switch_pm_param)
|
||||
|
||||
return await self._client(
|
||||
functions.messages.SetInlineBotResultsRequest(
|
||||
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)
|
||||
|
||||
f = helpers.get_running_loop().create_future()
|
||||
f.set_result(obj)
|
||||
return f
|
57
.venv2/Lib/site-packages/telethon/events/messagedeleted.py
Normal file
57
.venv2/Lib/site-packages/telethon/events/messagedeleted.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageDeleted(EventBuilder):
|
||||
"""
|
||||
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
|
||||
was deleted.
|
||||
|
||||
.. important::
|
||||
|
||||
Telegram **does not** send information about *where* a message
|
||||
was deleted if it occurs in private conversations with other users
|
||||
or in small group chats, because message IDs are *unique* and you
|
||||
can identify the chat with the message ID alone if you saved it
|
||||
previously.
|
||||
|
||||
Telethon **does not** save information of where messages occur,
|
||||
so it cannot know in which chat a message was deleted (this will
|
||||
only work in channels, where the channel ID *is* present).
|
||||
|
||||
This means that the ``chats=`` parameter will not work reliably,
|
||||
unless you intend on working with channels and super-groups only.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.MessageDeleted)
|
||||
async def handler(event):
|
||||
# Log all deleted message IDs
|
||||
for msg_id in event.deleted_ids:
|
||||
print('Message', msg_id, 'was deleted in', event.chat_id)
|
||||
"""
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateDeleteMessages):
|
||||
return cls.Event(
|
||||
deleted_ids=update.messages,
|
||||
peer=None
|
||||
)
|
||||
elif isinstance(update, types.UpdateDeleteChannelMessages):
|
||||
return cls.Event(
|
||||
deleted_ids=update.messages,
|
||||
peer=types.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
|
52
.venv2/Lib/site-packages/telethon/events/messageedited.py
Normal file
52
.venv2/Lib/site-packages/telethon/events/messageedited.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from .common import name_inner_event
|
||||
from .newmessage import NewMessage
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageEdited(NewMessage):
|
||||
"""
|
||||
Occurs whenever a message is edited. Just like `NewMessage
|
||||
<telethon.events.newmessage.NewMessage>`, you should treat
|
||||
this event as a `Message <telethon.tl.custom.message.Message>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
On channels, `Message.out <telethon.tl.custom.message.Message>`
|
||||
will be `True` if you sent the message originally, **not if
|
||||
you edited it**! This can be dangerous if you run outgoing
|
||||
commands on edits.
|
||||
|
||||
Some examples follow:
|
||||
|
||||
* You send a message "A", ``out is True``.
|
||||
* You edit "A" to "B", ``out is True``.
|
||||
* Someone else edits "B" to "C", ``out is True`` (**be careful!**).
|
||||
* Someone sends "X", ``out is False``.
|
||||
* Someone edits "X" to "Y", ``out is False``.
|
||||
* You edit "Y" to "Z", ``out is False``.
|
||||
|
||||
Since there are useful cases where you need the right ``out``
|
||||
value, the library cannot do anything automatically to help you.
|
||||
Instead, consider using ``from_users='me'`` (it won't work in
|
||||
broadcast channels at all since the sender is the channel and
|
||||
not you).
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.MessageEdited)
|
||||
async def handler(event):
|
||||
# Log the date of new edits
|
||||
print('Message', event.id, 'changed at', event.date)
|
||||
"""
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, (types.UpdateEditMessage,
|
||||
types.UpdateEditChannelMessage)):
|
||||
return cls.Event(update.message)
|
||||
|
||||
class Event(NewMessage.Event):
|
||||
pass # Required if we want a different name for it
|
143
.venv2/Lib/site-packages/telethon/events/messageread.py
Normal file
143
.venv2/Lib/site-packages/telethon/events/messageread.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.MessageRead)
|
||||
async def handler(event):
|
||||
# Log when someone reads your messages
|
||||
print('Someone has read all your messages until', event.max_id)
|
||||
|
||||
@client.on(events.MessageRead(inbox=True))
|
||||
async def handler(event):
|
||||
# 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
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateReadHistoryInbox):
|
||||
return cls.Event(update.peer, update.max_id, False)
|
||||
elif isinstance(update, types.UpdateReadHistoryOutbox):
|
||||
return cls.Event(update.peer, update.max_id, True)
|
||||
elif isinstance(update, types.UpdateReadChannelInbox):
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
update.max_id, False)
|
||||
elif isinstance(update, types.UpdateReadChannelOutbox):
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
update.max_id, True)
|
||||
elif isinstance(update, types.UpdateReadMessagesContents):
|
||||
return cls.Event(message_ids=update.messages,
|
||||
contents=True)
|
||||
elif isinstance(update, types.UpdateChannelReadMessagesContents):
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
message_ids=update.messages,
|
||||
contents=True)
|
||||
|
||||
def filter(self, event):
|
||||
if self.inbox == event.outbox:
|
||||
return
|
||||
|
||||
return super().filter(event)
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
Represents the event of one or more messages being read.
|
||||
|
||||
Members:
|
||||
max_id (`int`):
|
||||
Up to which message ID has been read. Every message
|
||||
with an ID equal or lower to it have been read.
|
||||
|
||||
outbox (`bool`):
|
||||
`True` if someone else has read your messages.
|
||||
|
||||
contents (`bool`):
|
||||
`True` if what was read were the contents of a message.
|
||||
This will be the case when e.g. you play a voice note.
|
||||
It may only be set on ``inbox`` events.
|
||||
"""
|
||||
def __init__(self, peer=None, max_id=None, out=False, contents=False,
|
||||
message_ids=None):
|
||||
self.outbox = out
|
||||
self.contents = contents
|
||||
self._message_ids = message_ids or []
|
||||
self._messages = None
|
||||
self.max_id = max_id or max(message_ids or [], default=None)
|
||||
super().__init__(peer, self.max_id)
|
||||
|
||||
@property
|
||||
def inbox(self):
|
||||
"""
|
||||
`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.
|
||||
|
||||
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.
|
||||
|
||||
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]
|
||||
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)
|
223
.venv2/Lib/site-packages/telethon/events/newmessage.py
Normal file
223
.venv2/Lib/site-packages/telethon/events/newmessage.py
Normal file
@@ -0,0 +1,223 @@
|
||||
import re
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set
|
||||
from .. import utils
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class NewMessage(EventBuilder):
|
||||
"""
|
||||
Occurs whenever a new text message or a message with media arrives.
|
||||
|
||||
Args:
|
||||
incoming (`bool`, optional):
|
||||
If set to `True`, only **incoming** messages will be handled.
|
||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
||||
|
||||
outgoing (`bool`, optional):
|
||||
If set to `True`, only **outgoing** messages will be handled.
|
||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.NewMessage(pattern='(?i)hello.+'))
|
||||
async def handler(event):
|
||||
# Respond whenever someone says "Hello" and something else
|
||||
await event.reply('Hey!')
|
||||
|
||||
@client.on(events.NewMessage(outgoing=True, pattern='!ping'))
|
||||
async def handler(event):
|
||||
# Say "!pong" whenever you send "!ping", then delete both messages
|
||||
m = await event.respond('!pong')
|
||||
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!")
|
||||
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update,
|
||||
(types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||
if not isinstance(update.message, types.Message):
|
||||
return # We don't care about MessageService's here
|
||||
event = cls.Event(update.message)
|
||||
elif isinstance(update, types.UpdateShortMessage):
|
||||
event = cls.Event(types.Message(
|
||||
out=update.out,
|
||||
mentioned=update.mentioned,
|
||||
media_unread=update.media_unread,
|
||||
silent=update.silent,
|
||||
id=update.id,
|
||||
peer_id=types.PeerUser(update.user_id),
|
||||
from_id=types.PeerUser(self_id if update.out else update.user_id),
|
||||
message=update.message,
|
||||
date=update.date,
|
||||
fwd_from=update.fwd_from,
|
||||
via_bot_id=update.via_bot_id,
|
||||
reply_to=update.reply_to,
|
||||
entities=update.entities,
|
||||
ttl_period=update.ttl_period
|
||||
))
|
||||
elif isinstance(update, types.UpdateShortChatMessage):
|
||||
event = cls.Event(types.Message(
|
||||
out=update.out,
|
||||
mentioned=update.mentioned,
|
||||
media_unread=update.media_unread,
|
||||
silent=update.silent,
|
||||
id=update.id,
|
||||
from_id=types.PeerUser(self_id if update.out else update.from_id),
|
||||
peer_id=types.PeerChat(update.chat_id),
|
||||
message=update.message,
|
||||
date=update.date,
|
||||
fwd_from=update.fwd_from,
|
||||
via_bot_id=update.via_bot_id,
|
||||
reply_to=update.reply_to,
|
||||
entities=update.entities,
|
||||
ttl_period=update.ttl_period
|
||||
))
|
||||
else:
|
||||
return
|
||||
|
||||
return event
|
||||
|
||||
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
|
||||
m._finish_init(client, self._entities, None)
|
||||
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)
|
53
.venv2/Lib/site-packages/telethon/events/raw.py
Normal file
53
.venv2/Lib/site-packages/telethon/events/raw.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from .common import EventBuilder
|
||||
from .. import utils
|
||||
|
||||
|
||||
class Raw(EventBuilder):
|
||||
"""
|
||||
Raw events are not actual events. Instead, they are the raw
|
||||
: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
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.Raw)
|
||||
async def handler(update):
|
||||
# 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):
|
||||
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
|
310
.venv2/Lib/site-packages/telethon/events/userupdate.py
Normal file
310
.venv2/Lib/site-packages/telethon/events/userupdate.py
Normal file
@@ -0,0 +1,310 @@
|
||||
import datetime
|
||||
import functools
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types
|
||||
from ..tl.custom.sendergetter import SenderGetter
|
||||
|
||||
|
||||
# TODO Either the properties are poorly named or they should be
|
||||
# different events, but that would be a breaking change.
|
||||
#
|
||||
# TODO There are more "user updates", but bundling them all up
|
||||
# in a single place will make it annoying to use (since
|
||||
# the user needs to check for the existence of `None`).
|
||||
#
|
||||
# TODO Handle UpdateUserBlocked, UpdateUserName, UpdateUserPhone, UpdateUser
|
||||
|
||||
def _requires_action(function):
|
||||
@functools.wraps(function)
|
||||
def wrapped(self):
|
||||
return None if self.action is None else function(self)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _requires_status(function):
|
||||
@functools.wraps(function)
|
||||
def wrapped(self):
|
||||
return None if self.status is None else function(self)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class UserUpdate(EventBuilder):
|
||||
"""
|
||||
Occurs whenever a user goes online, starts typing, etc.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import events
|
||||
|
||||
@client.on(events.UserUpdate)
|
||||
async def handler(event):
|
||||
# If someone is uploading, say something
|
||||
if event.uploading:
|
||||
await client.send_message(event.user_id, 'What are you sending?')
|
||||
"""
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateUserStatus):
|
||||
return cls.Event(types.PeerUser(update.user_id),
|
||||
status=update.status)
|
||||
elif isinstance(update, types.UpdateChannelUserTyping):
|
||||
return cls.Event(update.from_id,
|
||||
chat_peer=types.PeerChannel(update.channel_id),
|
||||
typing=update.action)
|
||||
elif isinstance(update, types.UpdateChatUserTyping):
|
||||
return cls.Event(update.from_id,
|
||||
chat_peer=types.PeerChat(update.chat_id),
|
||||
typing=update.action)
|
||||
elif isinstance(update, types.UpdateUserTyping):
|
||||
return cls.Event(update.user_id,
|
||||
typing=update.action)
|
||||
|
||||
class Event(EventCommon, SenderGetter):
|
||||
"""
|
||||
Represents the event of a user update
|
||||
such as gone online, started typing, etc.
|
||||
|
||||
Members:
|
||||
status (:tl:`UserStatus`, optional):
|
||||
The user status if the update is about going online or offline.
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self, peer, *, status=None, chat_peer=None, typing=None):
|
||||
super().__init__(chat_peer or peer)
|
||||
SenderGetter.__init__(self, utils.get_peer_id(peer))
|
||||
|
||||
self.status = status
|
||||
self.action = typing
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
self._sender, self._input_sender = utils._get_entity_pair(
|
||||
self.sender_id, self._entities, client._mb_entity_cache)
|
||||
|
||||
@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):
|
||||
"""
|
||||
`True` if the action is typing a message.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageTypingAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def uploading(self):
|
||||
"""
|
||||
`True` if the action is uploading something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
types.SendMessageChooseContactAction,
|
||||
types.SendMessageChooseStickerAction,
|
||||
types.SendMessageUploadAudioAction,
|
||||
types.SendMessageUploadDocumentAction,
|
||||
types.SendMessageUploadPhotoAction,
|
||||
types.SendMessageUploadRoundAction,
|
||||
types.SendMessageUploadVideoAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def recording(self):
|
||||
"""
|
||||
`True` if the action is recording something.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
types.SendMessageRecordAudioAction,
|
||||
types.SendMessageRecordRoundAction,
|
||||
types.SendMessageRecordVideoAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def playing(self):
|
||||
"""
|
||||
`True` if the action is playing a game.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageGamePlayAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def cancel(self):
|
||||
"""
|
||||
`True` if the action was cancelling other actions.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageCancelAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def geo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a geo.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageGeoLocationAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def audio(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an audio.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
types.SendMessageRecordAudioAction,
|
||||
types.SendMessageUploadAudioAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def round(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is a round video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
types.SendMessageRecordRoundAction,
|
||||
types.SendMessageUploadRoundAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def video(self):
|
||||
"""
|
||||
`True` if what's being recorded/uploaded is an video.
|
||||
"""
|
||||
return isinstance(self.action, (
|
||||
types.SendMessageRecordVideoAction,
|
||||
types.SendMessageUploadVideoAction
|
||||
))
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def contact(self):
|
||||
"""
|
||||
`True` if what's being uploaded (selected) is a contact.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageChooseContactAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def document(self):
|
||||
"""
|
||||
`True` if what's being uploaded is document.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageUploadDocumentAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def sticker(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a sticker.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageChooseStickerAction)
|
||||
|
||||
@property
|
||||
@_requires_action
|
||||
def photo(self):
|
||||
"""
|
||||
`True` if what's being uploaded is a photo.
|
||||
"""
|
||||
return isinstance(self.action, types.SendMessageUploadPhotoAction)
|
||||
|
||||
@property
|
||||
@_requires_status
|
||||
def last_seen(self):
|
||||
"""
|
||||
Exact `datetime.datetime` when the user was last seen if known.
|
||||
"""
|
||||
if isinstance(self.status, types.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, types.UserStatusOnline):
|
||||
return self.status.expires
|
||||
|
||||
def _last_seen_delta(self):
|
||||
if isinstance(self.status, types.UserStatusOffline):
|
||||
return datetime.datetime.now(tz=datetime.timezone.utc) - self.status.was_online
|
||||
elif isinstance(self.status, types.UserStatusOnline):
|
||||
return datetime.timedelta(days=0)
|
||||
elif isinstance(self.status, types.UserStatusRecently):
|
||||
return datetime.timedelta(days=1)
|
||||
elif isinstance(self.status, types.UserStatusLastWeek):
|
||||
return datetime.timedelta(days=7)
|
||||
elif isinstance(self.status, types.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)
|
Reference in New Issue
Block a user