telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +98 -67
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +68 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
@@ -1,52 +1,21 @@
1
- import inspect
2
1
  from abc import ABC, abstractmethod
2
+ from collections import deque
3
3
  from functools import cached_property
4
4
 
5
5
  import typing_extensions as typing
6
6
 
7
- from telegrinder.bot.cute_types import MessageCute, UpdateCute
7
+ from telegrinder.api.api import API
8
8
  from telegrinder.bot.dispatch.context import Context
9
9
  from telegrinder.bot.dispatch.process import check_rule
10
- from telegrinder.node.base import NodeType, get_nodes, is_node
11
- from telegrinder.tools.adapter import ABCAdapter
12
- from telegrinder.tools.adapter.node import Event
13
- from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
14
- from telegrinder.tools.i18n.abc import ABCTranslator
15
- from telegrinder.tools.magic import (
16
- cache_translation,
17
- get_annotations,
18
- get_cached_translation,
19
- get_default_args,
20
- )
21
- from telegrinder.types.objects import Update as UpdateObject
22
-
23
- if typing.TYPE_CHECKING:
24
- from telegrinder.node.composer import NodeCollection
25
-
26
- AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
10
+ from telegrinder.node.base import IsNode, get_nodes
11
+ from telegrinder.tools.fullname import fullname
12
+ from telegrinder.types.objects import Update
27
13
 
28
14
  type CheckResult = bool | typing.Awaitable[bool]
29
15
 
30
- Message: typing.TypeAlias = MessageCute
31
- Update: typing.TypeAlias = UpdateCute
32
-
33
-
34
- def with_caching_translations(func: typing.Callable[..., typing.Any]):
35
- """Should be used as decorator for .translate method. Caches rule translations."""
36
16
 
37
- async def wrapper(self: "ABCRule", translator: ABCTranslator):
38
- if translation := get_cached_translation(self, translator.locale):
39
- return translation
40
- translation = await func(self, translator)
41
- cache_translation(self, translator.locale, translation)
42
- return translation
43
-
44
- return wrapper
45
-
46
-
47
- class ABCRule(ABC, typing.Generic[AdaptTo]):
48
- adapter: ABCAdapter[UpdateObject, AdaptTo]
49
- requires: list["ABCRule"] = []
17
+ class ABCRule(ABC):
18
+ requires: deque["ABCRule"] = deque()
50
19
 
51
20
  if typing.TYPE_CHECKING:
52
21
 
@@ -54,7 +23,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
54
23
  def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult:
55
24
  pass
56
25
  else:
57
- adapter = RawUpdateAdapter()
58
26
 
59
27
  @abstractmethod
60
28
  def check(self, *args, **kwargs):
@@ -63,59 +31,41 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
63
31
  def __init_subclass__(
64
32
  cls,
65
33
  *,
66
- requires: list["ABCRule"] | None = None,
67
- adapter: ABCAdapter[UpdateObject, AdaptTo] | None = None,
34
+ requires: typing.Iterable["ABCRule"] | None = None,
68
35
  ) -> None:
69
36
  """Merges requirements from inherited classes and rule-specific requirements."""
70
- if adapter is not None:
71
- cls.adapter = adapter
72
-
73
- requirements = []
74
- for base in inspect.getmro(cls):
37
+ requirements = list[ABCRule]()
38
+ for base in cls.__mro__:
75
39
  if issubclass(base, ABCRule) and base != cls:
76
- requirements.extend(base.requires or ()) # type: ignore
40
+ requirements.extend(base.requires or ())
77
41
 
78
42
  requirements.extend(requires or ())
79
- cls.requires = list(dict.fromkeys(requirements))
80
-
81
- def __and__(self, other: "ABCRule") -> "AndRule":
82
- """And Rule.
43
+ cls.requires = deque(dict.fromkeys(requirements))
83
44
 
84
- ```python
85
- rule = HasText() & HasCaption()
86
- rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
87
- ```
88
- """
45
+ def __and__(self, other: object, /) -> "AndRule":
46
+ if not isinstance(other, ABCRule):
47
+ return NotImplemented
89
48
  return AndRule(self, other)
90
49
 
91
- def __or__(self, other: "ABCRule") -> "OrRule":
92
- """Or Rule.
50
+ def __iadd__(self, other: object, /) -> "AndRule":
51
+ return self.__and__(other)
93
52
 
94
- ```python
95
- rule = HasText() | HasCaption()
96
- rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
97
- ```
98
- """
53
+ def __or__(self, other: object, /) -> "OrRule":
54
+ if not isinstance(other, ABCRule):
55
+ return NotImplemented
99
56
  return OrRule(self, other)
100
57
 
101
- def __invert__(self) -> "NotRule":
102
- """Not Rule.
58
+ def __ior__(self, other: object, /) -> "OrRule":
59
+ return self.__or__(other)
103
60
 
104
- ```python
105
- rule = ~HasText()
106
- rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
107
- ```
108
- """
61
+ def __invert__(self) -> "NotRule":
109
62
  return NotRule(self)
110
63
 
111
64
  def __repr__(self) -> str:
112
- return "<{}: adapter={!r}>".format(
113
- self.__class__.__name__,
114
- self.adapter,
115
- )
65
+ return "<{}, requires={!r}>".format(fullname(self), self.requires)
116
66
 
117
67
  @cached_property
118
- def required_nodes(self) -> dict[str, type[NodeType]]:
68
+ def required_nodes(self) -> dict[str, IsNode]:
119
69
  return get_nodes(self.check)
120
70
 
121
71
  def as_optional(self) -> "ABCRule":
@@ -124,54 +74,18 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
124
74
  def should_fail(self) -> "ABCRule":
125
75
  return self & Never()
126
76
 
127
- async def bounding_check(
128
- self,
129
- ctx: Context,
130
- *,
131
- adapted_value: AdaptTo,
132
- node_col: "NodeCollection | None" = None,
133
- ) -> bool:
134
- bound_check_rule = self.check
135
- kw = {}
136
- node_col_values = node_col.values if node_col is not None else {}
137
- temp_ctx = get_default_args(bound_check_rule) | ctx
138
-
139
- for i, (k, v) in enumerate(get_annotations(bound_check_rule).items()):
140
- if (isinstance(adapted_value, Event) and i == 0) or ( # First arg is Event
141
- isinstance(v, type) and isinstance(adapted_value, v)
142
- ):
143
- kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
144
- elif is_node(v):
145
- assert k in node_col_values, "Node is undefined, error while bounding."
146
- kw[k] = node_col_values[k]
147
- elif k in temp_ctx:
148
- kw[k] = temp_ctx[k]
149
- elif v is Context:
150
- kw[k] = ctx
151
- else:
152
- raise LookupError(
153
- f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__qualname__}.check()', "
154
- "because it cannot be resolved."
155
- )
156
-
157
- result = bound_check_rule(**kw) # type: ignore
158
- if inspect.isawaitable(result):
159
- result = await result
160
- return result
161
-
162
- async def translate(self, translator: ABCTranslator) -> typing.Self:
163
- return self
164
-
165
77
 
166
78
  class AndRule(ABCRule):
167
79
  def __init__(self, *rules: ABCRule) -> None:
168
80
  self.rules = rules
169
81
 
170
- async def check(self, event: Update, ctx: Context) -> bool:
82
+ async def check(self, event: Update, api: API, ctx: Context) -> bool:
171
83
  ctx_copy = ctx.copy()
84
+
172
85
  for rule in self.rules:
173
- if not await check_rule(event.ctx_api, rule, event, ctx_copy):
86
+ if not await check_rule(api, rule, event, ctx_copy):
174
87
  return False
88
+
175
89
  ctx |= ctx_copy
176
90
  return True
177
91
 
@@ -180,12 +94,14 @@ class OrRule(ABCRule):
180
94
  def __init__(self, *rules: ABCRule) -> None:
181
95
  self.rules = rules
182
96
 
183
- async def check(self, event: Update, ctx: Context) -> bool:
97
+ async def check(self, event: Update, api: API, ctx: Context) -> bool:
184
98
  for rule in self.rules:
185
99
  ctx_copy = ctx.copy()
186
- if await check_rule(event.ctx_api, rule, event, ctx_copy):
100
+
101
+ if await check_rule(api, rule, event, ctx_copy):
187
102
  ctx |= ctx_copy
188
103
  return True
104
+
189
105
  return False
190
106
 
191
107
 
@@ -193,18 +109,22 @@ class NotRule(ABCRule):
193
109
  def __init__(self, rule: ABCRule) -> None:
194
110
  self.rule = rule
195
111
 
196
- async def check(self, event: Update, ctx: Context) -> bool:
112
+ async def check(self, event: Update, api: API, ctx: Context) -> bool:
197
113
  ctx_copy = ctx.copy()
198
- return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
114
+ return not await check_rule(api, self.rule, event, ctx_copy)
199
115
 
200
116
 
201
117
  class Never(ABCRule):
202
- async def check(self) -> typing.Literal[False]:
118
+ """Neutral element for `|` (OrRule)."""
119
+
120
+ def check(self) -> typing.Literal[False]:
203
121
  return False
204
122
 
205
123
 
206
124
  class Always(ABCRule):
207
- async def check(self) -> typing.Literal[True]:
125
+ """Neutral element for `&` (AndRule)."""
126
+
127
+ def check(self) -> typing.Literal[True]:
208
128
  return True
209
129
 
210
130
 
@@ -216,5 +136,5 @@ __all__ = (
216
136
  "Never",
217
137
  "NotRule",
218
138
  "OrRule",
219
- "with_caching_translations",
139
+ "check_rule",
220
140
  )
@@ -1,76 +1,56 @@
1
1
  import abc
2
- import inspect
3
2
  import typing
4
3
  from contextlib import suppress
5
4
 
6
5
  from telegrinder.bot.cute_types import CallbackQueryCute
7
6
  from telegrinder.bot.dispatch.context import Context
8
- from telegrinder.bot.rules.abc import ABCRule, CheckResult
7
+ from telegrinder.bot.rules.abc import ABCRule
9
8
  from telegrinder.bot.rules.payload import (
10
9
  PayloadEqRule,
11
10
  PayloadJsonEqRule,
12
11
  PayloadMarkupRule,
13
12
  PayloadModelRule,
14
13
  )
15
- from telegrinder.tools.adapter.event import EventAdapter
16
- from telegrinder.types.enums import UpdateType
14
+ from telegrinder.tools.aio import maybe_awaitable
15
+
16
+ type Validator = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
17
+ type CallbackMap = dict[str, typing.Any | type[typing.Any] | Validator | CallbackMap]
18
+ type CallbackMapStrict = dict[str, Validator | CallbackMapStrict]
17
19
 
18
20
  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
21
  CallbackDataEq: typing.TypeAlias = PayloadEqRule
24
22
  CallbackDataJsonEq: typing.TypeAlias = PayloadJsonEqRule
25
23
  CallbackDataMarkup: typing.TypeAlias = PayloadMarkupRule
26
24
  CallbackDataJsonModel: typing.TypeAlias = PayloadModelRule
27
25
 
28
26
 
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):
27
+ class HasData(ABCRule):
39
28
  def check(self, event: CallbackQuery) -> bool:
40
29
  return bool(event.data)
41
30
 
42
31
 
43
- class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
32
+ class CallbackQueryDataRule(ABCRule, abc.ABC, requires=[HasData()]):
44
33
  pass
45
34
 
46
35
 
47
36
  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
37
+ def __init__(self, mapping: CallbackMap, /, *, allow_extra_fields: bool = False) -> None:
38
+ """Callback data map validation.
39
+ :param mapping: A callback data mapping with validators.
40
+ :param allow_extra_fields: Allows extra fields in a callback query data.
41
+ """
42
+ self.mapping = self.transform_to_callbacks(mapping)
43
+ self.allow_extra_fields = allow_extra_fields
64
44
 
65
45
  @classmethod
66
46
  def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
67
47
  """Transforms `CallbackMap` to `CallbackMapStrict`."""
68
48
  callback_map_result = []
69
49
 
70
- for key, value in callback_map:
50
+ for key, value in callback_map.items():
71
51
  if isinstance(value, type):
72
52
  validator = (lambda tp: lambda v: isinstance(v, tp))(value)
73
- elif isinstance(value, list):
53
+ elif isinstance(value, dict):
74
54
  validator = cls.transform_to_callbacks(value)
75
55
  elif not callable(value):
76
56
  validator = (lambda val: lambda v: val == v)(value)
@@ -78,27 +58,23 @@ class CallbackDataMap(CallbackQueryDataRule):
78
58
  validator = value
79
59
  callback_map_result.append((key, validator))
80
60
 
81
- return callback_map_result
61
+ return dict(callback_map_result)
82
62
 
83
63
  @staticmethod
84
64
  async def run_validator(value: typing.Any, validator: Validator) -> bool:
85
- """Run async or sync validator."""
65
+ """Runs sync/async validator."""
86
66
  with suppress(BaseException):
87
- result = validator(value)
88
- if inspect.isawaitable(result):
89
- result = await result
90
- return result
91
-
67
+ return await maybe_awaitable(validator(value))
92
68
  return False
93
69
 
94
70
  @classmethod
95
71
  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:
72
+ """Matches `callback_data` with `callback_map` recursively."""
73
+ for key, validator in callback_map.items():
98
74
  if key not in callback_data:
99
75
  return False
100
76
 
101
- if isinstance(validator, list):
77
+ if isinstance(validator, dict):
102
78
  if not (isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)):
103
79
  return False
104
80
 
@@ -111,9 +87,14 @@ class CallbackDataMap(CallbackQueryDataRule):
111
87
  callback_data = event.decode_data().unwrap_or_none()
112
88
  if callback_data is None:
113
89
  return False
90
+
91
+ if not self.allow_extra_fields and self.mapping.keys() != callback_data.keys():
92
+ return False
93
+
114
94
  if await self.match(callback_data, self.mapping):
115
- ctx.update(callback_data)
95
+ ctx.update({k: callback_data[k] for k in self.mapping})
116
96
  return True
97
+
117
98
  return False
118
99
 
119
100
 
@@ -124,6 +105,5 @@ __all__ = (
124
105
  "CallbackDataMap",
125
106
  "CallbackDataMarkup",
126
107
  "CallbackQueryDataRule",
127
- "CallbackQueryRule",
128
108
  "HasData",
129
109
  )
@@ -1,30 +1,17 @@
1
- import abc
2
1
  import typing
3
2
 
4
3
  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
4
+ from telegrinder.bot.rules.abc import ABCRule
9
5
 
10
6
  ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
11
7
 
12
8
 
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):
9
+ class HasInviteLink(ABCRule):
23
10
  def check(self, event: ChatJoinRequest) -> bool:
24
11
  return bool(event.invite_link)
25
12
 
26
13
 
27
- class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
14
+ class InviteLinkName(ABCRule, requires=[HasInviteLink()]):
28
15
  def __init__(self, name: str, /) -> None:
29
16
  self.name = name
30
17
 
@@ -32,7 +19,7 @@ class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
32
19
  return event.invite_link.unwrap().name.unwrap_or_none() == self.name
33
20
 
34
21
 
35
- class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
22
+ class InviteLinkByCreator(ABCRule, requires=[HasInviteLink()]):
36
23
  def __init__(self, creator_id: int, /) -> None:
37
24
  self.creator_id = creator_id
38
25
 
@@ -40,9 +27,4 @@ class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
40
27
  return event.invite_link.unwrap().creator.id == self.creator_id
41
28
 
42
29
 
43
- __all__ = (
44
- "ChatJoinRequestRule",
45
- "HasInviteLink",
46
- "InviteLinkByCreator",
47
- "InviteLinkName",
48
- )
30
+ __all__ = ("HasInviteLink", "InviteLinkByCreator", "InviteLinkName")
@@ -2,13 +2,12 @@ import dataclasses
2
2
  import typing
3
3
 
4
4
  from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.abc import ABCRule
5
6
  from telegrinder.node.command import CommandInfo, single_split
6
7
  from telegrinder.node.me import Me
7
8
  from telegrinder.node.source import ChatSource
8
9
  from telegrinder.types.enums import ChatType
9
10
 
10
- from .abc import ABCRule
11
-
12
11
  type Validator = typing.Callable[[str], typing.Any | None]
13
12
 
14
13
 
@@ -36,13 +35,16 @@ class Command(ABCRule):
36
35
  lazy: bool = False,
37
36
  validate_mention: bool = True,
38
37
  mention_needed_in_chat: bool = False,
38
+ ignore_case: bool = False,
39
39
  ) -> None:
40
- self.names = [names] if isinstance(names, str) else names
40
+ names = [names] if isinstance(names, str) else names
41
+ self.names = [n.lower() for n in names] if ignore_case else names
41
42
  self.arguments = arguments
42
43
  self.prefixes = prefixes
43
44
  self.separator = separator
44
45
  self.lazy = lazy
45
46
  self.validate_mention = validate_mention
47
+ self.ignore_case = ignore_case
46
48
 
47
49
  # if true then we'll check for mention when message is from a group
48
50
  self.mention_needed_in_chat = mention_needed_in_chat
@@ -102,7 +104,9 @@ class Command(ABCRule):
102
104
  if name is None:
103
105
  return False
104
106
 
105
- if name not in self.names:
107
+ target_name = name.lower() if self.ignore_case else name
108
+
109
+ if target_name not in self.names:
106
110
  return False
107
111
 
108
112
  if not command.mention and self.mention_needed_in_chat and chat.type is not ChatType.PRIVATE:
@@ -1,15 +1,14 @@
1
1
  import enum
2
2
 
3
3
  from telegrinder.bot.dispatch.context import Context
4
+ from telegrinder.bot.rules.abc import ABCRule
4
5
  from telegrinder.node.text import Text
5
6
 
6
- from .abc import ABCRule
7
-
8
7
 
9
8
  class EnumTextRule[T: enum.Enum](ABCRule):
10
9
  def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
11
10
  self.enum_t = enum_t
12
- self.texts = list(
11
+ self.texts = set[str](
13
12
  map(
14
13
  lambda x: x.value.lower() if lower_case else x.value,
15
14
  self.enum_t,
@@ -23,7 +22,7 @@ class EnumTextRule[T: enum.Enum](ABCRule):
23
22
  raise KeyError("Enumeration is undefined.")
24
23
 
25
24
  def check(self, text: Text, ctx: Context) -> bool:
26
- text = text.lower() # type: ignore
25
+ text = text.lower()
27
26
  if text not in self.texts:
28
27
  return False
29
28
  ctx.enum_text = self.find(text)
@@ -1,28 +1,21 @@
1
- import inspect
2
1
  import typing
3
2
 
4
3
  from telegrinder.bot.dispatch.context import Context
5
- from telegrinder.tools.adapter.abc import ABCAdapter
6
- from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
4
+ from telegrinder.bot.rules.abc import ABCRule
5
+ from telegrinder.tools.aio import maybe_awaitable
7
6
  from telegrinder.types.objects import Update
8
7
 
9
- from .abc import ABCRule, AdaptTo
10
8
 
11
-
12
- class FuncRule(ABCRule, typing.Generic[AdaptTo]):
9
+ class FuncRule(ABCRule):
13
10
  def __init__(
14
11
  self,
15
- func: typing.Callable[[AdaptTo, Context], typing.Awaitable[bool] | bool],
16
- adapter: ABCAdapter[Update, AdaptTo] | None = None,
12
+ func: typing.Callable[[Update, Context], typing.Awaitable[bool] | bool],
13
+ /,
17
14
  ) -> None:
18
15
  self.func = func
19
- self.adapter = adapter or RawUpdateAdapter() # type: ignore
20
16
 
21
- async def check(self, event: AdaptTo, ctx: Context) -> bool:
22
- result = self.func(event, ctx)
23
- if inspect.isawaitable(result):
24
- result = await result
25
- return result
17
+ async def check(self, update: Update, ctx: Context) -> bool:
18
+ return await maybe_awaitable(self.func(update, ctx))
26
19
 
27
20
 
28
21
  __all__ = ("FuncRule",)
@@ -1,16 +1,15 @@
1
1
  import difflib
2
2
 
3
3
  from telegrinder.bot.dispatch.context import Context
4
+ from telegrinder.bot.rules.abc import ABCRule
4
5
  from telegrinder.node.text import Text
5
6
 
6
- from .abc import ABCRule
7
-
8
7
 
9
8
  class FuzzyText(ABCRule):
10
- def __init__(self, texts: str | list[str], /, min_ratio: float = 0.7) -> None:
9
+ def __init__(self, texts: str | list[str], /, *, min_ratio: float = 0.7) -> None:
11
10
  if isinstance(texts, str):
12
11
  texts = [texts]
13
- self.texts = texts
12
+ self.texts = set(texts)
14
13
  self.min_ratio = min_ratio
15
14
 
16
15
  def check(self, message_text: Text, ctx: Context) -> bool:
@@ -1,32 +1,21 @@
1
- import abc
2
1
  import typing
3
2
 
4
3
  from telegrinder.bot.cute_types import InlineQueryCute
5
4
  from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.abc import ABCRule, CheckResult
7
- from telegrinder.tools.adapter.event import EventAdapter
8
- from telegrinder.types.enums import ChatType, UpdateType
5
+ from telegrinder.bot.rules.abc import ABCRule
6
+ from telegrinder.types.enums import ChatType
9
7
 
10
8
  from .markup import Markup, PatternLike, check_string
11
9
 
12
10
  InlineQuery: typing.TypeAlias = InlineQueryCute
13
11
 
14
12
 
15
- class InlineQueryRule(
16
- ABCRule[InlineQuery],
17
- abc.ABC,
18
- adapter=EventAdapter(UpdateType.INLINE_QUERY, InlineQuery),
19
- ):
20
- @abc.abstractmethod
21
- def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
22
-
23
-
24
- class HasLocation(InlineQueryRule):
13
+ class HasLocation(ABCRule):
25
14
  def check(self, query: InlineQuery) -> bool:
26
15
  return bool(query.location)
27
16
 
28
17
 
29
- class InlineQueryChatType(InlineQueryRule):
18
+ class InlineQueryChatType(ABCRule):
30
19
  def __init__(self, chat_type: ChatType, /) -> None:
31
20
  self.chat_type = chat_type
32
21
 
@@ -34,18 +23,18 @@ class InlineQueryChatType(InlineQueryRule):
34
23
  return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
35
24
 
36
25
 
37
- class InlineQueryText(InlineQueryRule):
26
+ class InlineQueryText(ABCRule):
38
27
  def __init__(self, texts: str | list[str], *, lower_case: bool = False) -> None:
39
- self.texts = [
28
+ self.texts = {
40
29
  text.lower() if lower_case else text for text in ([texts] if isinstance(texts, str) else texts)
41
- ]
30
+ }
42
31
  self.lower_case = lower_case
43
32
 
44
33
  def check(self, query: InlineQuery) -> bool:
45
34
  return (query.query.lower() if self.lower_case else query.query) in self.texts
46
35
 
47
36
 
48
- class InlineQueryMarkup(InlineQueryRule):
37
+ class InlineQueryMarkup(ABCRule):
49
38
  def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
50
39
  self.patterns = Markup(patterns).patterns
51
40
 
@@ -57,6 +46,5 @@ __all__ = (
57
46
  "HasLocation",
58
47
  "InlineQueryChatType",
59
48
  "InlineQueryMarkup",
60
- "InlineQueryRule",
61
49
  "InlineQueryText",
62
50
  )
@@ -1,9 +1,8 @@
1
+ from telegrinder.bot.rules.abc import ABCRule
2
+ from telegrinder.bot.rules.node import NodeRule
1
3
  from telegrinder.node.base import as_node
2
4
  from telegrinder.node.text import TextInteger
3
5
 
4
- from .abc import ABCRule
5
- from .node import NodeRule
6
-
7
6
 
8
7
  class IsInteger(NodeRule):
9
8
  def __init__(self) -> None: