mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-09 05:19:41 +00:00
Begin unification of event builders and events
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
Reference in New Issue
Block a user