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.

Files changed (192) hide show
  1. telegrinder/__init__.py +148 -149
  2. telegrinder/api/__init__.py +9 -8
  3. telegrinder/api/api.py +101 -93
  4. telegrinder/api/error.py +20 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +72 -66
  8. telegrinder/bot/bot.py +83 -76
  9. telegrinder/bot/cute_types/__init__.py +19 -17
  10. telegrinder/bot/cute_types/base.py +184 -258
  11. telegrinder/bot/cute_types/callback_query.py +400 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +62 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +157 -160
  14. telegrinder/bot/cute_types/inline_query.py +44 -43
  15. telegrinder/bot/cute_types/message.py +2590 -2637
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +112 -104
  18. telegrinder/bot/cute_types/utils.py +62 -95
  19. telegrinder/bot/dispatch/__init__.py +59 -55
  20. telegrinder/bot/dispatch/abc.py +76 -77
  21. telegrinder/bot/dispatch/context.py +96 -98
  22. telegrinder/bot/dispatch/dispatch.py +254 -202
  23. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  24. telegrinder/bot/dispatch/handler/abc.py +23 -24
  25. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  26. telegrinder/bot/dispatch/handler/base.py +57 -57
  27. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  28. telegrinder/bot/dispatch/handler/func.py +129 -135
  29. telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
  30. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  31. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  32. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  33. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  34. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  35. telegrinder/bot/dispatch/middleware/abc.py +97 -22
  36. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  37. telegrinder/bot/dispatch/process.py +151 -157
  38. telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
  39. telegrinder/bot/dispatch/return_manager/abc.py +104 -108
  40. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  41. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  42. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  43. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  44. telegrinder/bot/dispatch/view/__init__.py +15 -13
  45. telegrinder/bot/dispatch/view/abc.py +45 -41
  46. telegrinder/bot/dispatch/view/base.py +231 -200
  47. telegrinder/bot/dispatch/view/box.py +140 -129
  48. telegrinder/bot/dispatch/view/callback_query.py +16 -17
  49. telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
  50. telegrinder/bot/dispatch/view/chat_member.py +37 -39
  51. telegrinder/bot/dispatch/view/inline_query.py +16 -17
  52. telegrinder/bot/dispatch/view/message.py +43 -44
  53. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  54. telegrinder/bot/dispatch/view/raw.py +116 -114
  55. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  56. telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
  57. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  58. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  59. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
  60. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  61. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
  62. telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
  63. telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
  64. telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
  65. telegrinder/bot/polling/__init__.py +4 -4
  66. telegrinder/bot/polling/abc.py +25 -25
  67. telegrinder/bot/polling/polling.py +139 -131
  68. telegrinder/bot/rules/__init__.py +85 -62
  69. telegrinder/bot/rules/abc.py +213 -206
  70. telegrinder/bot/rules/callback_data.py +122 -163
  71. telegrinder/bot/rules/chat_join.py +45 -43
  72. telegrinder/bot/rules/command.py +126 -126
  73. telegrinder/bot/rules/enum_text.py +33 -36
  74. telegrinder/bot/rules/func.py +28 -26
  75. telegrinder/bot/rules/fuzzy.py +24 -24
  76. telegrinder/bot/rules/id.py +24 -0
  77. telegrinder/bot/rules/inline.py +58 -56
  78. telegrinder/bot/rules/integer.py +21 -20
  79. telegrinder/bot/rules/is_from.py +127 -127
  80. telegrinder/bot/rules/logic.py +18 -0
  81. telegrinder/bot/rules/markup.py +42 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +15 -17
  84. telegrinder/bot/rules/message_entities.py +33 -35
  85. telegrinder/bot/rules/node.py +33 -27
  86. telegrinder/bot/rules/payload.py +81 -0
  87. telegrinder/bot/rules/payment_invoice.py +29 -0
  88. telegrinder/bot/rules/regex.py +36 -37
  89. telegrinder/bot/rules/rule_enum.py +72 -72
  90. telegrinder/bot/rules/start.py +42 -42
  91. telegrinder/bot/rules/state.py +35 -37
  92. telegrinder/bot/rules/text.py +38 -33
  93. telegrinder/bot/rules/update.py +15 -15
  94. telegrinder/bot/scenario/__init__.py +5 -5
  95. telegrinder/bot/scenario/abc.py +17 -19
  96. telegrinder/bot/scenario/checkbox.py +174 -176
  97. telegrinder/bot/scenario/choice.py +48 -51
  98. telegrinder/client/__init__.py +12 -4
  99. telegrinder/client/abc.py +100 -75
  100. telegrinder/client/aiohttp.py +134 -130
  101. telegrinder/client/form_data.py +31 -0
  102. telegrinder/client/sonic.py +212 -0
  103. telegrinder/model.py +208 -315
  104. telegrinder/modules.py +239 -237
  105. telegrinder/msgspec_json.py +14 -14
  106. telegrinder/msgspec_utils.py +478 -410
  107. telegrinder/node/__init__.py +86 -25
  108. telegrinder/node/attachment.py +163 -87
  109. telegrinder/node/base.py +288 -160
  110. telegrinder/node/callback_query.py +54 -53
  111. telegrinder/node/command.py +34 -33
  112. telegrinder/node/composer.py +163 -198
  113. telegrinder/node/container.py +33 -27
  114. telegrinder/node/either.py +82 -0
  115. telegrinder/node/event.py +54 -65
  116. telegrinder/node/file.py +51 -0
  117. telegrinder/node/me.py +15 -16
  118. telegrinder/node/payload.py +78 -0
  119. telegrinder/node/polymorphic.py +67 -48
  120. telegrinder/node/rule.py +72 -76
  121. telegrinder/node/scope.py +36 -38
  122. telegrinder/node/source.py +87 -71
  123. telegrinder/node/text.py +53 -41
  124. telegrinder/node/tools/__init__.py +3 -3
  125. telegrinder/node/tools/generator.py +36 -40
  126. telegrinder/py.typed +0 -0
  127. telegrinder/rules.py +1 -62
  128. telegrinder/tools/__init__.py +152 -93
  129. telegrinder/tools/adapter/__init__.py +19 -0
  130. telegrinder/tools/adapter/abc.py +49 -0
  131. telegrinder/tools/adapter/dataclass.py +56 -0
  132. telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
  133. telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
  134. telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
  135. telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
  136. telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
  137. telegrinder/tools/buttons.py +106 -80
  138. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  139. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  140. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  141. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  142. telegrinder/tools/error_handler/__init__.py +7 -7
  143. telegrinder/tools/error_handler/abc.py +30 -33
  144. telegrinder/tools/error_handler/error.py +9 -9
  145. telegrinder/tools/error_handler/error_handler.py +179 -193
  146. telegrinder/tools/formatting/__init__.py +83 -63
  147. telegrinder/tools/formatting/deep_links.py +541 -0
  148. telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
  149. telegrinder/tools/formatting/spec_html_formats.py +71 -117
  150. telegrinder/tools/functional.py +8 -12
  151. telegrinder/tools/global_context/__init__.py +7 -7
  152. telegrinder/tools/global_context/abc.py +63 -63
  153. telegrinder/tools/global_context/global_context.py +387 -412
  154. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  155. telegrinder/tools/i18n/__init__.py +7 -7
  156. telegrinder/tools/i18n/abc.py +30 -30
  157. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  158. telegrinder/tools/i18n/middleware/abc.py +22 -25
  159. telegrinder/tools/i18n/simple.py +43 -43
  160. telegrinder/tools/input_file_directory.py +30 -0
  161. telegrinder/tools/keyboard.py +128 -128
  162. telegrinder/tools/lifespan.py +105 -0
  163. telegrinder/tools/limited_dict.py +32 -37
  164. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  165. telegrinder/tools/loop_wrapper/abc.py +20 -15
  166. telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
  167. telegrinder/tools/magic.py +307 -157
  168. telegrinder/tools/parse_mode.py +6 -6
  169. telegrinder/tools/state_storage/__init__.py +4 -4
  170. telegrinder/tools/state_storage/abc.py +31 -35
  171. telegrinder/tools/state_storage/memory.py +25 -25
  172. telegrinder/tools/strings.py +13 -0
  173. telegrinder/types/__init__.py +268 -260
  174. telegrinder/types/enums.py +711 -701
  175. telegrinder/types/input_file.py +51 -0
  176. telegrinder/types/methods.py +5055 -4633
  177. telegrinder/types/objects.py +7058 -6950
  178. telegrinder/verification_utils.py +30 -32
  179. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
  180. telegrinder-0.4.0.dist-info/METADATA +144 -0
  181. telegrinder-0.4.0.dist-info/RECORD +182 -0
  182. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  183. telegrinder/bot/rules/adapter/__init__.py +0 -17
  184. telegrinder/bot/rules/adapter/abc.py +0 -31
  185. telegrinder/node/message.py +0 -14
  186. telegrinder/node/update.py +0 -15
  187. telegrinder/tools/formatting/links.py +0 -38
  188. telegrinder/tools/kb_set/__init__.py +0 -4
  189. telegrinder/tools/kb_set/base.py +0 -15
  190. telegrinder/tools/kb_set/yaml.py +0 -63
  191. telegrinder-0.3.4.dist-info/METADATA +0 -110
  192. 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 msgspec
7
-
8
- from telegrinder.bot.cute_types import CallbackQueryCute
9
- from telegrinder.bot.dispatch.context import Context
10
- from telegrinder.bot.rules.adapter import EventAdapter
11
- from telegrinder.model import decoder
12
- from telegrinder.tools.buttons import DataclassInstance
13
- from telegrinder.types.enums import UpdateType
14
-
15
- from .abc import ABCRule, CheckResult
16
- from .markup import Markup, PatternLike, check_string
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
- class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
- adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
-
28
- @abc.abstractmethod
29
- def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
30
-
31
-
32
- class HasData(CallbackQueryRule):
33
- def check(self, event: CallbackQuery) -> bool:
34
- return bool(event.data.unwrap_or_none())
35
-
36
-
37
- class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
38
- pass
39
-
40
-
41
- class CallbackDataMap(CallbackQueryDataRule):
42
- def __init__(self, mapping: MapDict, /) -> None:
43
- self.mapping = self.transform_to_callbacks(
44
- self.transform_to_map(mapping),
45
- )
46
-
47
- @classmethod
48
- def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
49
- """Transforms MapDict to CallbackMap."""
50
-
51
- callback_map = []
52
-
53
- for k, v in mapping.items():
54
- if isinstance(v, dict):
55
- v = cls.transform_to_map(v)
56
- callback_map.append((k, v))
57
-
58
- return callback_map
59
-
60
- @classmethod
61
- def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
62
- """Transforms `CallbackMap` to `CallbackMapStrict`."""
63
-
64
- callback_map_result = []
65
-
66
- for key, value in callback_map:
67
- if isinstance(value, type):
68
- validator = (lambda tp: lambda v: isinstance(v, tp))(value)
69
- elif isinstance(value, list):
70
- validator = cls.transform_to_callbacks(value)
71
- elif not callable(value):
72
- validator = (lambda val: lambda v: val == v)(value)
73
- else:
74
- validator = value
75
- callback_map_result.append((key, validator))
76
-
77
- return callback_map_result
78
-
79
- @staticmethod
80
- async def run_validator(value: typing.Any, validator: Validator) -> bool:
81
- """Run async or sync validator."""
82
-
83
- with suppress(BaseException):
84
- result = validator(value)
85
- if inspect.isawaitable(result):
86
- result = await result
87
- return result
88
-
89
- return False
90
-
91
- @classmethod
92
- async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
93
- """Matches callback_data with callback_map recursively."""
94
-
95
- for key, validator in callback_map:
96
- if key not in callback_data:
97
- return False
98
-
99
- if isinstance(validator, list):
100
- if not (
101
- isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
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.decode_callback_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
- class CallbackDataEq(CallbackQueryDataRule):
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.bot.rules.adapter 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(ABCRule[ChatJoinRequest], requires=[]):
14
- adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
15
-
16
- @abc.abstractmethod
17
- def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
18
-
19
-
20
- class HasInviteLink(ChatJoinRequestRule):
21
- def check(self, event: ChatJoinRequest) -> bool:
22
- return bool(event.invite_link)
23
-
24
-
25
- class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
26
- def __init__(self, name: str, /) -> None:
27
- self.name = name
28
-
29
- def check(self, event: ChatJoinRequest) -> bool:
30
- return event.invite_link.unwrap().name.unwrap_or_none() == self.name
31
-
32
-
33
- class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
34
- def __init__(self, creator_id: int, /) -> None:
35
- self.creator_id = creator_id
36
-
37
- def check(self, event: ChatJoinRequest) -> bool:
38
- return event.invite_link.unwrap().creator.id == self.creator_id
39
-
40
-
41
- __all__ = (
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
+ )
@@ -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 Source
8
- from telegrinder.types.enums import ChatType
9
-
10
- from .abc import ABCRule
11
-
12
- Validator: typing.TypeAlias = 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, src: Source, 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 src.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")
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")