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,43 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputFile
8
+
9
+
10
+ class AudioReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ audio: InputFile | str,
14
+ *rules: ABCRule,
15
+ caption: str | None = None,
16
+ parse_mode: str | None = None,
17
+ final: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.audio = audio
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ final=final,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_audio if not self.as_reply else message.reply_audio
35
+ await method(
36
+ audio=self.audio,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("AudioReplyHandler",)
@@ -0,0 +1,34 @@
1
+ import abc
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.dispatch.handler.func import FuncHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+
8
+
9
+ class BaseReplyHandler(FuncHandler, abc.ABC):
10
+ final: bool
11
+
12
+ def __init__(
13
+ self,
14
+ *rules: ABCRule,
15
+ final: bool = True,
16
+ as_reply: bool = False,
17
+ preset_context: Context | None = None,
18
+ **default_params: typing.Any,
19
+ ) -> None:
20
+ self.as_reply = as_reply
21
+ self.default_params = default_params
22
+ super().__init__(
23
+ function=self.handle,
24
+ rules=list(rules),
25
+ final=final,
26
+ preset_context=preset_context or Context(),
27
+ )
28
+
29
+ @abc.abstractmethod
30
+ async def handle(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
31
+ pass
32
+
33
+
34
+ __all__ = ("BaseReplyHandler",)
@@ -0,0 +1,43 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputFile
8
+
9
+
10
+ class DocumentReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ document: InputFile | str,
14
+ *rules: ABCRule,
15
+ caption: str | None = None,
16
+ parse_mode: str | None = None,
17
+ final: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.document = document
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ final=final,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_document if not self.as_reply else message.reply_document
35
+ await method(
36
+ document=self.document,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("DocumentReplyHandler",)
@@ -0,0 +1,73 @@
1
+ import dataclasses
2
+ import typing
3
+ from collections import deque
4
+
5
+ from kungfu.library.monad.result import Error, Result
6
+ from nodnod.agent.event_loop.agent import EventLoopAgent
7
+ from nodnod.error import NodeError
8
+
9
+ from telegrinder.api.api import API
10
+ from telegrinder.bot.dispatch.context import Context
11
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
12
+ from telegrinder.bot.dispatch.process import check_rule
13
+ from telegrinder.modules import logger
14
+ from telegrinder.node.compose import compose
15
+ from telegrinder.tools.fullname import fullname
16
+ from telegrinder.types.objects import Update
17
+
18
+ if typing.TYPE_CHECKING:
19
+ from nodnod.agent.base import Agent
20
+
21
+ from telegrinder.bot.rules.abc import ABCRule
22
+
23
+ type Function[**P = ..., R = typing.Any] = typing.Callable[P, R]
24
+
25
+
26
+ @dataclasses.dataclass(repr=False, slots=True)
27
+ class FuncHandler[T: Function](ABCHandler):
28
+ function: T
29
+ rules: dataclasses.InitVar[typing.Iterable[ABCRule] | None] = dataclasses.field(default=None)
30
+ agent: type[Agent] | None = dataclasses.field(default=None, kw_only=True)
31
+ final: bool = dataclasses.field(default=True, kw_only=True)
32
+ preset_context: Context = dataclasses.field(default_factory=lambda: Context(), kw_only=True)
33
+
34
+ def __post_init__(self, rules: typing.Iterable[ABCRule] | None) -> None:
35
+ self.check_rules = deque(rules or ())
36
+
37
+ def __repr__(self) -> str:
38
+ return fullname(self.function)
39
+
40
+ @property
41
+ def __call__(self) -> Function:
42
+ return self.function
43
+
44
+ async def run(
45
+ self,
46
+ api: API,
47
+ update: Update,
48
+ context: Context,
49
+ check: bool = True,
50
+ ) -> Result[typing.Any, str]:
51
+ temp_ctx = context | self.preset_context
52
+
53
+ if check and self.check_rules:
54
+ await logger.adebug("Checking rules for handler `{!r}`...", self)
55
+
56
+ for rule in self.check_rules:
57
+ if not await check_rule(rule, temp_ctx):
58
+ return Error(f"Rule {rule!r} failed.")
59
+
60
+ await logger.adebug("Rules passed, composing nodes and running handler `{!r}`...", self)
61
+ else:
62
+ await logger.adebug("Composing nodes and running handler `{!r}`...", self)
63
+
64
+ context |= temp_ctx
65
+ async with compose(self.function, context, agent_cls=self.agent or EventLoopAgent) as result:
66
+ return result.map_err(
67
+ lambda error: "{}\n".format(
68
+ NodeError(f"* failed to compose handler `{self!r}`", from_error=error),
69
+ ),
70
+ )
71
+
72
+
73
+ __all__ = ("FuncHandler",)
@@ -0,0 +1,43 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputMedia
8
+
9
+
10
+ class MediaGroupReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ media: InputMedia | list[InputMedia],
14
+ *rules: ABCRule,
15
+ caption: str | list[str] | None = None,
16
+ parse_mode: str | list[str] | None = None,
17
+ final: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.media = media
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ final=final,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_media_group if not self.as_reply else message.reply_media_group
35
+ await method(
36
+ media=self.media,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("MediaGroupReplyHandler",)
@@ -0,0 +1,35 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+
8
+
9
+ class MessageReplyHandler(BaseReplyHandler):
10
+ def __init__(
11
+ self,
12
+ text: str,
13
+ *rules: ABCRule,
14
+ parse_mode: str | None = None,
15
+ final: bool = True,
16
+ as_reply: bool = False,
17
+ preset_context: Context | None = None,
18
+ **default_params: typing.Any,
19
+ ) -> None:
20
+ self.text = text
21
+ self.parse_mode = parse_mode
22
+ super().__init__(
23
+ *rules,
24
+ final=final,
25
+ as_reply=as_reply,
26
+ preset_context=preset_context,
27
+ **default_params,
28
+ )
29
+
30
+ async def handle(self, message: MessageCute) -> None:
31
+ method = message.answer if not self.as_reply else message.reply
32
+ await method(text=self.text, parse_mode=self.parse_mode, **self.default_params)
33
+
34
+
35
+ __all__ = ("MessageReplyHandler",)
@@ -0,0 +1,43 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputFile
8
+
9
+
10
+ class PhotoReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ photo: InputFile | str,
14
+ *rules: ABCRule,
15
+ caption: str | None = None,
16
+ parse_mode: str | None = None,
17
+ final: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.photo = photo
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ final=final,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_photo if not self.as_reply else message.reply_photo
35
+ await method(
36
+ photo=self.photo,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("PhotoReplyHandler",)
@@ -0,0 +1,36 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputFile
8
+
9
+
10
+ class StickerReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ sticker: InputFile | str,
14
+ *rules: ABCRule,
15
+ emoji: str | None = None,
16
+ final: bool = True,
17
+ as_reply: bool = False,
18
+ preset_context: Context | None = None,
19
+ **default_params: typing.Any,
20
+ ) -> None:
21
+ self.sticker = sticker
22
+ self.emoji = emoji
23
+ super().__init__(
24
+ *rules,
25
+ final=final,
26
+ as_reply=as_reply,
27
+ preset_context=preset_context,
28
+ **default_params,
29
+ )
30
+
31
+ async def handle(self, message: MessageCute) -> None:
32
+ method = message.answer_sticker if not self.as_reply else message.reply_sticker
33
+ await method(sticker=self.sticker, emoji=self.emoji, **self.default_params)
34
+
35
+
36
+ __all__ = ("StickerReplyHandler",)
@@ -0,0 +1,43 @@
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.dispatch.handler.base import BaseReplyHandler
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.types.objects import InputFile
8
+
9
+
10
+ class VideoReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ video: InputFile | str,
14
+ *rules: ABCRule,
15
+ caption: str | None = None,
16
+ parse_mode: str | None = None,
17
+ final: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.video = video
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ final=final,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def run(self, message: MessageCute) -> None:
34
+ method = message.answer_video if not self.as_reply else message.reply_video
35
+ await method(
36
+ video=self.video,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("VideoReplyHandler",)
@@ -0,0 +1,13 @@
1
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware, run_post_middleware, run_pre_middleware
2
+ from telegrinder.bot.dispatch.middleware.box import MiddlewareBox
3
+ from telegrinder.bot.dispatch.middleware.filter import FilterMiddleware
4
+ from telegrinder.bot.dispatch.middleware.media_group import MediaGroupMiddleware
5
+
6
+ __all__ = (
7
+ "ABCMiddleware",
8
+ "FilterMiddleware",
9
+ "MediaGroupMiddleware",
10
+ "MiddlewareBox",
11
+ "run_post_middleware",
12
+ "run_pre_middleware",
13
+ )
@@ -0,0 +1,112 @@
1
+ import typing
2
+ from abc import ABC
3
+ from functools import cached_property
4
+
5
+ from kungfu.library.monad.result import Error, Ok
6
+ from nodnod.agent.event_loop.agent import EventLoopAgent
7
+ from nodnod.error import NodeError
8
+ from nodnod.interface.node_from_function import create_node_from_function
9
+
10
+ from telegrinder.bot.dispatch.context import Context
11
+ from telegrinder.modules import logger
12
+ from telegrinder.node.compose import compose, create_composable
13
+ from telegrinder.node.utils import get_globals_from_function, get_locals_from_function
14
+ from telegrinder.tools.fullname import fullname
15
+ from telegrinder.tools.lifespan import Lifespan
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from nodnod.agent.base import Agent
19
+
20
+ from telegrinder.node.compose import Composable
21
+
22
+ type Node = typing.Any
23
+ type MiddlewareResult = bool | typing.Awaitable[bool | None] | None
24
+
25
+
26
+ async def run_pre_middleware(middleware: ABCMiddleware, context: Context) -> bool:
27
+ if middleware.is_pre:
28
+ return bool(await run_middleware(middleware, context, composable=middleware.pre_composable))
29
+ return True
30
+
31
+
32
+ async def run_post_middleware(middleware: ABCMiddleware, context: Context) -> None:
33
+ if middleware.is_post:
34
+ await run_middleware(middleware, context, composable=middleware.post_composable)
35
+
36
+
37
+ async def run_middleware(
38
+ middleware: ABCMiddleware,
39
+ context: Context,
40
+ *,
41
+ composable: Composable[typing.Any],
42
+ ) -> bool | None:
43
+ async with compose(composable, context) as result:
44
+ match result:
45
+ case Ok(response):
46
+ return response
47
+ case Error(error):
48
+ await logger.adebug(
49
+ "Middleware `{!r}` failed with error:{}\n",
50
+ middleware_name := fullname(middleware),
51
+ NodeError(f"* failed to compose middleware `{middleware_name}`", from_error=error),
52
+ )
53
+
54
+
55
+ class ABCMiddleware(ABC):
56
+ agent_cls: type[Agent] = EventLoopAgent
57
+ pre_required_nodes: typing.Mapping[str, Node] | None = None
58
+ post_required_nodes: typing.Mapping[str, Node] | None = None
59
+
60
+ def __repr__(self) -> str:
61
+ return "<{}{}>".format(
62
+ "".join(
63
+ (
64
+ f"{'pre-' if self.is_pre else ''}",
65
+ f"{'post-' if self.is_post else ''}",
66
+ ),
67
+ ),
68
+ f"middleware `{fullname(self)}`",
69
+ )
70
+
71
+ @property
72
+ def is_pre(self) -> bool:
73
+ return type(self).pre is not ABCMiddleware.pre
74
+
75
+ @property
76
+ def is_post(self) -> bool:
77
+ return type(self).post is not ABCMiddleware.post
78
+
79
+ @cached_property
80
+ def pre_composable(self) -> Composable:
81
+ return self.get_composable(self.pre, self.agent_cls, required_nodes=self.pre_required_nodes)
82
+
83
+ @cached_property
84
+ def post_composable(self) -> Composable:
85
+ return self.get_composable(self.post, self.agent_cls, required_nodes=self.post_required_nodes)
86
+
87
+ @staticmethod
88
+ def get_composable(
89
+ method: typing.Callable[..., typing.Any],
90
+ agent_cls: type[Agent] | None,
91
+ required_nodes: typing.Mapping[str, Node] | None,
92
+ ) -> Composable:
93
+ node = create_node_from_function(
94
+ method,
95
+ dependencies=required_nodes,
96
+ forward_refs=get_globals_from_function(method),
97
+ namespace=get_locals_from_function(method),
98
+ )
99
+ return create_composable(node, agent_cls=agent_cls)
100
+
101
+ def pre(self, *args: typing.Any, **kwargs: typing.Any) -> MiddlewareResult: ...
102
+
103
+ def post(self, *args: typing.Any, **kwargs: typing.Any) -> MiddlewareResult: ...
104
+
105
+ def to_lifespan(self, context: Context) -> Lifespan:
106
+ return Lifespan(
107
+ startup_tasks=[run_pre_middleware(self, context)],
108
+ shutdown_tasks=[run_post_middleware(self, context)],
109
+ )
110
+
111
+
112
+ __all__ = ("ABCMiddleware", "run_post_middleware", "run_pre_middleware")
@@ -0,0 +1,32 @@
1
+ import dataclasses
2
+ import typing
3
+ from collections import deque
4
+
5
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
6
+ from telegrinder.bot.dispatch.middleware.filter import FilterMiddleware
7
+ from telegrinder.bot.dispatch.middleware.media_group import MediaGroupMiddleware
8
+ from telegrinder.tools.singleton import Singleton
9
+
10
+
11
+ @dataclasses.dataclass(frozen=True)
12
+ class MiddlewareBox(Singleton):
13
+ filter: FilterMiddleware = dataclasses.field(default_factory=FilterMiddleware)
14
+ media_group: MediaGroupMiddleware = dataclasses.field(default_factory=MediaGroupMiddleware)
15
+ _custom_middlewares: deque[ABCMiddleware] = dataclasses.field(
16
+ default_factory=deque,
17
+ repr=False,
18
+ )
19
+
20
+ def __call__[Middleware: ABCMiddleware](self, middleware: type[Middleware], /) -> type[Middleware]:
21
+ self.put(middleware())
22
+ return middleware
23
+
24
+ def __iter__(self) -> typing.Iterator[ABCMiddleware]:
25
+ for middleware in (self.filter, self.media_group, *self._custom_middlewares):
26
+ yield middleware
27
+
28
+ def put(self, middleware: ABCMiddleware, /) -> None:
29
+ self._custom_middlewares.append(middleware)
30
+
31
+
32
+ __all__ = ("MiddlewareBox",)
@@ -0,0 +1,88 @@
1
+ import typing
2
+ from contextlib import contextmanager
3
+
4
+ from kungfu.library.monad.result import Error, Ok
5
+ from nodnod.agent.base import Agent
6
+ from nodnod.agent.event_loop.agent import EventLoopAgent
7
+ from nodnod.interface.agent_from_node import create_agent_from_node
8
+
9
+ from telegrinder.bot.dispatch.context import Context
10
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
11
+ from telegrinder.node.compose import compose
12
+
13
+ if typing.TYPE_CHECKING:
14
+ from telegrinder.bot.dispatch.process import check_rule
15
+ from telegrinder.bot.rules.abc import ABCRule
16
+
17
+ else:
18
+
19
+ def check_rule(*args: typing.Any, **kwargs: typing.Any) -> bool:
20
+ from telegrinder.bot.dispatch.process import check_rule
21
+
22
+ return check_rule(*args, **kwargs)
23
+
24
+
25
+ type AnyNode = typing.Any
26
+ type Value = typing.Any
27
+
28
+
29
+ class FilterMiddleware(ABCMiddleware):
30
+ source_filters: dict[AnyNode, tuple[type[Agent], dict[Value, tuple[ABCRule, ...]]]]
31
+ _source_node_agents: dict[AnyNode, Agent]
32
+
33
+ def __init__(self) -> None:
34
+ self.source_filters = dict()
35
+ self._source_node_agents = dict()
36
+
37
+ async def pre(self, context: Context) -> bool:
38
+ for source_node, (agent_cls, source_filter) in self.source_filters.items():
39
+ if source_node not in self._source_node_agents:
40
+ agent = self._source_node_agents[source_node] = create_agent_from_node(source_node, agent_cls=agent_cls)
41
+ else:
42
+ agent = self._source_node_agents[source_node]
43
+
44
+ async with compose(source_node, context, agent=agent) as result:
45
+ match result:
46
+ case Error(_):
47
+ return False
48
+ case Ok(value) if value not in source_filter:
49
+ return False
50
+ case _:
51
+ for filter in source_filter[result.value]:
52
+ if not await check_rule(filter, context):
53
+ return False
54
+
55
+ return True
56
+
57
+ return True
58
+
59
+ @contextmanager
60
+ def hold(
61
+ self,
62
+ source_node: AnyNode,
63
+ source_value: Value,
64
+ /,
65
+ *filters: ABCRule,
66
+ agent_cls: type[Agent] | None = None,
67
+ ) -> typing.Generator[None, typing.Any, None]:
68
+ try:
69
+ self.source_filters.setdefault(source_node, (agent_cls or EventLoopAgent, {}))[1][source_value] = filters
70
+ yield
71
+ finally:
72
+ self.release(source_node, source_value)
73
+
74
+ def release(self, source_node: AnyNode, source_value: Value, /) -> None:
75
+ source_filter = self.source_filters.get(source_node)
76
+
77
+ if source_filter is not None:
78
+ _, source_filter = source_filter
79
+ source_filter.pop(source_value, None)
80
+
81
+ if not source_filter:
82
+ self.source_filters.pop(source_node, None)
83
+ self._source_node_agents.pop(source_node, None)
84
+ else:
85
+ self._source_node_agents.pop(source_node, None)
86
+
87
+
88
+ __all__ = ("FilterMiddleware",)