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,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,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",)
|