telegrinder 0.3.4__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
|
@@ -1,176 +1,174 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import enum
|
|
3
|
-
import secrets
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
7
|
-
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
8
|
-
from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine
|
|
9
|
-
from telegrinder.bot.scenario.abc import ABCScenario
|
|
10
|
-
from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
|
|
11
|
-
from telegrinder.tools.parse_mode import ParseMode
|
|
12
|
-
from telegrinder.types.objects import InlineKeyboardMarkup
|
|
13
|
-
|
|
14
|
-
if typing.TYPE_CHECKING:
|
|
15
|
-
from telegrinder.api.api import API
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
53
|
-
self.
|
|
54
|
-
self.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return False
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
__all__ = ("Checkbox", "Choice")
|
|
1
|
+
import dataclasses
|
|
2
|
+
import enum
|
|
3
|
+
import secrets
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
7
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
8
|
+
from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine
|
|
9
|
+
from telegrinder.bot.scenario.abc import ABCScenario
|
|
10
|
+
from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
|
|
11
|
+
from telegrinder.tools.parse_mode import ParseMode
|
|
12
|
+
from telegrinder.types.objects import InlineKeyboardMarkup
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
from telegrinder.api.api import API
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChoiceAction(enum.StrEnum):
|
|
19
|
+
READY = "ready"
|
|
20
|
+
CANCEL = "cancel"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclasses.dataclass(slots=True)
|
|
24
|
+
class Choice[Key: typing.Hashable]:
|
|
25
|
+
key: Key
|
|
26
|
+
is_picked: bool
|
|
27
|
+
default_text: str
|
|
28
|
+
picked_text: str
|
|
29
|
+
code: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _Checkbox(ABCScenario[CallbackQueryCute]):
|
|
33
|
+
INVALID_CODE = "Invalid code"
|
|
34
|
+
CALLBACK_ANSWER = "Done"
|
|
35
|
+
PARSE_MODE = ParseMode.HTML
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
waiter_machine: WaiterMachine,
|
|
40
|
+
chat_id: int,
|
|
41
|
+
message: str,
|
|
42
|
+
*,
|
|
43
|
+
ready_text: str = "Ready",
|
|
44
|
+
cancel_text: str | None = None,
|
|
45
|
+
max_in_row: int = 3,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.chat_id = chat_id
|
|
48
|
+
self.message = message
|
|
49
|
+
self.choices: list[Choice[typing.Hashable]] = []
|
|
50
|
+
self.ready = ready_text
|
|
51
|
+
self.max_in_row = max_in_row
|
|
52
|
+
self.random_code = secrets.token_hex(8)
|
|
53
|
+
self.waiter_machine = waiter_machine
|
|
54
|
+
self.cancel_text = cancel_text
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
return (
|
|
58
|
+
"<{}@{!r}: (choices={!r}, max_in_row={}) with waiter_machine={!r}, ready_text={!r} "
|
|
59
|
+
"for chat_id={} with message={!r}>"
|
|
60
|
+
).format(
|
|
61
|
+
self.__class__.__name__,
|
|
62
|
+
self.random_code,
|
|
63
|
+
self.choices,
|
|
64
|
+
self.max_in_row,
|
|
65
|
+
self.waiter_machine,
|
|
66
|
+
self.ready,
|
|
67
|
+
self.chat_id,
|
|
68
|
+
self.message,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def get_markup(self) -> InlineKeyboardMarkup:
|
|
72
|
+
kb = InlineKeyboard()
|
|
73
|
+
choices = self.choices.copy()
|
|
74
|
+
while choices:
|
|
75
|
+
while len(kb.keyboard[-1]) < self.max_in_row and choices:
|
|
76
|
+
choice = choices.pop(0)
|
|
77
|
+
kb.add(
|
|
78
|
+
InlineButton(
|
|
79
|
+
text=(choice.default_text if not choice.is_picked else choice.picked_text),
|
|
80
|
+
callback_data=self.random_code + "/" + choice.code,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
kb.row()
|
|
84
|
+
|
|
85
|
+
kb.add(InlineButton(self.ready, callback_data=self.random_code + "/" + ChoiceAction.READY))
|
|
86
|
+
if self.cancel_text is not None:
|
|
87
|
+
kb.row()
|
|
88
|
+
kb.add(InlineButton(self.cancel_text, callback_data=self.random_code + "/" + ChoiceAction.CANCEL))
|
|
89
|
+
|
|
90
|
+
return kb.get_markup()
|
|
91
|
+
|
|
92
|
+
def add_option[Key: typing.Hashable](
|
|
93
|
+
self,
|
|
94
|
+
key: Key,
|
|
95
|
+
default_text: str,
|
|
96
|
+
picked_text: str,
|
|
97
|
+
*,
|
|
98
|
+
is_picked: bool = False,
|
|
99
|
+
) -> "Checkbox[Key]":
|
|
100
|
+
self.choices.append(
|
|
101
|
+
Choice(key, is_picked, default_text, picked_text, secrets.token_hex(8)),
|
|
102
|
+
)
|
|
103
|
+
return self # type: ignore
|
|
104
|
+
|
|
105
|
+
async def handle(self, cb: CallbackQueryCute) -> bool:
|
|
106
|
+
code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
|
|
107
|
+
|
|
108
|
+
match code:
|
|
109
|
+
case ChoiceAction.READY:
|
|
110
|
+
return False
|
|
111
|
+
case ChoiceAction.CANCEL:
|
|
112
|
+
self.choices = []
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
for i, choice in enumerate(self.choices):
|
|
116
|
+
if choice.code == code:
|
|
117
|
+
# Toggle choice
|
|
118
|
+
self.choices[i].is_picked = not self.choices[i].is_picked
|
|
119
|
+
await cb.edit_text(
|
|
120
|
+
text=self.message,
|
|
121
|
+
parse_mode=self.PARSE_MODE,
|
|
122
|
+
reply_markup=self.get_markup(),
|
|
123
|
+
)
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
async def wait(
|
|
129
|
+
self,
|
|
130
|
+
hasher: Hasher[CallbackQueryCute, int],
|
|
131
|
+
api: "API",
|
|
132
|
+
) -> tuple[dict[typing.Hashable, bool], int]:
|
|
133
|
+
assert len(self.choices) > 0
|
|
134
|
+
message = (
|
|
135
|
+
await api.send_message(
|
|
136
|
+
chat_id=self.chat_id,
|
|
137
|
+
text=self.message,
|
|
138
|
+
parse_mode=self.PARSE_MODE,
|
|
139
|
+
reply_markup=self.get_markup(),
|
|
140
|
+
)
|
|
141
|
+
).unwrap()
|
|
142
|
+
|
|
143
|
+
while True:
|
|
144
|
+
q, _ = await self.waiter_machine.wait(
|
|
145
|
+
hasher,
|
|
146
|
+
data=message.message_id,
|
|
147
|
+
)
|
|
148
|
+
should_continue = await self.handle(q)
|
|
149
|
+
await q.answer(self.CALLBACK_ANSWER)
|
|
150
|
+
if not should_continue:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
{choice.key: choice.is_picked for choice in self.choices},
|
|
155
|
+
message.message_id,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if typing.TYPE_CHECKING:
|
|
160
|
+
|
|
161
|
+
class Checkbox[Key: typing.Hashable](_Checkbox):
|
|
162
|
+
choices: list[Choice[Key]]
|
|
163
|
+
|
|
164
|
+
async def wait(
|
|
165
|
+
self,
|
|
166
|
+
hasher: Hasher[CallbackQueryCute, int],
|
|
167
|
+
api: "API",
|
|
168
|
+
) -> tuple[dict[Key, bool], int]: ...
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
Checkbox = _Checkbox
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
__all__ = ("Checkbox", "Choice")
|
|
@@ -1,51 +1,48 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
4
|
-
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
5
|
-
from telegrinder.bot.scenario.checkbox import Checkbox,
|
|
6
|
-
|
|
7
|
-
if typing.TYPE_CHECKING:
|
|
8
|
-
from telegrinder.api.api import API
|
|
9
|
-
from telegrinder.bot.dispatch.view.base import BaseStateView
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for choice in self.choices:
|
|
29
|
-
choice.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
__all__ = ("Choice",)
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
4
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
5
|
+
from telegrinder.bot.scenario.checkbox import Checkbox, ChoiceAction
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from telegrinder.api.api import API
|
|
9
|
+
from telegrinder.bot.dispatch.view.base import BaseStateView
|
|
10
|
+
|
|
11
|
+
class Choice[Key: typing.Hashable](Checkbox[Key]):
|
|
12
|
+
async def wait(
|
|
13
|
+
self,
|
|
14
|
+
hasher: Hasher[CallbackQueryCute, int],
|
|
15
|
+
api: API,
|
|
16
|
+
) -> tuple[Key, int]: ...
|
|
17
|
+
|
|
18
|
+
else:
|
|
19
|
+
class Choice(Checkbox):
|
|
20
|
+
async def handle(self, cb):
|
|
21
|
+
code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
|
|
22
|
+
if code == ChoiceAction.READY:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
for choice in self.choices:
|
|
26
|
+
choice.is_picked = False
|
|
27
|
+
|
|
28
|
+
for i, choice in enumerate(self.choices):
|
|
29
|
+
if choice.code == code:
|
|
30
|
+
self.choices[i].is_picked = True
|
|
31
|
+
await cb.ctx_api.edit_message_text(
|
|
32
|
+
text=self.message,
|
|
33
|
+
chat_id=cb.message.unwrap().v.chat.id,
|
|
34
|
+
message_id=cb.message.unwrap().v.message_id,
|
|
35
|
+
parse_mode=self.PARSE_MODE,
|
|
36
|
+
reply_markup=self.get_markup(),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
async def wait(self, hasher, api):
|
|
42
|
+
if len(tuple(choice for choice in self.choices if choice.is_picked)) != 1:
|
|
43
|
+
raise ValueError("Exactly one choice must be picked.")
|
|
44
|
+
choices, m_id = await super().wait(hasher, api)
|
|
45
|
+
return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = ("Choice",)
|
telegrinder/client/__init__.py
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
from .abc import ABCClient
|
|
2
|
-
from .aiohttp import AiohttpClient
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
from .abc import ABCClient
|
|
2
|
+
from .aiohttp import AiohttpClient
|
|
3
|
+
from .form_data import MultipartFormProto, encode_form_data
|
|
4
|
+
from .sonic import AiosonicClient
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"ABCClient",
|
|
8
|
+
"AiohttpClient",
|
|
9
|
+
"AiosonicClient",
|
|
10
|
+
"MultipartFormProto",
|
|
11
|
+
"encode_form_data",
|
|
12
|
+
)
|
telegrinder/client/abc.py
CHANGED
|
@@ -1,75 +1,100 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
import io
|
|
2
|
+
import typing
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from telegrinder.client.form_data import MultipartFormProto, encode_form_data
|
|
6
|
+
|
|
7
|
+
type Data = dict[str, typing.Any] | MultipartFormProto
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ABCClient[MultipartForm: MultipartFormProto](ABC):
|
|
11
|
+
CONNECTION_TIMEOUT_ERRORS: tuple[type[BaseException], ...] = ()
|
|
12
|
+
CLIENT_CONNECTION_ERRORS: tuple[type[BaseException], ...] = ()
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def request_text(
|
|
20
|
+
self,
|
|
21
|
+
url: str,
|
|
22
|
+
method: str = "GET",
|
|
23
|
+
data: Data | None = None,
|
|
24
|
+
**kwargs: typing.Any,
|
|
25
|
+
) -> str:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
async def request_json(
|
|
30
|
+
self,
|
|
31
|
+
url: str,
|
|
32
|
+
method: str = "GET",
|
|
33
|
+
data: Data | None = None,
|
|
34
|
+
**kwargs: typing.Any,
|
|
35
|
+
) -> dict[str, typing.Any]:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def request_content(
|
|
40
|
+
self,
|
|
41
|
+
url: str,
|
|
42
|
+
method: str = "GET",
|
|
43
|
+
data: Data | None = None,
|
|
44
|
+
**kwargs: typing.Any,
|
|
45
|
+
) -> bytes:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def request_bytes(
|
|
50
|
+
self,
|
|
51
|
+
url: str,
|
|
52
|
+
method: str = "GET",
|
|
53
|
+
data: Data | None = None,
|
|
54
|
+
**kwargs: typing.Any,
|
|
55
|
+
) -> bytes:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def multipart_form_factory(cls) -> MultipartForm:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_form(
|
|
69
|
+
cls,
|
|
70
|
+
*,
|
|
71
|
+
data: dict[str, typing.Any],
|
|
72
|
+
files: dict[str, tuple[str, typing.Any]] | None = None,
|
|
73
|
+
) -> MultipartForm:
|
|
74
|
+
multipart_form = cls.multipart_form_factory()
|
|
75
|
+
files = files or {}
|
|
76
|
+
|
|
77
|
+
for k, v in encode_form_data(data, files).items():
|
|
78
|
+
multipart_form.add_field(k, v)
|
|
79
|
+
|
|
80
|
+
for n, (filename, content) in {
|
|
81
|
+
k: (n, io.BytesIO(c) if isinstance(c, bytes) else c) for k, (n, c) in files.items()
|
|
82
|
+
}.items():
|
|
83
|
+
multipart_form.add_field(n, content, filename=filename)
|
|
84
|
+
|
|
85
|
+
return multipart_form
|
|
86
|
+
|
|
87
|
+
async def __aenter__(self) -> typing.Self:
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
async def __aexit__(
|
|
91
|
+
self,
|
|
92
|
+
exc_type: type[BaseException],
|
|
93
|
+
exc_val: typing.Any,
|
|
94
|
+
exc_tb: typing.Any,
|
|
95
|
+
) -> bool:
|
|
96
|
+
await self.close()
|
|
97
|
+
return not bool(exc_val)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
__all__ = ("ABCClient",)
|