Use custom type for Keyboard and rename filter type (#4436)

This commit is contained in:
Jahongir Qurbonov 2024-08-21 19:41:06 +05:00 committed by GitHub
parent ee3248c3a4
commit babeba46d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 163 additions and 125 deletions

View File

@ -23,7 +23,7 @@ from ...session import (
) )
from ...tl import Request, abcs from ...tl import Request, abcs
from ..events import Event from ..events import Event
from ..events.filters import Filter from ..events.filters import FilterType
from ..types import ( from ..types import (
AdminRight, AdminRight,
AlbumBuilder, AlbumBuilder,
@ -36,6 +36,7 @@ from ..types import (
Group, Group,
InFileLike, InFileLike,
InlineResult, InlineResult,
KeyboardType,
LoginToken, LoginToken,
Message, Message,
OutFileLike, OutFileLike,
@ -45,7 +46,6 @@ from ..types import (
RecentAction, RecentAction,
User, User,
) )
from ..types import buttons as btns
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
@ -251,12 +252,13 @@ class Client:
self._message_box = MessageBox(base_logger=base_logger) self._message_box = MessageBox(base_logger=base_logger)
self._chat_hashes = ChatHashCache(None) self._chat_hashes = ChatHashCache(None)
self._last_update_limit_warn: Optional[float] = None self._last_update_limit_warn: Optional[float] = None
self._updates: asyncio.Queue[ self._updates: asyncio.Queue[tuple[abcs.Update, dict[int, Peer]]] = (
tuple[abcs.Update, dict[int, Peer]] asyncio.Queue(maxsize=self._config.update_queue_limit or 0)
] = asyncio.Queue(maxsize=self._config.update_queue_limit or 0) )
self._dispatcher: Optional[asyncio.Task[None]] = None self._dispatcher: Optional[asyncio.Task[None]] = None
self._handlers: dict[ self._handlers: dict[
Type[Event], list[tuple[Callable[[Any], Awaitable[Any]], Optional[Filter]]] Type[Event],
list[tuple[Callable[[Any], Awaitable[Any]], Optional[FilterType]]],
] = {} ] = {}
self._check_all_handlers = check_all_handlers self._check_all_handlers = check_all_handlers
@ -270,7 +272,7 @@ class Client:
handler: Callable[[AnyEvent], Awaitable[Any]], handler: Callable[[AnyEvent], Awaitable[Any]],
/, /,
event_cls: Type[AnyEvent], event_cls: Type[AnyEvent],
filter: Optional[Filter] = None, filter: Optional[FilterType] = None,
) -> None: ) -> None:
""" """
Register a callable to be invoked when the provided event type occurs. Register a callable to be invoked when the provided event type occurs.
@ -571,7 +573,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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Edit a message. Edit a message.
@ -586,10 +588,10 @@ class Client:
:param markdown: See :ref:`formatting`. :param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`. :param html: See :ref:`formatting`.
:param link_preview: See :ref:`formatting`. :param link_preview: See :ref:`formatting`.
:param buttons: :param keyboard:
The buttons to use for the message. The keyboard to use for the message.
Only bot accounts can send buttons. Only bot accounts can send keyboard.
:return: The edited message. :return: The edited message.
@ -615,7 +617,7 @@ class Client:
markdown=markdown, markdown=markdown,
html=html, html=html,
link_preview=link_preview, link_preview=link_preview,
buttons=buttons, keyboard=keyboard,
) )
async def forward_messages( async def forward_messages(
@ -759,7 +761,7 @@ class Client:
def get_handler_filter( def get_handler_filter(
self, handler: Callable[[AnyEvent], Awaitable[Any]], / self, handler: Callable[[AnyEvent], Awaitable[Any]], /
) -> Optional[Filter]: ) -> Optional[FilterType]:
""" """
Get the filter associated to the given event handler. Get the filter associated to the given event handler.
@ -1034,7 +1036,7 @@ class Client:
return await is_authorized(self) return await is_authorized(self)
def on( def on(
self, event_cls: Type[AnyEvent], /, filter: Optional[Filter] = None self, event_cls: Type[AnyEvent], /, filter: Optional[FilterType] = None
) -> Callable[ ) -> Callable[
[Callable[[AnyEvent], Awaitable[Any]]], Callable[[AnyEvent], Awaitable[Any]] [Callable[[AnyEvent], Awaitable[Any]]], Callable[[AnyEvent], Awaitable[Any]]
]: ]:
@ -1393,7 +1395,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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Send an audio file. Send an audio file.
@ -1437,7 +1439,7 @@ class Client:
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
async def send_file( async def send_file(
@ -1466,7 +1468,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]]], keyboard: Optional[KeyboardType],
) -> Message: ) -> Message:
""" """
Send any type of file with any amount of attributes. Send any type of file with any amount of attributes.
@ -1620,7 +1622,7 @@ class Client:
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
async def send_message( async def send_message(
@ -1633,7 +1635,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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Send a message. Send a message.
@ -1649,10 +1651,10 @@ class Client:
:param reply_to: :param reply_to:
The message identifier of the message to reply to. The message identifier of the message to reply to.
:param buttons: :param keyboard:
The buttons to use for the message. The keyboard to use for the message.
Only bot accounts can send buttons. Only bot accounts can send keyboard.
.. rubric:: Example .. rubric:: Example
@ -1668,7 +1670,7 @@ class Client:
html=html, html=html,
link_preview=link_preview, link_preview=link_preview,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
async def send_photo( async def send_photo(
@ -1687,7 +1689,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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Send a photo file. Send a photo file.
@ -1733,7 +1735,7 @@ class Client:
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
async def send_video( async def send_video(
@ -1755,7 +1757,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]]], keyboard: Optional[KeyboardType],
) -> Message: ) -> Message:
""" """
Send a video file. Send a video file.
@ -1802,7 +1804,7 @@ class Client:
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
async def set_chat_default_restrictions( async def set_chat_default_restrictions(
@ -1851,7 +1853,7 @@ class Client:
self, self,
handler: Callable[[AnyEvent], Awaitable[Any]], handler: Callable[[AnyEvent], Awaitable[Any]],
/, /,
filter: Optional[Filter] = None, filter: Optional[FilterType] = None,
) -> None: ) -> None:
""" """
Set the filter to use for the given event handler. Set the filter to use for the given event handler.

View File

@ -13,13 +13,11 @@ from ..types import (
AsyncList, AsyncList,
File, File,
InFileLike, InFileLike,
KeyboardType,
Message, Message,
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,
@ -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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -79,7 +77,7 @@ async def send_photo(
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
@ -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, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -117,7 +115,7 @@ async def send_audio(
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
@ -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]]], keyboard: Optional[KeyboardType],
) -> Message: ) -> Message:
return await send_file( return await send_file(
self, self,
@ -159,7 +157,7 @@ async def send_video(
caption_markdown=caption_markdown, caption_markdown=caption_markdown,
caption_html=caption_html, caption_html=caption_html,
reply_to=reply_to, reply_to=reply_to,
buttons=buttons, keyboard=keyboard,
) )
@ -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]]], keyboard: Optional[KeyboardType],
) -> 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
@ -198,7 +196,7 @@ async def send_file(
# Re-send existing file. # Re-send existing file.
if isinstance(file, File): if isinstance(file, File):
return await do_send_file( return await do_send_file(
self, chat, file._input_media, message, entities, reply_to, buttons self, chat, file._input_media, message, entities, reply_to, keyboard
) )
# URLs are handled early as they can't use any other attributes either. # URLs are handled early as they can't use any other attributes either.
@ -222,7 +220,7 @@ async def send_file(
spoiler=False, url=file, ttl_seconds=None spoiler=False, url=file, ttl_seconds=None
) )
return await do_send_file( return await do_send_file(
self, chat, input_media, message, entities, reply_to, buttons self, chat, input_media, message, entities, reply_to, keyboard
) )
input_file, name = await upload(self, file, size, name) input_file, name = await upload(self, file, size, name)
@ -288,7 +286,7 @@ async def send_file(
) )
return await do_send_file( return await do_send_file(
self, chat, input_media, message, entities, reply_to, buttons self, chat, input_media, message, entities, reply_to, keyboard
) )
@ -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]]], keyboard: Optional[KeyboardType],
) -> 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=keyboard._raw if keyboard 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 KeyboardType,
Message,
Peer,
build_chat_map,
generate_random_id,
parse_message,
peer_id,
)
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, keyboard: Optional[KeyboardType] = 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=keyboard._raw if keyboard 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, keyboard: Optional[KeyboardType] = 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=keyboard._raw if keyboard else None,
entities=entities, entities=entities,
schedule_date=None, schedule_date=None,
) )

View File

@ -9,7 +9,7 @@ from ...session import Gap
from ...tl import abcs from ...tl import abcs
from ..events import Continue from ..events import Continue
from ..events import Event as EventBase from ..events import Event as EventBase
from ..events.filters import Filter from ..events.filters import FilterType
from ..types import build_chat_map from ..types import build_chat_map
if TYPE_CHECKING: if TYPE_CHECKING:
@ -21,7 +21,7 @@ UPDATE_LIMIT_EXCEEDED_LOG_COOLDOWN = 300
def on( def on(
self: Client, event_cls: Type[Event], /, filter: Optional[Filter] = None self: Client, event_cls: Type[Event], /, filter: Optional[FilterType] = None
) -> Callable[[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]]: ) -> Callable[[Callable[[Event], Awaitable[Any]]], Callable[[Event], Awaitable[Any]]]:
def wrapper( def wrapper(
handler: Callable[[Event], Awaitable[Any]], handler: Callable[[Event], Awaitable[Any]],
@ -37,7 +37,7 @@ def add_event_handler(
handler: Callable[[Event], Awaitable[Any]], handler: Callable[[Event], Awaitable[Any]],
/, /,
event_cls: Type[Event], event_cls: Type[Event],
filter: Optional[Filter] = None, filter: Optional[FilterType] = None,
) -> None: ) -> None:
self._handlers.setdefault(event_cls, []).append((handler, filter)) self._handlers.setdefault(event_cls, []).append((handler, filter))
@ -55,7 +55,7 @@ def remove_event_handler(
def get_handler_filter( def get_handler_filter(
self: Client, handler: Callable[[Event], Awaitable[Any]], / self: Client, handler: Callable[[Event], Awaitable[Any]], /
) -> Optional[Filter]: ) -> Optional[FilterType]:
for handlers in self._handlers.values(): for handlers in self._handlers.values():
for h, f in handlers: for h, f in handlers:
if h == handler: if h == handler:
@ -67,7 +67,7 @@ def set_handler_filter(
self: Client, self: Client,
handler: Callable[[Event], Awaitable[Any]], handler: Callable[[Event], Awaitable[Any]],
/, /,
filter: Optional[Filter] = None, filter: Optional[FilterType] = None,
) -> None: ) -> None:
for handlers in self._handlers.values(): for handlers in self._handlers.values():
for i, (h, _) in enumerate(handlers): for i, (h, _) in enumerate(handlers):

View File

@ -1,12 +1,12 @@
from .callback import Data from .callback import Data
from .combinators import All, Any, Filter, Not from .combinators import All, Any, FilterType, Not
from .common import Chats, ChatType, Senders from .common import Chats, ChatType, Senders
from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text from .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text
__all__ = [ __all__ = [
"All", "All",
"Any", "Any",
"Filter", "FilterType",
"Not", "Not",
"Chats", "Chats",
"ChatType", "ChatType",

View File

@ -6,7 +6,7 @@ from typing import Awaitable, TypeAlias
from ..event import Event from ..event import Event
Filter: TypeAlias = Callable[[Event], bool | Awaitable[bool]] FilterType: TypeAlias = Callable[[Event], bool | Awaitable[bool]]
class Combinable(abc.ABC): class Combinable(abc.ABC):
@ -22,7 +22,7 @@ class Combinable(abc.ABC):
Multiple ``~`` will toggle between using :class:`Not` and not using it. Multiple ``~`` will toggle between using :class:`Not` and not using it.
""" """
def __or__(self, other: typing.Any) -> Filter: def __or__(self, other: typing.Any) -> FilterType:
if not callable(other): if not callable(other):
return NotImplemented return NotImplemented
@ -30,7 +30,7 @@ class Combinable(abc.ABC):
rhs = other.filters if isinstance(other, Any) else (other,) rhs = other.filters if isinstance(other, Any) else (other,)
return Any(*lhs, *rhs) # type: ignore [arg-type] return Any(*lhs, *rhs) # type: ignore [arg-type]
def __and__(self, other: typing.Any) -> Filter: def __and__(self, other: typing.Any) -> FilterType:
if not callable(other): if not callable(other):
return NotImplemented return NotImplemented
@ -38,7 +38,7 @@ class Combinable(abc.ABC):
rhs = other.filters if isinstance(other, All) else (other,) rhs = other.filters if isinstance(other, All) else (other,)
return All(*lhs, *rhs) # type: ignore [arg-type] return All(*lhs, *rhs) # type: ignore [arg-type]
def __invert__(self) -> Filter: def __invert__(self) -> FilterType:
return self.filter if isinstance(self, Not) else Not(self) # type: ignore [return-value] return self.filter if isinstance(self, Not) else Not(self) # type: ignore [return-value]
@abc.abstractmethod @abc.abstractmethod
@ -72,11 +72,13 @@ class Any(Combinable):
__slots__ = ("_filters",) __slots__ = ("_filters",)
def __init__(self, filter1: Filter, filter2: Filter, *filters: Filter) -> None: def __init__(
self, filter1: FilterType, filter2: FilterType, *filters: FilterType
) -> None:
self._filters = (filter1, filter2, *filters) self._filters = (filter1, filter2, *filters)
@property @property
def filters(self) -> tuple[Filter, ...]: def filters(self) -> tuple[FilterType, ...]:
""" """
The filters being checked, in order. The filters being checked, in order.
""" """
@ -116,11 +118,13 @@ class All(Combinable):
__slots__ = ("_filters",) __slots__ = ("_filters",)
def __init__(self, filter1: Filter, filter2: Filter, *filters: Filter) -> None: def __init__(
self, filter1: FilterType, filter2: FilterType, *filters: FilterType
) -> None:
self._filters = (filter1, filter2, *filters) self._filters = (filter1, filter2, *filters)
@property @property
def filters(self) -> tuple[Filter, ...]: def filters(self) -> tuple[FilterType, ...]:
""" """
The filters being checked, in order. The filters being checked, in order.
""" """
@ -158,11 +162,11 @@ class Not(Combinable):
__slots__ = ("_filter",) __slots__ = ("_filter",)
def __init__(self, filter: Filter) -> None: def __init__(self, filter: FilterType) -> None:
self._filter = filter self._filter = filter
@property @property
def filter(self) -> Filter: def filter(self) -> FilterType:
""" """
The filter being negated. The filter being negated.
""" """

View File

@ -14,6 +14,7 @@ from .file import (
try_get_url_path, try_get_url_path,
) )
from .inline_result import InlineResult from .inline_result import InlineResult
from .keyboard import InlineKeyboard, Keyboard, KeyboardType
from .login_token import LoginToken from .login_token import LoginToken
from .message import ( from .message import (
Message, Message,
@ -60,4 +61,7 @@ __all__ = [
"Participant", "Participant",
"PasswordToken", "PasswordToken",
"RecentAction", "RecentAction",
"Keyboard",
"InlineKeyboard",
"KeyboardType",
] ]

View File

@ -1,7 +1,7 @@
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
@ -18,45 +18,6 @@ if TYPE_CHECKING:
from ..message import Message from ..message import Message
def as_concrete_row(row: abcs.KeyboardButtonRow) -> types.KeyboardButtonRow:
assert isinstance(row, 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: 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.

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import weakref import weakref
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional, TypeAlias
from ....tl import types from ....tl import types
@ -9,7 +9,7 @@ if TYPE_CHECKING:
from ..message import Message from ..message import Message
ButtonTypes = ( RawButtonType: TypeAlias = (
types.KeyboardButton types.KeyboardButton
| types.KeyboardButtonUrl | types.KeyboardButtonUrl
| types.KeyboardButtonCallback | types.KeyboardButtonCallback
@ -53,7 +53,7 @@ class Button:
f"Can't instantiate abstract class {self.__class__.__name__}" f"Can't instantiate abstract class {self.__class__.__name__}"
) )
self._raw: ButtonTypes = types.KeyboardButton(text=text) self._raw: RawButtonType = types.KeyboardButton(text=text)
self._msg: Optional[weakref.ReferenceType[Message]] = None self._msg: Optional[weakref.ReferenceType[Message]] = None
@property @property

View File

@ -0,0 +1,53 @@
from typing import Optional, TypeAlias
from ...tl import abcs, types
from .buttons.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 Keyboard:
__slots__ = ("_raw",)
def __init__(
self,
buttons: list[Button] | list[list[Button]],
resize: bool,
single_use: bool,
selective: bool,
persistent: bool,
placeholder: Optional[str],
) -> None:
self._raw = types.ReplyKeyboardMarkup(
resize=resize,
single_use=single_use,
selective=selective,
persistent=persistent,
rows=_build_keyboard_rows(buttons),
placeholder=placeholder,
)
class InlineKeyboard:
__slots__ = ("_raw",)
def __init__(self, buttons: list[Button] | list[list[Button]]) -> None:
self._raw = types.ReplyInlineMarkup(rows=_build_keyboard_rows(buttons))
KeyboardType: TypeAlias = Keyboard | InlineKeyboard

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import datetime import datetime
import time import time
from typing import TYPE_CHECKING, Any, Optional, Self, Sequence from typing import TYPE_CHECKING, Any, Optional, Self, Sequence, cast
from ...session import PeerRef from ...session import PeerRef
from ...tl import abcs, types from ...tl import abcs, types
@ -12,8 +12,9 @@ from ..parsers import (
parse_html_message, parse_html_message,
parse_markdown_message, parse_markdown_message,
) )
from .buttons import Button, as_concrete_row, create_button from .buttons import Button, create_button
from .file import File from .file import File
from .keyboard import KeyboardType
from .meta import NoPublicConstructor from .meta import NoPublicConstructor
from .peer import Peer, expand_peer, peer_id from .peer import Peer, expand_peer, peer_id
@ -333,7 +334,7 @@ class Message(metaclass=NoPublicConstructor):
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[Button] | list[list[Button]]] = None, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Alias for :meth:`telethon.Client.send_message`. Alias for :meth:`telethon.Client.send_message`.
@ -342,7 +343,7 @@ class Message(metaclass=NoPublicConstructor):
:param markdown: See :ref:`formatting`. :param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`. :param html: See :ref:`formatting`.
:param link_preview: See :meth:`~telethon.Client.send_message`. :param link_preview: See :meth:`~telethon.Client.send_message`.
:param buttons: See :meth:`~telethon.Client.send_message`. :param keyboard: See :meth:`~telethon.Client.send_message`.
""" """
return await self._client.send_message( return await self._client.send_message(
self.chat, self.chat,
@ -350,7 +351,7 @@ class Message(metaclass=NoPublicConstructor):
markdown=markdown, markdown=markdown,
html=html, html=html,
link_preview=link_preview, link_preview=link_preview,
buttons=buttons, keyboard=keyboard,
) )
async def reply( async def reply(
@ -360,7 +361,7 @@ class Message(metaclass=NoPublicConstructor):
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[Button] | list[list[Button]]] = None, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Alias for :meth:`telethon.Client.send_message` with the ``reply_to`` parameter set to this message. Alias for :meth:`telethon.Client.send_message` with the ``reply_to`` parameter set to this message.
@ -369,7 +370,7 @@ class Message(metaclass=NoPublicConstructor):
:param markdown: See :ref:`formatting`. :param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`. :param html: See :ref:`formatting`.
:param link_preview: See :meth:`~telethon.Client.send_message`. :param link_preview: See :meth:`~telethon.Client.send_message`.
:param buttons: See :meth:`~telethon.Client.send_message`. :param keyboard: See :meth:`~telethon.Client.send_message`.
""" """
return await self._client.send_message( return await self._client.send_message(
self.chat, self.chat,
@ -378,7 +379,7 @@ class Message(metaclass=NoPublicConstructor):
html=html, html=html,
link_preview=link_preview, link_preview=link_preview,
reply_to=self.id, reply_to=self.id,
buttons=buttons, keyboard=keyboard,
) )
async def delete(self, *, revoke: bool = True) -> None: async def delete(self, *, revoke: bool = True) -> None:
@ -395,7 +396,7 @@ class Message(metaclass=NoPublicConstructor):
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[Button] | list[list[Button]]] = None, keyboard: Optional[KeyboardType] = None,
) -> Message: ) -> Message:
""" """
Alias for :meth:`telethon.Client.edit_message`. Alias for :meth:`telethon.Client.edit_message`.
@ -404,7 +405,7 @@ class Message(metaclass=NoPublicConstructor):
:param markdown: See :ref:`formatting`. :param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`. :param html: See :ref:`formatting`.
:param link_preview: See :meth:`~telethon.Client.send_message`. :param link_preview: See :meth:`~telethon.Client.send_message`.
:param buttons: See :meth:`~telethon.Client.send_message`. :param keyboard: See :meth:`~telethon.Client.send_message`.
""" """
return await self._client.edit_message( return await self._client.edit_message(
self.chat, self.chat,
@ -413,7 +414,7 @@ class Message(metaclass=NoPublicConstructor):
markdown=markdown, markdown=markdown,
html=html, html=html,
link_preview=link_preview, link_preview=link_preview,
buttons=buttons, keyboard=keyboard,
) )
async def forward(self, target: Peer | PeerRef) -> Message: async def forward(self, target: Peer | PeerRef) -> Message:
@ -476,8 +477,11 @@ class Message(metaclass=NoPublicConstructor):
return None return None
return [ return [
[create_button(self, button) for button in row.buttons] [
for row in map(as_concrete_row, markup.rows) create_button(self, button)
for button in cast(types.KeyboardButtonRow, row).buttons
]
for row in markup.rows
] ]
@property @property

View File

@ -7,6 +7,7 @@ When the return value is :data:`True`, the associated :mod:`~telethon.events` ha
The :doc:`/concepts/updates` concept to learn to combine filters or define your own. The :doc:`/concepts/updates` concept to learn to combine filters or define your own.
""" """
from .._impl.client.events.filters import ( from .._impl.client.events.filters import (
All, All,
Any, Any,
@ -14,7 +15,7 @@ from .._impl.client.events.filters import (
ChatType, ChatType,
Command, Command,
Data, Data,
Filter, FilterType,
Forward, Forward,
Incoming, Incoming,
Media, Media,
@ -31,7 +32,7 @@ __all__ = [
"Chats", "Chats",
"ChatType", "ChatType",
"Command", "Command",
"Filter", "FilterType",
"Forward", "Forward",
"Incoming", "Incoming",
"Media", "Media",

View File

@ -13,7 +13,9 @@ from .._impl.client.types import (
Draft, Draft,
File, File,
Group, Group,
InlineKeyboard,
InlineResult, InlineResult,
Keyboard,
LoginToken, LoginToken,
Message, Message,
Participant, Participant,
@ -37,7 +39,9 @@ __all__ = [
"Draft", "Draft",
"File", "File",
"Group", "Group",
"InlineKeyboard",
"InlineResult", "InlineResult",
"Keyboard",
"LoginToken", "LoginToken",
"Message", "Message",
"Participant", "Participant",