Create a custom.Button class and support send_message(buttons=...)

This commit is contained in:
Lonami Exo 2018-07-10 12:42:57 +02:00
parent a50d013ee6
commit 8c28be04bc
7 changed files with 213 additions and 12 deletions

View File

@ -45,3 +45,11 @@ telethon\.tl\.custom\.forward module
:members:
:undoc-members:
:show-inheritance:
telethon\.tl\.custom\.button module
-----------------------------------
.. automodule:: telethon.tl.custom.button
:members:
:undoc-members:
:show-inheritance:

View File

@ -13,10 +13,11 @@ from .telegrambaseclient import TelegramBaseClient
from .users import UserMethods # Required for everything
from .messageparse import MessageParseMethods # Required for messages
from .uploads import UploadMethods # Required for messages to send files
from .updates import UpdateMethods # Required for buttons (register callbacks)
from .buttons import ButtonMethods # Required for messages to use buttons
from .messages import MessageMethods
from .chats import ChatMethods
from .dialogs import DialogMethods
from .downloads import DownloadMethods
from .auth import AuthMethods
from .updates import UpdateMethods
from .telegramclient import TelegramClient

View File

@ -0,0 +1,48 @@
from .updates import UpdateMethods
from ..tl import types, custom
from .. import utils
class ButtonMethods(UpdateMethods):
def _build_reply_markup(self, buttons):
if buttons is None:
return None
try:
if buttons.SUBCLASS_OF_ID == 0xe2e10ef2:
return buttons # crc32(b'ReplyMarkup'):
except AttributeError:
pass
if not utils.is_list_like(buttons):
buttons = [[buttons]]
elif not utils.is_list_like(buttons[0]):
buttons = [buttons]
is_inline = False
is_normal = False
rows = []
for row in buttons:
current = []
for button in row:
inline = custom.Button._is_inline(button)
is_inline |= inline
is_normal |= not inline
if isinstance(button, custom.Button):
# TODO actually register callbacks
button = button.button
if button.SUBCLASS_OF_ID == 0xbad74a3:
# 0xbad74a3 == crc32(b'KeyboardButton')
current.append(button)
if current:
rows.append(types.KeyboardButtonRow(current))
if is_inline == is_normal and is_normal:
raise ValueError('You cannot mix inline with normal buttons')
elif is_inline:
return types.ReplyInlineMarkup(rows)
elif is_normal:
return types.ReplyKeyboardMarkup(rows)

View File

@ -8,13 +8,14 @@ from async_generator import async_generator, yield_
from .messageparse import MessageParseMethods
from .uploads import UploadMethods
from .buttons import ButtonMethods
from .. import utils
from ..tl import types, functions, custom
__log__ = logging.getLogger(__name__)
class MessageMethods(UploadMethods, MessageParseMethods):
class MessageMethods(ButtonMethods, UploadMethods, MessageParseMethods):
# region Public methods
@ -333,7 +334,7 @@ class MessageMethods(UploadMethods, MessageParseMethods):
async def send_message(
self, entity, message='', *, reply_to=None,
parse_mode=utils.Default, link_preview=True, file=None,
force_document=False, clear_draft=False):
force_document=False, clear_draft=False, buttons=None):
"""
Sends the given message to the specified entity (user/chat/channel).
@ -382,8 +383,15 @@ class MessageMethods(UploadMethods, MessageParseMethods):
Whether the existing draft should be cleared or not.
Has no effect when sending a file.
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`,
:tl:`KeyboardButton`):
The matrix (list of lists), row list or button to be shown
after sending the message. This parameter will only work if
you have signed in as a bot. You can also pass your own
:tl:`ReplyMarkup` here.
Returns:
The sent `telethon.tl.custom.message.Message`.
The sent `custom.Message <telethon.tl.custom.message.Message>`.
"""
if file is not None:
return await self.send_file(
@ -417,12 +425,17 @@ class MessageMethods(UploadMethods, MessageParseMethods):
else:
reply_id = None
if buttons is None:
markup = message.reply_markup
else:
markup = self._build_reply_markup(buttons)
request = functions.messages.SendMessageRequest(
peer=entity,
message=message.message or '',
silent=message.silent,
reply_to_msg_id=reply_id,
reply_markup=message.reply_markup,
reply_markup=markup,
entities=message.entities,
clear_draft=clear_draft,
no_webpage=not isinstance(
@ -438,7 +451,8 @@ class MessageMethods(UploadMethods, MessageParseMethods):
entities=msg_ent,
no_webpage=not link_preview,
reply_to_msg_id=utils.get_message_id(reply_to),
clear_draft=clear_draft
clear_draft=clear_draft,
reply_markup=self._build_reply_markup(buttons)
)
result = await self(request)

View File

@ -1,13 +1,13 @@
from . import (
UpdateMethods, AuthMethods, DownloadMethods, DialogMethods,
ChatMethods, MessageMethods, UploadMethods, MessageParseMethods,
UserMethods
AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
MessageMethods, ButtonMethods, UpdateMethods, UploadMethods,
MessageParseMethods, UserMethods
)
class TelegramClient(
UpdateMethods, AuthMethods, DownloadMethods, DialogMethods,
ChatMethods, MessageMethods, UploadMethods, MessageParseMethods,
UserMethods
AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
MessageMethods, ButtonMethods, UpdateMethods, UploadMethods,
MessageParseMethods, UserMethods
):
pass

View File

@ -4,3 +4,4 @@ from .input_sized_file import InputSizedFile
from .messagebutton import MessageButton
from .forward import Forward
from .message import Message
from .button import Button

View File

@ -0,0 +1,129 @@
from .. import types
class Button:
"""
Helper class to allow defining ``reply_markup`` when
sending a message with inline or keyboard buttons.
You should make use of the defined class methods to create button
instances instead making them yourself (i.e. don't do ``Button(...)``
but instead use methods line `Button.inline(...) <inline>` etc.)
You can use `inline`, `switch_inline` and `url`
together to create inline buttons (under the message).
You can use `text`, `request_location` and `request_phone`
together to create a reply markup (replaces the user keyboard).
You **cannot** mix the two type of buttons together,
and it will error if you try to do so.
The text for all buttons may be at most 142 characters.
If more characters are given, Telegram will cut the text
to 128 characters and add the ellipsis () character as
the 129.
"""
def __init__(self, button, callback=None):
self.button = button
self.callback = callback
self.is_inline = self._is_inline(button)
@classmethod
def _is_inline(cls, button):
"""
Returns ``True`` if the button belongs to an inline keyboard.
"""
if isinstance(button, cls):
return button.is_inline
else:
return isinstance(button, (
types.KeyboardButtonCallback,
types.KeyboardButtonSwitchInline,
types.KeyboardButtonUrl
))
@classmethod
def inline(cls, text, callback=None, data=None):
"""
Creates a new inline button.
The `callback` parameter should be a function callback accepting
a single parameter (the triggered event on click) if specified.
Otherwise, you should register the event manually.
If `data` is omitted, the given `text` will be used as `data`.
In any case `data` should be either ``bytes`` or ``str``.
Note that the given `data` must be less or equal to 64 bytes.
If more than 64 bytes are passed as data, ``ValueError`` is raised.
"""
if not data:
data = text.encode('utf-8')
if len(data) > 64:
raise ValueError('Too many bytes for the data')
return cls(types.KeyboardButtonCallback(text, data), callback)
@classmethod
def switch_inline(cls, text, query='', same_peer=False):
"""
Creates a new button to switch to inline query.
If `query` is given, it will be the default text to be used
when making the inline query.
If ``same_peer is True`` the inline query will directly be
set under the currently opened chat. Otherwise, the user will
have to select a different dialog to make the query.
"""
return cls(types.KeyboardButtonSwitchInline(text, query, same_peer))
@classmethod
def url(cls, text, url=None):
"""
Creates a new button to open the desired URL upon clicking it.
If no `url` is given, the `text` will be used as said URL instead.
"""
return cls(types.KeyboardButtonUrl(text, url or text))
@classmethod
def text(cls, text):
"""
Creates a new button with the given text.
"""
return cls(types.KeyboardButton(text))
@classmethod
def request_location(cls, text):
"""
Creates a new button that will request
the user's location upon being clicked.
"""
return cls(types.KeyboardButtonRequestGeoLocation(text))
@classmethod
def request_phone(cls, text):
"""
Creates a new button that will request
the user's phone number upon being clicked.
"""
return cls(types.KeyboardButtonRequestPhone(text))
@classmethod
def clear(cls):
"""
Clears all the buttons. When used, no other
button should be present or it will be ignored.
"""
return types.ReplyKeyboardHide()
@classmethod
def force_reply(cls):
"""
Forces a reply. If used, no other button
should be present or it will be ignored.
"""
return types.ReplyKeyboardForceReply()