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,69 @@
1
+ import asyncio
2
+ import dataclasses
3
+ import typing
4
+
5
+ from kungfu.library.monad.option import Some
6
+
7
+ from telegrinder.bot.cute_types.message import MessageCute
8
+ from telegrinder.bot.dispatch.context import Context
9
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
10
+
11
+ type MediaGroupId = str
12
+
13
+ WAIT_TIME: typing.Final = 2.0
14
+ MAX_GROUP_PARTS: typing.Final = 10
15
+
16
+
17
+ @dataclasses.dataclass(frozen=True, slots=True)
18
+ class MediaGroupData:
19
+ event: asyncio.Event
20
+ timer: asyncio.TimerHandle
21
+ messages: list[MessageCute] = dataclasses.field(default_factory=list)
22
+
23
+
24
+ class MediaGroupMiddleware(ABCMiddleware):
25
+ media_groups: dict[MediaGroupId, MediaGroupData]
26
+
27
+ def __init__(self, *, wait_time: float = WAIT_TIME) -> None:
28
+ self.wait_time = wait_time
29
+ self.media_groups = {}
30
+
31
+ async def pre(self, message: MessageCute | None, context: Context) -> bool:
32
+ if message is None:
33
+ return True
34
+
35
+ media_group_id = message.media_group_id.unwrap_or_none()
36
+
37
+ if not media_group_id:
38
+ return True
39
+
40
+ if media_group_id not in self.media_groups:
41
+ media_group_data = MediaGroupData(
42
+ event=(event := asyncio.Event()),
43
+ timer=asyncio.get_running_loop().call_later(self.wait_time, event.set),
44
+ messages=[message],
45
+ )
46
+ self.media_groups[media_group_id] = media_group_data
47
+
48
+ await event.wait()
49
+
50
+ message.media_group_messages = Some(media_group_data.messages)
51
+ self.media_groups.pop(media_group_id, None)
52
+ return True
53
+
54
+ media_group_data = self.media_groups[media_group_id]
55
+ media_group_data.messages.append(message)
56
+
57
+ if len(media_group_data.messages) >= MAX_GROUP_PARTS:
58
+ if not media_group_data.timer.cancelled():
59
+ media_group_data.timer.cancel()
60
+
61
+ if not media_group_data.event.is_set():
62
+ media_group_data.event.set()
63
+
64
+ self.media_groups.pop(media_group_id, None)
65
+
66
+ return False
67
+
68
+
69
+ __all__ = ("MediaGroupMiddleware",)
@@ -0,0 +1,93 @@
1
+ import typing
2
+
3
+ from kungfu.library.monad.result import Error, Ok, Result
4
+ from nodnod.error import NodeError
5
+
6
+ from telegrinder.api.api import API
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.bot.dispatch.middleware.abc import run_post_middleware, run_pre_middleware
9
+ from telegrinder.modules import logger
10
+ from telegrinder.node.compose import compose
11
+ from telegrinder.tools.fullname import fullname
12
+ from telegrinder.types.objects import Update
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
16
+ from telegrinder.bot.dispatch.view.base import View
17
+ from telegrinder.bot.rules.abc import ABCRule
18
+
19
+
20
+ async def process_inner(
21
+ api: API,
22
+ update: Update,
23
+ context: Context,
24
+ view: View,
25
+ ) -> Result[str, str]:
26
+ for middleware in view.middlewares:
27
+ if await run_pre_middleware(middleware, context) is False:
28
+ await logger.ainfo(
29
+ "Update(id={}, type={!r}) processed with view `{}`. Pre-middleware `{}` raised failure.",
30
+ update.update_id,
31
+ update.update_type,
32
+ view,
33
+ fullname(middleware),
34
+ )
35
+ return Error(f"Pre-middleware `{fullname(middleware)}` raised failure.")
36
+
37
+ found_handlers: list[ABCHandler] = []
38
+ responses: list[typing.Any] = []
39
+
40
+ for handler in view.handlers:
41
+ match await handler.run(api, update, context):
42
+ case Ok(response):
43
+ found_handlers.append(handler)
44
+ responses.append(response)
45
+
46
+ if view.return_manager is not None:
47
+ await view.return_manager.run(response, api, update, context)
48
+
49
+ if handler.final is True:
50
+ break
51
+ case Error(error):
52
+ await logger.adebug("Running handler `{!r}` failed with error: {}", handler, error)
53
+
54
+ context.responses = responses
55
+
56
+ for middleware in view.middlewares:
57
+ await run_post_middleware(middleware, context)
58
+
59
+ if not found_handlers:
60
+ return Error("No found corresponded handlers.")
61
+
62
+ return Ok(
63
+ f"Handler{'s' if len(found_handlers) > 1 else ''}: " + "".join(repr(handler) for handler in found_handlers)
64
+ )
65
+
66
+
67
+ async def check_rule(rule: ABCRule, context: Context) -> bool:
68
+ ctx_copy = context.copy()
69
+
70
+ for requirement in rule.requires:
71
+ if not await check_rule(requirement, ctx_copy):
72
+ return False
73
+
74
+ context |= ctx_copy
75
+
76
+ await logger.adebug(" → Checking rule `{!r}`...", rule)
77
+
78
+ async with compose(rule.composable, context) as result:
79
+ match result:
80
+ case Ok(result):
81
+ await logger.adebug(" * Rule `{!r}` is {}", rule, "ok" if result else "failed")
82
+ return result
83
+ case Error(error):
84
+ await logger.adebug(
85
+ " * Rule `{}` failed with error:{}\n",
86
+ fullname(rule),
87
+ NodeError(f"* failed to compose check of `{fullname(rule)}` rule", from_error=error),
88
+ )
89
+
90
+ return False
91
+
92
+
93
+ __all__ = ("check_rule", "process_inner")
@@ -0,0 +1,21 @@
1
+ from telegrinder.bot.dispatch.return_manager.abc import (
2
+ ABCReturnManager,
3
+ BaseReturnManager,
4
+ Manager,
5
+ register_manager,
6
+ )
7
+ from telegrinder.bot.dispatch.return_manager.callback_query import CallbackQueryReturnManager
8
+ from telegrinder.bot.dispatch.return_manager.inline_query import InlineQueryReturnManager
9
+ from telegrinder.bot.dispatch.return_manager.message import MessageReturnManager
10
+ from telegrinder.bot.dispatch.return_manager.pre_checkout_query import PreCheckoutQueryReturnManager
11
+
12
+ __all__ = (
13
+ "ABCReturnManager",
14
+ "BaseReturnManager",
15
+ "CallbackQueryReturnManager",
16
+ "InlineQueryReturnManager",
17
+ "Manager",
18
+ "MessageReturnManager",
19
+ "PreCheckoutQueryReturnManager",
20
+ "register_manager",
21
+ )
@@ -0,0 +1,107 @@
1
+ import dataclasses
2
+ import typing
3
+ from abc import ABC, abstractmethod
4
+ from functools import cached_property
5
+
6
+ from kungfu.library.monad.result import Error
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.return_manager.utils import _get_types
12
+ from telegrinder.modules import logger
13
+ from telegrinder.node.compose import compose
14
+ from telegrinder.tools import fullname
15
+ from telegrinder.types.objects import Update
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from nodnod.agent.base import Agent
19
+
20
+ type ManagerFunction = typing.Callable[..., typing.Any | typing.Awaitable[typing.Any]]
21
+
22
+
23
+ def register_manager(return_type: typing.Any, /) -> typing.Callable[[ManagerFunction], Manager]:
24
+ def wrapper(function: ManagerFunction, /) -> Manager:
25
+ function = function.__func__ if isinstance(function, classmethod | staticmethod) else function
26
+ types = _get_types(return_type)
27
+ return Manager((types,) if not isinstance(types, tuple) else types, function)
28
+
29
+ return wrapper
30
+
31
+
32
+ @dataclasses.dataclass
33
+ class Manager:
34
+ types: tuple[typing.Any, ...]
35
+ function: ManagerFunction
36
+ agent_cls: type[Agent] | None = None
37
+
38
+ async def __call__(self, response: typing.Any, context: Context) -> None:
39
+ ctx = context.copy()
40
+ ctx.handler_response = response
41
+
42
+ async with compose(
43
+ self.function,
44
+ ctx,
45
+ agent_cls=self.agent_cls,
46
+ ) as result:
47
+ match result:
48
+ case Error(error):
49
+ await logger.adebug(
50
+ "Return manager `{}` failed with error:{}",
51
+ fullname(self.function),
52
+ NodeError(f"failed to compose return manager `{fullname(self.function)}`", from_error=error),
53
+ )
54
+
55
+
56
+ class ABCReturnManager(ABC):
57
+ @property
58
+ @abstractmethod
59
+ def managers(self) -> list[Manager]:
60
+ pass
61
+
62
+ @abstractmethod
63
+ async def run(self, response: typing.Any, api: API, update: Update, context: Context) -> None:
64
+ pass
65
+
66
+
67
+ class BaseReturnManager(ABCReturnManager):
68
+ def __repr__(self) -> str:
69
+ return "<{}: {}>".format(fullname(self), self.managers)
70
+
71
+ @cached_property
72
+ def managers(self) -> list[Manager]:
73
+ return [
74
+ manager for manager in (vars(BaseReturnManager) | vars(type(self))).values() if isinstance(manager, Manager)
75
+ ]
76
+
77
+ async def run(
78
+ self,
79
+ response: typing.Any,
80
+ api: API,
81
+ update: Update,
82
+ context: Context,
83
+ ) -> None:
84
+ for manager in self.managers:
85
+ if typing.Any in manager.types or type(response) in manager.types:
86
+ await logger.adebug(
87
+ "Running manager `{}` for response of type `{}`",
88
+ fullname(manager.function),
89
+ fullname(response),
90
+ )
91
+ await manager(response, context)
92
+
93
+ def register_manager(self, return_type: typing.Any, /) -> typing.Callable[[ManagerFunction], Manager]:
94
+ def wrapper(function: ManagerFunction, /) -> Manager:
95
+ manager = register_manager(return_type)(function)
96
+ self.managers.append(manager)
97
+ return manager
98
+
99
+ return wrapper
100
+
101
+
102
+ __all__ = (
103
+ "ABCReturnManager",
104
+ "BaseReturnManager",
105
+ "Manager",
106
+ "register_manager",
107
+ )
@@ -0,0 +1,19 @@
1
+ import typing
2
+
3
+ from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
4
+ from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class CallbackQueryReturnManager(BaseReturnManager):
8
+ @register_manager(str)
9
+ @staticmethod
10
+ async def str_manager(handler_response: str, event: CallbackQueryCute) -> None:
11
+ await event.answer(handler_response)
12
+
13
+ @register_manager(dict)
14
+ @staticmethod
15
+ async def dict_manager(handler_response: dict[str, typing.Any], event: CallbackQueryCute) -> None:
16
+ await event.answer(**handler_response)
17
+
18
+
19
+ __all__ = ("CallbackQueryReturnManager",)
@@ -0,0 +1,14 @@
1
+ import typing
2
+
3
+ from telegrinder.bot.cute_types.inline_query import InlineQueryCute
4
+ from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class InlineQueryReturnManager(BaseReturnManager):
8
+ @register_manager(dict)
9
+ @staticmethod
10
+ async def dict_manager(handler_response: dict[str, typing.Any], event: InlineQueryCute) -> None:
11
+ await event.answer(**handler_response)
12
+
13
+
14
+ __all__ = ("InlineQueryReturnManager",)
@@ -0,0 +1,34 @@
1
+ import typing
2
+
3
+ from telegrinder.bot.cute_types.message import MessageCute
4
+ from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
5
+ from telegrinder.tools.formatting import HTML
6
+
7
+
8
+ class MessageReturnManager(BaseReturnManager):
9
+ @register_manager(str)
10
+ @staticmethod
11
+ async def str_manager(handler_response: str, event: MessageCute) -> None:
12
+ await event.answer(handler_response)
13
+
14
+ @register_manager(list | tuple)
15
+ @staticmethod
16
+ async def seq_manager(
17
+ handler_response: list[typing.Any] | tuple[typing.Any, ...],
18
+ event: MessageCute,
19
+ ) -> None:
20
+ for message in handler_response:
21
+ await event.answer(str(message))
22
+
23
+ @register_manager(dict)
24
+ @staticmethod
25
+ async def dict_manager(handler_response: dict[str, typing.Any], event: MessageCute) -> None:
26
+ await event.answer(**handler_response)
27
+
28
+ @register_manager(HTML)
29
+ @staticmethod
30
+ async def html_manager(handler_response: HTML, event: MessageCute) -> None:
31
+ await event.answer(handler_response, parse_mode=HTML.PARSE_MODE)
32
+
33
+
34
+ __all__ = ("MessageReturnManager",)
@@ -0,0 +1,19 @@
1
+ import typing
2
+
3
+ from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
4
+ from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class PreCheckoutQueryReturnManager(BaseReturnManager):
8
+ @register_manager(bool)
9
+ @staticmethod
10
+ async def bool_manager(handler_response: bool, event: PreCheckoutQueryCute) -> None:
11
+ await event.answer(handler_response)
12
+
13
+ @register_manager(dict)
14
+ @staticmethod
15
+ async def dict_manager(handler_response: dict[str, typing.Any], event: PreCheckoutQueryCute) -> None:
16
+ await event.answer(**handler_response)
17
+
18
+
19
+ __all__ = ("PreCheckoutQueryReturnManager",)
@@ -0,0 +1,20 @@
1
+ import types
2
+ import typing
3
+
4
+
5
+ def _get_types(x: typing.Any, /) -> type[typing.Any] | tuple[typing.Any, ...]:
6
+ while True:
7
+ if isinstance(x, types.UnionType | typing._UnionGenericAlias): # type: ignore
8
+ return tuple(_get_types(x) for x in typing.get_args(x))
9
+
10
+ if isinstance(x, typing.TypeAliasType):
11
+ x = x.__value__
12
+
13
+ if isinstance(x, types.GenericAlias | typing._GenericAlias): # type: ignore
14
+ x = typing.get_origin(x)
15
+
16
+ if isinstance(x, type):
17
+ return x
18
+
19
+
20
+ __all__ = ("_get_types",)
@@ -0,0 +1,4 @@
1
+ from telegrinder.bot.dispatch.router.abc import ABCRouter
2
+ from telegrinder.bot.dispatch.router.base import Router
3
+
4
+ __all__ = ("ABCRouter", "Router")
@@ -0,0 +1,15 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.types.objects import Update
7
+
8
+
9
+ class ABCRouter(ABC):
10
+ @abstractmethod
11
+ async def route(self, api: API, update: Update, context: Context) -> typing.Any:
12
+ pass
13
+
14
+
15
+ __all__ = ("ABCRouter",)
@@ -0,0 +1,154 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from kungfu.library.monad.result import Error as Err
5
+ from kungfu.library.monad.result import Ok
6
+
7
+ from telegrinder.api.api import API
8
+ from telegrinder.bot.dispatch.context import Context
9
+ from telegrinder.bot.dispatch.router.abc import ABCRouter
10
+ from telegrinder.bot.dispatch.view.box import ViewBox
11
+ from telegrinder.modules import logger
12
+ from telegrinder.tools.magic.inspect import get_frame_module_name
13
+ from telegrinder.types.objects import Update
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from telegrinder.bot.dispatch.view.base import ErrorView, EventView, RawEventView, View
17
+ from telegrinder.bot.dispatch.view.media_group import MediaGroupView
18
+
19
+
20
+ @dataclasses.dataclass(kw_only=True)
21
+ class Router[
22
+ MessageView: EventView = EventView,
23
+ EditedMessageView: EventView = EventView,
24
+ ChannelPostView: EventView = EventView,
25
+ EditedChannelPostView: EventView = EventView,
26
+ BusinessConnectionView: EventView = EventView,
27
+ BusinessMessageView: EventView = EventView,
28
+ EditedBusinessMessageView: EventView = EventView,
29
+ DeletedBusinessMessagesView: EventView = EventView,
30
+ MessageReactionView: EventView = EventView,
31
+ MessageReactionCountView: EventView = EventView,
32
+ InlineQueryView: EventView = EventView,
33
+ ChosenInlineResultView: EventView = EventView,
34
+ CallbackQueryView: EventView = EventView,
35
+ ShippingQueryView: EventView = EventView,
36
+ PreCheckoutQueryView: EventView = EventView,
37
+ PurchasedPaidMediaView: EventView = EventView,
38
+ PollView: EventView = EventView,
39
+ PollAnswerView: EventView = EventView,
40
+ MyChatMemberView: EventView = EventView,
41
+ ChatMemberView: EventView = EventView,
42
+ ChatJoinRequestView: EventView = EventView,
43
+ ChatBoostView: EventView = EventView,
44
+ RemovedChatBoostView: EventView = EventView,
45
+ MediaGroup: View = MediaGroupView,
46
+ Error: ErrorView = ErrorView,
47
+ RawEvent: RawEventView = RawEventView,
48
+ ](
49
+ ABCRouter,
50
+ ViewBox[
51
+ MessageView,
52
+ EditedMessageView,
53
+ ChannelPostView,
54
+ EditedChannelPostView,
55
+ BusinessConnectionView,
56
+ BusinessMessageView,
57
+ EditedBusinessMessageView,
58
+ DeletedBusinessMessagesView,
59
+ MessageReactionView,
60
+ MessageReactionCountView,
61
+ InlineQueryView,
62
+ ChosenInlineResultView,
63
+ CallbackQueryView,
64
+ ShippingQueryView,
65
+ PreCheckoutQueryView,
66
+ PurchasedPaidMediaView,
67
+ PollView,
68
+ PollAnswerView,
69
+ MyChatMemberView,
70
+ ChatMemberView,
71
+ ChatJoinRequestView,
72
+ ChatBoostView,
73
+ RemovedChatBoostView,
74
+ MediaGroup,
75
+ Error,
76
+ RawEvent,
77
+ ],
78
+ ):
79
+ def __post_init__(self) -> None:
80
+ self.name = ":".join((get_frame_module_name(), self.__class__.__name__, hex(id(self))))
81
+
82
+ def __repr__(self) -> str:
83
+ return f"<{self.name}>"
84
+
85
+ def __hash__(self) -> int:
86
+ return hash(self.name)
87
+
88
+ def __bool__(self) -> bool:
89
+ return any(self.views.values()) or bool(self.raw) or bool(self.event_error)
90
+
91
+ async def route_view(self, view: View, api: API, update: Update, context: Context) -> bool:
92
+ # Check if the view is applicable to the update
93
+
94
+ await logger.adebug(
95
+ "Checking view `{!r}` from router `{!r}` for update (id={}, type={!r})...",
96
+ view,
97
+ self,
98
+ update.update_id,
99
+ update.update_type,
100
+ )
101
+
102
+ match await view.check(api, update, context):
103
+ case Ok(_):
104
+ await logger.adebug(
105
+ "View `{!r}` from router `{!r}` for update (id={}, type={!r}) is happy, processing...",
106
+ view,
107
+ self,
108
+ update.update_id,
109
+ update.update_type,
110
+ )
111
+ result = await view.process(api, update, context)
112
+
113
+ match result:
114
+ case Err(error) if isinstance(error, Exception):
115
+ raise error from None
116
+
117
+ await logger.ainfo(
118
+ "Update(id={}, type={!r}) processed with view `{!r}` from router `{!r}`. {}",
119
+ update.update_id,
120
+ update.update_type,
121
+ view,
122
+ self,
123
+ result.error if not result else result.value,
124
+ )
125
+ return bool(result)
126
+ case Err(error):
127
+ await logger.adebug(
128
+ "Checking view `{!r}` from router `{!r}` for update (id={}, type={!r}) failed: {}",
129
+ view,
130
+ self,
131
+ update.update_id,
132
+ update.update_type,
133
+ error,
134
+ )
135
+
136
+ return False
137
+
138
+ async def route(self, api: API, update: Update, context: Context) -> bool:
139
+ try:
140
+ # Filtering non-empty views
141
+ for view in filter(None, self.views.values()):
142
+ # Route the non-empty view
143
+ if await self.route_view(view, api, update, context):
144
+ return True
145
+
146
+ return False
147
+ except Exception as exception:
148
+ if self.event_error:
149
+ context.exceptions_update[self] = exception # type: ignore
150
+
151
+ raise
152
+
153
+
154
+ __all__ = ("Router",)
@@ -0,0 +1,15 @@
1
+ from telegrinder.bot.dispatch.view.abc import ABCView
2
+ from telegrinder.bot.dispatch.view.base import ErrorView, EventModelView, EventView, RawEventView, View
3
+ from telegrinder.bot.dispatch.view.box import ViewBox
4
+ from telegrinder.bot.dispatch.view.media_group import MediaGroupView
5
+
6
+ __all__ = (
7
+ "ABCView",
8
+ "ErrorView",
9
+ "EventModelView",
10
+ "EventView",
11
+ "MediaGroupView",
12
+ "RawEventView",
13
+ "View",
14
+ "ViewBox",
15
+ )
@@ -0,0 +1,15 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.types.objects import Update
7
+
8
+
9
+ class ABCView(ABC):
10
+ @abstractmethod
11
+ async def process(self, api: API, update: Update, context: Context) -> typing.Any:
12
+ pass
13
+
14
+
15
+ __all__ = ("ABCView",)