Adding ReplyKeyboardMarkup and ReplyInlineMarkup custom types

This commit is contained in:
Jahongir Qurbonov 2024-08-19 17:54:51 +05:00
parent ee3248c3a4
commit 21d2ded546
6 changed files with 112 additions and 59 deletions

View File

@ -45,7 +45,7 @@ from ..types import (
RecentAction, RecentAction,
User, User,
) )
from ..types import buttons as btns from ..types.buttons.reply_markup import ReplyMarkupType
from .auth import ( from .auth import (
bot_sign_in, bot_sign_in,
check_password, check_password,
@ -216,6 +216,7 @@ class Client:
datacenter: Optional[DataCenter] = None, datacenter: Optional[DataCenter] = None,
connector: Optional[Connector] = None, connector: Optional[Connector] = None,
) -> None: ) -> None:
assert isinstance(__package__, str)
base_logger = logger or logging.getLogger(__package__[: __package__.index(".")]) base_logger = logger or logging.getLogger(__package__[: __package__.index(".")])
self._sender: Optional[Sender] = None self._sender: Optional[Sender] = None
@ -571,7 +572,7 @@ class Client:
markdown: Optional[str] = None, markdown: Optional[str] = None,
html: Optional[str] = None, html: Optional[str] = None,
link_preview: bool = False, link_preview: bool = False,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
""" """
Edit a message. Edit a message.
@ -1393,7 +1394,7 @@ class Client:
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
""" """
Send an audio file. Send an audio file.
@ -1466,7 +1467,7 @@ class Client:
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]], buttons: Optional[ReplyMarkupType],
) -> Message: ) -> Message:
""" """
Send any type of file with any amount of attributes. Send any type of file with any amount of attributes.
@ -1633,7 +1634,7 @@ class Client:
html: Optional[str] = None, html: Optional[str] = None,
link_preview: bool = False, link_preview: bool = False,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
""" """
Send a message. Send a message.
@ -1687,7 +1688,7 @@ class Client:
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
""" """
Send a photo file. Send a photo file.
@ -1755,7 +1756,7 @@ class Client:
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]], buttons: Optional[ReplyMarkupType],
) -> Message: ) -> Message:
""" """
Send a video file. Send a video file.

View File

@ -17,14 +17,12 @@ from ..types import (
OutFileLike, OutFileLike,
OutWrapper, OutWrapper,
Peer, Peer,
)
from ..types import buttons as btns
from ..types import (
expand_stripped_size, expand_stripped_size,
generate_random_id, generate_random_id,
parse_message, parse_message,
try_get_url_path, try_get_url_path,
) )
from ..types.buttons.reply_markup import ReplyMarkupType
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import Client from .client import Client
@ -59,7 +57,7 @@ async def send_photo(
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -100,7 +98,7 @@ async def send_audio(
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -140,7 +138,7 @@ async def send_video(
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]], buttons: Optional[ReplyMarkupType],
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -189,7 +187,7 @@ async def send_file(
caption_markdown: Optional[str] = None, caption_markdown: Optional[str] = None,
caption_html: Optional[str] = None, caption_html: Optional[str] = None,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]], buttons: Optional[ReplyMarkupType],
) -> Message: ) -> Message:
message, entities = parse_message( message, entities = parse_message(
text=caption, markdown=caption_markdown, html=caption_html, allow_empty=True text=caption, markdown=caption_markdown, html=caption_html, allow_empty=True
@ -299,7 +297,7 @@ async def do_send_file(
message: str, message: str,
entities: Optional[list[abcs.MessageEntity]], entities: Optional[list[abcs.MessageEntity]],
reply_to: Optional[int], reply_to: Optional[int],
buttons: Optional[list[btns.Button] | list[list[btns.Button]]], buttons: Optional[ReplyMarkupType],
) -> Message: ) -> Message:
random_id = generate_random_id() random_id = generate_random_id()
return client._build_message_map( return client._build_message_map(
@ -319,7 +317,7 @@ async def do_send_file(
media=input_media, media=input_media,
message=message, message=message,
random_id=random_id, random_id=random_id,
reply_markup=btns.build_keyboard(buttons), reply_markup=buttons.build() if buttons is not None else None,
entities=entities, entities=entities,
schedule_date=None, schedule_date=None,
send_as=None, send_as=None,

View File

@ -6,9 +6,16 @@ from typing import TYPE_CHECKING, Literal, Optional, Self
from ...session import ChannelRef, PeerRef from ...session import ChannelRef, PeerRef
from ...tl import abcs, functions, types from ...tl import abcs, functions, types
from ..types import AsyncList, Message, Peer, build_chat_map from ..types import (
from ..types import buttons as btns AsyncList,
from ..types import generate_random_id, parse_message, peer_id Message,
Peer,
build_chat_map,
generate_random_id,
parse_message,
peer_id,
)
from ..types.buttons.reply_markup import ReplyMarkupType
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import Client from .client import Client
@ -24,7 +31,7 @@ async def send_message(
html: Optional[str] = None, html: Optional[str] = None,
link_preview: bool = False, link_preview: bool = False,
reply_to: Optional[int] = None, reply_to: Optional[int] = None,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
random_id = generate_random_id() random_id = generate_random_id()
@ -71,7 +78,7 @@ async def send_message(
), ),
message=message, message=message,
random_id=random_id, random_id=random_id,
reply_markup=btns.build_keyboard(buttons), reply_markup=buttons.build() if buttons is not None else None,
entities=entities, entities=entities,
schedule_date=None, schedule_date=None,
send_as=None, send_as=None,
@ -121,7 +128,7 @@ async def edit_message(
markdown: Optional[str] = None, markdown: Optional[str] = None,
html: Optional[str] = None, html: Optional[str] = None,
link_preview: bool = False, link_preview: bool = False,
buttons: Optional[list[btns.Button] | list[list[btns.Button]]] = None, buttons: Optional[ReplyMarkupType] = None,
) -> Message: ) -> Message:
message, entities = parse_message( message, entities = parse_message(
text=text, markdown=markdown, html=html, allow_empty=False text=text, markdown=markdown, html=html, allow_empty=False
@ -134,7 +141,7 @@ async def edit_message(
id=message_id, id=message_id,
message=message, message=message,
media=None, media=None,
reply_markup=btns.build_keyboard(buttons), reply_markup=buttons.build() if buttons is not None else None,
entities=entities, entities=entities,
schedule_date=None, schedule_date=None,
) )

View File

@ -1,12 +1,13 @@
from __future__ import annotations from __future__ import annotations
import weakref import weakref
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING
from ....tl import abcs, types from ....tl import abcs, types
from .button import Button from .button import Button
from .callback import Callback from .callback import Callback
from .inline_button import InlineButton from .inline_button import InlineButton
from .reply_markup import ReplyInlineMarkup, ReplyKeyboardMarkup
from .request_geo_location import RequestGeoLocation from .request_geo_location import RequestGeoLocation
from .request_phone import RequestPhone from .request_phone import RequestPhone
from .request_poll import RequestPoll from .request_poll import RequestPoll
@ -23,40 +24,6 @@ def as_concrete_row(row: abcs.KeyboardButtonRow) -> types.KeyboardButtonRow:
return row 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: def create_button(message: Message, raw: abcs.KeyboardButton) -> Button:
""" """
Create a custom button from a Telegram button. Create a custom button from a Telegram button.
@ -113,6 +80,8 @@ __all__ = [
"Button", "Button",
"Callback", "Callback",
"InlineButton", "InlineButton",
"ReplyInlineMarkup",
"ReplyKeyboardMarkup",
"RequestGeoLocation", "RequestGeoLocation",
"RequestPhone", "RequestPhone",
"RequestPoll", "RequestPoll",

View File

@ -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

View File

@ -22,7 +22,12 @@ from .._impl.client.types import (
RecentAction, RecentAction,
User, 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 from .._impl.session import ChannelRef, GroupRef, PeerRef, UserRef
__all__ = [ __all__ = [
@ -42,6 +47,8 @@ __all__ = [
"Message", "Message",
"Participant", "Participant",
"PasswordToken", "PasswordToken",
"ReplyInlineMarkup",
"ReplyKeyboardMarkup",
"RecentAction", "RecentAction",
"User", "User",
"Button", "Button",