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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- 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",)
|