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

View File

@ -13,13 +13,11 @@ from ..types import (
AsyncList,
File,
InFileLike,
KeyboardType,
Message,
OutFileLike,
OutWrapper,
Peer,
)
from ..types import buttons as btns
from ..types import (
expand_stripped_size,
generate_random_id,
parse_message,
@ -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,
keyboard: Optional[KeyboardType] = None,
) -> Message:
return await send_file(
self,
@ -79,7 +77,7 @@ async def send_photo(
caption_markdown=caption_markdown,
caption_html=caption_html,
reply_to=reply_to,
buttons=buttons,
keyboard=keyboard,
)
@ -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,
keyboard: Optional[KeyboardType] = None,
) -> Message:
return await send_file(
self,
@ -117,7 +115,7 @@ async def send_audio(
caption_markdown=caption_markdown,
caption_html=caption_html,
reply_to=reply_to,
buttons=buttons,
keyboard=keyboard,
)
@ -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]]],
keyboard: Optional[KeyboardType],
) -> Message:
return await send_file(
self,
@ -159,7 +157,7 @@ async def send_video(
caption_markdown=caption_markdown,
caption_html=caption_html,
reply_to=reply_to,
buttons=buttons,
keyboard=keyboard,
)
@ -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]]],
keyboard: Optional[KeyboardType],
) -> Message:
message, entities = parse_message(
text=caption, markdown=caption_markdown, html=caption_html, allow_empty=True
@ -198,7 +196,7 @@ async def send_file(
# Re-send existing file.
if isinstance(file, 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.
@ -222,7 +220,7 @@ async def send_file(
spoiler=False, url=file, ttl_seconds=None
)
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)
@ -288,7 +286,7 @@ async def 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,
entities: Optional[list[abcs.MessageEntity]],
reply_to: Optional[int],
buttons: Optional[list[btns.Button] | list[list[btns.Button]]],
keyboard: Optional[KeyboardType],
) -> 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=keyboard._raw if keyboard else None,
entities=entities,
schedule_date=None,
send_as=None,

View File

@ -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,
KeyboardType,
Message,
Peer,
build_chat_map,
generate_random_id,
parse_message,
peer_id,
)
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,
keyboard: Optional[KeyboardType] = 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=keyboard._raw if keyboard 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,
keyboard: Optional[KeyboardType] = 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=keyboard._raw if keyboard else None,
entities=entities,
schedule_date=None,
)

View File

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

View File

@ -1,12 +1,12 @@
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 .messages import Command, Forward, Incoming, Media, Outgoing, Reply, Text
__all__ = [
"All",
"Any",
"Filter",
"FilterType",
"Not",
"Chats",
"ChatType",

View File

@ -6,7 +6,7 @@ from typing import Awaitable, TypeAlias
from ..event import Event
Filter: TypeAlias = Callable[[Event], bool | Awaitable[bool]]
FilterType: TypeAlias = Callable[[Event], bool | Awaitable[bool]]
class Combinable(abc.ABC):
@ -22,7 +22,7 @@ class Combinable(abc.ABC):
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):
return NotImplemented
@ -30,7 +30,7 @@ class Combinable(abc.ABC):
rhs = other.filters if isinstance(other, Any) else (other,)
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):
return NotImplemented
@ -38,7 +38,7 @@ class Combinable(abc.ABC):
rhs = other.filters if isinstance(other, All) else (other,)
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]
@abc.abstractmethod
@ -72,11 +72,13 @@ class Any(Combinable):
__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)
@property
def filters(self) -> tuple[Filter, ...]:
def filters(self) -> tuple[FilterType, ...]:
"""
The filters being checked, in order.
"""
@ -116,11 +118,13 @@ class All(Combinable):
__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)
@property
def filters(self) -> tuple[Filter, ...]:
def filters(self) -> tuple[FilterType, ...]:
"""
The filters being checked, in order.
"""
@ -158,11 +162,11 @@ class Not(Combinable):
__slots__ = ("_filter",)
def __init__(self, filter: Filter) -> None:
def __init__(self, filter: FilterType) -> None:
self._filter = filter
@property
def filter(self) -> Filter:
def filter(self) -> FilterType:
"""
The filter being negated.
"""

View File

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

View File

@ -1,7 +1,7 @@
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
@ -18,45 +18,6 @@ if TYPE_CHECKING:
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:
"""
Create a custom button from a Telegram button.

View File

@ -1,7 +1,7 @@
from __future__ import annotations
import weakref
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, TypeAlias
from ....tl import types
@ -9,7 +9,7 @@ if TYPE_CHECKING:
from ..message import Message
ButtonTypes = (
RawButtonType: TypeAlias = (
types.KeyboardButton
| types.KeyboardButtonUrl
| types.KeyboardButtonCallback
@ -53,7 +53,7 @@ class Button:
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
@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 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 ...tl import abcs, types
@ -12,8 +12,9 @@ from ..parsers import (
parse_html_message,
parse_markdown_message,
)
from .buttons import Button, as_concrete_row, create_button
from .buttons import Button, create_button
from .file import File
from .keyboard import KeyboardType
from .meta import NoPublicConstructor
from .peer import Peer, expand_peer, peer_id
@ -333,7 +334,7 @@ class Message(metaclass=NoPublicConstructor):
markdown: Optional[str] = None,
html: Optional[str] = None,
link_preview: bool = False,
buttons: Optional[list[Button] | list[list[Button]]] = None,
keyboard: Optional[KeyboardType] = None,
) -> Message:
"""
Alias for :meth:`telethon.Client.send_message`.
@ -342,7 +343,7 @@ class Message(metaclass=NoPublicConstructor):
:param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`.
: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(
self.chat,
@ -350,7 +351,7 @@ class Message(metaclass=NoPublicConstructor):
markdown=markdown,
html=html,
link_preview=link_preview,
buttons=buttons,
keyboard=keyboard,
)
async def reply(
@ -360,7 +361,7 @@ class Message(metaclass=NoPublicConstructor):
markdown: Optional[str] = None,
html: Optional[str] = None,
link_preview: bool = False,
buttons: Optional[list[Button] | list[list[Button]]] = None,
keyboard: Optional[KeyboardType] = None,
) -> 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 html: See :ref:`formatting`.
: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(
self.chat,
@ -378,7 +379,7 @@ class Message(metaclass=NoPublicConstructor):
html=html,
link_preview=link_preview,
reply_to=self.id,
buttons=buttons,
keyboard=keyboard,
)
async def delete(self, *, revoke: bool = True) -> None:
@ -395,7 +396,7 @@ class Message(metaclass=NoPublicConstructor):
markdown: Optional[str] = None,
html: Optional[str] = None,
link_preview: bool = False,
buttons: Optional[list[Button] | list[list[Button]]] = None,
keyboard: Optional[KeyboardType] = None,
) -> Message:
"""
Alias for :meth:`telethon.Client.edit_message`.
@ -404,7 +405,7 @@ class Message(metaclass=NoPublicConstructor):
:param markdown: See :ref:`formatting`.
:param html: See :ref:`formatting`.
: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(
self.chat,
@ -413,7 +414,7 @@ class Message(metaclass=NoPublicConstructor):
markdown=markdown,
html=html,
link_preview=link_preview,
buttons=buttons,
keyboard=keyboard,
)
async def forward(self, target: Peer | PeerRef) -> Message:
@ -476,8 +477,11 @@ class Message(metaclass=NoPublicConstructor):
return None
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

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.
"""
from .._impl.client.events.filters import (
All,
Any,
@ -14,7 +15,7 @@ from .._impl.client.events.filters import (
ChatType,
Command,
Data,
Filter,
FilterType,
Forward,
Incoming,
Media,
@ -31,7 +32,7 @@ __all__ = [
"Chats",
"ChatType",
"Command",
"Filter",
"FilterType",
"Forward",
"Incoming",
"Media",

View File

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