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,99 @@
1
+ import typing
2
+
3
+ from kungfu.library import Error, Ok, Result, unwrapping
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.handler.func import FuncHandler
9
+ from telegrinder.bot.dispatch.process import check_rule
10
+ from telegrinder.bot.rules.abc import ABCRule, Always, AndRule
11
+ from telegrinder.modules import logger
12
+ from telegrinder.node.compose import compose
13
+ from telegrinder.tools import fullname
14
+ from telegrinder.types.objects import Update
15
+
16
+ if typing.TYPE_CHECKING:
17
+ from nodnod.agent.base import Agent
18
+
19
+ type Handler = typing.Callable[..., typing.Any]
20
+ type ActionFunction = typing.Callable[..., ActionFunctionResult]
21
+ type ActionFunctionResult = typing.Union[
22
+ typing.AsyncGenerator[typing.Any, typing.Any],
23
+ typing.Awaitable[typing.Any],
24
+ typing.Generator[typing.Any, typing.Any, typing.Any],
25
+ typing.Any,
26
+ ]
27
+
28
+
29
+ @unwrapping
30
+ async def run_action_function[T: Handler](
31
+ func_handler: FuncHandler[T],
32
+ function: ActionFunction,
33
+ api: API,
34
+ update: Update,
35
+ context: Context,
36
+ agent: type[Agent] | None = None,
37
+ ) -> Result[typing.Any, str]:
38
+ async with compose(
39
+ function,
40
+ context,
41
+ agent_cls=agent,
42
+ ) as result:
43
+ match result:
44
+ case Ok():
45
+ return await func_handler.run(api, update, context)
46
+ case Error(error):
47
+ return Error(
48
+ "{}\n".format(
49
+ NodeError(f"* failed to compose action function `{fullname(function)}`", from_error=error),
50
+ ),
51
+ )
52
+
53
+
54
+ def action(function: ActionFunction, agent: type[Agent] | None = None) -> Action:
55
+ return Action(function, agent=agent)
56
+
57
+
58
+ class Action:
59
+ _on: ABCRule
60
+
61
+ def __init__(self, function: ActionFunction, agent: type[Agent] | None = None) -> None:
62
+ self.function = function
63
+ self.agent = agent
64
+ self._on = Always()
65
+
66
+ def on(self, *rules: ABCRule) -> typing.Self:
67
+ self._on &= AndRule(*rules)
68
+ return self
69
+
70
+ def __call__[T: Handler](self, handler: T) -> T:
71
+ func_handler = FuncHandler(function=handler, agent=self.agent)
72
+
73
+ async def action_wrapper(api: API, update: Update, context: Context) -> typing.Any:
74
+ if not await check_rule(self._on, context):
75
+ await logger.adebug("Action rule `{!r}` failed.", self._on)
76
+ result = await func_handler.run(api, update, context)
77
+ else:
78
+ result = await run_action_function(
79
+ func_handler,
80
+ self.function,
81
+ api,
82
+ update,
83
+ context,
84
+ agent=self.agent,
85
+ )
86
+
87
+ match result:
88
+ case Ok(value):
89
+ return value
90
+ case Error(error):
91
+ await logger.adebug(error)
92
+ return None
93
+
94
+ action_wrapper.__name__ = f"<action for {handler.__name__}>"
95
+ action_wrapper.__qualname__ = f"<action for {handler.__qualname__}>"
96
+ return action_wrapper # type: ignore
97
+
98
+
99
+ __all__ = ("Action", "action")
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from reprlib import recursive_repr
5
+
6
+ from kungfu.library.monad.option import NOTHING, Option, Some
7
+ from nodnod.interface.node_from_function import Externals
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from nodnod.scope import Scope
11
+
12
+ from telegrinder.api.api import API
13
+ from telegrinder.bot.cute_types.update import UpdateCute
14
+ from telegrinder.bot.dispatch.router.base import Router
15
+ from telegrinder.types.objects import Update
16
+
17
+ type Key = str
18
+ type AnyValue = typing.Any
19
+
20
+
21
+ class Context(Externals):
22
+ """Low level per event context storage."""
23
+
24
+ SELF_CONTEXT_KEYS: typing.Final = frozenset(("context", "ctx"))
25
+
26
+ api: API
27
+ update: Update
28
+ raw_update: Update
29
+ update_cute: UpdateCute
30
+ per_event_scope: Scope
31
+ exceptions_update: dict[Router, Exception] = {}
32
+ exception_update: Option[Exception] = NOTHING
33
+
34
+ @typing.overload
35
+ def __init__(self) -> None: ...
36
+
37
+ @typing.overload
38
+ def __init__(self, map: typing.Mapping[Key, AnyValue], /) -> None: ...
39
+
40
+ @typing.overload
41
+ def __init__(self, **kwargs: AnyValue) -> None: ...
42
+
43
+ def __init__(
44
+ self,
45
+ map: typing.Mapping[Key, AnyValue] | None = None,
46
+ **kwargs: AnyValue,
47
+ ) -> None:
48
+ Externals.__init__(self, map or kwargs)
49
+
50
+ @recursive_repr()
51
+ def __repr__(self) -> str:
52
+ return "{}({})".format(
53
+ type(self).__name__,
54
+ ", ".join(f"{k}={repr(v) if v is not self else '<self>'}" for k, v in self.items()),
55
+ )
56
+
57
+ def __setitem__(self, __key: Key, __value: AnyValue) -> None:
58
+ Externals.__setitem__(self, __key, __value)
59
+
60
+ def __getitem__(self, __key: Key) -> AnyValue:
61
+ if __key in type(self).SELF_CONTEXT_KEYS:
62
+ return self
63
+ return Externals.__getitem__(self, __key)
64
+
65
+ def __delitem__(self, __key: Key) -> None:
66
+ Externals.__delitem__(self, __key)
67
+
68
+ def __setattr__(self, __name: str, __value: AnyValue) -> None:
69
+ self.__setitem__(__name, __value)
70
+
71
+ def __getattribute__(self, __name: str) -> AnyValue:
72
+ if __name in type(self).SELF_CONTEXT_KEYS:
73
+ return self
74
+
75
+ if __name in _CONTEXT_CLASS_ATTRS and not Externals.__contains__(self, __name):
76
+ return super().__getattribute__(__name)
77
+
78
+ return self[__name]
79
+
80
+ def __delattr__(self, __name: str) -> None:
81
+ self.__delitem__(__name)
82
+
83
+ def __contains__(self, __key: object) -> bool:
84
+ if __key in type(self).SELF_CONTEXT_KEYS:
85
+ return True
86
+ return Externals.__contains__(self, __key)
87
+
88
+ def __or__(self, other: object, /) -> typing.Self:
89
+ if type(other) is not Context and not isinstance(other, dict):
90
+ return NotImplemented
91
+
92
+ new_context = type(self)(self)
93
+ new_context |= other
94
+ return new_context
95
+
96
+ def __ior__(self, other: object, /) -> typing.Self:
97
+ if type(other) is not Context and not isinstance(other, dict):
98
+ raise TypeError(f"Cannot update `Context` with `{type(other).__name__}`.")
99
+
100
+ for key, value in other.items():
101
+ self[key] = value
102
+
103
+ return self
104
+
105
+ def as_dict(self) -> dict[Key, AnyValue]:
106
+ return {key: value for key, value in Externals.items(self)}
107
+
108
+ def add_roots(
109
+ self,
110
+ api: API,
111
+ update: Update,
112
+ per_event_scope: Scope,
113
+ /,
114
+ ) -> typing.Self:
115
+ from telegrinder.bot.cute_types.update import UpdateCute
116
+
117
+ for key, value in {
118
+ "api": api,
119
+ "raw_update": update,
120
+ "update": update,
121
+ "update_cute": UpdateCute.from_update(update, bound_api=api),
122
+ "per_event_scope": per_event_scope,
123
+ }.items():
124
+ self[key] = value
125
+
126
+ return self
127
+
128
+ def add_exception_update(self, exception_update: Exception, /) -> typing.Self:
129
+ self.exception_update = Some(exception_update)
130
+ return self
131
+
132
+ def copy(self) -> typing.Self:
133
+ return type(self)(self)
134
+
135
+ def set(self, key: Key, value: AnyValue) -> None:
136
+ self[key] = value
137
+
138
+ @typing.overload
139
+ def get(self, key: Key) -> AnyValue | None: ...
140
+
141
+ @typing.overload
142
+ def get[T](self, key: Key, default: T) -> T | AnyValue: ...
143
+
144
+ @typing.overload
145
+ def get(self, key: Key, default: None = None) -> AnyValue | None: ...
146
+
147
+ def get[T](self, key: Key, default: T | None = None) -> T | AnyValue | None:
148
+ return dict.get(self, key, default)
149
+
150
+ def get_or_set[T](self, key: Key, default: T) -> T:
151
+ if key not in self:
152
+ self.set(key, default)
153
+ return self.get(key, default)
154
+
155
+ def delete(self, key: Key) -> None:
156
+ del self[key]
157
+
158
+
159
+ _CONTEXT_CLASS_ATTRS = frozenset(Context.__dict__ | dict.__dict__ | object.__dict__)
160
+
161
+
162
+ __all__ = ("Context",)
@@ -0,0 +1,362 @@
1
+ import typing
2
+ from collections import deque
3
+ from functools import cached_property
4
+
5
+ from nodnod.interface.inject import inject_internals
6
+
7
+ from telegrinder.api.api import API
8
+ from telegrinder.bot.dispatch.abc import ABCDispatch
9
+ from telegrinder.bot.dispatch.context import Context
10
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware, run_post_middleware, run_pre_middleware
11
+ from telegrinder.bot.dispatch.middleware.box import MiddlewareBox
12
+ from telegrinder.bot.dispatch.router.base import Router
13
+ from telegrinder.bot.dispatch.view.base import ErrorView, View
14
+ from telegrinder.bot.dispatch.view.box import ViewBox
15
+ from telegrinder.modules import logger
16
+ from telegrinder.node.scope import PER_EVENT
17
+ from telegrinder.tools.fullname import fullname
18
+ from telegrinder.tools.global_context import TelegrinderContext
19
+ from telegrinder.types.objects import Update
20
+
21
+ if typing.TYPE_CHECKING:
22
+ from vbml.patcher.abc import ABCPatcher
23
+
24
+ from telegrinder.bot.dispatch.view.base import EventView, RawEventView, View
25
+ from telegrinder.bot.dispatch.view.media_group import MediaGroupView
26
+
27
+ NANOSECONDS_PER_MILLISECOND: typing.Final = 1_000_000_000
28
+
29
+
30
+ class ViewGetter:
31
+ main_router: Router
32
+
33
+ def __getattr__(self, name: str, /) -> typing.Any:
34
+ if name in ViewBox.__dataclass_fields__ or name in ViewBox.__dict__:
35
+ return getattr(self.main_router, name)
36
+ return super().__getattribute__(name)
37
+
38
+
39
+ class Dispatch[
40
+ T: MiddlewareBox = MiddlewareBox,
41
+ ErrorHandler: ErrorView = ErrorView,
42
+ MessageView: EventView = EventView,
43
+ EditedMessageView: EventView = EventView,
44
+ ChannelPostView: EventView = EventView,
45
+ EditedChannelPostView: EventView = EventView,
46
+ BusinessConnectionView: EventView = EventView,
47
+ BusinessMessageView: EventView = EventView,
48
+ EditedBusinessMessageView: EventView = EventView,
49
+ DeletedBusinessMessagesView: EventView = EventView,
50
+ MessageReactionView: EventView = EventView,
51
+ MessageReactionCountView: EventView = EventView,
52
+ InlineQueryView: EventView = EventView,
53
+ ChosenInlineResultView: EventView = EventView,
54
+ CallbackQueryView: EventView = EventView,
55
+ ShippingQueryView: EventView = EventView,
56
+ PreCheckoutQueryView: EventView = EventView,
57
+ PurchasedPaidMediaView: EventView = EventView,
58
+ PollView: EventView = EventView,
59
+ PollAnswerView: EventView = EventView,
60
+ MyChatMemberView: EventView = EventView,
61
+ ChatMemberView: EventView = EventView,
62
+ ChatJoinRequestView: EventView = EventView,
63
+ ChatBoostView: EventView = EventView,
64
+ RemovedChatBoostView: EventView = EventView,
65
+ MediaGroup: View = MediaGroupView,
66
+ EventError: ErrorView = ErrorView,
67
+ RawEvent: RawEventView = RawEventView,
68
+ ](
69
+ ABCDispatch,
70
+ ViewBox[
71
+ MessageView,
72
+ EditedMessageView,
73
+ ChannelPostView,
74
+ EditedChannelPostView,
75
+ BusinessConnectionView,
76
+ BusinessMessageView,
77
+ EditedBusinessMessageView,
78
+ DeletedBusinessMessagesView,
79
+ MessageReactionView,
80
+ MessageReactionCountView,
81
+ InlineQueryView,
82
+ ChosenInlineResultView,
83
+ CallbackQueryView,
84
+ ShippingQueryView,
85
+ PreCheckoutQueryView,
86
+ PurchasedPaidMediaView,
87
+ PollView,
88
+ PollAnswerView,
89
+ MyChatMemberView,
90
+ ChatMemberView,
91
+ ChatJoinRequestView,
92
+ ChatBoostView,
93
+ RemovedChatBoostView,
94
+ MediaGroup,
95
+ EventError,
96
+ RawEvent,
97
+ ]
98
+ if typing.TYPE_CHECKING
99
+ else ViewGetter,
100
+ ):
101
+ type MainRouter = Router[
102
+ MessageView,
103
+ EditedMessageView,
104
+ ChannelPostView,
105
+ EditedChannelPostView,
106
+ BusinessConnectionView,
107
+ BusinessMessageView,
108
+ EditedBusinessMessageView,
109
+ DeletedBusinessMessagesView,
110
+ MessageReactionView,
111
+ MessageReactionCountView,
112
+ InlineQueryView,
113
+ ChosenInlineResultView,
114
+ CallbackQueryView,
115
+ ShippingQueryView,
116
+ PreCheckoutQueryView,
117
+ PurchasedPaidMediaView,
118
+ PollView,
119
+ PollAnswerView,
120
+ MyChatMemberView,
121
+ ChatMemberView,
122
+ ChatJoinRequestView,
123
+ ChatBoostView,
124
+ RemovedChatBoostView,
125
+ MediaGroup,
126
+ EventError,
127
+ RawEvent,
128
+ ]
129
+
130
+ main_router: MainRouter
131
+ error_handler: ErrorHandler
132
+ middlewares: T
133
+ _routers: deque[Router] | None = None
134
+
135
+ @typing.overload
136
+ def __init__(self) -> None: ...
137
+
138
+ @typing.overload
139
+ def __init__(self, *, router: MainRouter) -> None: ...
140
+
141
+ @typing.overload
142
+ def __init__(self, *, middleware_box: T) -> None: ...
143
+
144
+ @typing.overload
145
+ def __init__(self, *, router: MainRouter, middleware_box: T) -> None: ...
146
+
147
+ def __init__(
148
+ self,
149
+ *,
150
+ router: MainRouter | None = None,
151
+ error_handler: ErrorHandler | None = None,
152
+ middleware_box: MiddlewareBox | None = None,
153
+ ) -> None:
154
+ self.main_router = router or Router() # type: ignore
155
+ self.error_handler = error_handler or ErrorView() # type: ignore
156
+ self.global_context = TelegrinderContext()
157
+ self.global_scope = self.global_context.node_global_scope
158
+ self.loop_wrapper = self.global_context.loop_wrapper
159
+ self.middlewares = self.global_context.setdefault_value("middleware_box", middleware_box or MiddlewareBox())
160
+
161
+ def __setitem__(self, injection_type: typing.Any, injection_value: typing.Any, /) -> None:
162
+ self.global_scope.inject(injection_type, injection_value)
163
+
164
+ @property
165
+ def routers(self) -> deque[Router]:
166
+ if self._routers is None:
167
+ self._routers = deque((self.main_router,) if self.main_router else ()) # type: ignore
168
+ return self._routers # type: ignore
169
+
170
+ @cached_property
171
+ def raw_views(self) -> tuple[View, ...]:
172
+ return tuple(filter(None, (router.raw for router in self.routers)))
173
+
174
+ @property
175
+ def patcher(self) -> ABCPatcher:
176
+ """Alias `patcher` to get a vbml patcher from the global context."""
177
+ return self.global_context.vbml_patcher
178
+
179
+ @property
180
+ def register_middleware[Middleware: ABCMiddleware](self) -> typing.Callable[[type[Middleware]], type[Middleware]]:
181
+ """Decorator to register a custom middleware in the dispatch's middleware box."""
182
+ return self.middlewares.__call__
183
+
184
+ async def _handle_exceptions(
185
+ self,
186
+ api: API,
187
+ update: Update,
188
+ context: Context,
189
+ exceptions: tuple[BaseException | BaseExceptionGroup[BaseException], ...],
190
+ ) -> None:
191
+ unhandled_exceptions: list[BaseException] = []
192
+
193
+ try:
194
+ async with self.loop_wrapper.create_task_group() as task_group:
195
+ for exception in exceptions:
196
+ if isinstance(exception, BaseExceptionGroup):
197
+ task_group.create_task(self._handle_exceptions(api, update, context, exception.exceptions))
198
+ elif isinstance(exception, Exception):
199
+ task_group.create_task(
200
+ self.main_router.route_view(
201
+ self.error_handler,
202
+ api,
203
+ update,
204
+ context.copy().add_exception_update(exception),
205
+ ),
206
+ )
207
+ else:
208
+ unhandled_exceptions.append(exception)
209
+ except BaseExceptionGroup as group:
210
+ raise BaseExceptionGroup(
211
+ "Unhandled exception groups:",
212
+ [group, BaseExceptionGroup("Unhandled exceptions:", unhandled_exceptions)],
213
+ )
214
+
215
+ if unhandled_exceptions:
216
+ raise BaseExceptionGroup("Unhandled exceptions:", unhandled_exceptions)
217
+
218
+ async def _process_views(
219
+ self,
220
+ views: typing.Iterable[View],
221
+ api: API,
222
+ update: Update,
223
+ context: Context,
224
+ ) -> bool:
225
+ async with self.loop_wrapper.create_task_group() as task_group:
226
+ for view in views:
227
+ task_group.create_task(self.main_router.route_view(view, api, update, context))
228
+
229
+ return any(task_group.results())
230
+
231
+ async def _process_update_exceptions(
232
+ self,
233
+ api: API,
234
+ update: Update,
235
+ context: Context,
236
+ ) -> None:
237
+ await logger.adebug(
238
+ "Processing error views with exceptions [{}] for update (id={}, type={!r})",
239
+ ", ".join(f"{type(e).__name__}" for e in context.exceptions_update.values()),
240
+ update.update_id,
241
+ update.update_type,
242
+ )
243
+
244
+ async with self.loop_wrapper.create_task_group() as task_group:
245
+ for router, exception in context.exceptions_update.items():
246
+ await logger.adebug(
247
+ "Routing exception update (id={}, type={!r}) to router `{!r}`",
248
+ update.update_id,
249
+ update.update_type,
250
+ router,
251
+ )
252
+ task_group.create_task(
253
+ router.route_view(
254
+ router.event_error,
255
+ api,
256
+ update,
257
+ context.copy().add_exception_update(exception),
258
+ ),
259
+ )
260
+
261
+ async def _route_update(self, api: API, update: Update, context: Context) -> bool:
262
+ async with self.loop_wrapper.create_task_group() as task_group:
263
+ for router in self.routers:
264
+ await logger.adebug(
265
+ "Routing update (id={}, type={!r}) to router `{!r}`",
266
+ update.update_id,
267
+ update.update_type,
268
+ router,
269
+ )
270
+ task_group.create_task(router.route(api, update, context))
271
+
272
+ return any(task_group.results())
273
+
274
+ async def feed(self, api: API, update: Update) -> None:
275
+ await logger.ainfo(
276
+ "New Update(id={}, type={!r}) received by bot (id={})",
277
+ update.update_id,
278
+ update.update_type,
279
+ api.id,
280
+ )
281
+
282
+ per_event_scope = self.global_scope.create_child(detail=PER_EVENT)
283
+ context = Context().add_roots(api, update, per_event_scope)
284
+
285
+ inject_internals(per_event_scope, {API: api, Update: update})
286
+
287
+ failed = False
288
+ middlewares = self.middlewares
289
+ start_time = self.loop_wrapper.time
290
+
291
+ try:
292
+ for middleware in middlewares:
293
+ if await run_pre_middleware(middleware, context) is not True:
294
+ await logger.ainfo(
295
+ "Update(id={}, type={!r}) processed with dispatch's pre-middleware `{}` and raised failure.",
296
+ update.update_id,
297
+ update.update_type,
298
+ fullname(middleware),
299
+ )
300
+ return
301
+
302
+ if not self.routers:
303
+ await logger.adebug(
304
+ "No corresponding routers from dispatch found for update (id={}, type={!r}).",
305
+ update.update_id,
306
+ update.update_type,
307
+ )
308
+ elif not await self._route_update(api, update, context) and self.raw_views:
309
+ await self._process_views(self.raw_views, api, update, context)
310
+
311
+ for middleware in middlewares:
312
+ await run_post_middleware(middleware, context)
313
+ except BaseException as exc:
314
+ failed = True
315
+
316
+ if context.exceptions_update:
317
+ try:
318
+ await self._process_update_exceptions(api, update, context)
319
+ except BaseExceptionGroup as group:
320
+ if not self.error_handler:
321
+ raise
322
+
323
+ await logger.adebug(
324
+ "Dispatch caught unhandled exceptions while processing update (id={}, type={!r}), routing to error handler...",
325
+ update.update_id,
326
+ update.update_type,
327
+ )
328
+ await self._handle_exceptions(api, update, context, group.exceptions)
329
+
330
+ return
331
+
332
+ if isinstance(exc, Exception) and self.error_handler:
333
+ await logger.adebug(
334
+ "Dispatch caught an exception while processing update (id={}, type={!r}), routing to error handler...",
335
+ update.update_id,
336
+ update.update_type,
337
+ )
338
+ await self.main_router.route_view(self.error_handler, api, update, context.add_exception_update(exc))
339
+ return
340
+
341
+ raise
342
+ finally:
343
+ await per_event_scope.close()
344
+
345
+ if not failed:
346
+ elapsed_time = self.loop_wrapper.time - start_time
347
+ elapsed_ms = elapsed_time * 1000
348
+ await logger.adebug(
349
+ "Update (id={}, type={!r}) processed in {} {} by bot (id={})",
350
+ update.update_id,
351
+ update.update_type,
352
+ int(elapsed_time * NANOSECONDS_PER_MILLISECOND) if elapsed_ms < 1 else int(elapsed_ms),
353
+ "ns" if elapsed_ms < 1 else "ms",
354
+ api.id,
355
+ )
356
+
357
+ def load(self, external: typing.Self) -> None:
358
+ self.routers.extend(filter(None, external.routers))
359
+ self.error_handler.load(external.error_handler)
360
+
361
+
362
+ __all__ = ("Dispatch",)
@@ -0,0 +1,23 @@
1
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
2
+ from telegrinder.bot.dispatch.handler.audio_reply import AudioReplyHandler
3
+ from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
4
+ from telegrinder.bot.dispatch.handler.document_reply import DocumentReplyHandler
5
+ from telegrinder.bot.dispatch.handler.func import FuncHandler
6
+ from telegrinder.bot.dispatch.handler.media_group_reply import MediaGroupReplyHandler
7
+ from telegrinder.bot.dispatch.handler.message_reply import MessageReplyHandler
8
+ from telegrinder.bot.dispatch.handler.photo_reply import PhotoReplyHandler
9
+ from telegrinder.bot.dispatch.handler.sticker_reply import StickerReplyHandler
10
+ from telegrinder.bot.dispatch.handler.video_reply import VideoReplyHandler
11
+
12
+ __all__ = (
13
+ "ABCHandler",
14
+ "AudioReplyHandler",
15
+ "BaseReplyHandler",
16
+ "DocumentReplyHandler",
17
+ "FuncHandler",
18
+ "MediaGroupReplyHandler",
19
+ "MessageReplyHandler",
20
+ "PhotoReplyHandler",
21
+ "StickerReplyHandler",
22
+ "VideoReplyHandler",
23
+ )
@@ -0,0 +1,25 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ from kungfu.library.monad.result import Result
5
+
6
+ from telegrinder.api import API
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class ABCHandler(ABC):
12
+ final: bool
13
+
14
+ @abstractmethod
15
+ async def run(
16
+ self,
17
+ api: API,
18
+ update: Update,
19
+ context: Context,
20
+ check: bool = True,
21
+ ) -> Result[typing.Any, typing.Any]:
22
+ pass
23
+
24
+
25
+ __all__ = ("ABCHandler",)