telegrinder 1.0.0rc1__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.
Files changed (215) hide show
  1. telegrinder/__init__.py +258 -0
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +15 -0
  4. telegrinder/api/api.py +175 -0
  5. telegrinder/api/error.py +50 -0
  6. telegrinder/api/response.py +23 -0
  7. telegrinder/api/token.py +30 -0
  8. telegrinder/api/validators.py +30 -0
  9. telegrinder/bot/__init__.py +144 -0
  10. telegrinder/bot/bot.py +70 -0
  11. telegrinder/bot/cute_types/__init__.py +41 -0
  12. telegrinder/bot/cute_types/base.py +228 -0
  13. telegrinder/bot/cute_types/base.pyi +49 -0
  14. telegrinder/bot/cute_types/business_connection.py +9 -0
  15. telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
  16. telegrinder/bot/cute_types/callback_query.py +248 -0
  17. telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
  18. telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
  19. telegrinder/bot/cute_types/chat_join_request.py +59 -0
  20. telegrinder/bot/cute_types/chat_member_updated.py +158 -0
  21. telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
  22. telegrinder/bot/cute_types/inline_query.py +41 -0
  23. telegrinder/bot/cute_types/message.py +2809 -0
  24. telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
  25. telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
  26. telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
  27. telegrinder/bot/cute_types/poll.py +9 -0
  28. telegrinder/bot/cute_types/poll_answer.py +9 -0
  29. telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
  30. telegrinder/bot/cute_types/shipping_query.py +11 -0
  31. telegrinder/bot/cute_types/update.py +209 -0
  32. telegrinder/bot/cute_types/utils.py +141 -0
  33. telegrinder/bot/dispatch/__init__.py +99 -0
  34. telegrinder/bot/dispatch/abc.py +74 -0
  35. telegrinder/bot/dispatch/action.py +99 -0
  36. telegrinder/bot/dispatch/context.py +162 -0
  37. telegrinder/bot/dispatch/dispatch.py +362 -0
  38. telegrinder/bot/dispatch/handler/__init__.py +23 -0
  39. telegrinder/bot/dispatch/handler/abc.py +25 -0
  40. telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
  41. telegrinder/bot/dispatch/handler/base.py +34 -0
  42. telegrinder/bot/dispatch/handler/document_reply.py +43 -0
  43. telegrinder/bot/dispatch/handler/func.py +73 -0
  44. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
  45. telegrinder/bot/dispatch/handler/message_reply.py +35 -0
  46. telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
  47. telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
  48. telegrinder/bot/dispatch/handler/video_reply.py +43 -0
  49. telegrinder/bot/dispatch/middleware/__init__.py +13 -0
  50. telegrinder/bot/dispatch/middleware/abc.py +112 -0
  51. telegrinder/bot/dispatch/middleware/box.py +32 -0
  52. telegrinder/bot/dispatch/middleware/filter.py +88 -0
  53. telegrinder/bot/dispatch/middleware/media_group.py +69 -0
  54. telegrinder/bot/dispatch/process.py +93 -0
  55. telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
  56. telegrinder/bot/dispatch/return_manager/abc.py +107 -0
  57. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  58. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  59. telegrinder/bot/dispatch/return_manager/message.py +34 -0
  60. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
  61. telegrinder/bot/dispatch/return_manager/utils.py +20 -0
  62. telegrinder/bot/dispatch/router/__init__.py +4 -0
  63. telegrinder/bot/dispatch/router/abc.py +15 -0
  64. telegrinder/bot/dispatch/router/base.py +154 -0
  65. telegrinder/bot/dispatch/view/__init__.py +15 -0
  66. telegrinder/bot/dispatch/view/abc.py +15 -0
  67. telegrinder/bot/dispatch/view/base.py +226 -0
  68. telegrinder/bot/dispatch/view/box.py +207 -0
  69. telegrinder/bot/dispatch/view/media_group.py +25 -0
  70. telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
  71. telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
  72. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
  73. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
  74. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
  75. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
  76. telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
  77. telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
  78. telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
  79. telegrinder/bot/polling/__init__.py +4 -0
  80. telegrinder/bot/polling/abc.py +25 -0
  81. telegrinder/bot/polling/error_handler.py +93 -0
  82. telegrinder/bot/polling/polling.py +167 -0
  83. telegrinder/bot/polling/utils.py +12 -0
  84. telegrinder/bot/rules/__init__.py +166 -0
  85. telegrinder/bot/rules/abc.py +150 -0
  86. telegrinder/bot/rules/button.py +20 -0
  87. telegrinder/bot/rules/callback_data.py +109 -0
  88. telegrinder/bot/rules/chat_join.py +28 -0
  89. telegrinder/bot/rules/chat_member_updated.py +145 -0
  90. telegrinder/bot/rules/command.py +137 -0
  91. telegrinder/bot/rules/enum_text.py +29 -0
  92. telegrinder/bot/rules/func.py +21 -0
  93. telegrinder/bot/rules/fuzzy.py +21 -0
  94. telegrinder/bot/rules/inline.py +45 -0
  95. telegrinder/bot/rules/integer.py +19 -0
  96. telegrinder/bot/rules/is_from.py +213 -0
  97. telegrinder/bot/rules/logic.py +22 -0
  98. telegrinder/bot/rules/magic.py +60 -0
  99. telegrinder/bot/rules/markup.py +51 -0
  100. telegrinder/bot/rules/media.py +13 -0
  101. telegrinder/bot/rules/mention.py +15 -0
  102. telegrinder/bot/rules/message_entities.py +37 -0
  103. telegrinder/bot/rules/node.py +43 -0
  104. telegrinder/bot/rules/payload.py +89 -0
  105. telegrinder/bot/rules/payment_invoice.py +14 -0
  106. telegrinder/bot/rules/regex.py +34 -0
  107. telegrinder/bot/rules/rule_enum.py +71 -0
  108. telegrinder/bot/rules/start.py +73 -0
  109. telegrinder/bot/rules/state.py +35 -0
  110. telegrinder/bot/rules/text.py +27 -0
  111. telegrinder/bot/rules/update.py +14 -0
  112. telegrinder/bot/scenario/__init__.py +5 -0
  113. telegrinder/bot/scenario/abc.py +16 -0
  114. telegrinder/bot/scenario/checkbox.py +183 -0
  115. telegrinder/bot/scenario/choice.py +44 -0
  116. telegrinder/client/__init__.py +11 -0
  117. telegrinder/client/abc.py +136 -0
  118. telegrinder/client/form_data.py +34 -0
  119. telegrinder/client/rnet.py +198 -0
  120. telegrinder/model.py +133 -0
  121. telegrinder/model.pyi +57 -0
  122. telegrinder/modules.py +1081 -0
  123. telegrinder/msgspec_utils/__init__.py +42 -0
  124. telegrinder/msgspec_utils/abc.py +16 -0
  125. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  126. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  127. telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
  128. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  129. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  130. telegrinder/msgspec_utils/decoder.py +388 -0
  131. telegrinder/msgspec_utils/encoder.py +204 -0
  132. telegrinder/msgspec_utils/json.py +15 -0
  133. telegrinder/msgspec_utils/tools.py +80 -0
  134. telegrinder/node/__init__.py +80 -0
  135. telegrinder/node/compose.py +193 -0
  136. telegrinder/node/nodes/__init__.py +96 -0
  137. telegrinder/node/nodes/attachment.py +169 -0
  138. telegrinder/node/nodes/callback_query.py +25 -0
  139. telegrinder/node/nodes/channel.py +97 -0
  140. telegrinder/node/nodes/command.py +33 -0
  141. telegrinder/node/nodes/error.py +43 -0
  142. telegrinder/node/nodes/event.py +70 -0
  143. telegrinder/node/nodes/file.py +39 -0
  144. telegrinder/node/nodes/global_node.py +66 -0
  145. telegrinder/node/nodes/i18n.py +110 -0
  146. telegrinder/node/nodes/me.py +26 -0
  147. telegrinder/node/nodes/message_entities.py +15 -0
  148. telegrinder/node/nodes/payload.py +84 -0
  149. telegrinder/node/nodes/reply_message.py +14 -0
  150. telegrinder/node/nodes/source.py +172 -0
  151. telegrinder/node/nodes/state_mutator.py +71 -0
  152. telegrinder/node/nodes/text.py +62 -0
  153. telegrinder/node/scope.py +88 -0
  154. telegrinder/node/utils.py +38 -0
  155. telegrinder/py.typed +0 -0
  156. telegrinder/rules.py +1 -0
  157. telegrinder/tools/__init__.py +183 -0
  158. telegrinder/tools/aio.py +147 -0
  159. telegrinder/tools/final.py +21 -0
  160. telegrinder/tools/formatting/__init__.py +85 -0
  161. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  162. telegrinder/tools/formatting/deep_links/links.py +468 -0
  163. telegrinder/tools/formatting/deep_links/parsing.py +88 -0
  164. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  165. telegrinder/tools/formatting/html.py +241 -0
  166. telegrinder/tools/fullname.py +82 -0
  167. telegrinder/tools/global_context/__init__.py +13 -0
  168. telegrinder/tools/global_context/abc.py +63 -0
  169. telegrinder/tools/global_context/builtin_context.py +45 -0
  170. telegrinder/tools/global_context/global_context.py +614 -0
  171. telegrinder/tools/input_file_directory.py +30 -0
  172. telegrinder/tools/keyboard/__init__.py +6 -0
  173. telegrinder/tools/keyboard/abc.py +84 -0
  174. telegrinder/tools/keyboard/base.py +108 -0
  175. telegrinder/tools/keyboard/button.py +181 -0
  176. telegrinder/tools/keyboard/data.py +31 -0
  177. telegrinder/tools/keyboard/keyboard.py +160 -0
  178. telegrinder/tools/keyboard/utils.py +95 -0
  179. telegrinder/tools/lifespan.py +188 -0
  180. telegrinder/tools/limited_dict.py +35 -0
  181. telegrinder/tools/loop_wrapper.py +271 -0
  182. telegrinder/tools/magic/__init__.py +29 -0
  183. telegrinder/tools/magic/annotations.py +172 -0
  184. telegrinder/tools/magic/descriptors.py +57 -0
  185. telegrinder/tools/magic/function.py +254 -0
  186. telegrinder/tools/magic/inspect.py +16 -0
  187. telegrinder/tools/magic/shortcut.py +107 -0
  188. telegrinder/tools/member_descriptor_proxy.py +95 -0
  189. telegrinder/tools/parse_mode.py +12 -0
  190. telegrinder/tools/serialization/__init__.py +5 -0
  191. telegrinder/tools/serialization/abc.py +34 -0
  192. telegrinder/tools/serialization/json_ser.py +60 -0
  193. telegrinder/tools/serialization/msgpack_ser.py +197 -0
  194. telegrinder/tools/serialization/utils.py +18 -0
  195. telegrinder/tools/singleton/__init__.py +4 -0
  196. telegrinder/tools/singleton/abc.py +14 -0
  197. telegrinder/tools/singleton/singleton.py +18 -0
  198. telegrinder/tools/state_mutator/__init__.py +4 -0
  199. telegrinder/tools/state_mutator/mutation.py +85 -0
  200. telegrinder/tools/state_storage/__init__.py +4 -0
  201. telegrinder/tools/state_storage/abc.py +38 -0
  202. telegrinder/tools/state_storage/memory.py +27 -0
  203. telegrinder/tools/strings.py +22 -0
  204. telegrinder/types/__init__.py +323 -0
  205. telegrinder/types/enums.py +754 -0
  206. telegrinder/types/input_file.py +51 -0
  207. telegrinder/types/methods.py +6143 -0
  208. telegrinder/types/methods_utils.py +66 -0
  209. telegrinder/types/objects.py +8184 -0
  210. telegrinder/types/webapp.py +129 -0
  211. telegrinder/verification_utils.py +35 -0
  212. telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
  213. telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
  214. telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
  215. telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,60 @@
1
+ import typing
2
+ from contextlib import suppress
3
+
4
+ import kungfu
5
+
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.node import EventNode
8
+ from telegrinder.node.utils import is_node
9
+ from telegrinder.tools.member_descriptor_proxy import MemberDescriptorProxy, evaluate_operations
10
+
11
+
12
+ class Magic[R, **P = [R]](kungfu.F[R, P], ABCRule):
13
+ def __init__(
14
+ self,
15
+ field: R,
16
+ parent: typing.Any = None,
17
+ ) -> None:
18
+ if isinstance(field, MemberDescriptorProxy):
19
+ owner_cls, member_name, operations = field._objclass, field._member_name, field._operations
20
+ node = EventNode[owner_cls]
21
+ super().__init__(lambda obj: evaluate_operations(obj, member_name, operations)) # type: ignore
22
+
23
+ elif is_node(field):
24
+ node = field
25
+ super().__init__() # type: ignore
26
+
27
+ else:
28
+ node = parent
29
+ super().__init__(field) # type: ignore
30
+
31
+ self.node = node
32
+ self.required_nodes = dict(magic_value=node)
33
+
34
+ def check(self, magic_value: typing.Any) -> bool:
35
+ with suppress(kungfu.UnwrapError):
36
+ self(magic_value) # type: ignore
37
+ return True
38
+
39
+ return False
40
+
41
+ def new(self, f: typing.Any, /) -> typing.Any:
42
+ return Magic(f, parent=self.node)
43
+
44
+ if typing.TYPE_CHECKING:
45
+
46
+ def then[T](self, g: typing.Callable[[R], T], /) -> "Magic[T, P]": ...
47
+
48
+ def ensure(
49
+ self,
50
+ chk: typing.Callable[[R], bool],
51
+ error: typing.Callable[[R], BaseException] | BaseException | str | None = None,
52
+ ) -> "Magic[R, P]": ...
53
+
54
+ def expect[T, Err](
55
+ self: kungfu.F[kungfu.Result[T, Err], P],
56
+ error: typing.Callable[[kungfu.Result[T, Err]], BaseException] | BaseException | str | None = None,
57
+ ) -> "Magic[T, P]": ...
58
+
59
+
60
+ __all__ = ("Magic",)
@@ -0,0 +1,51 @@
1
+ import re
2
+ import typing
3
+
4
+ import vbml
5
+
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.node.nodes.text import Caption, Text
9
+ from telegrinder.tools.global_context.builtin_context import TelegrinderContext
10
+
11
+ type PatternLike = str | vbml.Pattern
12
+
13
+ TELEGRINDER_CONTEXT: typing.Final = TelegrinderContext()
14
+
15
+
16
+ def check_string(patterns: typing.Iterable[vbml.Pattern], s: str, ctx: Context) -> bool:
17
+ for pattern in patterns:
18
+ match TELEGRINDER_CONTEXT.vbml_patcher.check(pattern, s):
19
+ case {**response}:
20
+ ctx |= response
21
+ return True
22
+ case True:
23
+ return True
24
+ case _:
25
+ continue
26
+
27
+ return False
28
+
29
+
30
+ class Markup(ABCRule):
31
+ """Markup Language. See the [vbml documentation](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)."""
32
+
33
+ def __init__(
34
+ self,
35
+ patterns: PatternLike | list[PatternLike],
36
+ /,
37
+ *,
38
+ flags: re.RegexFlag | None = None,
39
+ ) -> None:
40
+ self.patterns = [
41
+ vbml.Pattern(text=pattern, flags=flags or TELEGRINDER_CONTEXT.vbml_pattern_flags)
42
+ if isinstance(pattern, str)
43
+ else pattern
44
+ for pattern in ([patterns] if not isinstance(patterns, list) else patterns)
45
+ ]
46
+
47
+ def check(self, text: Text | Caption, ctx: Context) -> bool:
48
+ return check_string(self.patterns, text, ctx)
49
+
50
+
51
+ __all__ = ("Markup", "check_string")
@@ -0,0 +1,13 @@
1
+ from telegrinder.bot.cute_types.message import MessageCute
2
+ from telegrinder.bot.cute_types.utils import MEDIA_TYPES
3
+ from telegrinder.bot.rules.abc import ABCRule
4
+
5
+
6
+ class IsMediaGroup(ABCRule):
7
+ def check(self, message: MessageCute) -> bool:
8
+ if not message.media_group_id:
9
+ return False
10
+ return message.content_type in MEDIA_TYPES
11
+
12
+
13
+ __all__ = ("IsMediaGroup",)
@@ -0,0 +1,15 @@
1
+ from telegrinder.bot.cute_types.message import MessageCute
2
+ from telegrinder.bot.rules.abc import ABCRule
3
+ from telegrinder.bot.rules.text import HasText
4
+ from telegrinder.types.enums import MessageEntityType
5
+
6
+
7
+ class HasMention(ABCRule, requires=[HasText()]):
8
+ def check(self, message: MessageCute) -> bool:
9
+ entities = message.entities.unwrap_or_none()
10
+ if not entities:
11
+ return False
12
+ return any(entity.type == MessageEntityType.MENTION for entity in entities)
13
+
14
+
15
+ __all__ = ("HasMention",)
@@ -0,0 +1,37 @@
1
+ import typing
2
+
3
+ from telegrinder.bot.cute_types.message import MessageCute
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.abc import ABCRule
6
+ from telegrinder.types.enums import MessageEntityType
7
+ from telegrinder.types.objects import MessageEntity
8
+
9
+ type Entity = str | MessageEntityType
10
+
11
+ Message: typing.TypeAlias = MessageCute
12
+
13
+
14
+ class HasEntities(ABCRule):
15
+ def check(self, message: Message) -> bool:
16
+ return bool(message.entities)
17
+
18
+
19
+ class MessageEntities(ABCRule, requires=[HasEntities()]):
20
+ def __init__(self, entities: Entity | list[Entity], /) -> None:
21
+ self.entities = [entities] if not isinstance(entities, list) else entities
22
+
23
+ def check(self, message: Message, ctx: Context) -> bool:
24
+ message_entities: list[MessageEntity] = []
25
+ for entity in message.entities.unwrap():
26
+ for entity_type in self.entities:
27
+ if entity_type == entity.type:
28
+ message_entities.append(entity)
29
+
30
+ if not message_entities:
31
+ return False
32
+
33
+ ctx.message_entities = message_entities
34
+ return True
35
+
36
+
37
+ __all__ = ("HasEntities", "MessageEntities")
@@ -0,0 +1,43 @@
1
+ import typing
2
+
3
+ from kungfu.library.monad.result import Ok
4
+ from nodnod.agent.event_loop.agent import EventLoopAgent
5
+
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.node.compose import run_agent
9
+ from telegrinder.node.utils import as_node
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from nodnod.agent.base import Agent
13
+
14
+ type Node = typing.Any
15
+
16
+
17
+ class NodeRule(ABCRule):
18
+ nodes: tuple[tuple[str | None, Node], ...]
19
+
20
+ def __init__(
21
+ self,
22
+ *nodes: Node | tuple[str, Node],
23
+ agent: type[Agent] | None = None,
24
+ roots: dict[type[typing.Any], typing.Any] | None = None,
25
+ ) -> None:
26
+ self.agent = (agent or EventLoopAgent).build(nodes=set(map(as_node, nodes)))
27
+ self.nodes = tuple((x if isinstance(x, tuple) else (None, x)) for x in nodes)
28
+ self.roots = roots
29
+
30
+ async def check(self, context: Context) -> bool:
31
+ async with run_agent(self.agent, context, roots=self.roots) as result:
32
+ match result:
33
+ case Ok(scope):
34
+ for key, node in self.nodes:
35
+ if key is not None and node in scope:
36
+ context[key] = scope[node].value
37
+
38
+ return True
39
+ case _:
40
+ return False
41
+
42
+
43
+ __all__ = ("NodeRule",)
@@ -0,0 +1,89 @@
1
+ import typing
2
+ from contextlib import suppress
3
+
4
+ import msgspec
5
+
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.bot.rules.markup import Markup, PatternLike, check_string
9
+ from telegrinder.msgspec_utils.json import loads
10
+ from telegrinder.node.nodes.payload import Payload, PayloadData
11
+ from telegrinder.tools.serialization.abc import ABCDataSerializer, ModelType
12
+ from telegrinder.tools.serialization.json_ser import JSONSerializer
13
+ from telegrinder.tools.serialization.utils import get_model_serializer
14
+
15
+ _ANY: typing.Final = object()
16
+
17
+
18
+ class PayloadRule[Data](ABCRule):
19
+ def __init__(
20
+ self,
21
+ data_type: type[Data],
22
+ serializer: type[ABCDataSerializer[Data]] | None = None,
23
+ *,
24
+ alias: str | None = None,
25
+ ) -> None:
26
+ self.data_type = data_type
27
+ self.serializer = serializer or get_model_serializer(data_type) or JSONSerializer[typing.Any]
28
+ self.alias = alias or "data"
29
+ self.required_nodes = dict(payload=PayloadData[self.data_type, self.serializer])
30
+
31
+ def check(self, payload: PayloadData, context: Context) -> typing.Literal[True]:
32
+ context.set(self.alias, payload)
33
+ return True
34
+
35
+
36
+ class PayloadModelRule[Model: ModelType](PayloadRule):
37
+ def __init__(
38
+ self,
39
+ model_t: type[Model],
40
+ /,
41
+ *,
42
+ payload: typing.Any = _ANY,
43
+ serializer: type[ABCDataSerializer[Model]] | None = None,
44
+ alias: str | None = None,
45
+ ) -> None:
46
+ super().__init__(model_t, serializer, alias=alias or "model")
47
+ self.payload = payload
48
+
49
+ def check(self, payload: PayloadData, context: Context) -> bool:
50
+ if self.payload is not _ANY and payload != self.payload:
51
+ return False
52
+
53
+ context.set(self.alias, payload)
54
+ return True
55
+
56
+
57
+ class PayloadEqRule(ABCRule):
58
+ def __init__(self, payloads: str | list[str], /) -> None:
59
+ self.payloads = [payloads] if isinstance(payloads, str) else payloads
60
+
61
+ def check(self, payload: Payload) -> bool:
62
+ return any(p == payload for p in self.payloads)
63
+
64
+
65
+ class PayloadMarkupRule(ABCRule):
66
+ def __init__(self, pattern: PatternLike | list[PatternLike], /) -> None:
67
+ self.patterns = Markup(pattern).patterns
68
+
69
+ def check(self, payload: Payload, context: Context) -> bool:
70
+ return check_string(self.patterns, payload, context)
71
+
72
+
73
+ class PayloadJsonEqRule(ABCRule):
74
+ def __init__(self, payload: dict[str, typing.Any], /) -> None:
75
+ self.payload = payload
76
+
77
+ def check(self, payload: Payload) -> bool:
78
+ with suppress(msgspec.DecodeError, msgspec.ValidationError):
79
+ return self.payload == loads(payload)
80
+ return False
81
+
82
+
83
+ __all__ = (
84
+ "PayloadEqRule",
85
+ "PayloadJsonEqRule",
86
+ "PayloadMarkupRule",
87
+ "PayloadModelRule",
88
+ "PayloadRule",
89
+ )
@@ -0,0 +1,14 @@
1
+ from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
2
+ from telegrinder.bot.rules.abc import ABCRule
3
+ from telegrinder.types.enums import Currency
4
+
5
+
6
+ class PaymentInvoiceCurrency(ABCRule):
7
+ def __init__(self, currency: Currency, /) -> None:
8
+ self.currency = currency
9
+
10
+ def check(self, query: PreCheckoutQueryCute) -> bool:
11
+ return self.currency == query.currency
12
+
13
+
14
+ __all__ = ("PaymentInvoiceCurrency",)
@@ -0,0 +1,34 @@
1
+ import re
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.abc import ABCRule
6
+ from telegrinder.node.nodes.text import Caption, Text
7
+
8
+ type PatternLike = str | typing.Pattern[str]
9
+
10
+
11
+ class Regex(ABCRule):
12
+ def __init__(self, regexp: PatternLike | list[PatternLike]) -> None:
13
+ self.regexp: list[re.Pattern[str]] = []
14
+ match regexp:
15
+ case re.Pattern() as pattern:
16
+ self.regexp.append(pattern)
17
+ case str(regex):
18
+ self.regexp.append(re.compile(regex))
19
+ case _:
20
+ self.regexp.extend(re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp)
21
+
22
+ def check(self, text: Text | Caption, ctx: Context) -> bool:
23
+ for regexp in self.regexp:
24
+ response = re.match(regexp, text)
25
+ if response is not None:
26
+ if matches := response.groupdict():
27
+ ctx |= matches
28
+ else:
29
+ ctx |= {"matches": response.groups() or (response.group(),)}
30
+ return True
31
+ return False
32
+
33
+
34
+ __all__ = ("Regex",)
@@ -0,0 +1,71 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.abc import ABCRule, check_rule
6
+ from telegrinder.bot.rules.func import FuncRule
7
+
8
+
9
+ @dataclasses.dataclass(slots=True)
10
+ class RuleEnumState:
11
+ name: str
12
+ rule: ABCRule
13
+ cls: type["RuleEnum"]
14
+
15
+ def __eq__(self, other: typing.Self) -> bool:
16
+ return self.cls == other.cls and self.name == other.name
17
+
18
+
19
+ class RuleEnum(ABCRule):
20
+ __enum__: list[RuleEnumState]
21
+
22
+ def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
23
+ new_attributes = set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
24
+ enum_lst: list[RuleEnumState] = []
25
+
26
+ self = cls.__new__(cls)
27
+ self.__init__()
28
+
29
+ for attribute_name in new_attributes:
30
+ rules = getattr(cls, attribute_name)
31
+ attribute = RuleEnumState(attribute_name, rules, cls)
32
+
33
+ setattr(
34
+ self,
35
+ attribute.name,
36
+ self & FuncRule(lambda _, ctx: self.must_be_state(ctx, attribute)), # type: ignore
37
+ )
38
+ enum_lst.append(attribute)
39
+
40
+ setattr(cls, "__enum__", enum_lst)
41
+
42
+ @classmethod
43
+ def save_state(cls, ctx: Context, enum: RuleEnumState) -> None:
44
+ ctx |= {cls.__name__ + "_state": enum}
45
+
46
+ @classmethod
47
+ def check_state(cls, ctx: Context) -> RuleEnumState | None:
48
+ return ctx.get(cls.__name__ + "_state")
49
+
50
+ @classmethod
51
+ def must_be_state(cls, ctx: Context, state: RuleEnumState) -> bool:
52
+ real_state = cls.check_state(ctx)
53
+ if not real_state:
54
+ return False
55
+ return real_state == state
56
+
57
+ async def check(self, ctx: Context) -> bool:
58
+ if self.check_state(ctx):
59
+ return True
60
+
61
+ for enum in self.__enum__:
62
+ ctx_copy = ctx.copy()
63
+ if await check_rule(enum.rule, ctx_copy):
64
+ ctx |= ctx_copy
65
+ self.save_state(ctx, enum)
66
+ return True
67
+
68
+ return False
69
+
70
+
71
+ __all__ = ("RuleEnum", "RuleEnumState")
@@ -0,0 +1,73 @@
1
+ import base64
2
+ import typing
3
+
4
+ import kungfu
5
+
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.bot.rules.is_from import IsPrivate
9
+ from telegrinder.bot.rules.markup import Markup
10
+ from telegrinder.node.nodes.me import BotUsername
11
+ from telegrinder.node.nodes.message_entities import MessageEntities
12
+ from telegrinder.types.enums import MessageEntityType
13
+
14
+
15
+ class StartCommand(
16
+ ABCRule,
17
+ requires=[
18
+ IsPrivate(),
19
+ Markup(
20
+ [
21
+ "/start <param>",
22
+ "/start",
23
+ "tg://resolve?domain=<bot_username>&start=<param>",
24
+ ]
25
+ ),
26
+ ],
27
+ ):
28
+ def __init__(
29
+ self,
30
+ validator: typing.Callable[[str], typing.Any | None] | None = None,
31
+ *,
32
+ deep_link: bool | None = None,
33
+ decode_deep_link_param: bool = False,
34
+ param_required: bool = False,
35
+ alias: str | None = None,
36
+ ) -> None:
37
+ self.param_required = param_required
38
+ self.validator = validator
39
+ self.alias = alias
40
+ self.deep_link = deep_link
41
+ self.decode_deep_link_param = decode_deep_link_param
42
+
43
+ def check(
44
+ self,
45
+ bot_username: BotUsername,
46
+ message_entities: kungfu.Option[MessageEntities],
47
+ ctx: Context,
48
+ ) -> bool:
49
+ if self.deep_link is not None and all(
50
+ (
51
+ message_entities.map(
52
+ lambda entities: entities and entities[0].type == MessageEntityType.BOT_COMMAND,
53
+ ).unwrap_or(False),
54
+ self.deep_link and bot_username == ctx.get("bot_username"),
55
+ ),
56
+ ):
57
+ return False
58
+
59
+ param: str | None = ctx.pop("param", None)
60
+
61
+ if param is not None and self.decode_deep_link_param:
62
+ param = base64.urlsafe_b64decode(param.encode()).decode()
63
+
64
+ validated_param = self.validator(param) if self.validator and param is not None else param
65
+
66
+ if self.param_required and validated_param is None:
67
+ return False
68
+
69
+ ctx.set(self.alias or "param", validated_param)
70
+ return True
71
+
72
+
73
+ __all__ = ("StartCommand",)
@@ -0,0 +1,35 @@
1
+ import dataclasses
2
+ import enum
3
+ import typing
4
+
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.node.nodes.source import Source
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from telegrinder.tools.state_storage.abc import ABCStateStorage
11
+
12
+
13
+ class StateMeta(enum.Enum):
14
+ NO_STATE = enum.auto()
15
+ ANY = enum.auto()
16
+
17
+
18
+ @dataclasses.dataclass(frozen=True, slots=True, repr=False)
19
+ class State[Payload](ABCRule):
20
+ storage: "ABCStateStorage[Payload]"
21
+ key: str | StateMeta | enum.Enum
22
+
23
+ async def check(self, source: Source, ctx: Context) -> bool:
24
+ state = await self.storage.get(source.from_user.id)
25
+ if not state:
26
+ return self.key == StateMeta.NO_STATE
27
+
28
+ if self.key != StateMeta.ANY and self.key != state.unwrap().key:
29
+ return False
30
+
31
+ ctx.state = state.unwrap()
32
+ return True
33
+
34
+
35
+ __all__ = ("State", "StateMeta")
@@ -0,0 +1,27 @@
1
+ from telegrinder import node
2
+ from telegrinder.bot.rules.abc import ABCRule
3
+ from telegrinder.bot.rules.node import NodeRule
4
+
5
+
6
+ class HasText(NodeRule):
7
+ def __init__(self) -> None:
8
+ super().__init__(node.as_node(node.Text))
9
+
10
+
11
+ class HasCaption(NodeRule):
12
+ def __init__(self) -> None:
13
+ super().__init__(node.as_node(node.Caption))
14
+
15
+
16
+ class Text(ABCRule):
17
+ def __init__(self, texts: str | list[str], /, *, ignore_case: bool = False) -> None:
18
+ if not isinstance(texts, list):
19
+ texts = [texts]
20
+ self.texts = set(texts) if not ignore_case else set(map(str.lower, texts))
21
+ self.ignore_case = ignore_case
22
+
23
+ def check(self, text: node.Text | node.Caption) -> bool:
24
+ return (text if not self.ignore_case else text.lower()) in self.texts
25
+
26
+
27
+ __all__ = ("HasCaption", "HasText", "Text")
@@ -0,0 +1,14 @@
1
+ from telegrinder.bot.rules.abc import ABCRule
2
+ from telegrinder.types.enums import UpdateType
3
+ from telegrinder.types.objects import Update
4
+
5
+
6
+ class IsUpdateType(ABCRule):
7
+ def __init__(self, update_type: UpdateType, /) -> None:
8
+ self.update_type = update_type
9
+
10
+ def check(self, update: Update) -> bool:
11
+ return update.update_type == self.update_type
12
+
13
+
14
+ __all__ = ("IsUpdateType",)
@@ -0,0 +1,5 @@
1
+ from .abc import ABCScenario
2
+ from .checkbox import Checkbox
3
+ from .choice import Choice
4
+
5
+ __all__ = ("ABCScenario", "Checkbox", "Choice")
@@ -0,0 +1,16 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ if typing.TYPE_CHECKING:
5
+ from telegrinder.api.api import API
6
+ from telegrinder.bot.dispatch.view.abc import ABCView
7
+ from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
8
+
9
+
10
+ class ABCScenario(ABC):
11
+ @abstractmethod
12
+ def wait(self, hasher: Hasher, view: ABCView, api: API) -> typing.Any:
13
+ pass
14
+
15
+
16
+ __all__ = ("ABCScenario",)