telegrinder 0.3.4__py3-none-any.whl → 0.4.0__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 +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- 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/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- 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/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.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.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
telegrinder/node/base.py
CHANGED
|
@@ -1,166 +1,294 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import inspect
|
|
5
|
+
from collections import deque
|
|
6
|
+
from types import AsyncGeneratorType, CodeType, resolve_bases
|
|
7
|
+
|
|
8
|
+
import typing_extensions as typing
|
|
9
|
+
|
|
10
|
+
from telegrinder.node.scope import NodeScope
|
|
11
|
+
from telegrinder.tools.magic import cache_magic_value, get_annotations
|
|
12
|
+
from telegrinder.tools.strings import to_pascal_case
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
from telegrinder.node.tools.generator import generate_node
|
|
16
|
+
else:
|
|
17
|
+
|
|
18
|
+
def generate_node(*args, **kwargs):
|
|
19
|
+
globalns = globals()
|
|
20
|
+
if "__generate_node" not in globalns:
|
|
21
|
+
import telegrinder.node.tools.generator
|
|
22
|
+
|
|
23
|
+
globals()["__generate_node"] = telegrinder.node.tools.generator.generate_node
|
|
24
|
+
|
|
25
|
+
return globals()["__generate_node"](*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
type NodeType = Node | NodeProto[typing.Any]
|
|
29
|
+
type IsNode = NodeType | type[NodeType]
|
|
30
|
+
|
|
31
|
+
T = typing.TypeVar("T", default=typing.Any)
|
|
32
|
+
|
|
33
|
+
ComposeResult: typing.TypeAlias = T | typing.Awaitable[T] | typing.AsyncGenerator[T, None]
|
|
34
|
+
|
|
35
|
+
UNWRAPPED_NODE_KEY = "__unwrapped_node__"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@typing.overload
|
|
39
|
+
def is_node(maybe_node: type[typing.Any], /) -> typing.TypeIs[type[NodeType]]: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@typing.overload
|
|
43
|
+
def is_node(maybe_node: typing.Any, /) -> typing.TypeIs[NodeType]: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_node(maybe_node: typing.Any, /) -> bool:
|
|
47
|
+
if isinstance(maybe_node, typing.TypeAliasType):
|
|
48
|
+
maybe_node = maybe_node.__value__
|
|
49
|
+
if not isinstance(maybe_node, type):
|
|
50
|
+
maybe_node = typing.get_origin(maybe_node) or maybe_node
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
hasattr(maybe_node, "as_node")
|
|
54
|
+
or isinstance(maybe_node, type)
|
|
55
|
+
and issubclass(maybe_node, (Node, NodeProto))
|
|
56
|
+
or not isinstance(maybe_node, type)
|
|
57
|
+
and isinstance(maybe_node, (Node, NodeProto))
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@typing.overload
|
|
62
|
+
def as_node(maybe_node: type[typing.Any], /) -> type[NodeType]: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@typing.overload
|
|
66
|
+
def as_node(maybe_node: typing.Any, /) -> NodeType: ...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@typing.overload
|
|
70
|
+
def as_node(*maybe_nodes: type[typing.Any]) -> tuple[type[NodeType], ...]: ...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@typing.overload
|
|
74
|
+
def as_node(*maybe_nodes: typing.Any) -> tuple[NodeType, ...]: ...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@typing.overload
|
|
78
|
+
def as_node(*maybe_nodes: type[typing.Any] | typing.Any) -> tuple[IsNode, ...]: ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def as_node(*maybe_nodes: typing.Any) -> typing.Any | tuple[typing.Any, ...]:
|
|
82
|
+
for maybe_node in maybe_nodes:
|
|
83
|
+
if not is_node(maybe_node):
|
|
84
|
+
is_type = isinstance(maybe_node, type)
|
|
85
|
+
raise LookupError(
|
|
86
|
+
f"{'Type of' if is_type else 'Object of type'} "
|
|
87
|
+
f"{maybe_node.__name__ if is_type else maybe_node.__class__.__name__!r} "
|
|
88
|
+
"cannot be resolved as Node."
|
|
89
|
+
)
|
|
90
|
+
return maybe_nodes[0] if len(maybe_nodes) == 1 else maybe_nodes
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cache_magic_value("__nodes__")
|
|
94
|
+
def get_nodes(function: typing.Callable[..., typing.Any], /) -> dict[str, type[NodeType]]:
|
|
95
|
+
return {k: v.as_node() for k, v in get_annotations(function).items() if is_node(v)}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@cache_magic_value("__is_generator__")
|
|
99
|
+
def is_generator(
|
|
100
|
+
function: typing.Callable[..., typing.Any],
|
|
101
|
+
/,
|
|
102
|
+
) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
|
|
103
|
+
return inspect.isasyncgenfunction(function)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def unwrap_node(node: type[NodeType], /) -> tuple[type[NodeType], ...]:
|
|
107
|
+
"""Unwrap node as flattened tuple of node types in ordering required to calculate given node.
|
|
108
|
+
|
|
109
|
+
Provides caching for passed node type.
|
|
110
|
+
"""
|
|
111
|
+
if (unwrapped := getattr(node, UNWRAPPED_NODE_KEY, None)) is not None:
|
|
112
|
+
return unwrapped
|
|
113
|
+
|
|
114
|
+
stack = deque([(node, node.get_subnodes().values())])
|
|
115
|
+
visited = list[type[NodeType]]()
|
|
116
|
+
|
|
117
|
+
while stack:
|
|
118
|
+
parent, child_nodes = stack.pop()
|
|
119
|
+
|
|
120
|
+
if parent not in visited:
|
|
121
|
+
visited.insert(0, parent)
|
|
122
|
+
|
|
123
|
+
for child in child_nodes:
|
|
124
|
+
stack.append((child, child.get_subnodes().values()))
|
|
125
|
+
|
|
126
|
+
unwrapped = tuple(visited)
|
|
127
|
+
setattr(node, UNWRAPPED_NODE_KEY, unwrapped)
|
|
128
|
+
return unwrapped
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ComposeError(BaseException):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@typing.runtime_checkable
|
|
136
|
+
class Composable[R](typing.Protocol):
|
|
137
|
+
@classmethod
|
|
138
|
+
def compose(cls, *args: typing.Any, **kwargs: typing.Any) -> ComposeResult[R]: ...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class NodeImpersonation(typing.Protocol):
|
|
142
|
+
@classmethod
|
|
143
|
+
def as_node(cls) -> type[NodeProto[typing.Any]]: ...
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NodeComposeFunction[R](typing.Protocol):
|
|
147
|
+
__name__: str
|
|
148
|
+
__code__: CodeType
|
|
149
|
+
|
|
150
|
+
def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> ComposeResult[R]: ...
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@typing.runtime_checkable
|
|
154
|
+
class NodeProto[R](Composable[R], NodeImpersonation, typing.Protocol):
|
|
155
|
+
@classmethod
|
|
156
|
+
def get_subnodes(cls) -> dict[str, type[NodeType]]: ...
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def is_generator(cls) -> bool: ...
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class Node(abc.ABC):
|
|
163
|
+
node: str = "node"
|
|
164
|
+
scope: NodeScope = NodeScope.PER_EVENT
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
@abc.abstractmethod
|
|
168
|
+
def compose(cls, *args, **kwargs) -> ComposeResult:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def get_subnodes(cls) -> dict[str, type[NodeType]]:
|
|
173
|
+
return get_nodes(cls.compose)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def as_node(cls) -> type[typing.Self]:
|
|
177
|
+
return cls
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def is_generator(cls) -> bool:
|
|
181
|
+
return is_generator(cls.compose)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class scalar_node[T]: # noqa: N801
|
|
185
|
+
@typing.overload
|
|
186
|
+
def __new__(cls, x: NodeComposeFunction[Composable[T]], /) -> type[T]: ...
|
|
187
|
+
|
|
188
|
+
@typing.overload
|
|
189
|
+
def __new__(cls, x: NodeComposeFunction[T], /) -> type[T]: ...
|
|
190
|
+
|
|
191
|
+
@typing.overload
|
|
192
|
+
def __new__(
|
|
193
|
+
cls,
|
|
194
|
+
/,
|
|
195
|
+
*,
|
|
196
|
+
scope: NodeScope,
|
|
197
|
+
) -> typing.Callable[[NodeComposeFunction[Composable[T]] | NodeComposeFunction[T]], type[T]]: ...
|
|
198
|
+
|
|
199
|
+
def __new__(cls, x=None, /, *, scope=NodeScope.PER_EVENT) -> typing.Any:
|
|
200
|
+
def inner(node_or_func, /) -> typing.Any:
|
|
201
|
+
namespace = {"node": "scalar", "scope": scope, "__module__": node_or_func.__module__}
|
|
202
|
+
|
|
203
|
+
if isinstance(node_or_func, type):
|
|
204
|
+
bases: list[type[typing.Any]] = [node_or_func]
|
|
205
|
+
node_bases = resolve_bases(node_or_func.__bases__)
|
|
206
|
+
if not any(issubclass(base, Node) for base in node_bases if isinstance(base, type)):
|
|
207
|
+
bases.append(Node)
|
|
208
|
+
return type(node_or_func.__name__, tuple(bases), namespace)
|
|
209
|
+
else:
|
|
210
|
+
base_node = generate_node(
|
|
211
|
+
func=node_or_func,
|
|
212
|
+
subnodes=tuple(get_nodes(node_or_func).values()),
|
|
213
|
+
)
|
|
214
|
+
return type(to_pascal_case(node_or_func.__name__), (base_node,), namespace)
|
|
215
|
+
|
|
216
|
+
return inner if x is None else inner(x)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@typing.dataclass_transform(kw_only_default=True)
|
|
220
|
+
class FactoryNode(Node, abc.ABC):
|
|
221
|
+
node = "factory"
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
@abc.abstractmethod
|
|
225
|
+
def compose(cls, *args, **kwargs) -> ComposeResult:
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
def __new__(cls, **context: typing.Any) -> type[typing.Self]:
|
|
229
|
+
namespace = dict(**cls.__dict__)
|
|
230
|
+
namespace.pop("__new__", None)
|
|
231
|
+
return type(cls.__name__, (cls,), namespace | context) # type: ignore
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@typing.dataclass_transform()
|
|
235
|
+
class DataNode(Node, abc.ABC):
|
|
236
|
+
node = "data"
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
@abc.abstractmethod
|
|
240
|
+
def compose(cls, *args, **kwargs) -> ComposeResult[typing.Self]:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@scalar_node(scope=NodeScope.PER_CALL)
|
|
245
|
+
class Name:
|
|
246
|
+
@classmethod
|
|
247
|
+
def compose(cls) -> str: ...
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class GlobalNode[Value](Node):
|
|
251
|
+
scope = NodeScope.GLOBAL
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def set(cls, value: Value, /) -> None:
|
|
255
|
+
setattr(cls, "_value", value)
|
|
256
|
+
|
|
257
|
+
@typing.overload
|
|
258
|
+
@classmethod
|
|
259
|
+
def get(cls) -> Value: ...
|
|
260
|
+
|
|
261
|
+
@typing.overload
|
|
262
|
+
@classmethod
|
|
263
|
+
def get[Default](cls, *, default: Default) -> Value | Default: ...
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def get(cls, **kwargs: typing.Any) -> typing.Any:
|
|
267
|
+
sentinel = object()
|
|
268
|
+
default = kwargs.pop("default", sentinel)
|
|
269
|
+
return getattr(cls, "_value") if default is sentinel else getattr(cls, "_value", default)
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def unset(cls) -> None:
|
|
273
|
+
if hasattr(cls, "_value"):
|
|
274
|
+
delattr(cls, "_value")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
__all__ = (
|
|
278
|
+
"Composable",
|
|
156
279
|
"ComposeError",
|
|
157
280
|
"DataNode",
|
|
158
281
|
"FactoryNode",
|
|
282
|
+
"GlobalNode",
|
|
283
|
+
"IsNode",
|
|
159
284
|
"Name",
|
|
160
285
|
"Node",
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
286
|
+
"NodeImpersonation",
|
|
287
|
+
"NodeProto",
|
|
288
|
+
"NodeType",
|
|
289
|
+
"as_node",
|
|
164
290
|
"get_nodes",
|
|
165
|
-
"is_node",
|
|
166
|
-
|
|
291
|
+
"is_node",
|
|
292
|
+
"scalar_node",
|
|
293
|
+
"unwrap_node",
|
|
294
|
+
)
|
|
@@ -1,53 +1,54 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from fntypes.result import Error, Ok
|
|
4
|
-
|
|
5
|
-
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
6
|
-
from telegrinder.msgspec_utils import msgspec_convert
|
|
7
|
-
from telegrinder.node.base import ComposeError, FactoryNode, Name,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes.result import Error, Ok
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
6
|
+
from telegrinder.msgspec_utils import msgspec_convert
|
|
7
|
+
from telegrinder.node.base import ComposeError, FactoryNode, Name, scalar_node
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@scalar_node
|
|
11
|
+
class CallbackQueryData:
|
|
12
|
+
@classmethod
|
|
13
|
+
def compose(cls, callback_query: CallbackQueryCute) -> str:
|
|
14
|
+
return callback_query.data.expect(ComposeError("Cannot complete decode callback query data."))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@scalar_node
|
|
18
|
+
class CallbackQueryDataJson:
|
|
19
|
+
@classmethod
|
|
20
|
+
def compose(cls, callback_query: CallbackQueryCute) -> dict:
|
|
21
|
+
return callback_query.decode_data().expect(
|
|
22
|
+
ComposeError("Cannot complete decode callback query data."),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _Field(FactoryNode):
|
|
27
|
+
field_type: type[typing.Any]
|
|
28
|
+
|
|
29
|
+
def __class_getitem__(cls, field_type: type[typing.Any], /) -> typing.Self:
|
|
30
|
+
return cls(field_type=field_type)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def compose(cls, callback_query_data: CallbackQueryDataJson, data_name: Name) -> typing.Any:
|
|
34
|
+
if data := callback_query_data.get(data_name):
|
|
35
|
+
match msgspec_convert(data, cls.field_type):
|
|
36
|
+
case Ok(value):
|
|
37
|
+
return value
|
|
38
|
+
case Error(err):
|
|
39
|
+
raise ComposeError(err)
|
|
40
|
+
|
|
41
|
+
raise ComposeError(f"Cannot find callback data with name {data_name!r}.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if typing.TYPE_CHECKING:
|
|
45
|
+
type Field[FieldType] = typing.Annotated[FieldType, ...]
|
|
46
|
+
else:
|
|
47
|
+
Field = _Field
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = (
|
|
51
|
+
"CallbackQueryData",
|
|
52
|
+
"CallbackQueryDataJson",
|
|
53
|
+
"Field",
|
|
54
|
+
)
|
telegrinder/node/command.py
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
|
|
4
|
-
from fntypes.option import Nothing, Option, Some
|
|
5
|
-
|
|
6
|
-
from telegrinder.node.base import DataNode
|
|
7
|
-
from telegrinder.node.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
name,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
import typing
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from fntypes.option import Nothing, Option, Some
|
|
5
|
+
|
|
6
|
+
from telegrinder.node.base import DataNode
|
|
7
|
+
from telegrinder.node.either import Either
|
|
8
|
+
from telegrinder.node.text import Caption, Text
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def single_split(s: str, separator: str) -> tuple[str, str]:
|
|
12
|
+
left, *right = s.split(separator, 1)
|
|
13
|
+
return left, (right[0] if right else "")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cut_mention(text: str) -> tuple[str, Option[str]]:
|
|
17
|
+
left, right = single_split(text, "@")
|
|
18
|
+
return left, Some(right) if right else Nothing()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class CommandInfo(DataNode):
|
|
23
|
+
name: str
|
|
24
|
+
arguments: str
|
|
25
|
+
mention: Option[str] = field(default_factory=Nothing)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def compose(cls, text: Either[Text, Caption]) -> typing.Self:
|
|
29
|
+
name, arguments = single_split(text, separator=" ")
|
|
30
|
+
name, mention = cut_mention(name)
|
|
31
|
+
return cls(name, arguments, mention)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ("CommandInfo", "cut_mention", "single_split")
|