telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.1__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.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +30 -31
- telegrinder/api/__init__.py +2 -1
- telegrinder/api/api.py +28 -20
- telegrinder/api/error.py +8 -4
- telegrinder/api/response.py +2 -2
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +6 -0
- telegrinder/bot/bot.py +38 -31
- telegrinder/bot/cute_types/__init__.py +2 -0
- telegrinder/bot/cute_types/base.py +55 -129
- telegrinder/bot/cute_types/callback_query.py +76 -61
- telegrinder/bot/cute_types/chat_join_request.py +4 -3
- telegrinder/bot/cute_types/chat_member_updated.py +28 -31
- telegrinder/bot/cute_types/inline_query.py +5 -4
- telegrinder/bot/cute_types/message.py +555 -602
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +20 -12
- telegrinder/bot/cute_types/utils.py +3 -36
- telegrinder/bot/dispatch/__init__.py +4 -0
- telegrinder/bot/dispatch/abc.py +8 -9
- telegrinder/bot/dispatch/context.py +5 -7
- telegrinder/bot/dispatch/dispatch.py +85 -33
- telegrinder/bot/dispatch/handler/abc.py +5 -6
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
- telegrinder/bot/dispatch/handler/base.py +3 -3
- telegrinder/bot/dispatch/handler/document_reply.py +2 -2
- telegrinder/bot/dispatch/handler/func.py +36 -42
- telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
- telegrinder/bot/dispatch/handler/message_reply.py +2 -2
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
- telegrinder/bot/dispatch/handler/video_reply.py +2 -2
- telegrinder/bot/dispatch/middleware/abc.py +83 -8
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +44 -50
- telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
- telegrinder/bot/dispatch/return_manager/abc.py +6 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +2 -0
- telegrinder/bot/dispatch/view/abc.py +10 -6
- telegrinder/bot/dispatch/view/base.py +81 -50
- telegrinder/bot/dispatch/view/box.py +20 -9
- telegrinder/bot/dispatch/view/callback_query.py +3 -4
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
- telegrinder/bot/dispatch/view/chat_member.py +3 -5
- telegrinder/bot/dispatch/view/inline_query.py +3 -4
- telegrinder/bot/dispatch/view/message.py +3 -4
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +42 -40
- telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
- telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
- telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
- telegrinder/bot/polling/polling.py +62 -54
- telegrinder/bot/rules/__init__.py +24 -1
- telegrinder/bot/rules/abc.py +17 -10
- telegrinder/bot/rules/callback_data.py +20 -61
- telegrinder/bot/rules/chat_join.py +6 -4
- telegrinder/bot/rules/command.py +4 -4
- telegrinder/bot/rules/enum_text.py +1 -4
- telegrinder/bot/rules/func.py +5 -3
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +6 -4
- telegrinder/bot/rules/integer.py +2 -1
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +5 -6
- telegrinder/bot/rules/message.py +2 -4
- telegrinder/bot/rules/message_entities.py +1 -3
- telegrinder/bot/rules/node.py +15 -9
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +5 -6
- telegrinder/bot/rules/state.py +1 -3
- telegrinder/bot/rules/text.py +10 -5
- telegrinder/bot/rules/update.py +0 -0
- telegrinder/bot/scenario/abc.py +2 -4
- telegrinder/bot/scenario/checkbox.py +12 -14
- telegrinder/bot/scenario/choice.py +6 -9
- telegrinder/client/__init__.py +9 -1
- telegrinder/client/abc.py +35 -10
- telegrinder/client/aiohttp.py +28 -24
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +38 -145
- telegrinder/modules.py +3 -1
- telegrinder/msgspec_utils.py +136 -68
- telegrinder/node/__init__.py +74 -13
- telegrinder/node/attachment.py +92 -16
- telegrinder/node/base.py +196 -68
- telegrinder/node/callback_query.py +17 -16
- telegrinder/node/command.py +3 -2
- telegrinder/node/composer.py +40 -75
- telegrinder/node/container.py +13 -7
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +20 -31
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +4 -5
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +28 -9
- telegrinder/node/rule.py +2 -6
- telegrinder/node/scope.py +4 -6
- telegrinder/node/source.py +37 -21
- telegrinder/node/text.py +20 -8
- telegrinder/node/tools/generator.py +7 -11
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -61
- telegrinder/tools/__init__.py +97 -38
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
- telegrinder/tools/buttons.py +52 -26
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/abc.py +4 -7
- telegrinder/tools/error_handler/error.py +0 -0
- telegrinder/tools/error_handler/error_handler.py +34 -48
- telegrinder/tools/formatting/__init__.py +57 -37
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
- telegrinder/tools/formatting/spec_html_formats.py +14 -60
- telegrinder/tools/functional.py +1 -5
- telegrinder/tools/global_context/global_context.py +26 -51
- telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
- telegrinder/tools/i18n/abc.py +0 -0
- telegrinder/tools/i18n/middleware/abc.py +3 -6
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +5 -10
- telegrinder/tools/loop_wrapper/abc.py +7 -2
- telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
- telegrinder/tools/magic.py +236 -60
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +5 -9
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +8 -0
- telegrinder/types/enums.py +31 -21
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +531 -109
- telegrinder/types/objects.py +934 -826
- telegrinder/verification_utils.py +0 -2
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.dist-info}/LICENSE +2 -2
- telegrinder-0.4.1.dist-info/METADATA +143 -0
- telegrinder-0.4.1.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
- telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
- /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
|
@@ -4,15 +4,15 @@ from functools import cached_property
|
|
|
4
4
|
import typing_extensions as typing
|
|
5
5
|
|
|
6
6
|
from telegrinder.api.api import API
|
|
7
|
-
from telegrinder.bot.cute_types import BaseCute, UpdateCute
|
|
8
7
|
from telegrinder.bot.dispatch.context import Context
|
|
9
8
|
from telegrinder.bot.dispatch.process import check_rule
|
|
10
|
-
from telegrinder.model import Model
|
|
11
9
|
from telegrinder.modules import logger
|
|
12
|
-
from telegrinder.node.base import
|
|
10
|
+
from telegrinder.node.base import NodeType, get_nodes
|
|
13
11
|
from telegrinder.node.composer import NodeCollection, compose_nodes
|
|
14
|
-
from telegrinder.
|
|
12
|
+
from telegrinder.tools.adapter.abc import ABCAdapter
|
|
13
|
+
from telegrinder.tools.adapter.dataclass import DataclassAdapter
|
|
15
14
|
from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
|
|
15
|
+
from telegrinder.tools.magic import get_annotations, magic_bundle
|
|
16
16
|
from telegrinder.types.enums import UpdateType
|
|
17
17
|
from telegrinder.types.objects import Update
|
|
18
18
|
|
|
@@ -22,21 +22,20 @@ if typing.TYPE_CHECKING:
|
|
|
22
22
|
from telegrinder.bot.rules.abc import ABCRule
|
|
23
23
|
from telegrinder.node.composer import NodeCollection
|
|
24
24
|
|
|
25
|
-
Rest = typing.ParamSpec("Rest")
|
|
26
|
-
Result = typing.TypeVar("Result")
|
|
27
25
|
Function = typing.TypeVar("Function", bound="Func[..., typing.Any]")
|
|
28
|
-
Event = typing.TypeVar("Event"
|
|
26
|
+
Event = typing.TypeVar("Event")
|
|
29
27
|
ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
|
|
30
28
|
|
|
31
|
-
Func
|
|
29
|
+
type Func[**Rest, Result] = typing.Callable[Rest, typing.Coroutine[typing.Any, typing.Any, Result]]
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
35
33
|
class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandlerT]):
|
|
36
34
|
function: Function
|
|
37
35
|
rules: list["ABCRule"]
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
adapter: ABCAdapter[Update, Event] | None = dataclasses.field(default=None, kw_only=True)
|
|
37
|
+
final: bool = dataclasses.field(default=True, kw_only=True)
|
|
38
|
+
dataclass: type[typing.Any] | None = dataclasses.field(default=dict[str, typing.Any], kw_only=True)
|
|
40
39
|
error_handler: ErrorHandlerT = dataclasses.field(
|
|
41
40
|
default_factory=lambda: typing.cast(ErrorHandlerT, ErrorHandler()),
|
|
42
41
|
kw_only=True,
|
|
@@ -47,6 +46,9 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
47
46
|
def __post_init__(self) -> None:
|
|
48
47
|
self.dataclass = typing.get_origin(self.dataclass) or self.dataclass
|
|
49
48
|
|
|
49
|
+
if self.dataclass is not None and self.adapter is None:
|
|
50
|
+
self.adapter = DataclassAdapter(self.dataclass, self.update_type)
|
|
51
|
+
|
|
50
52
|
@property
|
|
51
53
|
def __call__(self) -> Function:
|
|
52
54
|
return self.function
|
|
@@ -54,7 +56,7 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
54
56
|
def __repr__(self) -> str:
|
|
55
57
|
return "<{}: {}={!r} with rules={!r}, dataclass={!r}, error_handler={!r}>".format(
|
|
56
58
|
self.__class__.__name__,
|
|
57
|
-
"
|
|
59
|
+
"final function" if self.final else "function",
|
|
58
60
|
self.function.__qualname__,
|
|
59
61
|
self.rules,
|
|
60
62
|
self.dataclass,
|
|
@@ -62,9 +64,17 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
@cached_property
|
|
65
|
-
def required_nodes(self) -> dict[str, type[
|
|
67
|
+
def required_nodes(self) -> dict[str, type[NodeType]]:
|
|
66
68
|
return get_nodes(self.function)
|
|
67
69
|
|
|
70
|
+
def get_name_event_param(self, event: Event) -> str | None:
|
|
71
|
+
event_class = self.dataclass or event.__class__
|
|
72
|
+
for k, v in get_annotations(self.function).items():
|
|
73
|
+
if isinstance(v := typing.get_origin(v) or v, type) and v is event_class:
|
|
74
|
+
self.func_event_param = k
|
|
75
|
+
return k
|
|
76
|
+
return None
|
|
77
|
+
|
|
68
78
|
async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
|
|
69
79
|
if self.update_type is not None and self.update_type != event.update_type:
|
|
70
80
|
return False
|
|
@@ -72,7 +82,7 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
72
82
|
logger.debug("Checking handler {!r}...", self)
|
|
73
83
|
ctx = Context(raw_update=event) if ctx is None else ctx
|
|
74
84
|
temp_ctx = ctx.copy()
|
|
75
|
-
temp_ctx |= self.preset_context
|
|
85
|
+
temp_ctx |= self.preset_context.copy()
|
|
76
86
|
update = event
|
|
77
87
|
|
|
78
88
|
for rule in self.rules:
|
|
@@ -83,21 +93,15 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
83
93
|
nodes = self.required_nodes
|
|
84
94
|
node_col = None
|
|
85
95
|
if nodes:
|
|
86
|
-
result = await compose_nodes(nodes, ctx, data={Update:
|
|
96
|
+
result = await compose_nodes(nodes, ctx, data={Update: update, API: api})
|
|
87
97
|
if not result:
|
|
88
|
-
logger.debug(f"Cannot compose nodes for handler
|
|
98
|
+
logger.debug(f"Cannot compose nodes for handler, error: {str(result.error)}")
|
|
89
99
|
return False
|
|
90
100
|
|
|
91
101
|
node_col = result.value
|
|
92
102
|
temp_ctx |= node_col.values
|
|
93
103
|
|
|
94
|
-
if EVENT_NODE_KEY in ctx:
|
|
95
|
-
for name, node in nodes.items():
|
|
96
|
-
if node is ctx[EVENT_NODE_KEY] and name in temp_ctx:
|
|
97
|
-
ctx[name] = temp_ctx.pop(name)
|
|
98
|
-
|
|
99
104
|
logger.debug("All checks passed for handler.")
|
|
100
|
-
|
|
101
105
|
temp_ctx["node_col"] = node_col
|
|
102
106
|
ctx |= temp_ctx
|
|
103
107
|
return True
|
|
@@ -109,27 +113,17 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandle
|
|
|
109
113
|
ctx: Context,
|
|
110
114
|
node_col: "NodeCollection | None" = None,
|
|
111
115
|
) -> typing.Any:
|
|
112
|
-
logger.debug(f"Running
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if self.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
elif issubclass(self.dataclass, UpdateCute) and isinstance(event, Update):
|
|
124
|
-
event = self.dataclass.from_update(event, bound_api=api)
|
|
125
|
-
|
|
126
|
-
else:
|
|
127
|
-
event = self.dataclass(**event.to_dict()) # type: ignore
|
|
128
|
-
|
|
129
|
-
result = (await self.error_handler.run(self.function, event, api, ctx)).unwrap()
|
|
130
|
-
if node_col := ctx.node_col:
|
|
131
|
-
await node_col.close_all()
|
|
132
|
-
return result
|
|
116
|
+
logger.debug(f"Running handler {self!r}...")
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
if event_param := self.get_name_event_param(event):
|
|
120
|
+
ctx = Context(**{event_param: event, **ctx})
|
|
121
|
+
return await self(**magic_bundle(self.function, ctx, start_idx=0))
|
|
122
|
+
except BaseException as exception:
|
|
123
|
+
return await self.error_handler.run(exception, event, api, ctx)
|
|
124
|
+
finally:
|
|
125
|
+
if node_col := ctx.node_col:
|
|
126
|
+
await node_col.close_all()
|
|
133
127
|
|
|
134
128
|
|
|
135
129
|
__all__ = ("FuncHandler",)
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
3
|
from telegrinder.api.api import API
|
|
4
|
-
from telegrinder.bot.cute_types.message import
|
|
4
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
5
5
|
from telegrinder.bot.dispatch.context import Context
|
|
6
6
|
from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
|
|
7
7
|
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.types.objects import InputMedia
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class MediaGroupReplyHandler(BaseReplyHandler):
|
|
11
12
|
def __init__(
|
|
12
13
|
self,
|
|
13
|
-
media:
|
|
14
|
+
media: InputMedia | list[InputMedia],
|
|
14
15
|
*rules: ABCRule,
|
|
15
16
|
caption: str | list[str] | None = None,
|
|
16
17
|
parse_mode: str | list[str] | None = None,
|
|
17
|
-
|
|
18
|
+
final: bool = True,
|
|
18
19
|
as_reply: bool = False,
|
|
19
20
|
preset_context: Context | None = None,
|
|
20
21
|
**default_params: typing.Any,
|
|
@@ -24,7 +25,7 @@ class MediaGroupReplyHandler(BaseReplyHandler):
|
|
|
24
25
|
self.caption = caption
|
|
25
26
|
super().__init__(
|
|
26
27
|
*rules,
|
|
27
|
-
|
|
28
|
+
final=final,
|
|
28
29
|
as_reply=as_reply,
|
|
29
30
|
preset_context=preset_context,
|
|
30
31
|
**default_params,
|
|
@@ -13,7 +13,7 @@ class MessageReplyHandler(BaseReplyHandler):
|
|
|
13
13
|
text: str,
|
|
14
14
|
*rules: ABCRule,
|
|
15
15
|
parse_mode: str | None = None,
|
|
16
|
-
|
|
16
|
+
final: bool = True,
|
|
17
17
|
as_reply: bool = False,
|
|
18
18
|
preset_context: Context | None = None,
|
|
19
19
|
**default_params: typing.Any,
|
|
@@ -22,7 +22,7 @@ class MessageReplyHandler(BaseReplyHandler):
|
|
|
22
22
|
self.parse_mode = parse_mode
|
|
23
23
|
super().__init__(
|
|
24
24
|
*rules,
|
|
25
|
-
|
|
25
|
+
final=final,
|
|
26
26
|
as_reply=as_reply,
|
|
27
27
|
preset_context=preset_context,
|
|
28
28
|
**default_params,
|
|
@@ -15,7 +15,7 @@ class PhotoReplyHandler(BaseReplyHandler):
|
|
|
15
15
|
*rules: ABCRule,
|
|
16
16
|
caption: str | None = None,
|
|
17
17
|
parse_mode: str | None = None,
|
|
18
|
-
|
|
18
|
+
final: bool = True,
|
|
19
19
|
as_reply: bool = False,
|
|
20
20
|
preset_context: Context | None = None,
|
|
21
21
|
**default_params: typing.Any,
|
|
@@ -25,7 +25,7 @@ class PhotoReplyHandler(BaseReplyHandler):
|
|
|
25
25
|
self.caption = caption
|
|
26
26
|
super().__init__(
|
|
27
27
|
*rules,
|
|
28
|
-
|
|
28
|
+
final=final,
|
|
29
29
|
as_reply=as_reply,
|
|
30
30
|
preset_context=preset_context,
|
|
31
31
|
**default_params,
|
|
@@ -14,7 +14,7 @@ class StickerReplyHandler(BaseReplyHandler):
|
|
|
14
14
|
sticker: InputFile | str,
|
|
15
15
|
*rules: ABCRule,
|
|
16
16
|
emoji: str | None = None,
|
|
17
|
-
|
|
17
|
+
final: bool = True,
|
|
18
18
|
as_reply: bool = False,
|
|
19
19
|
preset_context: Context | None = None,
|
|
20
20
|
**default_params: typing.Any,
|
|
@@ -23,7 +23,7 @@ class StickerReplyHandler(BaseReplyHandler):
|
|
|
23
23
|
self.emoji = emoji
|
|
24
24
|
super().__init__(
|
|
25
25
|
*rules,
|
|
26
|
-
|
|
26
|
+
final=final,
|
|
27
27
|
as_reply=as_reply,
|
|
28
28
|
preset_context=preset_context,
|
|
29
29
|
**default_params,
|
|
@@ -15,7 +15,7 @@ class VideoReplyHandler(BaseReplyHandler):
|
|
|
15
15
|
*rules: ABCRule,
|
|
16
16
|
caption: str | None = None,
|
|
17
17
|
parse_mode: str | None = None,
|
|
18
|
-
|
|
18
|
+
final: bool = True,
|
|
19
19
|
as_reply: bool = False,
|
|
20
20
|
preset_context: Context | None = None,
|
|
21
21
|
**default_params: typing.Any,
|
|
@@ -25,7 +25,7 @@ class VideoReplyHandler(BaseReplyHandler):
|
|
|
25
25
|
self.caption = caption
|
|
26
26
|
super().__init__(
|
|
27
27
|
*rules,
|
|
28
|
-
|
|
28
|
+
final=final,
|
|
29
29
|
as_reply=as_reply,
|
|
30
30
|
preset_context=preset_context,
|
|
31
31
|
**default_params,
|
|
@@ -1,22 +1,97 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from abc import ABC
|
|
3
4
|
|
|
5
|
+
import typing_extensions as typing
|
|
6
|
+
from fntypes import Some
|
|
7
|
+
|
|
8
|
+
from telegrinder.api import API
|
|
9
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
4
10
|
from telegrinder.bot.dispatch.context import Context
|
|
5
11
|
from telegrinder.model import Model
|
|
12
|
+
from telegrinder.modules import logger
|
|
13
|
+
from telegrinder.tools.adapter.abc import ABCAdapter, run_adapter
|
|
14
|
+
from telegrinder.tools.lifespan import Lifespan
|
|
6
15
|
from telegrinder.types.objects import Update
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
ToEvent = typing.TypeVar("ToEvent", bound=Model, default=typing.Any)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def run_middleware[Event: Model, R: bool | None](
|
|
21
|
+
method: typing.Callable[typing.Concatenate[Event, Context, ...], typing.Awaitable[R]],
|
|
22
|
+
api: API[typing.Any],
|
|
23
|
+
event: Event,
|
|
24
|
+
ctx: Context,
|
|
25
|
+
raw_event: Update | None = None,
|
|
26
|
+
adapter: "ABCAdapter[Update, Event] | None" = None,
|
|
27
|
+
*args: typing.Any,
|
|
28
|
+
**kwargs: typing.Any,
|
|
29
|
+
) -> R:
|
|
30
|
+
if adapter is not None:
|
|
31
|
+
if raw_event is None:
|
|
32
|
+
raise RuntimeError("raw_event must be specified to apply adapter")
|
|
33
|
+
match await run_adapter(adapter, api, raw_event, ctx):
|
|
34
|
+
case Some(val):
|
|
35
|
+
event = val
|
|
36
|
+
case _:
|
|
37
|
+
return False # type: ignore
|
|
38
|
+
|
|
39
|
+
logger.debug("Running {}-middleware {!r}...", method.__name__, method.__qualname__.split(".")[0])
|
|
40
|
+
return await method(event, ctx, *args, **kwargs) # type: ignore
|
|
41
|
+
|
|
10
42
|
|
|
11
|
-
Event
|
|
43
|
+
class ABCMiddleware[Event: Model | BaseCute](ABC):
|
|
44
|
+
adapter: ABCAdapter[Update, Event] | None = None
|
|
12
45
|
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
name = f"middleware {self.__class__.__name__!r}:"
|
|
48
|
+
has_pre = self.pre.__qualname__.split(".")[0] != "ABCMiddleware"
|
|
49
|
+
has_post = self.post.__qualname__.split(".")[0] != "ABCMiddleware"
|
|
13
50
|
|
|
14
|
-
|
|
15
|
-
|
|
51
|
+
if has_post:
|
|
52
|
+
name = "post-" + name
|
|
53
|
+
if has_pre:
|
|
54
|
+
name = "pre-" + name
|
|
55
|
+
|
|
56
|
+
return "<{} with adapter={!r}>".format(name, self.adapter)
|
|
16
57
|
|
|
17
58
|
async def pre(self, event: Event, ctx: Context) -> bool: ...
|
|
18
59
|
|
|
19
|
-
async def post(self, event: Event,
|
|
60
|
+
async def post(self, event: Event, ctx: Context) -> None: ...
|
|
61
|
+
|
|
62
|
+
@typing.overload
|
|
63
|
+
def to_lifespan(self, event: Event, ctx: Context | None = None, *, api: API) -> Lifespan: ...
|
|
64
|
+
|
|
65
|
+
@typing.overload
|
|
66
|
+
def to_lifespan(self, event: Event, ctx: Context | None = None) -> Lifespan: ...
|
|
67
|
+
|
|
68
|
+
def to_lifespan(
|
|
69
|
+
self,
|
|
70
|
+
event: Event,
|
|
71
|
+
ctx: Context | None = None,
|
|
72
|
+
api: API | None = None,
|
|
73
|
+
**add_context: typing.Any,
|
|
74
|
+
) -> Lifespan:
|
|
75
|
+
if api is None:
|
|
76
|
+
if not isinstance(event, BaseCute):
|
|
77
|
+
raise LookupError("Cannot get api, please pass as kwarg or provide BaseCute api-bound event")
|
|
78
|
+
api = event.api
|
|
79
|
+
|
|
80
|
+
ctx = ctx or Context()
|
|
81
|
+
ctx |= add_context
|
|
82
|
+
return Lifespan(
|
|
83
|
+
startup_tasks=[run_middleware(self.pre, api, event, raw_event=None, ctx=ctx, adapter=None)],
|
|
84
|
+
shutdown_tasks=[
|
|
85
|
+
run_middleware(
|
|
86
|
+
self.post,
|
|
87
|
+
api,
|
|
88
|
+
event,
|
|
89
|
+
raw_event=None,
|
|
90
|
+
ctx=ctx,
|
|
91
|
+
adapter=None,
|
|
92
|
+
)
|
|
93
|
+
],
|
|
94
|
+
)
|
|
20
95
|
|
|
21
96
|
|
|
22
|
-
__all__ = ("ABCMiddleware",)
|
|
97
|
+
__all__ = ("ABCMiddleware", "run_middleware")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import typing
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from telegrinder.api import API
|
|
6
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
|
+
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
|
|
9
|
+
from telegrinder.bot.rules.abc import ABCRule, check_rule
|
|
10
|
+
from telegrinder.node import IsNode, compose_nodes
|
|
11
|
+
from telegrinder.tools.adapter.abc import ABCAdapter
|
|
12
|
+
from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
|
|
13
|
+
from telegrinder.types import Update
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GlobalMiddleware(ABCMiddleware):
|
|
17
|
+
adapter = RawUpdateAdapter()
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.filters: set[ABCRule] = set()
|
|
21
|
+
self.source_filters: dict[ABCAdapter | IsNode, dict[typing.Any, ABCRule]] = {}
|
|
22
|
+
|
|
23
|
+
async def pre(self, event: UpdateCute, ctx: Context) -> bool:
|
|
24
|
+
for filter in self.filters:
|
|
25
|
+
if not await check_rule(event.api, filter, event, ctx):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
# Simple implication.... Grouped by source categories
|
|
29
|
+
for source, identifiers in self.source_filters.items():
|
|
30
|
+
if isinstance(source, ABCAdapter):
|
|
31
|
+
result = source.adapt(event.api, event, ctx)
|
|
32
|
+
if inspect.isawaitable(result):
|
|
33
|
+
result = await result
|
|
34
|
+
|
|
35
|
+
result = result.unwrap_or_none()
|
|
36
|
+
if result is None:
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
result = await compose_nodes({"value": source}, ctx, {Update: event, API: event.api})
|
|
41
|
+
if result := result.unwrap():
|
|
42
|
+
result = result.values["value"]
|
|
43
|
+
else:
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
if result in identifiers:
|
|
47
|
+
return await check_rule(event.api, identifiers[result], event, ctx)
|
|
48
|
+
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
@contextmanager
|
|
52
|
+
def apply_filters(
|
|
53
|
+
self,
|
|
54
|
+
*filters: ABCRule,
|
|
55
|
+
source_filter: tuple[ABCAdapter | IsNode, typing.Any, ABCRule] | None = None,
|
|
56
|
+
):
|
|
57
|
+
if source_filter is not None:
|
|
58
|
+
self.source_filters.setdefault(source_filter[0], {})
|
|
59
|
+
self.source_filters[source_filter[0]].update({source_filter[1]: source_filter[2]})
|
|
60
|
+
|
|
61
|
+
self.filters |= set(filters)
|
|
62
|
+
yield
|
|
63
|
+
self.filters.difference_update(filters)
|
|
64
|
+
|
|
65
|
+
if source_filter is not None: # noqa: SIM102
|
|
66
|
+
if identifiers := self.source_filters.get(source_filter[0]):
|
|
67
|
+
identifiers.pop(source_filter[1], None)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = ("GlobalMiddleware",)
|
|
@@ -1,68 +1,41 @@
|
|
|
1
|
-
import inspect
|
|
2
1
|
import typing
|
|
3
2
|
|
|
4
|
-
from fntypes.option import Nothing,
|
|
5
|
-
from fntypes.result import Error, Ok
|
|
3
|
+
from fntypes.option import Nothing, Some
|
|
6
4
|
|
|
7
5
|
from telegrinder.api.api import API
|
|
8
6
|
from telegrinder.bot.cute_types.update import UpdateCute
|
|
9
7
|
from telegrinder.bot.dispatch.context import Context
|
|
10
|
-
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
|
|
8
|
+
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware, run_middleware
|
|
11
9
|
from telegrinder.bot.dispatch.return_manager.abc import ABCReturnManager
|
|
12
10
|
from telegrinder.model import Model
|
|
13
11
|
from telegrinder.modules import logger
|
|
14
12
|
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeScope, compose_nodes
|
|
13
|
+
from telegrinder.tools.adapter.abc import run_adapter
|
|
15
14
|
from telegrinder.tools.i18n.abc import I18nEnum
|
|
16
15
|
from telegrinder.types.objects import Update
|
|
17
16
|
|
|
18
17
|
if typing.TYPE_CHECKING:
|
|
19
18
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
20
19
|
from telegrinder.bot.rules.abc import ABCRule
|
|
21
|
-
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
22
20
|
|
|
23
|
-
T = typing.TypeVar("T")
|
|
24
|
-
Event = typing.TypeVar("Event", bound=Model)
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
async def run_adapter(
|
|
28
|
-
adapter: "ABCAdapter[Update, T]",
|
|
29
|
-
api: API,
|
|
30
|
-
update: Update,
|
|
31
|
-
context: Context,
|
|
32
|
-
) -> Option[T]:
|
|
33
|
-
adapt_result = adapter.adapt(api, update, context)
|
|
34
|
-
match await adapt_result if inspect.isawaitable(adapt_result) else adapt_result:
|
|
35
|
-
case Ok(value):
|
|
36
|
-
return Some(value)
|
|
37
|
-
case Error(err):
|
|
38
|
-
logger.debug("Adapter failed with error message: {!r}", str(err))
|
|
39
|
-
return Nothing()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async def process_inner(
|
|
22
|
+
async def process_inner[Event: Model](
|
|
43
23
|
api: API,
|
|
44
24
|
event: Event,
|
|
45
25
|
raw_event: Update,
|
|
26
|
+
ctx: Context,
|
|
46
27
|
middlewares: list[ABCMiddleware[Event]],
|
|
47
28
|
handlers: list["ABCHandler[Event]"],
|
|
48
29
|
return_manager: ABCReturnManager[Event] | None = None,
|
|
49
30
|
) -> bool:
|
|
50
31
|
logger.debug("Processing {!r}...", event.__class__.__name__)
|
|
51
|
-
ctx = Context(raw_update=raw_event)
|
|
52
32
|
ctx[CONTEXT_STORE_NODES_KEY] = {} # For per-event shared nodes
|
|
53
33
|
|
|
54
34
|
logger.debug("Run pre middlewares...")
|
|
55
|
-
for
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
event = val
|
|
60
|
-
case Nothing():
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
middleware_result = await middleware.pre(event, ctx)
|
|
64
|
-
logger.debug("Middleware {!r} returned: {!r}", middleware.__class__.__qualname__, middleware_result)
|
|
65
|
-
if middleware_result is False:
|
|
35
|
+
for m in middlewares:
|
|
36
|
+
result = await run_middleware(m.pre, api, event, raw_event=raw_event, ctx=ctx, adapter=m.adapter)
|
|
37
|
+
logger.debug("Middleware {!r} returned: {!r}", m, result)
|
|
38
|
+
if result is False:
|
|
66
39
|
return False
|
|
67
40
|
|
|
68
41
|
found = False
|
|
@@ -70,23 +43,41 @@ async def process_inner(
|
|
|
70
43
|
ctx_copy = ctx.copy()
|
|
71
44
|
|
|
72
45
|
for handler in handlers:
|
|
46
|
+
adapted_event = event
|
|
47
|
+
|
|
73
48
|
if await handler.check(api, raw_event, ctx):
|
|
74
|
-
|
|
49
|
+
if handler.adapter is not None:
|
|
50
|
+
match await run_adapter(handler.adapter, api, raw_event, ctx):
|
|
51
|
+
case Some(value):
|
|
52
|
+
adapted_event = value
|
|
53
|
+
case Nothing():
|
|
54
|
+
continue
|
|
55
|
+
|
|
75
56
|
found = True
|
|
76
|
-
|
|
57
|
+
logger.debug("Handler {!r} matched, run...", handler)
|
|
58
|
+
response = await handler.run(api, adapted_event, ctx)
|
|
77
59
|
logger.debug("Handler {!r} returned: {!r}", handler, response)
|
|
78
60
|
responses.append(response)
|
|
61
|
+
|
|
79
62
|
if return_manager is not None:
|
|
80
63
|
await return_manager.run(response, event, ctx)
|
|
81
|
-
if handler.
|
|
64
|
+
if handler.final:
|
|
82
65
|
break
|
|
83
66
|
|
|
84
67
|
ctx = ctx_copy
|
|
85
68
|
|
|
86
69
|
logger.debug("Run post middlewares...")
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
ctx.set("responses", responses)
|
|
71
|
+
|
|
72
|
+
for m in middlewares:
|
|
73
|
+
await run_middleware(
|
|
74
|
+
m.post,
|
|
75
|
+
api,
|
|
76
|
+
event,
|
|
77
|
+
raw_event=raw_event,
|
|
78
|
+
ctx=ctx,
|
|
79
|
+
adapter=m.adapter,
|
|
80
|
+
)
|
|
90
81
|
|
|
91
82
|
for session in ctx.get(CONTEXT_STORE_NODES_KEY, {}).values():
|
|
92
83
|
await session.close(scopes=(NodeScope.PER_EVENT,))
|
|
@@ -106,7 +97,9 @@ async def check_rule(
|
|
|
106
97
|
ctx: Context,
|
|
107
98
|
) -> bool:
|
|
108
99
|
"""Checks requirements, adapts update.
|
|
109
|
-
Returns check result.
|
|
100
|
+
Returns check result.
|
|
101
|
+
"""
|
|
102
|
+
update_cute = None if not isinstance(update, UpdateCute) else update
|
|
110
103
|
|
|
111
104
|
# Running adapter
|
|
112
105
|
match await run_adapter(rule.adapter, api, update, ctx):
|
|
@@ -116,17 +109,17 @@ async def check_rule(
|
|
|
116
109
|
return False
|
|
117
110
|
|
|
118
111
|
# Preparing update
|
|
119
|
-
if isinstance(
|
|
120
|
-
|
|
121
|
-
elif isinstance(
|
|
122
|
-
|
|
112
|
+
if isinstance(adapted_value, UpdateCute):
|
|
113
|
+
update_cute = adapted_value
|
|
114
|
+
elif isinstance(adapted_val := ctx.get(rule.adapter.ADAPTED_VALUE_KEY or ""), UpdateCute):
|
|
115
|
+
update_cute = adapted_val
|
|
123
116
|
else:
|
|
124
|
-
|
|
117
|
+
update_cute = UpdateCute.from_update(update, bound_api=api)
|
|
125
118
|
|
|
126
119
|
# Running subrules to fetch requirements
|
|
127
120
|
ctx_copy = ctx.copy()
|
|
128
121
|
for requirement in rule.requires:
|
|
129
|
-
if not await check_rule(api, requirement,
|
|
122
|
+
if not await check_rule(api, requirement, update_cute, ctx_copy):
|
|
130
123
|
return False
|
|
131
124
|
|
|
132
125
|
# Translating translatable rules
|
|
@@ -141,11 +134,12 @@ async def check_rule(
|
|
|
141
134
|
if nodes:
|
|
142
135
|
result = await compose_nodes(nodes, ctx, data={Update: update, API: api})
|
|
143
136
|
if not result:
|
|
137
|
+
logger.debug(f"Cannot compose nodes for rule, error: {str(result.error)}")
|
|
144
138
|
return False
|
|
145
139
|
node_col = result.value
|
|
146
140
|
|
|
147
141
|
# Running check
|
|
148
|
-
result = await rule.bounding_check(
|
|
142
|
+
result = await rule.bounding_check(ctx, adapted_value=adapted_value, node_col=node_col)
|
|
149
143
|
|
|
150
144
|
# Closing node sessions if there are any
|
|
151
145
|
if node_col is not None:
|
|
@@ -7,6 +7,7 @@ from telegrinder.bot.dispatch.return_manager.abc import (
|
|
|
7
7
|
from telegrinder.bot.dispatch.return_manager.callback_query import CallbackQueryReturnManager
|
|
8
8
|
from telegrinder.bot.dispatch.return_manager.inline_query import InlineQueryReturnManager
|
|
9
9
|
from telegrinder.bot.dispatch.return_manager.message import MessageReturnManager
|
|
10
|
+
from telegrinder.bot.dispatch.return_manager.pre_checkout_query import PreCheckoutQueryManager
|
|
10
11
|
|
|
11
12
|
__all__ = (
|
|
12
13
|
"ABCReturnManager",
|
|
@@ -15,5 +16,6 @@ __all__ = (
|
|
|
15
16
|
"InlineQueryReturnManager",
|
|
16
17
|
"Manager",
|
|
17
18
|
"MessageReturnManager",
|
|
19
|
+
"PreCheckoutQueryManager",
|
|
18
20
|
"register_manager",
|
|
19
21
|
)
|