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,164 +1,123 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import inspect
|
|
3
|
-
import typing
|
|
4
|
-
from contextlib import suppress
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
from telegrinder.bot.
|
|
9
|
-
from telegrinder.bot.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
17
|
-
|
|
18
|
-
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
19
|
-
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
20
|
-
MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
|
|
21
|
-
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
|
|
22
|
-
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
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
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
validator = (lambda
|
|
73
|
-
|
|
74
|
-
validator = value
|
|
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
|
-
return False
|
|
104
|
-
|
|
105
|
-
elif not await cls.run_validator(callback_data[key], validator):
|
|
106
|
-
return False
|
|
107
|
-
|
|
108
|
-
return True
|
|
109
|
-
|
|
110
|
-
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
111
|
-
callback_data = event.
|
|
112
|
-
if callback_data is None:
|
|
113
|
-
return False
|
|
114
|
-
if await self.match(callback_data, self.mapping):
|
|
115
|
-
ctx.update(callback_data)
|
|
116
|
-
return True
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def __init__(self, value: str, /) -> None:
|
|
122
|
-
self.value = value
|
|
123
|
-
|
|
124
|
-
def check(self, event: CallbackQuery) -> bool:
|
|
125
|
-
return event.data.unwrap() == self.value
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
129
|
-
def __init__(self, d: dict[str, typing.Any], /) -> None:
|
|
130
|
-
self.d = d
|
|
131
|
-
|
|
132
|
-
def check(self, event: CallbackQuery) -> bool:
|
|
133
|
-
return event.decode_callback_data().unwrap_or_none() == self.d
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
137
|
-
def __init__(
|
|
138
|
-
self,
|
|
139
|
-
model: type[msgspec.Struct] | type[DataclassInstance],
|
|
140
|
-
*,
|
|
141
|
-
alias: str | None = None,
|
|
142
|
-
) -> None:
|
|
143
|
-
self.model = model
|
|
144
|
-
self.alias = alias or "data"
|
|
145
|
-
|
|
146
|
-
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
147
|
-
with suppress(BaseException):
|
|
148
|
-
ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
|
|
149
|
-
return True
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class CallbackDataMarkup(CallbackQueryDataRule):
|
|
154
|
-
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
155
|
-
self.patterns = Markup(patterns).patterns
|
|
156
|
-
|
|
157
|
-
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
158
|
-
return check_string(self.patterns, event.data.unwrap(), ctx)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
__all__ = (
|
|
1
|
+
import abc
|
|
2
|
+
import inspect
|
|
3
|
+
import typing
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
|
+
from telegrinder.bot.rules.abc import ABCRule, CheckResult
|
|
9
|
+
from telegrinder.bot.rules.payload import (
|
|
10
|
+
PayloadEqRule,
|
|
11
|
+
PayloadJsonEqRule,
|
|
12
|
+
PayloadMarkupRule,
|
|
13
|
+
PayloadModelRule,
|
|
14
|
+
)
|
|
15
|
+
from telegrinder.tools.adapter.event import EventAdapter
|
|
16
|
+
from telegrinder.types.enums import UpdateType
|
|
17
|
+
|
|
18
|
+
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
19
|
+
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
20
|
+
MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
|
|
21
|
+
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
|
|
22
|
+
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
23
|
+
CallbackDataEq: typing.TypeAlias = PayloadEqRule
|
|
24
|
+
CallbackDataJsonEq: typing.TypeAlias = PayloadJsonEqRule
|
|
25
|
+
CallbackDataMarkup: typing.TypeAlias = PayloadMarkupRule
|
|
26
|
+
CallbackDataJsonModel: typing.TypeAlias = PayloadModelRule
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CallbackQueryRule(
|
|
30
|
+
ABCRule[CallbackQuery],
|
|
31
|
+
abc.ABC,
|
|
32
|
+
adapter=EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery),
|
|
33
|
+
):
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class HasData(CallbackQueryRule):
|
|
39
|
+
def check(self, event: CallbackQuery) -> bool:
|
|
40
|
+
return bool(event.data)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CallbackDataMap(CallbackQueryDataRule):
|
|
48
|
+
def __init__(self, mapping: MapDict, /) -> None:
|
|
49
|
+
self.mapping = self.transform_to_callbacks(
|
|
50
|
+
self.transform_to_map(mapping),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
|
|
55
|
+
"""Transforms MapDict to CallbackMap."""
|
|
56
|
+
callback_map = []
|
|
57
|
+
|
|
58
|
+
for k, v in mapping.items():
|
|
59
|
+
if isinstance(v, dict):
|
|
60
|
+
v = cls.transform_to_map(v)
|
|
61
|
+
callback_map.append((k, v))
|
|
62
|
+
|
|
63
|
+
return callback_map
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
|
|
67
|
+
"""Transforms `CallbackMap` to `CallbackMapStrict`."""
|
|
68
|
+
callback_map_result = []
|
|
69
|
+
|
|
70
|
+
for key, value in callback_map:
|
|
71
|
+
if isinstance(value, type):
|
|
72
|
+
validator = (lambda tp: lambda v: isinstance(v, tp))(value)
|
|
73
|
+
elif isinstance(value, list):
|
|
74
|
+
validator = cls.transform_to_callbacks(value)
|
|
75
|
+
elif not callable(value):
|
|
76
|
+
validator = (lambda val: lambda v: val == v)(value)
|
|
77
|
+
else:
|
|
78
|
+
validator = value
|
|
79
|
+
callback_map_result.append((key, validator))
|
|
80
|
+
|
|
81
|
+
return callback_map_result
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
async def run_validator(value: typing.Any, validator: Validator) -> bool:
|
|
85
|
+
"""Run async or sync validator."""
|
|
86
|
+
with suppress(BaseException):
|
|
87
|
+
result = validator(value)
|
|
88
|
+
if inspect.isawaitable(result):
|
|
89
|
+
result = await result
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
|
|
96
|
+
"""Matches callback_data with callback_map recursively."""
|
|
97
|
+
for key, validator in callback_map:
|
|
98
|
+
if key not in callback_data:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
if isinstance(validator, list):
|
|
102
|
+
if not (isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
elif not await cls.run_validator(callback_data[key], validator):
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
111
|
+
callback_data = event.decode_data().unwrap_or_none()
|
|
112
|
+
if callback_data is None:
|
|
113
|
+
return False
|
|
114
|
+
if await self.match(callback_data, self.mapping):
|
|
115
|
+
ctx.update(callback_data)
|
|
116
|
+
return True
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = (
|
|
162
121
|
"CallbackDataEq",
|
|
163
122
|
"CallbackDataJsonEq",
|
|
164
123
|
"CallbackDataJsonModel",
|
|
@@ -166,5 +125,5 @@ __all__ = (
|
|
|
166
125
|
"CallbackDataMarkup",
|
|
167
126
|
"CallbackQueryDataRule",
|
|
168
127
|
"CallbackQueryRule",
|
|
169
|
-
"HasData",
|
|
170
|
-
)
|
|
128
|
+
"HasData",
|
|
129
|
+
)
|
|
@@ -1,46 +1,48 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
|
-
from telegrinder.
|
|
6
|
-
from telegrinder.types.enums import UpdateType
|
|
7
|
-
|
|
8
|
-
from .abc import ABCRule, CheckResult
|
|
9
|
-
|
|
10
|
-
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ChatJoinRequestRule(
|
|
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
|
-
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
|
+
from telegrinder.tools.adapter.event import EventAdapter
|
|
6
|
+
from telegrinder.types.enums import UpdateType
|
|
7
|
+
|
|
8
|
+
from .abc import ABCRule, CheckResult
|
|
9
|
+
|
|
10
|
+
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChatJoinRequestRule(
|
|
14
|
+
ABCRule[ChatJoinRequest],
|
|
15
|
+
abc.ABC,
|
|
16
|
+
adapter=EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest),
|
|
17
|
+
):
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HasInviteLink(ChatJoinRequestRule):
|
|
23
|
+
def check(self, event: ChatJoinRequest) -> bool:
|
|
24
|
+
return bool(event.invite_link)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
28
|
+
def __init__(self, name: str, /) -> None:
|
|
29
|
+
self.name = name
|
|
30
|
+
|
|
31
|
+
def check(self, event: ChatJoinRequest) -> bool:
|
|
32
|
+
return event.invite_link.unwrap().name.unwrap_or_none() == self.name
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
36
|
+
def __init__(self, creator_id: int, /) -> None:
|
|
37
|
+
self.creator_id = creator_id
|
|
38
|
+
|
|
39
|
+
def check(self, event: ChatJoinRequest) -> bool:
|
|
40
|
+
return event.invite_link.unwrap().creator.id == self.creator_id
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = (
|
|
42
44
|
"ChatJoinRequestRule",
|
|
43
45
|
"HasInviteLink",
|
|
44
46
|
"InviteLinkByCreator",
|
|
45
|
-
"InviteLinkName",
|
|
46
|
-
)
|
|
47
|
+
"InviteLinkName",
|
|
48
|
+
)
|
telegrinder/bot/rules/command.py
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from telegrinder.bot.dispatch.context import Context
|
|
5
|
-
from telegrinder.node.command import CommandInfo, single_split
|
|
6
|
-
from telegrinder.node.me import Me
|
|
7
|
-
from telegrinder.node.source import
|
|
8
|
-
from telegrinder.types.enums import ChatType
|
|
9
|
-
|
|
10
|
-
from .abc import ABCRule
|
|
11
|
-
|
|
12
|
-
Validator
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclasses.dataclass(frozen=True, slots=True)
|
|
16
|
-
class Argument:
|
|
17
|
-
name: str
|
|
18
|
-
validators: list[Validator] = dataclasses.field(default_factory=lambda: [])
|
|
19
|
-
optional: bool = dataclasses.field(default=False, kw_only=True)
|
|
20
|
-
|
|
21
|
-
def check(self, data: str) -> typing.Any | None:
|
|
22
|
-
for validator in self.validators:
|
|
23
|
-
data = validator(data) # type: ignore
|
|
24
|
-
if data is None:
|
|
25
|
-
return None
|
|
26
|
-
return data
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class Command(ABCRule):
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
names: str | typing.Iterable[str],
|
|
33
|
-
*arguments: Argument,
|
|
34
|
-
prefixes: tuple[str, ...] = ("/",),
|
|
35
|
-
separator: str = " ",
|
|
36
|
-
lazy: bool = False,
|
|
37
|
-
validate_mention: bool = True,
|
|
38
|
-
mention_needed_in_chat: bool = False,
|
|
39
|
-
) -> None:
|
|
40
|
-
self.names = [names] if isinstance(names, str) else names
|
|
41
|
-
self.arguments = arguments
|
|
42
|
-
self.prefixes = prefixes
|
|
43
|
-
self.separator = separator
|
|
44
|
-
self.lazy = lazy
|
|
45
|
-
self.validate_mention = validate_mention
|
|
46
|
-
|
|
47
|
-
# if true then we'll check for mention when message is from a group
|
|
48
|
-
self.mention_needed_in_chat = mention_needed_in_chat
|
|
49
|
-
|
|
50
|
-
def remove_prefix(self, text: str) -> str | None:
|
|
51
|
-
for prefix in self.prefixes:
|
|
52
|
-
if text.startswith(prefix):
|
|
53
|
-
return text.removeprefix(prefix)
|
|
54
|
-
return None
|
|
55
|
-
|
|
56
|
-
def parse_argument(
|
|
57
|
-
self,
|
|
58
|
-
arguments: list[Argument],
|
|
59
|
-
data_s: str,
|
|
60
|
-
new_s: str,
|
|
61
|
-
s: str,
|
|
62
|
-
) -> dict | None:
|
|
63
|
-
argument = arguments[0]
|
|
64
|
-
data = argument.check(data_s)
|
|
65
|
-
if data is None and not argument.optional:
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
if data is None:
|
|
69
|
-
return self.parse_arguments(arguments[1:], s)
|
|
70
|
-
|
|
71
|
-
with_argument = self.parse_arguments(arguments[1:], new_s)
|
|
72
|
-
if with_argument is not None:
|
|
73
|
-
return {argument.name: data, **with_argument}
|
|
74
|
-
|
|
75
|
-
if not argument.optional:
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
|
-
return self.parse_arguments(arguments[1:], s)
|
|
79
|
-
|
|
80
|
-
def parse_arguments(self, arguments: list[Argument], s: str) -> dict[str, typing.Any] | None:
|
|
81
|
-
if not arguments:
|
|
82
|
-
return {} if not s else None
|
|
83
|
-
|
|
84
|
-
if self.lazy:
|
|
85
|
-
return self.parse_argument(arguments, *single_split(s, self.separator), s)
|
|
86
|
-
|
|
87
|
-
all_split = s.split(self.separator)
|
|
88
|
-
for i in range(1, len(all_split) + 1):
|
|
89
|
-
ctx = self.parse_argument(
|
|
90
|
-
arguments,
|
|
91
|
-
self.separator.join(all_split[:i]),
|
|
92
|
-
self.separator.join(all_split[i:]),
|
|
93
|
-
s,
|
|
94
|
-
)
|
|
95
|
-
if ctx is not None:
|
|
96
|
-
return ctx
|
|
97
|
-
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
def check(self, command: CommandInfo, me: Me,
|
|
101
|
-
name = self.remove_prefix(command.name)
|
|
102
|
-
if name is None:
|
|
103
|
-
return False
|
|
104
|
-
|
|
105
|
-
if name not in self.names:
|
|
106
|
-
return False
|
|
107
|
-
|
|
108
|
-
if not command.mention and self.mention_needed_in_chat and
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
if command.mention and self.validate_mention: # noqa
|
|
112
|
-
if command.mention.unwrap().lower() != me.username.unwrap().lower():
|
|
113
|
-
return False
|
|
114
|
-
|
|
115
|
-
if not self.arguments:
|
|
116
|
-
return not command.arguments
|
|
117
|
-
|
|
118
|
-
result = self.parse_arguments(list(self.arguments), command.arguments)
|
|
119
|
-
if result is None:
|
|
120
|
-
return False
|
|
121
|
-
|
|
122
|
-
ctx.update(result)
|
|
123
|
-
return True
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
__all__ = ("Argument", "Command", "single_split")
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
|
+
from telegrinder.node.command import CommandInfo, single_split
|
|
6
|
+
from telegrinder.node.me import Me
|
|
7
|
+
from telegrinder.node.source import ChatSource
|
|
8
|
+
from telegrinder.types.enums import ChatType
|
|
9
|
+
|
|
10
|
+
from .abc import ABCRule
|
|
11
|
+
|
|
12
|
+
type Validator = typing.Callable[[str], typing.Any | None]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
16
|
+
class Argument:
|
|
17
|
+
name: str
|
|
18
|
+
validators: list[Validator] = dataclasses.field(default_factory=lambda: [])
|
|
19
|
+
optional: bool = dataclasses.field(default=False, kw_only=True)
|
|
20
|
+
|
|
21
|
+
def check(self, data: str) -> typing.Any | None:
|
|
22
|
+
for validator in self.validators:
|
|
23
|
+
data = validator(data) # type: ignore
|
|
24
|
+
if data is None:
|
|
25
|
+
return None
|
|
26
|
+
return data
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Command(ABCRule):
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
names: str | typing.Iterable[str],
|
|
33
|
+
*arguments: Argument,
|
|
34
|
+
prefixes: tuple[str, ...] = ("/",),
|
|
35
|
+
separator: str = " ",
|
|
36
|
+
lazy: bool = False,
|
|
37
|
+
validate_mention: bool = True,
|
|
38
|
+
mention_needed_in_chat: bool = False,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.names = [names] if isinstance(names, str) else names
|
|
41
|
+
self.arguments = arguments
|
|
42
|
+
self.prefixes = prefixes
|
|
43
|
+
self.separator = separator
|
|
44
|
+
self.lazy = lazy
|
|
45
|
+
self.validate_mention = validate_mention
|
|
46
|
+
|
|
47
|
+
# if true then we'll check for mention when message is from a group
|
|
48
|
+
self.mention_needed_in_chat = mention_needed_in_chat
|
|
49
|
+
|
|
50
|
+
def remove_prefix(self, text: str) -> str | None:
|
|
51
|
+
for prefix in self.prefixes:
|
|
52
|
+
if text.startswith(prefix):
|
|
53
|
+
return text.removeprefix(prefix)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def parse_argument(
|
|
57
|
+
self,
|
|
58
|
+
arguments: list[Argument],
|
|
59
|
+
data_s: str,
|
|
60
|
+
new_s: str,
|
|
61
|
+
s: str,
|
|
62
|
+
) -> dict | None:
|
|
63
|
+
argument = arguments[0]
|
|
64
|
+
data = argument.check(data_s)
|
|
65
|
+
if data is None and not argument.optional:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
if data is None:
|
|
69
|
+
return self.parse_arguments(arguments[1:], s)
|
|
70
|
+
|
|
71
|
+
with_argument = self.parse_arguments(arguments[1:], new_s)
|
|
72
|
+
if with_argument is not None:
|
|
73
|
+
return {argument.name: data, **with_argument}
|
|
74
|
+
|
|
75
|
+
if not argument.optional:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
return self.parse_arguments(arguments[1:], s)
|
|
79
|
+
|
|
80
|
+
def parse_arguments(self, arguments: list[Argument], s: str) -> dict[str, typing.Any] | None:
|
|
81
|
+
if not arguments:
|
|
82
|
+
return {} if not s else None
|
|
83
|
+
|
|
84
|
+
if self.lazy:
|
|
85
|
+
return self.parse_argument(arguments, *single_split(s, self.separator), s)
|
|
86
|
+
|
|
87
|
+
all_split = s.split(self.separator)
|
|
88
|
+
for i in range(1, len(all_split) + 1):
|
|
89
|
+
ctx = self.parse_argument(
|
|
90
|
+
arguments,
|
|
91
|
+
self.separator.join(all_split[:i]),
|
|
92
|
+
self.separator.join(all_split[i:]),
|
|
93
|
+
s,
|
|
94
|
+
)
|
|
95
|
+
if ctx is not None:
|
|
96
|
+
return ctx
|
|
97
|
+
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def check(self, command: CommandInfo, me: Me, chat: ChatSource, ctx: Context) -> bool:
|
|
101
|
+
name = self.remove_prefix(command.name)
|
|
102
|
+
if name is None:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
if name not in self.names:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
if not command.mention and self.mention_needed_in_chat and chat.type is not ChatType.PRIVATE:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
if command.mention and self.validate_mention: # noqa
|
|
112
|
+
if command.mention.unwrap().lower() != me.username.unwrap().lower():
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
if not self.arguments:
|
|
116
|
+
return not command.arguments
|
|
117
|
+
|
|
118
|
+
result = self.parse_arguments(list(self.arguments), command.arguments)
|
|
119
|
+
if result is None:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
ctx.update(result)
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = ("Argument", "Command", "single_split")
|