From 21d2ded546f415feadaec118b60fa170741a15f4 Mon Sep 17 00:00:00 2001 From: Jahongir Qurbonov Date: Mon, 19 Aug 2024 17:54:51 +0500 Subject: [PATCH] Adding ReplyKeyboardMarkup and ReplyInlineMarkup custom types --- .../telethon/_impl/client/client/client.py | 15 ++-- .../src/telethon/_impl/client/client/files.py | 16 ++--- .../telethon/_impl/client/client/messages.py | 21 ++++-- .../_impl/client/types/buttons/__init__.py | 39 ++-------- .../client/types/buttons/reply_markup.py | 71 +++++++++++++++++++ client/src/telethon/types/__init__.py | 9 ++- 6 files changed, 112 insertions(+), 59 deletions(-) create mode 100644 client/src/telethon/_impl/client/types/buttons/reply_markup.py diff --git a/client/src/telethon/_impl/client/client/client.py b/client/src/telethon/_impl/client/client/client.py index 7896bff2..b00b5d35 100644 --- a/client/src/telethon/_impl/client/client/client.py +++ b/client/src/telethon/_impl/client/client/client.py @@ -45,7 +45,7 @@ from ..types import ( RecentAction, User, ) -from ..types import buttons as btns +from ..types.buttons.reply_markup import ReplyMarkupType from .auth import ( bot_sign_in, check_password, @@ -216,6 +216,7 @@ class Client: datacenter: Optional[DataCenter] = None, connector: Optional[Connector] = None, ) -> None: + assert isinstance(__package__, str) base_logger = logger or logging.getLogger(__package__[: __package__.index(".")]) self._sender: Optional[Sender] = None @@ -571,7 +572,7 @@ class Client: markdown: Optional[str] = None, html: Optional[str] = None, link_preview: bool = False, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: """ Edit a message. @@ -1393,7 +1394,7 @@ class Client: caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: """ Send an audio file. @@ -1466,7 +1467,7 @@ class Client: caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]], + buttons: Optional[ReplyMarkupType], ) -> Message: """ Send any type of file with any amount of attributes. @@ -1633,7 +1634,7 @@ class Client: html: Optional[str] = None, link_preview: bool = False, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: """ Send a message. @@ -1687,7 +1688,7 @@ class Client: caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: """ Send a photo file. @@ -1755,7 +1756,7 @@ class Client: caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]], + buttons: Optional[ReplyMarkupType], ) -> Message: """ Send a video file. diff --git a/client/src/telethon/_impl/client/client/files.py b/client/src/telethon/_impl/client/client/files.py index f7a91645..78a97865 100644 --- a/client/src/telethon/_impl/client/client/files.py +++ b/client/src/telethon/_impl/client/client/files.py @@ -17,14 +17,12 @@ from ..types import ( OutFileLike, OutWrapper, Peer, -) -from ..types import buttons as btns -from ..types import ( expand_stripped_size, generate_random_id, parse_message, try_get_url_path, ) +from ..types.buttons.reply_markup import ReplyMarkupType if TYPE_CHECKING: from .client import Client @@ -59,7 +57,7 @@ async def send_photo( caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: return await send_file( self, @@ -100,7 +98,7 @@ async def send_audio( caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: return await send_file( self, @@ -140,7 +138,7 @@ async def send_video( caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]], + buttons: Optional[ReplyMarkupType], ) -> Message: return await send_file( self, @@ -189,7 +187,7 @@ async def send_file( caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]], + buttons: Optional[ReplyMarkupType], ) -> Message: message, entities = parse_message( text=caption, markdown=caption_markdown, html=caption_html, allow_empty=True @@ -299,7 +297,7 @@ async def do_send_file( message: str, entities: Optional[list[abcs.MessageEntity]], reply_to: Optional[int], - buttons: Optional[list[btns.Button] | list[list[btns.Button]]], + buttons: Optional[ReplyMarkupType], ) -> Message: random_id = generate_random_id() return client._build_message_map( @@ -319,7 +317,7 @@ async def do_send_file( media=input_media, message=message, random_id=random_id, - reply_markup=btns.build_keyboard(buttons), + reply_markup=buttons.build() if buttons is not None else None, entities=entities, schedule_date=None, send_as=None, diff --git a/client/src/telethon/_impl/client/client/messages.py b/client/src/telethon/_impl/client/client/messages.py index 13d31985..95d2db23 100644 --- a/client/src/telethon/_impl/client/client/messages.py +++ b/client/src/telethon/_impl/client/client/messages.py @@ -6,9 +6,16 @@ from typing import TYPE_CHECKING, Literal, Optional, Self from ...session import ChannelRef, PeerRef from ...tl import abcs, functions, types -from ..types import AsyncList, Message, Peer, build_chat_map -from ..types import buttons as btns -from ..types import generate_random_id, parse_message, peer_id +from ..types import ( + AsyncList, + Message, + Peer, + build_chat_map, + generate_random_id, + parse_message, + peer_id, +) +from ..types.buttons.reply_markup import ReplyMarkupType if TYPE_CHECKING: from .client import Client @@ -24,7 +31,7 @@ async def send_message( html: Optional[str] = None, link_preview: bool = False, reply_to: Optional[int] = None, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: random_id = generate_random_id() @@ -71,7 +78,7 @@ async def send_message( ), message=message, random_id=random_id, - reply_markup=btns.build_keyboard(buttons), + reply_markup=buttons.build() if buttons is not None else None, entities=entities, schedule_date=None, send_as=None, @@ -121,7 +128,7 @@ async def edit_message( markdown: Optional[str] = None, html: Optional[str] = None, link_preview: bool = False, - buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, + buttons: Optional[ReplyMarkupType] = None, ) -> Message: message, entities = parse_message( text=text, markdown=markdown, html=html, allow_empty=False @@ -134,7 +141,7 @@ async def edit_message( id=message_id, message=message, media=None, - reply_markup=btns.build_keyboard(buttons), + reply_markup=buttons.build() if buttons is not None else None, entities=entities, schedule_date=None, ) diff --git a/client/src/telethon/_impl/client/types/buttons/__init__.py b/client/src/telethon/_impl/client/types/buttons/__init__.py index 89e7c4a0..429af7f6 100644 --- a/client/src/telethon/_impl/client/types/buttons/__init__.py +++ b/client/src/telethon/_impl/client/types/buttons/__init__.py @@ -1,12 +1,13 @@ from __future__ import annotations import weakref -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from ....tl import abcs, types from .button import Button from .callback import Callback from .inline_button import InlineButton +from .reply_markup import ReplyInlineMarkup, ReplyKeyboardMarkup from .request_geo_location import RequestGeoLocation from .request_phone import RequestPhone from .request_poll import RequestPoll @@ -23,40 +24,6 @@ def as_concrete_row(row: abcs.KeyboardButtonRow) -> types.KeyboardButtonRow: return row -def build_keyboard( - btns: Optional[list[Button] | list[list[Button]]], -) -> Optional[abcs.ReplyMarkup]: - # list[button] -> list[list[button]] - # This does allow for "invalid" inputs (mixing lists and non-lists), but that's acceptable. - buttons_lists_iter = ( - button if isinstance(button, list) else [button] for button in (btns or []) - ) - # Remove empty rows (also making it easy to check if all-empty). - buttons_lists = [bs for bs in buttons_lists_iter if bs] - - if not buttons_lists: - return None - - rows: list[abcs.KeyboardButtonRow] = [ - types.KeyboardButtonRow(buttons=[btn._raw for btn in btns]) - for btns in buttons_lists - ] - - # Guaranteed to have at least one, first one used to check if it's inline. - # If the user mixed inline with non-inline, Telegram will complain. - if isinstance(buttons_lists[0][0], InlineButton): - return types.ReplyInlineMarkup(rows=rows) - else: - return types.ReplyKeyboardMarkup( - resize=False, - single_use=False, - selective=False, - persistent=False, - rows=rows, - placeholder=None, - ) - - def create_button(message: Message, raw: abcs.KeyboardButton) -> Button: """ Create a custom button from a Telegram button. @@ -113,6 +80,8 @@ __all__ = [ "Button", "Callback", "InlineButton", + "ReplyInlineMarkup", + "ReplyKeyboardMarkup", "RequestGeoLocation", "RequestPhone", "RequestPoll", diff --git a/client/src/telethon/_impl/client/types/buttons/reply_markup.py b/client/src/telethon/_impl/client/types/buttons/reply_markup.py new file mode 100644 index 00000000..7d25eaa8 --- /dev/null +++ b/client/src/telethon/_impl/client/types/buttons/reply_markup.py @@ -0,0 +1,71 @@ +from typing import Optional, TypeAlias + +from ....tl import abcs, types +from .button import Button + + +def build_keyboard_rows( + btns: list[Button] | list[list[Button]], +) -> list[abcs.KeyboardButtonRow]: + # list[button] -> list[list[button]] + # This does allow for "invalid" inputs (mixing lists and non-lists), but that's acceptable. + buttons_lists_iter = [ + button if isinstance(button, list) else [button] for button in (btns or []) + ] + # Remove empty rows (also making it easy to check if all-empty). + buttons_lists = [bs for bs in buttons_lists_iter if bs] + + return [ + types.KeyboardButtonRow(buttons=[btn._raw for btn in btns]) + for btns in buttons_lists + ] + + +class ReplyKeyboardMarkup: + __slots__ = ( + "_btns", + "resize", + "single_use", + "selective", + "persistent", + "placeholder", + ) + + def __init__( + self, + btns: list[Button] | list[list[Button]], + resize: bool, + single_use: bool, + selective: bool, + persistent: bool, + placeholder: Optional[str], + ) -> None: + self._btns = build_keyboard_rows(btns) + self.resize = resize + self.single_use = single_use + self.selective = selective + self.persistent = persistent + self.placeholder = placeholder + + def build(self) -> abcs.ReplyMarkup: + return types.ReplyKeyboardMarkup( + resize=self.resize, + single_use=self.single_use, + selective=self.selective, + persistent=self.persistent, + rows=self._btns, + placeholder=self.placeholder, + ) + + +class ReplyInlineMarkup: + __slots__ = ("_btns",) + + def __init__(self, btns: list[Button] | list[list[Button]]) -> None: + self._btns = build_keyboard_rows(btns) + + def build(self) -> abcs.ReplyMarkup: + return types.ReplyInlineMarkup(rows=self._btns) + + +ReplyMarkupType: TypeAlias = ReplyKeyboardMarkup | ReplyInlineMarkup diff --git a/client/src/telethon/types/__init__.py b/client/src/telethon/types/__init__.py index 01c3c013..8333901a 100644 --- a/client/src/telethon/types/__init__.py +++ b/client/src/telethon/types/__init__.py @@ -22,7 +22,12 @@ from .._impl.client.types import ( RecentAction, User, ) -from .._impl.client.types.buttons import Button, InlineButton +from .._impl.client.types.buttons import ( + Button, + InlineButton, + ReplyInlineMarkup, + ReplyKeyboardMarkup, +) from .._impl.session import ChannelRef, GroupRef, PeerRef, UserRef __all__ = [ @@ -42,6 +47,8 @@ __all__ = [ "Message", "Participant", "PasswordToken", + "ReplyInlineMarkup", + "ReplyKeyboardMarkup", "RecentAction", "User", "Button",