telegrinder 1.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.types.objects import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
|
5
|
+
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from telegrinder.tools.keyboard.button import BaseButton
|
|
8
|
+
|
|
9
|
+
from telegrinder.tools.keyboard.utils import RowButtons, copy_keyboard
|
|
10
|
+
|
|
11
|
+
type DictStrAny = dict[str, typing.Any]
|
|
12
|
+
type AnyMarkup = ReplyKeyboardMarkup | InlineKeyboardMarkup
|
|
13
|
+
type RawKeyboard = list[list[DictStrAny]]
|
|
14
|
+
type Button = DictStrAny | list[DictStrAny] | BaseButton | RowButtons[BaseButton]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ABCKeyboard(typing.Protocol):
|
|
18
|
+
keyboard: RawKeyboard
|
|
19
|
+
|
|
20
|
+
__button_class__: typing.ClassVar[type[BaseButton]]
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def dict(self) -> DictStrAny:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def get_markup(self) -> AnyMarkup:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def add(self, button: Button, /) -> typing.Self:
|
|
35
|
+
if not self.keyboard:
|
|
36
|
+
self.row()
|
|
37
|
+
|
|
38
|
+
if isinstance(button, RowButtons):
|
|
39
|
+
self.keyboard[-1].extend(button.get_data())
|
|
40
|
+
if button.auto_row:
|
|
41
|
+
self.row()
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
if isinstance(button, list):
|
|
45
|
+
self.keyboard[-1].extend(button)
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
self.keyboard[-1].append(button if isinstance(button, dict) else button.get_data())
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def row(self) -> typing.Self:
|
|
52
|
+
if len(self.keyboard) and not len(self.keyboard[-1]):
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
self.keyboard.append([])
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def format_text(self, **format_data: typing.Any) -> typing.Self:
|
|
59
|
+
copy_keyboard = self.copy()
|
|
60
|
+
|
|
61
|
+
for row in self.keyboard:
|
|
62
|
+
for button in row:
|
|
63
|
+
button.update(dict(text=button["text"].format(**format_data)))
|
|
64
|
+
|
|
65
|
+
return copy_keyboard
|
|
66
|
+
|
|
67
|
+
def merge(self, other: typing.Self, /) -> typing.Self:
|
|
68
|
+
self.keyboard.extend(copy_keyboard(other.keyboard))
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def merge_to_last_row(self, other: typing.Self, /) -> typing.Self:
|
|
72
|
+
total_rows = len(other.keyboard)
|
|
73
|
+
|
|
74
|
+
for index, row in enumerate(copy_keyboard(other.keyboard), start=1):
|
|
75
|
+
for button in row:
|
|
76
|
+
self.keyboard[-1].append(button)
|
|
77
|
+
|
|
78
|
+
if index < total_rows:
|
|
79
|
+
self.keyboard.append([])
|
|
80
|
+
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ("ABCKeyboard",)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from telegrinder.tools.keyboard.abc import Button, RawKeyboard
|
|
6
|
+
from telegrinder.tools.keyboard.button import BaseButton
|
|
7
|
+
|
|
8
|
+
from telegrinder.tools.keyboard.abc import ABCKeyboard
|
|
9
|
+
from telegrinder.tools.keyboard.utils import RowButtons, copy_keyboard, get_keyboard_button_rules, is_dunder
|
|
10
|
+
|
|
11
|
+
BUTTON_CLASS_KEY: typing.Final = "__button_class__"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KeyboardMeta(type):
|
|
15
|
+
if not typing.TYPE_CHECKING:
|
|
16
|
+
|
|
17
|
+
def __getattribute__(cls, __name: str) -> typing.Any:
|
|
18
|
+
if (
|
|
19
|
+
not is_dunder(__name)
|
|
20
|
+
and ABCKeyboard not in type.__getattribute__(cls, "__bases__")
|
|
21
|
+
and hasattr(cls, BUTTON_CLASS_KEY)
|
|
22
|
+
and (button_rule := get_keyboard_button_rules(cls).get(__name)) is not None
|
|
23
|
+
):
|
|
24
|
+
return button_rule
|
|
25
|
+
|
|
26
|
+
return super().__getattribute__(__name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ABCBaseKeyboard(typing._ProtocolMeta, KeyboardMeta): # type: ignore
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BaseKeyboard[KeyboardButton: BaseButton = typing.Any](typing.Protocol, metaclass=ABCBaseKeyboard):
|
|
34
|
+
keyboard: RawKeyboard
|
|
35
|
+
|
|
36
|
+
__keyboard_instance__: typing.ClassVar[typing.Self]
|
|
37
|
+
__button_class__: typing.ClassVar[type[BaseButton[typing.Self]]]
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
def __init_subclass__(cls) -> None:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def __and__(self, other: object, /) -> typing.Self:
|
|
48
|
+
if not isinstance(other, self.__button_class__ | type(self)):
|
|
49
|
+
return NotImplemented
|
|
50
|
+
return self.add(other) if isinstance(other, self.__button_class__) else self.merge(other) # type: ignore
|
|
51
|
+
|
|
52
|
+
def __or__(self, other: object, /) -> typing.Self:
|
|
53
|
+
if not isinstance(other, self.__button_class__ | type(self)):
|
|
54
|
+
return NotImplemented
|
|
55
|
+
kb = self.row()
|
|
56
|
+
return kb.add(other) if isinstance(other, self.__button_class__) else kb.merge_to_last_row(other) # type: ignore
|
|
57
|
+
|
|
58
|
+
def add(self, button: Button, /) -> typing.Self:
|
|
59
|
+
if not self.keyboard:
|
|
60
|
+
self.row()
|
|
61
|
+
|
|
62
|
+
if isinstance(button, RowButtons):
|
|
63
|
+
self.keyboard[-1].extend(button.get_data())
|
|
64
|
+
if button.auto_row:
|
|
65
|
+
self.row()
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
if isinstance(button, list):
|
|
69
|
+
self.keyboard[-1].extend(button)
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
self.keyboard[-1].append(button if isinstance(button, dict) else button.get_data())
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def row(self) -> typing.Self:
|
|
76
|
+
if len(self.keyboard) and not len(self.keyboard[-1]):
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
self.keyboard.append([])
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def format_text(self, **format_data: typing.Any) -> typing.Self:
|
|
83
|
+
copy_keyboard = self.copy()
|
|
84
|
+
|
|
85
|
+
for row in self.keyboard:
|
|
86
|
+
for button in row:
|
|
87
|
+
button.update(dict(text=button["text"].format(**format_data)))
|
|
88
|
+
|
|
89
|
+
return copy_keyboard
|
|
90
|
+
|
|
91
|
+
def merge(self, other: BaseKeyboard[KeyboardButton], /) -> typing.Self:
|
|
92
|
+
self.keyboard.extend(copy_keyboard(other.keyboard))
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def merge_to_last_row(self, other: BaseKeyboard[KeyboardButton], /) -> typing.Self:
|
|
96
|
+
total_rows = len(other.keyboard)
|
|
97
|
+
|
|
98
|
+
for index, row in enumerate(copy_keyboard(other.keyboard), start=1):
|
|
99
|
+
for button in row:
|
|
100
|
+
self.keyboard[-1].append(button)
|
|
101
|
+
|
|
102
|
+
if index < total_rows:
|
|
103
|
+
self.keyboard.append([])
|
|
104
|
+
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = ("BaseKeyboard",)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
|
|
6
|
+
import msgspec
|
|
7
|
+
|
|
8
|
+
from telegrinder.msgspec_utils.encoder import encoder
|
|
9
|
+
from telegrinder.tools.keyboard.utils import freaky_keyboard_merge
|
|
10
|
+
from telegrinder.tools.serialization.json_ser import JSONSerializer
|
|
11
|
+
from telegrinder.tools.serialization.utils import get_model_serializer
|
|
12
|
+
from telegrinder.types.objects import (
|
|
13
|
+
CallbackGame,
|
|
14
|
+
CopyTextButton,
|
|
15
|
+
KeyboardButtonPollType,
|
|
16
|
+
KeyboardButtonRequestChat,
|
|
17
|
+
KeyboardButtonRequestUsers,
|
|
18
|
+
LoginUrl,
|
|
19
|
+
SwitchInlineQueryChosenChat,
|
|
20
|
+
WebAppInfo,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if typing.TYPE_CHECKING:
|
|
24
|
+
from _typeshed import DataclassInstance
|
|
25
|
+
|
|
26
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
27
|
+
from telegrinder.bot.rules.button import ButtonRule
|
|
28
|
+
from telegrinder.tools.keyboard import keyboard
|
|
29
|
+
from telegrinder.tools.keyboard.base import BaseKeyboard
|
|
30
|
+
from telegrinder.tools.keyboard.button import BaseButton
|
|
31
|
+
from telegrinder.tools.serialization.abc import ABCDataSerializer
|
|
32
|
+
|
|
33
|
+
type CallbackData = str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct
|
|
34
|
+
type Keyboard = keyboard.Keyboard
|
|
35
|
+
type InlineKeyboard = keyboard.InlineKeyboard
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclasses.dataclass(kw_only=True)
|
|
39
|
+
class BaseButton[T: BaseKeyboard = typing.Any](abc.ABC):
|
|
40
|
+
new_row: bool = dataclasses.field(default=False)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
def keyboard_class(self) -> type[T]:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
def rule(self) -> ABCRule:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
if typing.TYPE_CHECKING:
|
|
53
|
+
|
|
54
|
+
def __get__(self, instance: T | None, owner: type[T]) -> ButtonRule[typing.Self]: ...
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def as_keyboard(self: BaseButton[T]) -> type[T]: ...
|
|
58
|
+
else:
|
|
59
|
+
|
|
60
|
+
def as_keyboard(self, *args, **kwargs):
|
|
61
|
+
return self.keyboard_class(*args, **kwargs).add(self)
|
|
62
|
+
|
|
63
|
+
def __and__(self, other: object, /) -> T:
|
|
64
|
+
if not isinstance(other, self.keyboard_class | type(self)):
|
|
65
|
+
return NotImplemented
|
|
66
|
+
return freaky_keyboard_merge(self, other)
|
|
67
|
+
|
|
68
|
+
def __or__(self, other: object, /) -> T:
|
|
69
|
+
if not isinstance(other, self.keyboard_class | type(self)):
|
|
70
|
+
return NotImplemented
|
|
71
|
+
return freaky_keyboard_merge(self, other, row=True)
|
|
72
|
+
|
|
73
|
+
def get_data(self) -> dict[str, typing.Any]:
|
|
74
|
+
return {k: v for k, v in dataclasses.asdict(self).items() if v is not None and k != "new_row"}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclasses.dataclass
|
|
78
|
+
class Button(BaseButton[Keyboard]):
|
|
79
|
+
text: str
|
|
80
|
+
request_contact: bool = dataclasses.field(default=False, kw_only=True)
|
|
81
|
+
request_location: bool = dataclasses.field(default=False, kw_only=True)
|
|
82
|
+
request_chat: KeyboardButtonRequestChat | None = dataclasses.field(
|
|
83
|
+
default=None,
|
|
84
|
+
kw_only=True,
|
|
85
|
+
)
|
|
86
|
+
request_user: KeyboardButtonRequestUsers | None = dataclasses.field(default=None, kw_only=True)
|
|
87
|
+
request_poll: KeyboardButtonPollType | None = dataclasses.field(
|
|
88
|
+
default=None,
|
|
89
|
+
kw_only=True,
|
|
90
|
+
)
|
|
91
|
+
web_app: WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
|
|
92
|
+
|
|
93
|
+
@cached_property
|
|
94
|
+
def rule(self) -> ABCRule:
|
|
95
|
+
from telegrinder.bot.rules.text import Text
|
|
96
|
+
|
|
97
|
+
return Text(self.text)
|
|
98
|
+
|
|
99
|
+
@cached_property
|
|
100
|
+
def keyboard_class(self) -> type[Keyboard]:
|
|
101
|
+
from telegrinder.tools.keyboard.keyboard import Keyboard
|
|
102
|
+
|
|
103
|
+
return Keyboard
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclasses.dataclass
|
|
107
|
+
class InlineButton(BaseButton[InlineKeyboard]):
|
|
108
|
+
text: str
|
|
109
|
+
url: str | None = dataclasses.field(default=None, kw_only=True)
|
|
110
|
+
login_url: LoginUrl | None = dataclasses.field(default=None, kw_only=True)
|
|
111
|
+
pay: bool | None = dataclasses.field(default=None, kw_only=True)
|
|
112
|
+
callback_data: CallbackData | None = dataclasses.field(default=None, kw_only=True)
|
|
113
|
+
callback_data_serializer: ABCDataSerializer[typing.Any] | None = dataclasses.field(
|
|
114
|
+
default=None,
|
|
115
|
+
kw_only=True,
|
|
116
|
+
)
|
|
117
|
+
callback_game: CallbackGame | None = dataclasses.field(default=None, kw_only=True)
|
|
118
|
+
copy_text: str | CopyTextButton | None = dataclasses.field(default=None, kw_only=True)
|
|
119
|
+
switch_inline_query: str | None = dataclasses.field(default=None, kw_only=True)
|
|
120
|
+
switch_inline_query_current_chat: str | None = dataclasses.field(default=None, kw_only=True)
|
|
121
|
+
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None = dataclasses.field(
|
|
122
|
+
default=None,
|
|
123
|
+
kw_only=True,
|
|
124
|
+
)
|
|
125
|
+
web_app: str | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
|
|
126
|
+
|
|
127
|
+
def __post_init__(self) -> None:
|
|
128
|
+
model_serializer = get_model_serializer(self.callback_data)
|
|
129
|
+
|
|
130
|
+
self.input_callback_data = self.callback_data
|
|
131
|
+
self.callback_data_serializer = self.callback_data_serializer or (
|
|
132
|
+
None if model_serializer is None else model_serializer(type(self.callback_data))
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
self.callback_data_serializer is None
|
|
137
|
+
and isinstance(self.callback_data, msgspec.Struct | dict)
|
|
138
|
+
or dataclasses.is_dataclass(self.callback_data)
|
|
139
|
+
):
|
|
140
|
+
self.callback_data_serializer = self.callback_data_serializer or JSONSerializer(
|
|
141
|
+
type(self.callback_data),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if self.callback_data_serializer is not None:
|
|
145
|
+
self.callback_data = self.callback_data_serializer.serialize(self.callback_data)
|
|
146
|
+
elif self.callback_data is not None and not isinstance(self.callback_data, str):
|
|
147
|
+
self.callback_data = encoder.encode(self.callback_data)
|
|
148
|
+
|
|
149
|
+
if isinstance(self.copy_text, str):
|
|
150
|
+
self.copy_text = CopyTextButton(text=self.copy_text)
|
|
151
|
+
|
|
152
|
+
if isinstance(self.web_app, str):
|
|
153
|
+
self.web_app = WebAppInfo(url=self.web_app)
|
|
154
|
+
|
|
155
|
+
@cached_property
|
|
156
|
+
def rule(self) -> ABCRule:
|
|
157
|
+
from telegrinder.bot.rules.payload import PayloadEqRule, PayloadJsonEqRule, PayloadModelRule
|
|
158
|
+
|
|
159
|
+
if isinstance(self.input_callback_data, str):
|
|
160
|
+
return PayloadEqRule(self.input_callback_data)
|
|
161
|
+
|
|
162
|
+
if isinstance(self.input_callback_data, dict):
|
|
163
|
+
return PayloadJsonEqRule(self.input_callback_data)
|
|
164
|
+
|
|
165
|
+
if self.input_callback_data is not None:
|
|
166
|
+
return PayloadModelRule(
|
|
167
|
+
type(self.input_callback_data),
|
|
168
|
+
payload=self.input_callback_data,
|
|
169
|
+
serializer=type(self.callback_data_serializer) if self.callback_data_serializer else None,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
raise ValueError("Cannot create rule, because callback data is not defined.")
|
|
173
|
+
|
|
174
|
+
@cached_property
|
|
175
|
+
def keyboard_class(self) -> type[InlineKeyboard]:
|
|
176
|
+
from telegrinder.tools.keyboard.keyboard import InlineKeyboard
|
|
177
|
+
|
|
178
|
+
return InlineKeyboard
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = ("BaseButton", "Button", "InlineButton")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from telegrinder.tools.keyboard.abc import DictStrAny, RawKeyboard
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyboardParams(typing.TypedDict, total=False):
|
|
9
|
+
is_persistent: bool
|
|
10
|
+
one_time_keyboard: bool
|
|
11
|
+
resize_keyboard: bool
|
|
12
|
+
is_selective: bool
|
|
13
|
+
input_field_placeholder: str | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclasses.dataclass(frozen=True)
|
|
17
|
+
class KeyboardModel:
|
|
18
|
+
keyboard: RawKeyboard
|
|
19
|
+
is_persistent: bool = dataclasses.field(default=False, kw_only=True)
|
|
20
|
+
one_time_keyboard: bool = dataclasses.field(default=False, kw_only=True)
|
|
21
|
+
resize_keyboard: bool = dataclasses.field(default=False, kw_only=True)
|
|
22
|
+
is_selective: bool = dataclasses.field(default=False, kw_only=True)
|
|
23
|
+
input_field_placeholder: str | None = dataclasses.field(default=None, kw_only=True)
|
|
24
|
+
|
|
25
|
+
def dict(self) -> DictStrAny:
|
|
26
|
+
dct = dataclasses.asdict(self)
|
|
27
|
+
dct["keyboard"] = [row for row in self.keyboard if row]
|
|
28
|
+
return dct
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ("KeyboardModel", "KeyboardParams")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.tools.keyboard.abc import ABCKeyboard, DictStrAny, RawKeyboard
|
|
5
|
+
from telegrinder.tools.keyboard.abc import Button as ButtonType
|
|
6
|
+
from telegrinder.tools.keyboard.base import BaseKeyboard
|
|
7
|
+
from telegrinder.tools.keyboard.button import BaseButton, Button, InlineButton
|
|
8
|
+
from telegrinder.tools.keyboard.data import KeyboardModel, KeyboardParams
|
|
9
|
+
from telegrinder.tools.keyboard.utils import (
|
|
10
|
+
RowButtons,
|
|
11
|
+
bound_keyboard_method,
|
|
12
|
+
copy_keyboard,
|
|
13
|
+
init_keyboard,
|
|
14
|
+
)
|
|
15
|
+
from telegrinder.types.objects import InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
from telegrinder.tools.serialization.abc import ABCDataSerializer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Keyboard(BaseKeyboard[Button], ABCKeyboard):
|
|
22
|
+
__button_class__ = Button
|
|
23
|
+
__slots__ = ("keyboard_model",)
|
|
24
|
+
|
|
25
|
+
def __init_subclass__(cls, *, max_in_row: int = 0, **kwargs: typing.Unpack[KeyboardParams]) -> None:
|
|
26
|
+
cls.__keyboard_instance__ = init_keyboard(cls(**kwargs), max_in_row=max_in_row)
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
keyboard_model: KeyboardModel | None = None,
|
|
32
|
+
**kwargs: typing.Unpack[KeyboardParams],
|
|
33
|
+
) -> None:
|
|
34
|
+
self.keyboard_model = keyboard_model or KeyboardModel(keyboard=[[]], **kwargs)
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return f"<{type(self).__name__}: keyboard_model={self.keyboard_model!r}>"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def keyboard(self) -> RawKeyboard:
|
|
41
|
+
return self.keyboard_model.keyboard
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def is_persistent(self) -> bool:
|
|
45
|
+
return self.keyboard_model.is_persistent
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def one_time_keyboard(self) -> bool:
|
|
49
|
+
return self.keyboard_model.one_time_keyboard
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def resize_keyboard(self) -> bool:
|
|
53
|
+
return self.keyboard_model.resize_keyboard
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def is_selective(self) -> bool:
|
|
57
|
+
return self.keyboard_model.is_selective
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def input_field_placeholder(self) -> str | None:
|
|
61
|
+
return self.keyboard_model.input_field_placeholder
|
|
62
|
+
|
|
63
|
+
@bound_keyboard_method
|
|
64
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
65
|
+
keyboard_model = dataclasses.replace(
|
|
66
|
+
self.keyboard_model,
|
|
67
|
+
keyboard=copy_keyboard(self.keyboard),
|
|
68
|
+
**with_changes,
|
|
69
|
+
)
|
|
70
|
+
return type(self)(keyboard_model=keyboard_model)
|
|
71
|
+
|
|
72
|
+
@bound_keyboard_method
|
|
73
|
+
def dict(self) -> DictStrAny:
|
|
74
|
+
return self.keyboard_model.dict()
|
|
75
|
+
|
|
76
|
+
@bound_keyboard_method
|
|
77
|
+
def get_markup(self) -> ReplyKeyboardMarkup:
|
|
78
|
+
return ReplyKeyboardMarkup.from_dict(self.dict())
|
|
79
|
+
|
|
80
|
+
@bound_keyboard_method
|
|
81
|
+
def get_keyboard_remove(self) -> ReplyKeyboardRemove:
|
|
82
|
+
return ReplyKeyboardRemove(remove_keyboard=True, selective=self.is_selective)
|
|
83
|
+
|
|
84
|
+
def placeholder(self, value: str | None, /) -> typing.Self:
|
|
85
|
+
return self.copy(input_field_placeholder=value)
|
|
86
|
+
|
|
87
|
+
def resize(self) -> typing.Self:
|
|
88
|
+
return self.copy(resize_keyboard=True)
|
|
89
|
+
|
|
90
|
+
def one_time(self) -> typing.Self:
|
|
91
|
+
return self.copy(one_time_keyboard=True)
|
|
92
|
+
|
|
93
|
+
def selective(self) -> typing.Self:
|
|
94
|
+
return self.copy(is_selective=True)
|
|
95
|
+
|
|
96
|
+
def persistent(self) -> typing.Self:
|
|
97
|
+
return self.copy(is_persistent=True)
|
|
98
|
+
|
|
99
|
+
def no_resize(self) -> typing.Self:
|
|
100
|
+
return self.copy(resize_keyboard=False)
|
|
101
|
+
|
|
102
|
+
def no_one_time(self) -> typing.Self:
|
|
103
|
+
return self.copy(one_time_keyboard=False)
|
|
104
|
+
|
|
105
|
+
def no_selective(self) -> typing.Self:
|
|
106
|
+
return self.copy(is_selective=False)
|
|
107
|
+
|
|
108
|
+
def no_persistent(self) -> typing.Self:
|
|
109
|
+
return self.copy(is_persistent=False)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class InlineKeyboard(BaseKeyboard[InlineButton], ABCKeyboard):
|
|
113
|
+
__button_class__ = InlineButton
|
|
114
|
+
__serializer__: ABCDataSerializer | None = None
|
|
115
|
+
__slots__ = ("keyboard", "serializer")
|
|
116
|
+
|
|
117
|
+
def __init_subclass__(
|
|
118
|
+
cls,
|
|
119
|
+
*,
|
|
120
|
+
max_in_row: int = 0,
|
|
121
|
+
serializer: ABCDataSerializer | None = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
cls.__keyboard_instance__ = init_keyboard(cls(), max_in_row=max_in_row)
|
|
124
|
+
cls.__serializer__ = serializer
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
*,
|
|
129
|
+
serializer: ABCDataSerializer | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
self.keyboard = [[]]
|
|
132
|
+
self.serializer = serializer or self.__serializer__
|
|
133
|
+
|
|
134
|
+
def __repr__(self) -> str:
|
|
135
|
+
return f"<{type(self).__name__}: keyboard={self.keyboard!r}>"
|
|
136
|
+
|
|
137
|
+
def add(self, button: ButtonType, /) -> typing.Self:
|
|
138
|
+
if isinstance(button, BaseButton | RowButtons):
|
|
139
|
+
for b in button.buttons if isinstance(button, RowButtons) else (button,):
|
|
140
|
+
if isinstance(b, InlineButton) and b.callback_data_serializer is None:
|
|
141
|
+
b.callback_data_serializer = self.serializer
|
|
142
|
+
|
|
143
|
+
return super().add(button)
|
|
144
|
+
|
|
145
|
+
@bound_keyboard_method
|
|
146
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
147
|
+
new_keyboard = type(self)()
|
|
148
|
+
new_keyboard.keyboard = copy_keyboard(self.keyboard)
|
|
149
|
+
return new_keyboard
|
|
150
|
+
|
|
151
|
+
@bound_keyboard_method
|
|
152
|
+
def dict(self) -> DictStrAny:
|
|
153
|
+
return dict(inline_keyboard=[row for row in self.keyboard if row])
|
|
154
|
+
|
|
155
|
+
@bound_keyboard_method
|
|
156
|
+
def get_markup(self) -> InlineKeyboardMarkup:
|
|
157
|
+
return InlineKeyboardMarkup.from_dict(self.dict())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
__all__ = ("InlineKeyboard", "Keyboard")
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from telegrinder.bot.rules.button import ButtonRule
|
|
6
|
+
from telegrinder.tools.keyboard.abc import ABCKeyboard, RawKeyboard
|
|
7
|
+
from telegrinder.tools.keyboard.base import BaseKeyboard
|
|
8
|
+
from telegrinder.tools.keyboard.button import BaseButton
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@lru_cache(maxsize=1024)
|
|
12
|
+
def get_keyboard_button_rules(
|
|
13
|
+
keyboard_class: type[ABCKeyboard],
|
|
14
|
+
/,
|
|
15
|
+
) -> dict[str, ButtonRule[BaseButton[typing.Any]]]:
|
|
16
|
+
from telegrinder.bot.rules.button import ButtonRule
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: ButtonRule(button=button, rule=button.rule)
|
|
20
|
+
for name, button in dict(vars(keyboard_class)).items()
|
|
21
|
+
if isinstance(button, keyboard_class.__button_class__) and not is_dunder(name)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_dunder(name: str, /) -> bool:
|
|
26
|
+
return name.startswith("__") and name.endswith("__")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def copy_keyboard(keyboard: RawKeyboard, /) -> RawKeyboard:
|
|
30
|
+
return [row.copy() for row in keyboard if row]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def freaky_keyboard_merge[T: BaseKeyboard = typing.Any](
|
|
34
|
+
button: BaseButton[T],
|
|
35
|
+
keyboard_or_button: T | BaseButton[T],
|
|
36
|
+
/,
|
|
37
|
+
*,
|
|
38
|
+
row: bool = False,
|
|
39
|
+
) -> T:
|
|
40
|
+
from telegrinder.tools.keyboard.button import BaseButton
|
|
41
|
+
|
|
42
|
+
keyboard = button.keyboard_class().add(button)
|
|
43
|
+
|
|
44
|
+
if row:
|
|
45
|
+
keyboard = keyboard.row()
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
keyboard.merge_to_last_row(keyboard_or_button)
|
|
49
|
+
if not isinstance(keyboard_or_button, BaseButton)
|
|
50
|
+
else keyboard.add(keyboard_or_button)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def init_keyboard[T: BaseKeyboard = typing.Any](
|
|
55
|
+
keyboard_instance: T,
|
|
56
|
+
/,
|
|
57
|
+
*,
|
|
58
|
+
max_in_row: int = 0,
|
|
59
|
+
) -> T:
|
|
60
|
+
for button in get_keyboard_button_rules(type(keyboard_instance)).values():
|
|
61
|
+
if button.button.new_row or (max_in_row and len(keyboard_instance.keyboard[-1]) >= max_in_row):
|
|
62
|
+
keyboard_instance.row()
|
|
63
|
+
|
|
64
|
+
keyboard_instance.add(button.button)
|
|
65
|
+
|
|
66
|
+
return keyboard_instance
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class bound_keyboard_method[T: BaseKeyboard = typing.Any, **P = ..., R = typing.Any]: # noqa: N801
|
|
70
|
+
def __init__(self, func: typing.Callable[typing.Concatenate[T, P], R], /) -> None:
|
|
71
|
+
self.func = func
|
|
72
|
+
|
|
73
|
+
def __get__(self, instance: T | None, owner: type[T]) -> typing.Callable[P, R]:
|
|
74
|
+
return self.func.__get__(instance or owner.__keyboard_instance__, owner)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RowButtons[KeyboardButton: BaseButton]:
|
|
78
|
+
buttons: typing.Iterable[KeyboardButton]
|
|
79
|
+
|
|
80
|
+
def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
|
|
81
|
+
self.buttons = buttons
|
|
82
|
+
self.auto_row = auto_row
|
|
83
|
+
|
|
84
|
+
def get_data(self) -> list[dict[str, typing.Any]]:
|
|
85
|
+
return [b.get_data() for b in self.buttons]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = (
|
|
89
|
+
"RowButtons",
|
|
90
|
+
"bound_keyboard_method",
|
|
91
|
+
"copy_keyboard",
|
|
92
|
+
"freaky_keyboard_merge",
|
|
93
|
+
"get_keyboard_button_rules",
|
|
94
|
+
"init_keyboard",
|
|
95
|
+
)
|