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