telegrinder 0.3.4__py3-none-any.whl → 0.3.4.post1__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 +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +17 -17
- telegrinder/bot/cute_types/base.py +258 -258
- telegrinder/bot/cute_types/callback_query.py +385 -385
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +43 -43
- telegrinder/bot/cute_types/message.py +2637 -2637
- telegrinder/bot/cute_types/update.py +104 -104
- telegrinder/bot/cute_types/utils.py +95 -95
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +98 -98
- telegrinder/bot/dispatch/dispatch.py +202 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -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 +135 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -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 +22 -22
- telegrinder/bot/dispatch/process.py +157 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -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/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -200
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -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 +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +172 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +206 -206
- telegrinder/bot/rules/adapter/__init__.py +17 -17
- telegrinder/bot/rules/adapter/abc.py +31 -31
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +65 -65
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_event.py +27 -27
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +163 -163
- telegrinder/bot/rules/chat_join.py +43 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -36
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +56 -56
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +176 -176
- telegrinder/bot/scenario/choice.py +51 -51
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +313 -313
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +20 -20
- telegrinder/node/attachment.py +87 -87
- telegrinder/node/base.py +157 -157
- telegrinder/node/callback_query.py +53 -53
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +198 -198
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +65 -65
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -48
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +41 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +5 -5
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +283 -283
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -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 +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
- telegrinder/tools/magic.py +157 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +260 -260
- telegrinder/types/enums.py +701 -701
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6950 -6950
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
- telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
- telegrinder-0.3.4.dist-info/RECORD +0 -165
- {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
telegrinder/node/me.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from telegrinder.api.api import API
|
|
2
|
-
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
-
from telegrinder.node.scope import GLOBAL
|
|
4
|
-
from telegrinder.types.objects import User
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Me(ScalarNode, User):
|
|
8
|
-
scope = GLOBAL
|
|
9
|
-
|
|
10
|
-
@classmethod
|
|
11
|
-
async def compose(cls, api: API) -> User:
|
|
12
|
-
me = await api.get_me()
|
|
13
|
-
return me.expect(ComposeError("Can't complete get_me request"))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = ("Me",)
|
|
1
|
+
from telegrinder.api.api import API
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.scope import GLOBAL
|
|
4
|
+
from telegrinder.types.objects import User
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Me(ScalarNode, User):
|
|
8
|
+
scope = GLOBAL
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
async def compose(cls, api: API) -> User:
|
|
12
|
+
me = await api.get_me()
|
|
13
|
+
return me.expect(ComposeError("Can't complete get_me request"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("Me",)
|
telegrinder/node/message.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
-
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
-
from telegrinder.node.update import UpdateNode
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MessageNode(ScalarNode, MessageCute):
|
|
7
|
-
@classmethod
|
|
8
|
-
def compose(cls, update: UpdateNode) -> MessageCute:
|
|
9
|
-
if not update.message:
|
|
10
|
-
raise ComposeError("Update is not a message.")
|
|
11
|
-
return update.message.unwrap()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__all__ = ("MessageNode",)
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.update import UpdateNode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MessageNode(ScalarNode, MessageCute):
|
|
7
|
+
@classmethod
|
|
8
|
+
def compose(cls, update: UpdateNode) -> MessageCute:
|
|
9
|
+
if not update.message:
|
|
10
|
+
raise ComposeError("Update is not a message.")
|
|
11
|
+
return update.message.unwrap()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("MessageNode",)
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from telegrinder.bot.dispatch.context import Context
|
|
4
|
-
from telegrinder.modules import logger
|
|
5
|
-
from telegrinder.node.base import ComposeError, Node
|
|
6
|
-
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
|
|
7
|
-
from telegrinder.node.scope import NodeScope
|
|
8
|
-
from telegrinder.node.update import UpdateNode
|
|
9
|
-
from telegrinder.tools.magic import get_impls, impl
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Polymorphic(Node):
|
|
13
|
-
@classmethod
|
|
14
|
-
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
15
|
-
logger.debug(f"Composing polymorphic node {cls.__name__!r}...")
|
|
16
|
-
scope = getattr(cls, "scope", None)
|
|
17
|
-
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
18
|
-
|
|
19
|
-
for i, impl_ in enumerate(get_impls(cls)):
|
|
20
|
-
logger.debug("Checking impl {!r}...", impl_.__name__)
|
|
21
|
-
composition = Composition(impl_, True)
|
|
22
|
-
node_collection = await composition.compose_nodes(update, context)
|
|
23
|
-
if node_collection is None:
|
|
24
|
-
logger.debug("Impl {!r} composition failed!", impl_.__name__)
|
|
25
|
-
continue
|
|
26
|
-
|
|
27
|
-
# To determine whether this is a right morph, all subnodes must be resolved
|
|
28
|
-
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
29
|
-
logger.debug(
|
|
30
|
-
"Morph is already cached as per_event node, using its value. Impl {!r} succeeded!",
|
|
31
|
-
impl_.__name__,
|
|
32
|
-
)
|
|
33
|
-
res: NodeSession = node_ctx[(cls, i)]
|
|
34
|
-
await node_collection.close_all()
|
|
35
|
-
return res.value
|
|
36
|
-
|
|
37
|
-
result = await composition(cls, **node_collection.values)
|
|
38
|
-
if scope is NodeScope.PER_EVENT:
|
|
39
|
-
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
40
|
-
|
|
41
|
-
await node_collection.close_all(with_value=result)
|
|
42
|
-
logger.debug("Impl {!r} succeeded with value: {!r}", impl_.__name__, result)
|
|
43
|
-
return result
|
|
44
|
-
|
|
45
|
-
raise ComposeError("No implementation found.")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
__all__ = ("Polymorphic", "impl")
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.context import Context
|
|
4
|
+
from telegrinder.modules import logger
|
|
5
|
+
from telegrinder.node.base import ComposeError, Node
|
|
6
|
+
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
|
|
7
|
+
from telegrinder.node.scope import NodeScope
|
|
8
|
+
from telegrinder.node.update import UpdateNode
|
|
9
|
+
from telegrinder.tools.magic import get_impls, impl
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Polymorphic(Node):
|
|
13
|
+
@classmethod
|
|
14
|
+
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
15
|
+
logger.debug(f"Composing polymorphic node {cls.__name__!r}...")
|
|
16
|
+
scope = getattr(cls, "scope", None)
|
|
17
|
+
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
18
|
+
|
|
19
|
+
for i, impl_ in enumerate(get_impls(cls)):
|
|
20
|
+
logger.debug("Checking impl {!r}...", impl_.__name__)
|
|
21
|
+
composition = Composition(impl_, True)
|
|
22
|
+
node_collection = await composition.compose_nodes(update, context)
|
|
23
|
+
if node_collection is None:
|
|
24
|
+
logger.debug("Impl {!r} composition failed!", impl_.__name__)
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
# To determine whether this is a right morph, all subnodes must be resolved
|
|
28
|
+
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
29
|
+
logger.debug(
|
|
30
|
+
"Morph is already cached as per_event node, using its value. Impl {!r} succeeded!",
|
|
31
|
+
impl_.__name__,
|
|
32
|
+
)
|
|
33
|
+
res: NodeSession = node_ctx[(cls, i)]
|
|
34
|
+
await node_collection.close_all()
|
|
35
|
+
return res.value
|
|
36
|
+
|
|
37
|
+
result = await composition(cls, **node_collection.values)
|
|
38
|
+
if scope is NodeScope.PER_EVENT:
|
|
39
|
+
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
40
|
+
|
|
41
|
+
await node_collection.close_all(with_value=result)
|
|
42
|
+
logger.debug("Impl {!r} succeeded with value: {!r}", impl_.__name__, result)
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
raise ComposeError("No implementation found.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = ("Polymorphic", "impl")
|
telegrinder/node/rule.py
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import importlib
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
from telegrinder.bot.dispatch.context import Context
|
|
6
|
-
from telegrinder.node.base import ComposeError, Node
|
|
7
|
-
from telegrinder.node.update import UpdateNode
|
|
8
|
-
|
|
9
|
-
if typing.TYPE_CHECKING:
|
|
10
|
-
from telegrinder.bot.dispatch.process import check_rule
|
|
11
|
-
from telegrinder.bot.rules.abc import ABCRule
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class RuleChain(dict[str, typing.Any], Node):
|
|
15
|
-
dataclass: type[typing.Any] = dict
|
|
16
|
-
rules: tuple["ABCRule", ...] = ()
|
|
17
|
-
|
|
18
|
-
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
19
|
-
super().__init_subclass__(*args, **kwargs)
|
|
20
|
-
|
|
21
|
-
if cls.__name__ == "_RuleNode":
|
|
22
|
-
return
|
|
23
|
-
cls.dataclass = cls.generate_node_dataclass(cls)
|
|
24
|
-
|
|
25
|
-
def __new__(cls, *rules: "ABCRule") -> type[Node]:
|
|
26
|
-
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
27
|
-
|
|
28
|
-
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
29
|
-
if not isinstance(items, tuple):
|
|
30
|
-
items = (items,)
|
|
31
|
-
return cls(*items)
|
|
32
|
-
|
|
33
|
-
@staticmethod
|
|
34
|
-
def generate_node_dataclass(cls_: type["RuleChain"]): # noqa: ANN205
|
|
35
|
-
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
async def compose(cls, update: UpdateNode) -> typing.Any:
|
|
39
|
-
# Hack to avoid circular import
|
|
40
|
-
globalns = globals()
|
|
41
|
-
if "check_rule" not in globalns:
|
|
42
|
-
globalns.update(
|
|
43
|
-
{
|
|
44
|
-
"check_rule": getattr(
|
|
45
|
-
importlib.import_module("telegrinder.bot.dispatch.process"),
|
|
46
|
-
"check_rule",
|
|
47
|
-
),
|
|
48
|
-
},
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
ctx = Context()
|
|
52
|
-
for rule in cls.rules:
|
|
53
|
-
if not await check_rule(update.api, rule, update, ctx):
|
|
54
|
-
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
if dataclasses.is_dataclass(cls.dataclass):
|
|
58
|
-
return cls.dataclass(**{k: ctx[k] for k in cls.__annotations__})
|
|
59
|
-
return cls.dataclass(**ctx)
|
|
60
|
-
except Exception as exc:
|
|
61
|
-
raise ComposeError(f"Dataclass validation error: {exc}")
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def as_node(cls) -> type[typing.Self]:
|
|
65
|
-
return cls
|
|
66
|
-
|
|
67
|
-
@classmethod
|
|
68
|
-
def get_subnodes(cls) -> dict[typing.Literal["update"], type[UpdateNode]]:
|
|
69
|
-
return {"update": UpdateNode}
|
|
70
|
-
|
|
71
|
-
@classmethod
|
|
72
|
-
def is_generator(cls) -> typing.Literal[False]:
|
|
73
|
-
return False
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
__all__ = ("RuleChain",)
|
|
1
|
+
import dataclasses
|
|
2
|
+
import importlib
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.node.base import ComposeError, Node
|
|
7
|
+
from telegrinder.node.update import UpdateNode
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
11
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RuleChain(dict[str, typing.Any], Node):
|
|
15
|
+
dataclass: type[typing.Any] = dict
|
|
16
|
+
rules: tuple["ABCRule", ...] = ()
|
|
17
|
+
|
|
18
|
+
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
19
|
+
super().__init_subclass__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
if cls.__name__ == "_RuleNode":
|
|
22
|
+
return
|
|
23
|
+
cls.dataclass = cls.generate_node_dataclass(cls)
|
|
24
|
+
|
|
25
|
+
def __new__(cls, *rules: "ABCRule") -> type[Node]:
|
|
26
|
+
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
27
|
+
|
|
28
|
+
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
29
|
+
if not isinstance(items, tuple):
|
|
30
|
+
items = (items,)
|
|
31
|
+
return cls(*items)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def generate_node_dataclass(cls_: type["RuleChain"]): # noqa: ANN205
|
|
35
|
+
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
async def compose(cls, update: UpdateNode) -> typing.Any:
|
|
39
|
+
# Hack to avoid circular import
|
|
40
|
+
globalns = globals()
|
|
41
|
+
if "check_rule" not in globalns:
|
|
42
|
+
globalns.update(
|
|
43
|
+
{
|
|
44
|
+
"check_rule": getattr(
|
|
45
|
+
importlib.import_module("telegrinder.bot.dispatch.process"),
|
|
46
|
+
"check_rule",
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
ctx = Context()
|
|
52
|
+
for rule in cls.rules:
|
|
53
|
+
if not await check_rule(update.api, rule, update, ctx):
|
|
54
|
+
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
if dataclasses.is_dataclass(cls.dataclass):
|
|
58
|
+
return cls.dataclass(**{k: ctx[k] for k in cls.__annotations__})
|
|
59
|
+
return cls.dataclass(**ctx)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
raise ComposeError(f"Dataclass validation error: {exc}")
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def as_node(cls) -> type[typing.Self]:
|
|
65
|
+
return cls
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_subnodes(cls) -> dict[typing.Literal["update"], type[UpdateNode]]:
|
|
69
|
+
return {"update": UpdateNode}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def is_generator(cls) -> typing.Literal[False]:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = ("RuleChain",)
|
telegrinder/node/scope.py
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
if typing.TYPE_CHECKING:
|
|
5
|
-
from .base import Node
|
|
6
|
-
|
|
7
|
-
T = typing.TypeVar("T", bound=type["Node"])
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class NodeScope(enum.Enum):
|
|
11
|
-
GLOBAL = enum.auto()
|
|
12
|
-
PER_EVENT = enum.auto()
|
|
13
|
-
PER_CALL = enum.auto()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
PER_EVENT = NodeScope.PER_EVENT
|
|
17
|
-
PER_CALL = NodeScope.PER_CALL
|
|
18
|
-
GLOBAL = NodeScope.GLOBAL
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def per_call(node: T) -> T:
|
|
22
|
-
setattr(node, "scope", PER_CALL)
|
|
23
|
-
return node
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def per_event(node: T) -> T:
|
|
27
|
-
setattr(node, "scope", PER_EVENT)
|
|
28
|
-
return node
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def global_node(node: T) -> T:
|
|
32
|
-
setattr(node, "scope", GLOBAL)
|
|
33
|
-
return node
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
__all__ = (
|
|
1
|
+
import enum
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from .base import Node
|
|
6
|
+
|
|
7
|
+
T = typing.TypeVar("T", bound=type["Node"])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NodeScope(enum.Enum):
|
|
11
|
+
GLOBAL = enum.auto()
|
|
12
|
+
PER_EVENT = enum.auto()
|
|
13
|
+
PER_CALL = enum.auto()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PER_EVENT = NodeScope.PER_EVENT
|
|
17
|
+
PER_CALL = NodeScope.PER_CALL
|
|
18
|
+
GLOBAL = NodeScope.GLOBAL
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def per_call(node: T) -> T:
|
|
22
|
+
setattr(node, "scope", PER_CALL)
|
|
23
|
+
return node
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def per_event(node: T) -> T:
|
|
27
|
+
setattr(node, "scope", PER_EVENT)
|
|
28
|
+
return node
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def global_node(node: T) -> T:
|
|
32
|
+
setattr(node, "scope", GLOBAL)
|
|
33
|
+
return node
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = (
|
|
37
37
|
"GLOBAL",
|
|
38
38
|
"NodeScope",
|
|
39
39
|
"PER_CALL",
|
|
40
40
|
"PER_EVENT",
|
|
41
41
|
"global_node",
|
|
42
42
|
"per_call",
|
|
43
|
-
"per_event",
|
|
44
|
-
)
|
|
43
|
+
"per_event",
|
|
44
|
+
)
|
telegrinder/node/source.py
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from fntypes.option import Nothing, Option
|
|
5
|
-
|
|
6
|
-
from telegrinder.api.api import API
|
|
7
|
-
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
8
|
-
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
9
|
-
from telegrinder.node.callback_query import CallbackQueryNode
|
|
10
|
-
from telegrinder.node.event import EventNode
|
|
11
|
-
from telegrinder.node.message import MessageNode
|
|
12
|
-
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
13
|
-
from telegrinder.types.objects import Chat, Message, User
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
17
|
-
class Source(Polymorphic, DataNode):
|
|
18
|
-
api: API
|
|
19
|
-
chat: Chat
|
|
20
|
-
from_user: User
|
|
21
|
-
thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
|
|
22
|
-
|
|
23
|
-
@impl
|
|
24
|
-
def compose_message(cls, message: MessageNode) -> typing.Self:
|
|
25
|
-
return cls(
|
|
26
|
-
api=message.ctx_api,
|
|
27
|
-
chat=message.chat,
|
|
28
|
-
from_user=message.from_.expect(ComposeError("MessageNode has no from_user")),
|
|
29
|
-
thread_id=message.message_thread_id,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
@impl
|
|
33
|
-
def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
|
|
34
|
-
return cls(
|
|
35
|
-
api=callback_query.ctx_api,
|
|
36
|
-
chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
|
|
37
|
-
from_user=callback_query.from_user,
|
|
38
|
-
thread_id=callback_query.message_thread_id,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
@impl
|
|
42
|
-
def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
|
|
43
|
-
return cls(
|
|
44
|
-
api=chat_join_request.ctx_api,
|
|
45
|
-
chat=chat_join_request.chat,
|
|
46
|
-
from_user=chat_join_request.from_user,
|
|
47
|
-
thread_id=Nothing(),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
async def send(self, text: str) -> Message:
|
|
51
|
-
result = await self.api.send_message(
|
|
52
|
-
chat_id=self.chat.id,
|
|
53
|
-
message_thread_id=self.thread_id.unwrap_or_none(),
|
|
54
|
-
text=text,
|
|
55
|
-
)
|
|
56
|
-
return result.unwrap()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class ChatSource(ScalarNode, Chat):
|
|
60
|
-
@classmethod
|
|
61
|
-
def compose(cls, source: Source) -> Chat:
|
|
62
|
-
return source.chat
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class UserSource(ScalarNode, User):
|
|
66
|
-
@classmethod
|
|
67
|
-
def compose(cls, source: Source) -> User:
|
|
68
|
-
return source.from_user
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
__all__ = ("ChatSource", "Source", "UserSource")
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.option import Nothing, Option
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
8
|
+
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
9
|
+
from telegrinder.node.callback_query import CallbackQueryNode
|
|
10
|
+
from telegrinder.node.event import EventNode
|
|
11
|
+
from telegrinder.node.message import MessageNode
|
|
12
|
+
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
13
|
+
from telegrinder.types.objects import Chat, Message, User
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
17
|
+
class Source(Polymorphic, DataNode):
|
|
18
|
+
api: API
|
|
19
|
+
chat: Chat
|
|
20
|
+
from_user: User
|
|
21
|
+
thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
|
|
22
|
+
|
|
23
|
+
@impl
|
|
24
|
+
def compose_message(cls, message: MessageNode) -> typing.Self:
|
|
25
|
+
return cls(
|
|
26
|
+
api=message.ctx_api,
|
|
27
|
+
chat=message.chat,
|
|
28
|
+
from_user=message.from_.expect(ComposeError("MessageNode has no from_user")),
|
|
29
|
+
thread_id=message.message_thread_id,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
@impl
|
|
33
|
+
def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
|
|
34
|
+
return cls(
|
|
35
|
+
api=callback_query.ctx_api,
|
|
36
|
+
chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
|
|
37
|
+
from_user=callback_query.from_user,
|
|
38
|
+
thread_id=callback_query.message_thread_id,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@impl
|
|
42
|
+
def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
|
|
43
|
+
return cls(
|
|
44
|
+
api=chat_join_request.ctx_api,
|
|
45
|
+
chat=chat_join_request.chat,
|
|
46
|
+
from_user=chat_join_request.from_user,
|
|
47
|
+
thread_id=Nothing(),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def send(self, text: str) -> Message:
|
|
51
|
+
result = await self.api.send_message(
|
|
52
|
+
chat_id=self.chat.id,
|
|
53
|
+
message_thread_id=self.thread_id.unwrap_or_none(),
|
|
54
|
+
text=text,
|
|
55
|
+
)
|
|
56
|
+
return result.unwrap()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ChatSource(ScalarNode, Chat):
|
|
60
|
+
@classmethod
|
|
61
|
+
def compose(cls, source: Source) -> Chat:
|
|
62
|
+
return source.chat
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class UserSource(ScalarNode, User):
|
|
66
|
+
@classmethod
|
|
67
|
+
def compose(cls, source: Source) -> User:
|
|
68
|
+
return source.from_user
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = ("ChatSource", "Source", "UserSource")
|
telegrinder/node/text.py
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from telegrinder.node.base import ComposeError, FactoryNode, ScalarNode
|
|
4
|
-
from telegrinder.node.message import MessageNode
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Text(ScalarNode, str):
|
|
8
|
-
@classmethod
|
|
9
|
-
def compose(cls, message: MessageNode) -> str:
|
|
10
|
-
if not message.text:
|
|
11
|
-
raise ComposeError("Message has no text.")
|
|
12
|
-
return message.text.unwrap()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TextInteger(ScalarNode, int):
|
|
16
|
-
@classmethod
|
|
17
|
-
def compose(cls, text: Text) -> int:
|
|
18
|
-
if not text.isdigit():
|
|
19
|
-
raise ComposeError("Text is not digit.")
|
|
20
|
-
return int(text)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if typing.TYPE_CHECKING:
|
|
24
|
-
from typing import Literal as TextLiteral
|
|
25
|
-
|
|
26
|
-
else:
|
|
27
|
-
|
|
28
|
-
class TextLiteral(FactoryNode):
|
|
29
|
-
texts: tuple[str, ...]
|
|
30
|
-
|
|
31
|
-
def __class_getitem__(cls, texts, /):
|
|
32
|
-
return cls(texts=(texts,) if not isinstance(texts, tuple) else texts)
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def compose(cls, text: Text) -> str:
|
|
36
|
-
if text in cls.texts:
|
|
37
|
-
return text
|
|
38
|
-
raise ComposeError("Text matching failed.")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
__all__ = ("Text", "TextInteger", "TextLiteral")
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.node.base import ComposeError, FactoryNode, ScalarNode
|
|
4
|
+
from telegrinder.node.message import MessageNode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Text(ScalarNode, str):
|
|
8
|
+
@classmethod
|
|
9
|
+
def compose(cls, message: MessageNode) -> str:
|
|
10
|
+
if not message.text:
|
|
11
|
+
raise ComposeError("Message has no text.")
|
|
12
|
+
return message.text.unwrap()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TextInteger(ScalarNode, int):
|
|
16
|
+
@classmethod
|
|
17
|
+
def compose(cls, text: Text) -> int:
|
|
18
|
+
if not text.isdigit():
|
|
19
|
+
raise ComposeError("Text is not digit.")
|
|
20
|
+
return int(text)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if typing.TYPE_CHECKING:
|
|
24
|
+
from typing import Literal as TextLiteral
|
|
25
|
+
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
class TextLiteral(FactoryNode):
|
|
29
|
+
texts: tuple[str, ...]
|
|
30
|
+
|
|
31
|
+
def __class_getitem__(cls, texts, /):
|
|
32
|
+
return cls(texts=(texts,) if not isinstance(texts, tuple) else texts)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def compose(cls, text: Text) -> str:
|
|
36
|
+
if text in cls.texts:
|
|
37
|
+
return text
|
|
38
|
+
raise ComposeError("Text matching failed.")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ("Text", "TextInteger", "TextLiteral")
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .generator import generate_node
|
|
2
|
-
|
|
3
|
-
__all__ = ("generate_node",)
|
|
1
|
+
from .generator import generate_node
|
|
2
|
+
|
|
3
|
+
__all__ = ("generate_node",)
|