mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-09 05:19:41 +00:00
@@ -1,4 +1 @@
|
||||
from .tlobject import TLObject
|
||||
from .gzip_packed import GzipPacked
|
||||
from .tl_message import TLMessage
|
||||
from .message_container import MessageContainer
|
||||
from .tlobject import TLObject, TLRequest
|
||||
|
26
telethon/tl/core/__init__.py
Normal file
26
telethon/tl/core/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
This module holds core "special" types, which are more convenient ways
|
||||
to do stuff in a `telethon.network.mtprotosender.MTProtoSender` instance.
|
||||
|
||||
Only special cases are gzip-packed data, the response message (not a
|
||||
client message), the message container which references these messages
|
||||
and would otherwise conflict with the rest, and finally the RpcResult:
|
||||
|
||||
rpc_result#f35c6d01 req_msg_id:long result:bytes = RpcResult;
|
||||
|
||||
Three things to note with this definition:
|
||||
1. The constructor ID is actually ``42d36c2c``.
|
||||
2. Those bytes are not read like the rest of bytes (length + payload).
|
||||
They are actually the raw bytes of another object, which can't be
|
||||
read directly because it depends on per-request information (since
|
||||
some can return ``Vector<int>`` and ``Vector<long>``).
|
||||
3. Those bytes may be gzipped data, which needs to be treated early.
|
||||
"""
|
||||
from .tlmessage import TLMessage
|
||||
from .gzippacked import GzipPacked
|
||||
from .messagecontainer import MessageContainer
|
||||
from .rpcresult import RpcResult
|
||||
|
||||
core_objects = {x.CONSTRUCTOR_ID: x for x in (
|
||||
GzipPacked, MessageContainer, RpcResult
|
||||
)}
|
@@ -1,7 +1,7 @@
|
||||
import gzip
|
||||
import struct
|
||||
|
||||
from . import TLObject
|
||||
from .. import TLObject, TLRequest
|
||||
|
||||
|
||||
class GzipPacked(TLObject):
|
||||
@@ -21,7 +21,7 @@ class GzipPacked(TLObject):
|
||||
"""
|
||||
data = bytes(request)
|
||||
# TODO This threshold could be configurable
|
||||
if request.content_related and len(data) > 512:
|
||||
if isinstance(request, TLRequest) and len(data) > 512:
|
||||
gzipped = bytes(GzipPacked(data))
|
||||
return gzipped if len(gzipped) < len(data) else data
|
||||
else:
|
||||
@@ -36,3 +36,7 @@ class GzipPacked(TLObject):
|
||||
def read(reader):
|
||||
assert reader.read_int(signed=False) == GzipPacked.CONSTRUCTOR_ID
|
||||
return gzip.decompress(reader.tgread_bytes())
|
||||
|
||||
@classmethod
|
||||
def from_reader(cls, reader):
|
||||
return GzipPacked(gzip.decompress(reader.tgread_bytes()))
|
50
telethon/tl/core/messagecontainer.py
Normal file
50
telethon/tl/core/messagecontainer.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from .tlmessage import TLMessage
|
||||
from ..tlobject import TLObject
|
||||
|
||||
__log__ = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MessageContainer(TLObject):
|
||||
CONSTRUCTOR_ID = 0x73f1f8dc
|
||||
|
||||
def __init__(self, messages):
|
||||
self.messages = messages
|
||||
|
||||
def to_dict(self, recursive=True):
|
||||
return {
|
||||
'messages':
|
||||
([] if self.messages is None else [
|
||||
None if x is None else x.to_dict() for x in self.messages
|
||||
]) if recursive else self.messages,
|
||||
}
|
||||
|
||||
def __bytes__(self):
|
||||
return struct.pack(
|
||||
'<Ii', MessageContainer.CONSTRUCTOR_ID, len(self.messages)
|
||||
) + b''.join(bytes(m) for m in self.messages)
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self)
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self, indent=0)
|
||||
|
||||
@classmethod
|
||||
def from_reader(cls, reader):
|
||||
# This assumes that .read_* calls are done in the order they appear
|
||||
messages = []
|
||||
for _ in range(reader.read_int()):
|
||||
msg_id = reader.read_long()
|
||||
seq_no = reader.read_int()
|
||||
length = reader.read_int()
|
||||
before = reader.tell_position()
|
||||
obj = reader.tgread_object()
|
||||
messages.append(TLMessage(msg_id, seq_no, obj))
|
||||
if reader.tell_position() != before + length:
|
||||
reader.set_position(before)
|
||||
__log__.warning('Data left after TLObject {}: {!r}'
|
||||
.format(obj, reader.read(length)))
|
||||
return MessageContainer(messages)
|
23
telethon/tl/core/rpcresult.py
Normal file
23
telethon/tl/core/rpcresult.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from .gzippacked import GzipPacked
|
||||
from ..types import RpcError
|
||||
|
||||
|
||||
class RpcResult:
|
||||
CONSTRUCTOR_ID = 0xf35c6d01
|
||||
|
||||
def __init__(self, req_msg_id, body, error):
|
||||
self.req_msg_id = req_msg_id
|
||||
self.body = body
|
||||
self.error = error
|
||||
|
||||
@classmethod
|
||||
def from_reader(cls, reader):
|
||||
msg_id = reader.read_long()
|
||||
inner_code = reader.read_int(signed=False)
|
||||
if inner_code == RpcError.CONSTRUCTOR_ID:
|
||||
return RpcResult(msg_id, None, RpcError.from_reader(reader))
|
||||
if inner_code == GzipPacked.CONSTRUCTOR_ID:
|
||||
return RpcResult(msg_id, GzipPacked.from_reader(reader).data, None)
|
||||
|
||||
reader.seek(-4)
|
||||
return RpcResult(msg_id, reader.read(), None)
|
59
telethon/tl/core/tlmessage.py
Normal file
59
telethon/tl/core/tlmessage.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import asyncio
|
||||
import struct
|
||||
|
||||
from .gzippacked import GzipPacked
|
||||
from .. import TLObject
|
||||
from ..functions import InvokeAfterMsgRequest
|
||||
|
||||
|
||||
class TLMessage(TLObject):
|
||||
"""
|
||||
https://core.telegram.org/mtproto/service_messages#simple-container.
|
||||
|
||||
Messages are what's ultimately sent to Telegram:
|
||||
message msg_id:long seqno:int bytes:int body:bytes = Message;
|
||||
|
||||
Each message has its own unique identifier, and the body is simply
|
||||
the serialized request that should be executed on the server. Then
|
||||
Telegram will, at some point, respond with the result for this msg.
|
||||
|
||||
Thus it makes sense that requests and their result are bound to a
|
||||
sent `TLMessage`, and this result can be represented as a `Future`
|
||||
that will eventually be set with either a result, error or cancelled.
|
||||
"""
|
||||
def __init__(self, msg_id, seq_no, obj=None, after_id=0):
|
||||
super().__init__()
|
||||
self.msg_id = msg_id
|
||||
self.seq_no = seq_no
|
||||
self.obj = obj
|
||||
self.container_msg_id = None
|
||||
self.future = asyncio.Future()
|
||||
|
||||
# After which message ID this one should run. We do this so
|
||||
# InvokeAfterMsgRequest is transparent to the user and we can
|
||||
# easily invoke after while confirming the original request.
|
||||
self.after_id = after_id
|
||||
|
||||
def to_dict(self, recursive=True):
|
||||
return {
|
||||
'msg_id': self.msg_id,
|
||||
'seq_no': self.seq_no,
|
||||
'obj': self.obj,
|
||||
'container_msg_id': self.container_msg_id,
|
||||
'after_id': self.after_id
|
||||
}
|
||||
|
||||
def __bytes__(self):
|
||||
if self.after_id is None:
|
||||
body = GzipPacked.gzip_if_smaller(self.obj)
|
||||
else:
|
||||
body = GzipPacked.gzip_if_smaller(
|
||||
InvokeAfterMsgRequest(self.after_id, self.obj))
|
||||
|
||||
return struct.pack('<qii', self.msg_id, self.seq_no, len(body)) + body
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self)
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self, indent=0)
|
@@ -88,12 +88,13 @@ class Dialog:
|
||||
)
|
||||
self.is_channel = isinstance(self.entity, types.Channel)
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
async def send_message(self, *args, **kwargs):
|
||||
"""
|
||||
Sends a message to this dialog. This is just a wrapper around
|
||||
``client.send_message(dialog.input_entity, *args, **kwargs)``.
|
||||
"""
|
||||
return self._client.send_message(self.input_entity, *args, **kwargs)
|
||||
return await self._client.send_message(
|
||||
self.input_entity, *args, **kwargs)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
|
@@ -47,18 +47,18 @@ class Draft:
|
||||
return cls(client=client, peer=update.peer, draft=update.draft)
|
||||
|
||||
@property
|
||||
def entity(self):
|
||||
async def entity(self):
|
||||
"""
|
||||
The entity that belongs to this dialog (user, chat or channel).
|
||||
"""
|
||||
return self._client.get_entity(self._peer)
|
||||
return await self._client.get_entity(self._peer)
|
||||
|
||||
@property
|
||||
def input_entity(self):
|
||||
async def input_entity(self):
|
||||
"""
|
||||
Input version of the entity.
|
||||
"""
|
||||
return self._client.get_input_entity(self._peer)
|
||||
return await self._client.get_input_entity(self._peer)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
@@ -83,8 +83,9 @@ class Draft:
|
||||
"""
|
||||
return not self._text
|
||||
|
||||
def set_message(self, text=None, reply_to=0, parse_mode=Default,
|
||||
link_preview=None):
|
||||
async def set_message(
|
||||
self, text=None, reply_to=0, parse_mode=Default,
|
||||
link_preview=None):
|
||||
"""
|
||||
Changes the draft message on the Telegram servers. The changes are
|
||||
reflected in this object.
|
||||
@@ -110,8 +111,10 @@ class Draft:
|
||||
if link_preview is None:
|
||||
link_preview = self.link_preview
|
||||
|
||||
raw_text, entities = self._client._parse_message_text(text, parse_mode)
|
||||
result = self._client(SaveDraftRequest(
|
||||
raw_text, entities =\
|
||||
await self._client._parse_message_text(text, parse_mode)
|
||||
|
||||
result = await self._client(SaveDraftRequest(
|
||||
peer=self._peer,
|
||||
message=raw_text,
|
||||
no_webpage=not link_preview,
|
||||
@@ -128,22 +131,22 @@ class Draft:
|
||||
|
||||
return result
|
||||
|
||||
def send(self, clear=True, parse_mode=Default):
|
||||
async def send(self, clear=True, parse_mode=Default):
|
||||
"""
|
||||
Sends the contents of this draft to the dialog. This is just a
|
||||
wrapper around ``send_message(dialog.input_entity, *args, **kwargs)``.
|
||||
"""
|
||||
self._client.send_message(self._peer, self.text,
|
||||
reply_to=self.reply_to_msg_id,
|
||||
link_preview=self.link_preview,
|
||||
parse_mode=parse_mode,
|
||||
clear_draft=clear)
|
||||
await self._client.send_message(
|
||||
self._peer, self.text, reply_to=self.reply_to_msg_id,
|
||||
link_preview=self.link_preview, parse_mode=parse_mode,
|
||||
clear_draft=clear
|
||||
)
|
||||
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""
|
||||
Deletes this draft, and returns ``True`` on success.
|
||||
"""
|
||||
return self.set_message(text='')
|
||||
return await self.set_message(text='')
|
||||
|
||||
def to_dict(self):
|
||||
try:
|
||||
|
@@ -134,14 +134,15 @@ class Message:
|
||||
if isinstance(self.original_message, types.MessageService):
|
||||
return self.original_message.action
|
||||
|
||||
def _reload_message(self):
|
||||
async def _reload_message(self):
|
||||
"""
|
||||
Re-fetches this message to reload the sender and chat entities,
|
||||
along with their input versions.
|
||||
"""
|
||||
try:
|
||||
chat = self.input_chat if self.is_channel else None
|
||||
msg = self._client.get_messages(chat, ids=self.original_message.id)
|
||||
chat = await self.input_chat if self.is_channel else None
|
||||
msg = await self._client.get_messages(
|
||||
chat, ids=self.original_message.id)
|
||||
except ValueError:
|
||||
return # We may not have the input chat/get message failed
|
||||
if not msg:
|
||||
@@ -153,7 +154,7 @@ class Message:
|
||||
self._input_chat = msg._input_chat
|
||||
|
||||
@property
|
||||
def sender(self):
|
||||
async def sender(self):
|
||||
"""
|
||||
This (:tl:`User`) may make an API call the first time to get
|
||||
the most up to date version of the sender (mostly when the event
|
||||
@@ -163,22 +164,24 @@ class Message:
|
||||
"""
|
||||
if self._sender is None:
|
||||
try:
|
||||
self._sender = self._client.get_entity(self.input_sender)
|
||||
self._sender =\
|
||||
await self._client.get_entity(await self.input_sender)
|
||||
except ValueError:
|
||||
self._reload_message()
|
||||
await self._reload_message()
|
||||
return self._sender
|
||||
|
||||
@property
|
||||
def chat(self):
|
||||
async def chat(self):
|
||||
if self._chat is None:
|
||||
try:
|
||||
self._chat = self._client.get_entity(self.input_chat)
|
||||
self._chat =\
|
||||
await self._client.get_entity(await self.input_chat)
|
||||
except ValueError:
|
||||
self._reload_message()
|
||||
await self._reload_message()
|
||||
return self._chat
|
||||
|
||||
@property
|
||||
def input_sender(self):
|
||||
async def input_sender(self):
|
||||
"""
|
||||
This (:tl:`InputPeer`) is the input version of the user who
|
||||
sent the message. Similarly to `input_chat`, this doesn't have
|
||||
@@ -194,14 +197,14 @@ class Message:
|
||||
self._input_sender = get_input_peer(self._sender)
|
||||
else:
|
||||
try:
|
||||
self._input_sender = self._client.get_input_entity(
|
||||
self._input_sender = await self._client.get_input_entity(
|
||||
self.original_message.from_id)
|
||||
except ValueError:
|
||||
self._reload_message()
|
||||
await self._reload_message()
|
||||
return self._input_sender
|
||||
|
||||
@property
|
||||
def input_chat(self):
|
||||
async def input_chat(self):
|
||||
"""
|
||||
This (:tl:`InputPeer`) is the input version of the chat where the
|
||||
message was sent. Similarly to `input_sender`, this doesn't have
|
||||
@@ -214,14 +217,14 @@ class Message:
|
||||
if self._input_chat is None:
|
||||
if self._chat is None:
|
||||
try:
|
||||
self._chat = self._client.get_input_entity(
|
||||
self._chat = await self._client.get_input_entity(
|
||||
self.original_message.to_id)
|
||||
except ValueError:
|
||||
# There's a chance that the chat is a recent new dialog.
|
||||
# The input chat cannot rely on ._reload_message() because
|
||||
# said method may need the input chat.
|
||||
target = self.chat_id
|
||||
for d in self._client.iter_dialogs(100):
|
||||
async for d in self._client.iter_dialogs(100):
|
||||
if d.id == target:
|
||||
self._chat = d.entity
|
||||
break
|
||||
@@ -269,24 +272,26 @@ class Message:
|
||||
return bool(self.original_message.reply_to_msg_id)
|
||||
|
||||
@property
|
||||
def buttons(self):
|
||||
async def buttons(self):
|
||||
"""
|
||||
Returns a matrix (list of lists) containing all buttons of the message
|
||||
as `telethon.tl.custom.messagebutton.MessageButton` instances.
|
||||
"""
|
||||
if self._buttons is None and self.original_message.reply_markup:
|
||||
sender = await self.input_sender
|
||||
chat = await self.input_chat
|
||||
if isinstance(self.original_message.reply_markup, (
|
||||
types.ReplyInlineMarkup, types.ReplyKeyboardMarkup)):
|
||||
self._buttons = [[
|
||||
MessageButton(self._client, button, self.input_sender,
|
||||
self.input_chat, self.original_message.id)
|
||||
MessageButton(self._client, button, sender, chat,
|
||||
self.original_message.id)
|
||||
for button in row.buttons
|
||||
] for row in self.original_message.reply_markup.rows]
|
||||
self._buttons_flat = [x for row in self._buttons for x in row]
|
||||
return self._buttons
|
||||
|
||||
@property
|
||||
def button_count(self):
|
||||
async def button_count(self):
|
||||
"""
|
||||
Returns the total button count.
|
||||
"""
|
||||
@@ -386,7 +391,7 @@ class Message:
|
||||
return self.original_message.out
|
||||
|
||||
@property
|
||||
def reply_message(self):
|
||||
async def reply_message(self):
|
||||
"""
|
||||
The `telethon.tl.custom.message.Message` that this message is replying
|
||||
to, or ``None``.
|
||||
@@ -397,15 +402,15 @@ class Message:
|
||||
if self._reply_message is None:
|
||||
if not self.original_message.reply_to_msg_id:
|
||||
return None
|
||||
self._reply_message = self._client.get_messages(
|
||||
self.input_chat if self.is_channel else None,
|
||||
self._reply_message = await self._client.get_messages(
|
||||
await self.input_chat if self.is_channel else None,
|
||||
ids=self.original_message.reply_to_msg_id
|
||||
)
|
||||
|
||||
return self._reply_message
|
||||
|
||||
@property
|
||||
def fwd_from_entity(self):
|
||||
async def fwd_from_entity(self):
|
||||
"""
|
||||
If the :tl:`Message` is a forwarded message, returns the :tl:`User`
|
||||
or :tl:`Channel` who originally sent the message, or ``None``.
|
||||
@@ -414,32 +419,33 @@ class Message:
|
||||
if getattr(self.original_message, 'fwd_from', None):
|
||||
fwd = self.original_message.fwd_from
|
||||
if fwd.from_id:
|
||||
self._fwd_from_entity = self._client.get_entity(
|
||||
fwd.from_id)
|
||||
self._fwd_from_entity =\
|
||||
await self._client.get_entity(fwd.from_id)
|
||||
elif fwd.channel_id:
|
||||
self._fwd_from_entity = self._client.get_entity(
|
||||
self._fwd_from_entity = await self._client.get_entity(
|
||||
get_peer_id(types.PeerChannel(fwd.channel_id)))
|
||||
return self._fwd_from_entity
|
||||
|
||||
def respond(self, *args, **kwargs):
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the message (not as a reply). Shorthand for
|
||||
`telethon.telegram_client.TelegramClient.send_message` with
|
||||
``entity`` already set.
|
||||
"""
|
||||
return self._client.send_message(self.input_chat, *args, **kwargs)
|
||||
return await self._client.send_message(
|
||||
await self.input_chat, *args, **kwargs)
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the message (as a reply). Shorthand for
|
||||
`telethon.telegram_client.TelegramClient.send_message` with
|
||||
both ``entity`` and ``reply_to`` already set.
|
||||
"""
|
||||
kwargs['reply_to'] = self.original_message.id
|
||||
return self._client.send_message(self.original_message.to_id,
|
||||
*args, **kwargs)
|
||||
return await self._client.send_message(
|
||||
await self.input_chat, *args, **kwargs)
|
||||
|
||||
def forward_to(self, *args, **kwargs):
|
||||
async def forward_to(self, *args, **kwargs):
|
||||
"""
|
||||
Forwards the message. Shorthand for
|
||||
`telethon.telegram_client.TelegramClient.forward_messages` with
|
||||
@@ -450,10 +456,10 @@ class Message:
|
||||
`telethon.telegram_client.TelegramClient` instance directly.
|
||||
"""
|
||||
kwargs['messages'] = self.original_message.id
|
||||
kwargs['from_peer'] = self.input_chat
|
||||
return self._client.forward_messages(*args, **kwargs)
|
||||
kwargs['from_peer'] = await self.input_chat
|
||||
return await self._client.forward_messages(*args, **kwargs)
|
||||
|
||||
def edit(self, *args, **kwargs):
|
||||
async def edit(self, *args, **kwargs):
|
||||
"""
|
||||
Edits the message iff it's outgoing. Shorthand for
|
||||
`telethon.telegram_client.TelegramClient.edit_message` with
|
||||
@@ -471,10 +477,10 @@ class Message:
|
||||
if self.original_message.to_id.user_id != me.user_id:
|
||||
return None
|
||||
|
||||
return self._client.edit_message(
|
||||
self.input_chat, self.original_message, *args, **kwargs)
|
||||
return await self._client.edit_message(
|
||||
await self.input_chat, self.original_message, *args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the message. You're responsible for checking whether you
|
||||
have the permission to do so, or to except the error otherwise.
|
||||
@@ -486,17 +492,17 @@ class Message:
|
||||
this `delete` method. Use a
|
||||
`telethon.telegram_client.TelegramClient` instance directly.
|
||||
"""
|
||||
return self._client.delete_messages(
|
||||
self.input_chat, [self.original_message], *args, **kwargs)
|
||||
return await self._client.delete_messages(
|
||||
await self.input_chat, [self.original_message], *args, **kwargs)
|
||||
|
||||
def download_media(self, *args, **kwargs):
|
||||
async def download_media(self, *args, **kwargs):
|
||||
"""
|
||||
Downloads the media contained in the message, if any.
|
||||
`telethon.telegram_client.TelegramClient.download_media` with
|
||||
the ``message`` already set.
|
||||
"""
|
||||
return self._client.download_media(self.original_message,
|
||||
*args, **kwargs)
|
||||
return await self._client.download_media(
|
||||
self.original_message, *args, **kwargs)
|
||||
|
||||
def get_entities_text(self, cls=None):
|
||||
"""
|
||||
@@ -525,12 +531,10 @@ class Message:
|
||||
self.original_message.entities)
|
||||
return list(zip(self.original_message.entities, texts))
|
||||
|
||||
def click(self, i=None, j=None, *, text=None, filter=None):
|
||||
async def click(self, i=None, j=None, *, text=None, filter=None):
|
||||
"""
|
||||
Clicks the inline keyboard button of the message, if any.
|
||||
|
||||
If the message has a non-inline keyboard, clicking it will
|
||||
send the message, switch to inline, or open its URL.
|
||||
Calls `telethon.tl.custom.messagebutton.MessageButton.click`
|
||||
for the specified button.
|
||||
|
||||
Does nothing if the message has no buttons.
|
||||
|
||||
@@ -571,32 +575,32 @@ class Message:
|
||||
if sum(int(x is not None) for x in (i, text, filter)) >= 2:
|
||||
raise ValueError('You can only set either of i, text or filter')
|
||||
|
||||
if not self.buttons:
|
||||
if not await self.buttons:
|
||||
return # Accessing the property sets self._buttons[_flat]
|
||||
|
||||
if text is not None:
|
||||
if callable(text):
|
||||
for button in self._buttons_flat:
|
||||
if text(button.text):
|
||||
return button.click()
|
||||
return await button.click()
|
||||
else:
|
||||
for button in self._buttons_flat:
|
||||
if button.text == text:
|
||||
return button.click()
|
||||
return await button.click()
|
||||
return
|
||||
|
||||
if filter is not None:
|
||||
for button in self._buttons_flat:
|
||||
if filter(button):
|
||||
return button.click()
|
||||
return await button.click()
|
||||
return
|
||||
|
||||
if i is None:
|
||||
i = 0
|
||||
if j is None:
|
||||
return self._buttons_flat[i].click()
|
||||
return await self._buttons_flat[i].click()
|
||||
else:
|
||||
return self._buttons[i][j].click()
|
||||
return await self._buttons[i][j].click()
|
||||
|
||||
|
||||
class _CustomMessage(Message, types.Message):
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from .. import types, functions
|
||||
from ...errors import BotTimeout
|
||||
import webbrowser
|
||||
|
||||
|
||||
@@ -51,23 +52,37 @@ class MessageButton:
|
||||
if isinstance(self.button, types.KeyboardButtonUrl):
|
||||
return self.button.url
|
||||
|
||||
def click(self):
|
||||
async def click(self):
|
||||
"""
|
||||
Clicks the inline keyboard button of the message, if any.
|
||||
Emulates the behaviour of clicking this button.
|
||||
|
||||
If the message has a non-inline keyboard, clicking it will
|
||||
send the message, switch to inline, or open its URL.
|
||||
If it's a normal :tl:`KeyboardButton` with text, a message will be
|
||||
sent, and the sent `telethon.tl.custom.message.Message` returned.
|
||||
|
||||
If it's an inline :tl:`KeyboardButtonCallback` with text and data,
|
||||
it will be "clicked" and the :tl:`BotCallbackAnswer` returned.
|
||||
|
||||
If it's an inline :tl:`KeyboardButtonSwitchInline` button, the
|
||||
:tl:`StartBotRequest` will be invoked and the resulting updates
|
||||
returned.
|
||||
|
||||
If it's a :tl:`KeyboardButtonUrl`, the URL of the button will
|
||||
be passed to ``webbrowser.open`` and return ``True`` on success.
|
||||
"""
|
||||
if isinstance(self.button, types.KeyboardButton):
|
||||
return self._client.send_message(
|
||||
return await self._client.send_message(
|
||||
self._chat, self.button.text, reply_to=self._msg_id)
|
||||
elif isinstance(self.button, types.KeyboardButtonCallback):
|
||||
return self._client(functions.messages.GetBotCallbackAnswerRequest(
|
||||
req = functions.messages.GetBotCallbackAnswerRequest(
|
||||
peer=self._chat, msg_id=self._msg_id, data=self.button.data
|
||||
), retries=1)
|
||||
)
|
||||
try:
|
||||
return await self._client(req)
|
||||
except BotTimeout:
|
||||
return None
|
||||
elif isinstance(self.button, types.KeyboardButtonSwitchInline):
|
||||
return self._client(functions.messages.StartBotRequest(
|
||||
return await self._client(functions.messages.StartBotRequest(
|
||||
bot=self._from, peer=self._chat, start_param=self.button.query
|
||||
), retries=1)
|
||||
))
|
||||
elif isinstance(self.button, types.KeyboardButtonUrl):
|
||||
return webbrowser.open(self.button.url)
|
||||
|
@@ -1,42 +0,0 @@
|
||||
import struct
|
||||
|
||||
from . import TLObject
|
||||
|
||||
|
||||
class MessageContainer(TLObject):
|
||||
CONSTRUCTOR_ID = 0x73f1f8dc
|
||||
|
||||
def __init__(self, messages):
|
||||
super().__init__()
|
||||
self.content_related = False
|
||||
self.messages = messages
|
||||
|
||||
def to_dict(self, recursive=True):
|
||||
return {
|
||||
'content_related': self.content_related,
|
||||
'messages':
|
||||
([] if self.messages is None else [
|
||||
None if x is None else x.to_dict() for x in self.messages
|
||||
]) if recursive else self.messages,
|
||||
}
|
||||
|
||||
def __bytes__(self):
|
||||
return struct.pack(
|
||||
'<Ii', MessageContainer.CONSTRUCTOR_ID, len(self.messages)
|
||||
) + b''.join(bytes(m) for m in self.messages)
|
||||
|
||||
@staticmethod
|
||||
def iter_read(reader):
|
||||
reader.read_int(signed=False) # code
|
||||
size = reader.read_int()
|
||||
for _ in range(size):
|
||||
inner_msg_id = reader.read_long()
|
||||
inner_sequence = reader.read_int()
|
||||
inner_length = reader.read_int()
|
||||
yield inner_msg_id, inner_sequence, inner_length
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self)
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self, indent=0)
|
@@ -1,44 +0,0 @@
|
||||
import struct
|
||||
|
||||
from . import TLObject, GzipPacked
|
||||
from ..tl.functions import InvokeAfterMsgRequest
|
||||
|
||||
|
||||
class TLMessage(TLObject):
|
||||
"""https://core.telegram.org/mtproto/service_messages#simple-container"""
|
||||
def __init__(self, session, request, after_id=None):
|
||||
super().__init__()
|
||||
del self.content_related
|
||||
self.msg_id = session.get_new_msg_id()
|
||||
self.seq_no = session.generate_sequence(request.content_related)
|
||||
self.request = request
|
||||
self.container_msg_id = None
|
||||
|
||||
# After which message ID this one should run. We do this so
|
||||
# InvokeAfterMsgRequest is transparent to the user and we can
|
||||
# easily invoke after while confirming the original request.
|
||||
self.after_id = after_id
|
||||
|
||||
def to_dict(self, recursive=True):
|
||||
return {
|
||||
'msg_id': self.msg_id,
|
||||
'seq_no': self.seq_no,
|
||||
'request': self.request,
|
||||
'container_msg_id': self.container_msg_id,
|
||||
'after_id': self.after_id
|
||||
}
|
||||
|
||||
def __bytes__(self):
|
||||
if self.after_id is None:
|
||||
body = GzipPacked.gzip_if_smaller(self.request)
|
||||
else:
|
||||
body = GzipPacked.gzip_if_smaller(
|
||||
InvokeAfterMsgRequest(self.after_id, self.request))
|
||||
|
||||
return struct.pack('<qii', self.msg_id, self.seq_no, len(body)) + body
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self)
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self, indent=0)
|
@@ -1,45 +1,13 @@
|
||||
import struct
|
||||
from datetime import datetime, date
|
||||
from threading import Event
|
||||
|
||||
|
||||
class TLObject:
|
||||
def __init__(self):
|
||||
self.rpc_error = None
|
||||
self.result = None
|
||||
|
||||
# These should be overrode
|
||||
self.content_related = False # Only requests/functions/queries are
|
||||
|
||||
# Internal parameter to tell pickler in which state Event object was
|
||||
self._event_is_set = False
|
||||
self._set_event()
|
||||
|
||||
def _set_event(self):
|
||||
self.confirm_received = Event()
|
||||
|
||||
# Set Event state to 'set' if needed
|
||||
if self._event_is_set:
|
||||
self.confirm_received.set()
|
||||
|
||||
def __getstate__(self):
|
||||
# Save state of the Event object
|
||||
self._event_is_set = self.confirm_received.is_set()
|
||||
|
||||
# Exclude Event object from dict and return new state
|
||||
new_dct = dict(self.__dict__)
|
||||
del new_dct["confirm_received"]
|
||||
return new_dct
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self._set_event()
|
||||
|
||||
# These should not be overrode
|
||||
@staticmethod
|
||||
def pretty_format(obj, indent=None):
|
||||
"""Pretty formats the given object as a string which is returned.
|
||||
If indent is None, a single line will be returned.
|
||||
"""
|
||||
Pretty formats the given object as a string which is returned.
|
||||
If indent is None, a single line will be returned.
|
||||
"""
|
||||
if indent is None:
|
||||
if isinstance(obj, TLObject):
|
||||
@@ -163,10 +131,6 @@ class TLObject:
|
||||
|
||||
raise TypeError('Cannot interpret "{}" as a date.'.format(dt))
|
||||
|
||||
# These are nearly always the same for all subclasses
|
||||
def on_response(self, reader):
|
||||
self.result = reader.tgread_object()
|
||||
|
||||
def __eq__(self, o):
|
||||
return isinstance(o, type(self)) and self.to_dict() == o.to_dict()
|
||||
|
||||
@@ -179,16 +143,24 @@ class TLObject:
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self, indent=0)
|
||||
|
||||
# These should be overrode
|
||||
def resolve(self, client, utils):
|
||||
pass
|
||||
|
||||
def to_dict(self):
|
||||
return {}
|
||||
raise NotImplementedError
|
||||
|
||||
def __bytes__(self):
|
||||
return b''
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def from_reader(cls, reader):
|
||||
return TLObject()
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class TLRequest(TLObject):
|
||||
"""
|
||||
Represents a content-related `TLObject` (a request that can be sent).
|
||||
"""
|
||||
@staticmethod
|
||||
def read_result(reader):
|
||||
return reader.tgread_object()
|
||||
|
||||
async def resolve(self, client, utils):
|
||||
pass
|
||||
|
Reference in New Issue
Block a user