telegrinder 0.3.3.post1__py3-none-any.whl → 0.3.4.post1__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 (165) hide show
  1. telegrinder/__init__.py +144 -144
  2. telegrinder/api/__init__.py +8 -8
  3. telegrinder/api/api.py +93 -93
  4. telegrinder/api/error.py +16 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +66 -66
  8. telegrinder/bot/bot.py +76 -76
  9. telegrinder/bot/cute_types/__init__.py +17 -17
  10. telegrinder/bot/cute_types/base.py +258 -258
  11. telegrinder/bot/cute_types/callback_query.py +385 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +61 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +160 -160
  14. telegrinder/bot/cute_types/inline_query.py +43 -43
  15. telegrinder/bot/cute_types/message.py +2637 -2637
  16. telegrinder/bot/cute_types/update.py +104 -109
  17. telegrinder/bot/cute_types/utils.py +95 -95
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +98 -98
  21. telegrinder/bot/dispatch/dispatch.py +202 -202
  22. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  23. telegrinder/bot/dispatch/handler/abc.py +24 -24
  24. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  25. telegrinder/bot/dispatch/handler/base.py +57 -57
  26. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  27. telegrinder/bot/dispatch/handler/func.py +135 -135
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
  29. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  30. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  32. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  33. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  34. telegrinder/bot/dispatch/middleware/abc.py +22 -16
  35. telegrinder/bot/dispatch/process.py +157 -132
  36. telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
  37. telegrinder/bot/dispatch/return_manager/abc.py +108 -108
  38. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  39. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  40. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  41. telegrinder/bot/dispatch/view/__init__.py +13 -13
  42. telegrinder/bot/dispatch/view/abc.py +41 -41
  43. telegrinder/bot/dispatch/view/base.py +200 -200
  44. telegrinder/bot/dispatch/view/box.py +129 -129
  45. telegrinder/bot/dispatch/view/callback_query.py +17 -17
  46. telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
  47. telegrinder/bot/dispatch/view/chat_member.py +39 -39
  48. telegrinder/bot/dispatch/view/inline_query.py +17 -17
  49. telegrinder/bot/dispatch/view/message.py +44 -44
  50. telegrinder/bot/dispatch/view/raw.py +114 -114
  51. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  52. telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
  53. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  54. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +172 -167
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
  61. telegrinder/bot/polling/__init__.py +4 -4
  62. telegrinder/bot/polling/abc.py +25 -25
  63. telegrinder/bot/polling/polling.py +131 -131
  64. telegrinder/bot/rules/__init__.py +62 -62
  65. telegrinder/bot/rules/abc.py +213 -213
  66. telegrinder/bot/rules/adapter/__init__.py +12 -9
  67. telegrinder/bot/rules/adapter/abc.py +31 -29
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +65 -67
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_event.py +27 -0
  72. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  73. telegrinder/bot/rules/callback_data.py +170 -170
  74. telegrinder/bot/rules/chat_join.py +46 -46
  75. telegrinder/bot/rules/command.py +126 -126
  76. telegrinder/bot/rules/enum_text.py +36 -36
  77. telegrinder/bot/rules/func.py +26 -26
  78. telegrinder/bot/rules/fuzzy.py +24 -24
  79. telegrinder/bot/rules/inline.py +60 -60
  80. telegrinder/bot/rules/integer.py +20 -20
  81. telegrinder/bot/rules/is_from.py +127 -127
  82. telegrinder/bot/rules/markup.py +43 -43
  83. telegrinder/bot/rules/mention.py +14 -14
  84. telegrinder/bot/rules/message.py +17 -17
  85. telegrinder/bot/rules/message_entities.py +35 -35
  86. telegrinder/bot/rules/node.py +27 -27
  87. telegrinder/bot/rules/regex.py +37 -37
  88. telegrinder/bot/rules/rule_enum.py +72 -72
  89. telegrinder/bot/rules/start.py +42 -42
  90. telegrinder/bot/rules/state.py +37 -37
  91. telegrinder/bot/rules/text.py +33 -33
  92. telegrinder/bot/rules/update.py +15 -15
  93. telegrinder/bot/scenario/__init__.py +5 -5
  94. telegrinder/bot/scenario/abc.py +19 -19
  95. telegrinder/bot/scenario/checkbox.py +176 -167
  96. telegrinder/bot/scenario/choice.py +51 -46
  97. telegrinder/client/__init__.py +4 -4
  98. telegrinder/client/abc.py +75 -75
  99. telegrinder/client/aiohttp.py +130 -130
  100. telegrinder/model.py +320 -295
  101. telegrinder/modules.py +237 -237
  102. telegrinder/msgspec_json.py +14 -14
  103. telegrinder/msgspec_utils.py +410 -410
  104. telegrinder/node/__init__.py +0 -0
  105. telegrinder/node/attachment.py +87 -87
  106. telegrinder/node/base.py +166 -166
  107. telegrinder/node/callback_query.py +53 -53
  108. telegrinder/node/command.py +33 -33
  109. telegrinder/node/composer.py +198 -198
  110. telegrinder/node/container.py +27 -27
  111. telegrinder/node/event.py +65 -65
  112. telegrinder/node/me.py +16 -16
  113. telegrinder/node/message.py +14 -14
  114. telegrinder/node/polymorphic.py +48 -48
  115. telegrinder/node/rule.py +76 -76
  116. telegrinder/node/scope.py +38 -38
  117. telegrinder/node/source.py +71 -71
  118. telegrinder/node/text.py +41 -41
  119. telegrinder/node/tools/__init__.py +3 -3
  120. telegrinder/node/tools/generator.py +40 -40
  121. telegrinder/node/update.py +15 -15
  122. telegrinder/rules.py +0 -0
  123. telegrinder/tools/__init__.py +74 -74
  124. telegrinder/tools/buttons.py +79 -79
  125. telegrinder/tools/error_handler/__init__.py +7 -7
  126. telegrinder/tools/error_handler/abc.py +33 -33
  127. telegrinder/tools/error_handler/error.py +9 -9
  128. telegrinder/tools/error_handler/error_handler.py +193 -193
  129. telegrinder/tools/formatting/__init__.py +46 -46
  130. telegrinder/tools/formatting/html.py +283 -283
  131. telegrinder/tools/formatting/links.py +33 -33
  132. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  133. telegrinder/tools/functional.py +12 -12
  134. telegrinder/tools/global_context/__init__.py +7 -7
  135. telegrinder/tools/global_context/abc.py +63 -63
  136. telegrinder/tools/global_context/global_context.py +412 -412
  137. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  138. telegrinder/tools/i18n/__init__.py +7 -7
  139. telegrinder/tools/i18n/abc.py +30 -30
  140. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  141. telegrinder/tools/i18n/middleware/abc.py +25 -25
  142. telegrinder/tools/i18n/simple.py +43 -43
  143. telegrinder/tools/kb_set/__init__.py +4 -4
  144. telegrinder/tools/kb_set/base.py +15 -15
  145. telegrinder/tools/kb_set/yaml.py +63 -63
  146. telegrinder/tools/keyboard.py +132 -132
  147. telegrinder/tools/limited_dict.py +37 -37
  148. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  149. telegrinder/tools/loop_wrapper/abc.py +15 -15
  150. telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
  151. telegrinder/tools/magic.py +157 -157
  152. telegrinder/tools/parse_mode.py +6 -6
  153. telegrinder/tools/state_storage/__init__.py +4 -4
  154. telegrinder/tools/state_storage/abc.py +35 -35
  155. telegrinder/tools/state_storage/memory.py +25 -25
  156. telegrinder/types/__init__.py +260 -260
  157. telegrinder/types/enums.py +701 -701
  158. telegrinder/types/methods.py +4633 -4633
  159. telegrinder/types/objects.py +6950 -8561
  160. telegrinder/verification_utils.py +32 -32
  161. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
  162. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
  163. telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
  164. telegrinder-0.3.3.post1.dist-info/RECORD +0 -164
  165. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
@@ -1,48 +1,48 @@
1
- import typing_extensions as typing
2
- from fntypes.result import Error, Ok, Result
3
-
4
- from telegrinder.api.api import API
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.msgspec_utils import repr_type
9
- from telegrinder.node.composer import NodeSession, compose_nodes
10
- from telegrinder.types.objects import Update
11
-
12
- if typing.TYPE_CHECKING:
13
- from telegrinder.node.base import Node
14
-
15
- Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
16
-
17
-
18
- class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
- def __init__(self, *nodes: *Ts) -> None:
20
- self.nodes = nodes
21
-
22
- def __repr__(self) -> str:
23
- return "<{}: adapt Update -> ({})>".format(
24
- self.__class__.__name__,
25
- ", ".join(repr_type(node) for node in self.nodes),
26
- )
27
-
28
- async def adapt(
29
- self,
30
- api: API,
31
- update: Update,
32
- context: Context,
33
- ) -> Result[Event[tuple[*Ts]], AdapterError]:
34
- result = await compose_nodes(
35
- nodes={str(i): typing.cast(type["Node"], node) for i, node in enumerate(self.nodes)},
36
- ctx=context,
37
- data={Update: update, API: api},
38
- )
39
-
40
- match result:
41
- case Ok(collection):
42
- sessions: list[NodeSession] = list(collection.sessions.values())
43
- return Ok(Event(tuple(sessions))) # type: ignore
44
- case Error(err):
45
- return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
46
-
47
-
48
- __all__ = ("NodeAdapter",)
1
+ import typing_extensions as typing
2
+ from fntypes.result import Error, Ok, Result
3
+
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
7
+ from telegrinder.bot.rules.adapter.errors import AdapterError
8
+ from telegrinder.msgspec_utils import repr_type
9
+ from telegrinder.node.composer import NodeSession, compose_nodes
10
+ from telegrinder.types.objects import Update
11
+
12
+ if typing.TYPE_CHECKING:
13
+ from telegrinder.node.base import Node
14
+
15
+ Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
16
+
17
+
18
+ class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
+ def __init__(self, *nodes: *Ts) -> None:
20
+ self.nodes = nodes
21
+
22
+ def __repr__(self) -> str:
23
+ return "<{}: adapt Update -> ({})>".format(
24
+ self.__class__.__name__,
25
+ ", ".join(repr_type(node) for node in self.nodes),
26
+ )
27
+
28
+ async def adapt(
29
+ self,
30
+ api: API,
31
+ update: Update,
32
+ context: Context,
33
+ ) -> Result[Event[tuple[*Ts]], AdapterError]:
34
+ result = await compose_nodes(
35
+ nodes={str(i): typing.cast(type["Node"], node) for i, node in enumerate(self.nodes)},
36
+ ctx=context,
37
+ data={Update: update, API: api},
38
+ )
39
+
40
+ match result:
41
+ case Ok(collection):
42
+ sessions: list[NodeSession] = list(collection.sessions.values())
43
+ return Ok(Event(tuple(sessions))) # type: ignore
44
+ case Error(err):
45
+ return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
46
+
47
+
48
+ __all__ = ("NodeAdapter",)
@@ -0,0 +1,27 @@
1
+ from fntypes.result import Error, Ok, Result
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
+ from telegrinder.bot.rules.adapter.errors import AdapterError
7
+ from telegrinder.model import Model
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawEventAdapter(ABCAdapter[Update, Model]):
12
+ def __init__(self, event_model: type[Model], /) -> None:
13
+ self.event_model = event_model
14
+
15
+ def __repr__(self) -> str:
16
+ return "<{}: adapt Update -> {}>".format(
17
+ self.__class__.__name__,
18
+ self.event_model.__name__,
19
+ )
20
+
21
+ def adapt(self, api: API, update: Update, context: Context) -> Result[Model, AdapterError]:
22
+ if isinstance(update.incoming_update, self.event_model):
23
+ return Ok(update.incoming_update)
24
+ return Error(AdapterError(f"Update is not an {self.event_model.__name__!r}."))
25
+
26
+
27
+ __all__ = ("RawEventAdapter",)
@@ -1,30 +1,30 @@
1
- from fntypes.result import Ok, Result
2
-
3
- from telegrinder.api.api import API
4
- from telegrinder.bot.cute_types.update import UpdateCute
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.types.objects import Update
9
-
10
-
11
- class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
- ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
-
14
- def __repr__(self) -> str:
15
- return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
-
17
- async def adapt(
18
- self,
19
- api: API,
20
- update: Update,
21
- context: Context,
22
- ) -> Result[UpdateCute, AdapterError]:
23
- if self.ADAPTED_VALUE_KEY not in context:
24
- context[self.ADAPTED_VALUE_KEY] = (
25
- UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
- )
27
- return Ok(context[self.ADAPTED_VALUE_KEY])
28
-
29
-
30
- __all__ = ("RawUpdateAdapter",)
1
+ from fntypes.result import Ok, Result
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.update import UpdateCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
7
+ from telegrinder.bot.rules.adapter.errors import AdapterError
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
+ ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
+
14
+ def __repr__(self) -> str:
15
+ return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
+
17
+ def adapt(
18
+ self,
19
+ api: API,
20
+ update: Update,
21
+ context: Context,
22
+ ) -> Result[UpdateCute, AdapterError]:
23
+ if self.ADAPTED_VALUE_KEY not in context:
24
+ context[self.ADAPTED_VALUE_KEY] = (
25
+ UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
+ )
27
+ return Ok(context[self.ADAPTED_VALUE_KEY])
28
+
29
+
30
+ __all__ = ("RawUpdateAdapter",)
@@ -1,170 +1,170 @@
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__ = (
162
- "CallbackDataEq",
163
- "CallbackDataJsonEq",
164
- "CallbackDataJsonModel",
165
- "CallbackDataMap",
166
- "CallbackDataMarkup",
167
- "CallbackQueryDataRule",
168
- "CallbackQueryRule",
169
- "HasData",
170
- )
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__ = (
162
+ "CallbackDataEq",
163
+ "CallbackDataJsonEq",
164
+ "CallbackDataJsonModel",
165
+ "CallbackDataMap",
166
+ "CallbackDataMarkup",
167
+ "CallbackQueryDataRule",
168
+ "CallbackQueryRule",
169
+ "HasData",
170
+ )
@@ -1,46 +1,46 @@
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__ = (
42
- "ChatJoinRequestRule",
43
- "HasInviteLink",
44
- "InviteLinkByCreator",
45
- "InviteLinkName",
46
- )
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__ = (
42
+ "ChatJoinRequestRule",
43
+ "HasInviteLink",
44
+ "InviteLinkByCreator",
45
+ "InviteLinkName",
46
+ )