telegrinder 0.3.0.post1__py3-none-any.whl → 0.3.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 +1 -1
- telegrinder/bot/cute_types/callback_query.py +2 -14
- telegrinder/bot/cute_types/chat_join_request.py +1 -1
- telegrinder/bot/cute_types/chat_member_updated.py +1 -1
- telegrinder/bot/cute_types/inline_query.py +1 -6
- telegrinder/bot/cute_types/message.py +1 -21
- telegrinder/bot/cute_types/update.py +1 -1
- telegrinder/bot/dispatch/abc.py +45 -3
- telegrinder/bot/dispatch/dispatch.py +8 -8
- telegrinder/bot/dispatch/handler/func.py +8 -10
- telegrinder/bot/dispatch/process.py +1 -1
- telegrinder/bot/dispatch/view/base.py +31 -20
- telegrinder/bot/dispatch/view/raw.py +20 -16
- telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/machine.py +24 -30
- telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
- telegrinder/bot/rules/abc.py +42 -7
- telegrinder/bot/rules/adapter/raw_update.py +1 -3
- telegrinder/bot/rules/callback_data.py +7 -7
- telegrinder/bot/rules/chat_join.py +5 -5
- telegrinder/bot/rules/command.py +1 -1
- telegrinder/bot/rules/enum_text.py +4 -1
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/inline.py +6 -7
- telegrinder/bot/rules/integer.py +1 -1
- telegrinder/bot/rules/is_from.py +20 -20
- telegrinder/bot/rules/markup.py +6 -3
- telegrinder/bot/rules/mention.py +1 -1
- telegrinder/bot/rules/message.py +2 -2
- telegrinder/bot/rules/message_entities.py +2 -2
- telegrinder/bot/rules/node.py +1 -1
- telegrinder/bot/rules/regex.py +1 -1
- telegrinder/bot/rules/start.py +2 -2
- telegrinder/bot/rules/text.py +6 -4
- telegrinder/bot/rules/update.py +1 -1
- telegrinder/bot/scenario/checkbox.py +9 -1
- telegrinder/msgspec_utils.py +11 -3
- telegrinder/node/attachment.py +6 -6
- telegrinder/node/base.py +17 -11
- telegrinder/node/callback_query.py +1 -1
- telegrinder/node/command.py +1 -1
- telegrinder/node/composer.py +5 -2
- telegrinder/node/container.py +1 -1
- telegrinder/node/event.py +1 -1
- telegrinder/node/message.py +1 -1
- telegrinder/node/polymorphic.py +6 -3
- telegrinder/node/rule.py +1 -1
- telegrinder/node/source.py +5 -7
- telegrinder/node/text.py +2 -2
- telegrinder/node/tools/generator.py +1 -2
- telegrinder/node/update.py +3 -3
- telegrinder/rules.py +2 -0
- telegrinder/tools/buttons.py +4 -4
- telegrinder/tools/error_handler/abc.py +7 -7
- telegrinder/tools/error_handler/error_handler.py +58 -47
- telegrinder/tools/formatting/html.py +0 -2
- telegrinder/tools/functional.py +3 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
- telegrinder/tools/i18n/__init__.py +1 -1
- telegrinder/tools/i18n/{base.py → abc.py} +0 -0
- telegrinder/tools/i18n/middleware/__init__.py +1 -1
- telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
- telegrinder/tools/i18n/simple.py +11 -12
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/magic.py +8 -4
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/METADATA +1 -1
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/RECORD +73 -73
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/LICENSE +0 -0
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/WHEEL +0 -0
telegrinder/bot/rules/mention.py
CHANGED
|
@@ -5,7 +5,7 @@ from .text import HasText
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class HasMention(MessageRule, requires=[HasText()]):
|
|
8
|
-
|
|
8
|
+
def check(self, message: Message) -> bool:
|
|
9
9
|
if not message.entities.unwrap_or_none():
|
|
10
10
|
return False
|
|
11
11
|
return any(entity.type == MessageEntityType.MENTION for entity in message.entities.unwrap())
|
telegrinder/bot/rules/message.py
CHANGED
|
@@ -3,7 +3,7 @@ import abc
|
|
|
3
3
|
from telegrinder.bot.dispatch.context import Context
|
|
4
4
|
from telegrinder.types.objects import Message as MessageEvent
|
|
5
5
|
|
|
6
|
-
from .abc import ABCRule, Message
|
|
6
|
+
from .abc import ABCRule, CheckResult, Message
|
|
7
7
|
from .adapter import EventAdapter
|
|
8
8
|
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ class MessageRule(ABCRule[Message], abc.ABC):
|
|
|
11
11
|
adapter: EventAdapter[Message] = EventAdapter(MessageEvent, Message)
|
|
12
12
|
|
|
13
13
|
@abc.abstractmethod
|
|
14
|
-
|
|
14
|
+
def check(self, message: Message, ctx: Context) -> CheckResult: ...
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
__all__ = ("MessageRule",)
|
|
@@ -10,7 +10,7 @@ Entity: typing.TypeAlias = str | MessageEntityType
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class HasEntities(MessageRule):
|
|
13
|
-
|
|
13
|
+
def check(self, message: Message) -> bool:
|
|
14
14
|
return bool(message.entities)
|
|
15
15
|
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ class MessageEntities(MessageRule, requires=[HasEntities()]):
|
|
|
18
18
|
def __init__(self, entities: Entity | list[Entity], /) -> None:
|
|
19
19
|
self.entities = [entities] if not isinstance(entities, list) else entities
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
def check(self, message: Message, ctx: Context) -> bool:
|
|
22
22
|
message_entities: list[MessageEntity] = []
|
|
23
23
|
for entity in message.entities.unwrap():
|
|
24
24
|
for entity_type in self.entities:
|
telegrinder/bot/rules/node.py
CHANGED
|
@@ -17,7 +17,7 @@ class NodeRule(ABCRule[tuple[Node, ...]]):
|
|
|
17
17
|
def adapter(self) -> NodeAdapter:
|
|
18
18
|
return NodeAdapter(*self.nodes) # type: ignore
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
def check(self, resolved_nodes: tuple[Node, ...], ctx: Context) -> typing.Literal[True]:
|
|
21
21
|
for i, node in enumerate(resolved_nodes):
|
|
22
22
|
if key := self.node_keys[i]:
|
|
23
23
|
ctx[key] = node
|
telegrinder/bot/rules/regex.py
CHANGED
|
@@ -22,7 +22,7 @@ class Regex(ABCRule):
|
|
|
22
22
|
re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
def check(self, text: Text, ctx: Context) -> bool:
|
|
26
26
|
for regexp in self.regexp:
|
|
27
27
|
response = re.match(regexp, text)
|
|
28
28
|
if response is not None:
|
telegrinder/bot/rules/start.py
CHANGED
|
@@ -12,7 +12,7 @@ from .message_entities import MessageEntities
|
|
|
12
12
|
class StartCommand(
|
|
13
13
|
MessageRule,
|
|
14
14
|
requires=[
|
|
15
|
-
IsPrivate(),
|
|
15
|
+
IsPrivate(),
|
|
16
16
|
MessageEntities(MessageEntityType.BOT_COMMAND),
|
|
17
17
|
Markup(["/start <param>", "/start"]),
|
|
18
18
|
],
|
|
@@ -28,7 +28,7 @@ class StartCommand(
|
|
|
28
28
|
self.validator = validator
|
|
29
29
|
self.alias = alias
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
def check(self, ctx: Context) -> bool:
|
|
32
32
|
param: str | None = ctx.pop("param", None)
|
|
33
33
|
validated_param = self.validator(param) if self.validator and param is not None else param
|
|
34
34
|
|
telegrinder/bot/rules/text.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
1
3
|
from telegrinder import node
|
|
2
|
-
from telegrinder.tools.i18n.
|
|
4
|
+
from telegrinder.tools.i18n.abc import ABCTranslator
|
|
3
5
|
|
|
4
6
|
from .abc import ABCRule, with_caching_translations
|
|
5
7
|
from .node import NodeRule
|
|
@@ -17,12 +19,12 @@ class Text(ABCRule):
|
|
|
17
19
|
self.texts = texts if not ignore_case else list(map(str.lower, texts))
|
|
18
20
|
self.ignore_case = ignore_case
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
def check(self, text: node.text.Text) -> bool:
|
|
21
23
|
return (text if not self.ignore_case else text.lower()) in self.texts
|
|
22
24
|
|
|
23
25
|
@with_caching_translations
|
|
24
|
-
async def translate(self, translator: ABCTranslator) ->
|
|
25
|
-
return
|
|
26
|
+
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
27
|
+
return self.__class__(
|
|
26
28
|
texts=[translator.get(text) for text in self.texts],
|
|
27
29
|
ignore_case=self.ignore_case,
|
|
28
30
|
)
|
telegrinder/bot/rules/update.py
CHANGED
|
@@ -8,7 +8,7 @@ class IsUpdateType(ABCRule):
|
|
|
8
8
|
def __init__(self, update_type: UpdateType, /) -> None:
|
|
9
9
|
self.update_type = update_type
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
def check(self, event: UpdateCute) -> bool:
|
|
12
12
|
return event.update_type == self.update_type
|
|
13
13
|
|
|
14
14
|
|
|
@@ -35,6 +35,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
35
35
|
message: str,
|
|
36
36
|
*,
|
|
37
37
|
ready_text: str = "Ready",
|
|
38
|
+
cancel_text: str | None = None,
|
|
38
39
|
max_in_row: int = 3,
|
|
39
40
|
) -> None:
|
|
40
41
|
self.chat_id = chat_id
|
|
@@ -44,6 +45,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
44
45
|
self.max_in_row = max_in_row
|
|
45
46
|
self.random_code = secrets.token_hex(8)
|
|
46
47
|
self.waiter_machine = waiter_machine
|
|
48
|
+
self.cancel_text = cancel_text
|
|
47
49
|
|
|
48
50
|
def __repr__(self) -> str:
|
|
49
51
|
return (
|
|
@@ -75,6 +77,9 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
75
77
|
kb.row()
|
|
76
78
|
|
|
77
79
|
kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
|
|
80
|
+
if self.cancel_text is not None:
|
|
81
|
+
kb.row()
|
|
82
|
+
kb.add(InlineButton(self.cancel_text, callback_data=self.random_code + "/cancel"))
|
|
78
83
|
return kb.get_markup()
|
|
79
84
|
|
|
80
85
|
def add_option(
|
|
@@ -94,6 +99,9 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
94
99
|
code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
|
|
95
100
|
if code == "ready":
|
|
96
101
|
return False
|
|
102
|
+
elif code == "cancel":
|
|
103
|
+
self.choices = []
|
|
104
|
+
return False
|
|
97
105
|
|
|
98
106
|
for i, choice in enumerate(self.choices):
|
|
99
107
|
if choice.code == code:
|
|
@@ -124,7 +132,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
124
132
|
).unwrap()
|
|
125
133
|
|
|
126
134
|
while True:
|
|
127
|
-
q, _ = await self.waiter_machine.wait(StateViewHasher(view
|
|
135
|
+
q, _ = await self.waiter_machine.wait(StateViewHasher(view), message.message_id)
|
|
128
136
|
should_continue = await self.handle(q)
|
|
129
137
|
await q.answer(self.CALLBACK_ANSWER)
|
|
130
138
|
if not should_continue:
|
telegrinder/msgspec_utils.py
CHANGED
|
@@ -210,12 +210,18 @@ class Decoder:
|
|
|
210
210
|
|
|
211
211
|
@typing.overload
|
|
212
212
|
def __call__(
|
|
213
|
-
self,
|
|
213
|
+
self,
|
|
214
|
+
type: type[T],
|
|
215
|
+
*,
|
|
216
|
+
strict: bool = True,
|
|
214
217
|
) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
215
218
|
|
|
216
219
|
@typing.overload
|
|
217
220
|
def __call__(
|
|
218
|
-
self,
|
|
221
|
+
self,
|
|
222
|
+
type: typing.Any,
|
|
223
|
+
*,
|
|
224
|
+
strict: bool = True,
|
|
219
225
|
) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
220
226
|
|
|
221
227
|
@contextmanager
|
|
@@ -223,7 +229,9 @@ class Decoder:
|
|
|
223
229
|
"""Context manager returns the `msgspec.json.Decoder` object with the `dec_hook`."""
|
|
224
230
|
|
|
225
231
|
dec_obj = msgspec.json.Decoder(
|
|
226
|
-
type=typing.Any if type is object else type,
|
|
232
|
+
type=typing.Any if type is object else type,
|
|
233
|
+
strict=strict,
|
|
234
|
+
dec_hook=self.dec_hook,
|
|
227
235
|
)
|
|
228
236
|
yield dec_obj
|
|
229
237
|
|
telegrinder/node/attachment.py
CHANGED
|
@@ -31,7 +31,7 @@ class Attachment(DataNode):
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
|
|
34
|
+
def compose(cls, message: MessageNode) -> "Attachment":
|
|
35
35
|
for attachment_type in ("audio", "document", "photo", "poll", "video"):
|
|
36
36
|
match getattr(message, attachment_type, Nothing()):
|
|
37
37
|
case Some(attachment):
|
|
@@ -44,7 +44,7 @@ class Photo(DataNode):
|
|
|
44
44
|
sizes: list[telegrinder.types.PhotoSize]
|
|
45
45
|
|
|
46
46
|
@classmethod
|
|
47
|
-
|
|
47
|
+
def compose(cls, attachment: Attachment) -> typing.Self:
|
|
48
48
|
if not attachment.photo:
|
|
49
49
|
raise ComposeError("Attachment is not a photo.")
|
|
50
50
|
return cls(attachment.photo.unwrap())
|
|
@@ -52,7 +52,7 @@ class Photo(DataNode):
|
|
|
52
52
|
|
|
53
53
|
class Video(ScalarNode, telegrinder.types.Video):
|
|
54
54
|
@classmethod
|
|
55
|
-
|
|
55
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
|
|
56
56
|
if not attachment.video:
|
|
57
57
|
raise ComposeError("Attachment is not a video.")
|
|
58
58
|
return attachment.video.unwrap()
|
|
@@ -60,7 +60,7 @@ class Video(ScalarNode, telegrinder.types.Video):
|
|
|
60
60
|
|
|
61
61
|
class Audio(ScalarNode, telegrinder.types.Audio):
|
|
62
62
|
@classmethod
|
|
63
|
-
|
|
63
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
|
|
64
64
|
if not attachment.audio:
|
|
65
65
|
raise ComposeError("Attachment is not an audio.")
|
|
66
66
|
return attachment.audio.unwrap()
|
|
@@ -68,7 +68,7 @@ class Audio(ScalarNode, telegrinder.types.Audio):
|
|
|
68
68
|
|
|
69
69
|
class Document(ScalarNode, telegrinder.types.Document):
|
|
70
70
|
@classmethod
|
|
71
|
-
|
|
71
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
|
|
72
72
|
if not attachment.document:
|
|
73
73
|
raise ComposeError("Attachment is not a document.")
|
|
74
74
|
return attachment.document.unwrap()
|
|
@@ -76,7 +76,7 @@ class Document(ScalarNode, telegrinder.types.Document):
|
|
|
76
76
|
|
|
77
77
|
class Poll(ScalarNode, telegrinder.types.Poll):
|
|
78
78
|
@classmethod
|
|
79
|
-
|
|
79
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
|
|
80
80
|
if not attachment.poll:
|
|
81
81
|
raise ComposeError("Attachment is not a poll.")
|
|
82
82
|
return attachment.poll.unwrap()
|
telegrinder/node/base.py
CHANGED
|
@@ -4,12 +4,11 @@ import typing
|
|
|
4
4
|
from types import AsyncGeneratorType
|
|
5
5
|
|
|
6
6
|
from telegrinder.node.scope import NodeScope
|
|
7
|
-
from telegrinder.tools.magic import
|
|
8
|
-
cache_magic_value,
|
|
9
|
-
get_annotations,
|
|
10
|
-
)
|
|
7
|
+
from telegrinder.tools.magic import cache_magic_value, get_annotations
|
|
11
8
|
|
|
12
|
-
ComposeResult: typing.TypeAlias =
|
|
9
|
+
ComposeResult: typing.TypeAlias = (
|
|
10
|
+
typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None] | typing.Any
|
|
11
|
+
)
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
|
|
@@ -28,7 +27,9 @@ def get_nodes(function: typing.Callable[..., typing.Any]) -> dict[str, type["Nod
|
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
@cache_magic_value("__is_generator__")
|
|
31
|
-
def is_generator(
|
|
30
|
+
def is_generator(
|
|
31
|
+
function: typing.Callable[..., typing.Any],
|
|
32
|
+
) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
|
|
32
33
|
return inspect.isasyncgenfunction(function)
|
|
33
34
|
|
|
34
35
|
|
|
@@ -103,20 +104,25 @@ if typing.TYPE_CHECKING:
|
|
|
103
104
|
pass
|
|
104
105
|
|
|
105
106
|
else:
|
|
107
|
+
|
|
106
108
|
def __init_subclass__(cls, *args, **kwargs): # noqa: N807
|
|
107
|
-
if any(issubclass(base,
|
|
109
|
+
if any(issubclass(base, ScalarNodeProto) for base in cls.__bases__ if base is not ScalarNode):
|
|
108
110
|
raise RuntimeError("Scalar nodes do not support inheritance.")
|
|
109
111
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
def _as_node(cls, bases, dct):
|
|
113
|
+
if not hasattr(cls, "_scalar_node_type"):
|
|
114
|
+
dct.update(cls.__dict__)
|
|
115
|
+
scalar_node_type = type(cls.__name__, bases, dct)
|
|
116
|
+
setattr(cls, "_scalar_node_type", scalar_node_type)
|
|
117
|
+
return scalar_node_type
|
|
118
|
+
return getattr(cls, "_scalar_node_type")
|
|
113
119
|
|
|
114
120
|
def create_class(name, bases, dct):
|
|
115
121
|
return type(
|
|
116
122
|
"Scalar",
|
|
117
123
|
(SCALAR_NODE,),
|
|
118
124
|
{
|
|
119
|
-
"as_node": classmethod(lambda cls:
|
|
125
|
+
"as_node": classmethod(lambda cls: _as_node(cls, bases, dct)),
|
|
120
126
|
"scope": Node.scope,
|
|
121
127
|
"__init_subclass__": __init_subclass__,
|
|
122
128
|
},
|
|
@@ -5,7 +5,7 @@ from telegrinder.node.update import UpdateNode
|
|
|
5
5
|
|
|
6
6
|
class CallbackQueryNode(ScalarNode, CallbackQueryCute):
|
|
7
7
|
@classmethod
|
|
8
|
-
|
|
8
|
+
def compose(cls, update: UpdateNode) -> CallbackQueryCute:
|
|
9
9
|
if not update.callback_query:
|
|
10
10
|
raise ComposeError("Update is not a callback_query.")
|
|
11
11
|
return update.callback_query.unwrap()
|
telegrinder/node/command.py
CHANGED
|
@@ -24,7 +24,7 @@ class CommandInfo(DataNode):
|
|
|
24
24
|
mention: Option[str] = field(default_factory=Nothing)
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
|
-
|
|
27
|
+
def compose(cls, text: Text) -> typing.Self:
|
|
28
28
|
name, arguments = single_split(text, separator=" ")
|
|
29
29
|
name, mention = cut_mention(name)
|
|
30
30
|
return cls(name, arguments, mention)
|
telegrinder/node/composer.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import inspect
|
|
2
3
|
import typing
|
|
3
4
|
|
|
4
|
-
from fntypes import Error, Ok, Result
|
|
5
5
|
from fntypes.error import UnwrapError
|
|
6
|
+
from fntypes.result import Error, Ok, Result
|
|
6
7
|
|
|
7
8
|
from telegrinder.api.api import API
|
|
8
9
|
from telegrinder.bot.cute_types.update import Update, UpdateCute
|
|
@@ -33,7 +34,9 @@ async def compose_node(
|
|
|
33
34
|
value = await generator.asend(None)
|
|
34
35
|
else:
|
|
35
36
|
generator = None
|
|
36
|
-
value =
|
|
37
|
+
value = typing.cast(typing.Awaitable[typing.Any] | typing.Any, node.compose(**kwargs))
|
|
38
|
+
if inspect.isawaitable(value):
|
|
39
|
+
value = await value
|
|
37
40
|
|
|
38
41
|
return NodeSession(_node, value, {}, generator)
|
|
39
42
|
|
telegrinder/node/container.py
CHANGED
|
@@ -7,7 +7,7 @@ class ContainerNode(Node):
|
|
|
7
7
|
linked_nodes: typing.ClassVar[list[type[Node]]]
|
|
8
8
|
|
|
9
9
|
@classmethod
|
|
10
|
-
|
|
10
|
+
def compose(cls, **kw) -> tuple[Node, ...]:
|
|
11
11
|
return tuple(t[1] for t in sorted(kw.items(), key=lambda t: t[0]))
|
|
12
12
|
|
|
13
13
|
@classmethod
|
telegrinder/node/event.py
CHANGED
|
@@ -31,7 +31,7 @@ class _EventNode(Node):
|
|
|
31
31
|
return cls(dataclass)
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
|
|
34
|
+
def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
|
|
35
35
|
dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
|
|
36
36
|
|
|
37
37
|
try:
|
telegrinder/node/message.py
CHANGED
|
@@ -5,7 +5,7 @@ from telegrinder.node.update import UpdateNode
|
|
|
5
5
|
|
|
6
6
|
class MessageNode(ScalarNode, MessageCute):
|
|
7
7
|
@classmethod
|
|
8
|
-
|
|
8
|
+
def compose(cls, update: UpdateNode) -> MessageCute:
|
|
9
9
|
if not update.message:
|
|
10
10
|
raise ComposeError("Update is not a message.")
|
|
11
11
|
return update.message.unwrap()
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -13,7 +13,7 @@ from telegrinder.tools.magic import get_impls, impl
|
|
|
13
13
|
class Polymorphic(Node):
|
|
14
14
|
@classmethod
|
|
15
15
|
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
16
|
-
logger.debug(f"Composing
|
|
16
|
+
logger.debug(f"Composing polymorphic node {cls.__name__!r}...")
|
|
17
17
|
scope = getattr(cls, "scope", None)
|
|
18
18
|
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
19
19
|
|
|
@@ -27,7 +27,10 @@ class Polymorphic(Node):
|
|
|
27
27
|
|
|
28
28
|
# To determine whether this is a right morph, all subnodes must be resolved
|
|
29
29
|
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
30
|
-
logger.debug(
|
|
30
|
+
logger.debug(
|
|
31
|
+
"Morph is already cached as per_event node, using its value. Impl {!r} succeeded!",
|
|
32
|
+
impl_.__name__,
|
|
33
|
+
)
|
|
31
34
|
res: NodeSession = node_ctx[(cls, i)]
|
|
32
35
|
await node_collection.close_all()
|
|
33
36
|
return res.value
|
|
@@ -40,7 +43,7 @@ class Polymorphic(Node):
|
|
|
40
43
|
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
41
44
|
|
|
42
45
|
await node_collection.close_all(with_value=result)
|
|
43
|
-
logger.debug("Impl {!r} succeeded with value: {}", impl_.__name__, result)
|
|
46
|
+
logger.debug("Impl {!r} succeeded with value: {!r}", impl_.__name__, result)
|
|
44
47
|
return result
|
|
45
48
|
|
|
46
49
|
raise ComposeError("No implementation found.")
|
telegrinder/node/rule.py
CHANGED
telegrinder/node/source.py
CHANGED
|
@@ -21,7 +21,7 @@ class Source(Polymorphic, DataNode):
|
|
|
21
21
|
thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
|
|
22
22
|
|
|
23
23
|
@impl
|
|
24
|
-
|
|
24
|
+
def compose_message(cls, message: MessageNode) -> typing.Self:
|
|
25
25
|
return cls(
|
|
26
26
|
api=message.ctx_api,
|
|
27
27
|
chat=message.chat,
|
|
@@ -30,7 +30,7 @@ class Source(Polymorphic, DataNode):
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
@impl
|
|
33
|
-
|
|
33
|
+
def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
|
|
34
34
|
return cls(
|
|
35
35
|
api=callback_query.ctx_api,
|
|
36
36
|
chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
|
|
@@ -39,9 +39,7 @@ class Source(Polymorphic, DataNode):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
@impl
|
|
42
|
-
|
|
43
|
-
cls, chat_join_request: EventNode[ChatJoinRequestCute]
|
|
44
|
-
) -> typing.Self:
|
|
42
|
+
def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
|
|
45
43
|
return cls(
|
|
46
44
|
api=chat_join_request.ctx_api,
|
|
47
45
|
chat=chat_join_request.chat,
|
|
@@ -60,13 +58,13 @@ class Source(Polymorphic, DataNode):
|
|
|
60
58
|
|
|
61
59
|
class ChatSource(ScalarNode, Chat):
|
|
62
60
|
@classmethod
|
|
63
|
-
|
|
61
|
+
def compose(cls, source: Source) -> Chat:
|
|
64
62
|
return source.chat
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
class UserSource(ScalarNode, User):
|
|
68
66
|
@classmethod
|
|
69
|
-
|
|
67
|
+
def compose(cls, source: Source) -> User:
|
|
70
68
|
return source.from_user
|
|
71
69
|
|
|
72
70
|
|
telegrinder/node/text.py
CHANGED
|
@@ -4,7 +4,7 @@ from telegrinder.node.message import MessageNode
|
|
|
4
4
|
|
|
5
5
|
class Text(ScalarNode, str):
|
|
6
6
|
@classmethod
|
|
7
|
-
|
|
7
|
+
def compose(cls, message: MessageNode) -> str:
|
|
8
8
|
if not message.text:
|
|
9
9
|
raise ComposeError("Message has no text.")
|
|
10
10
|
return message.text.unwrap()
|
|
@@ -12,7 +12,7 @@ class Text(ScalarNode, str):
|
|
|
12
12
|
|
|
13
13
|
class TextInteger(ScalarNode, int):
|
|
14
14
|
@classmethod
|
|
15
|
-
|
|
15
|
+
def compose(cls, text: Text) -> int:
|
|
16
16
|
if not text.isdigit():
|
|
17
17
|
raise ComposeError("Text is not digit.")
|
|
18
18
|
return int(text)
|
|
@@ -25,8 +25,7 @@ def generate_node(
|
|
|
25
25
|
casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
|
|
26
26
|
) -> type[Node]:
|
|
27
27
|
async def compose(cls, **kw) -> typing.Any:
|
|
28
|
-
|
|
29
|
-
result = func(*args) # type: ignore
|
|
28
|
+
result = func(*ContainerNode.compose(**kw)) # type: ignore
|
|
30
29
|
if inspect.isawaitable(result):
|
|
31
30
|
result = await result
|
|
32
31
|
for cast in casts:
|
telegrinder/node/update.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from telegrinder.api import API
|
|
1
|
+
from telegrinder.api.api import API
|
|
2
2
|
from telegrinder.bot.cute_types import UpdateCute
|
|
3
3
|
from telegrinder.node.base import ScalarNode
|
|
4
|
-
from telegrinder.types import Update
|
|
4
|
+
from telegrinder.types.objects import Update
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class UpdateNode(ScalarNode, UpdateCute):
|
|
8
8
|
@classmethod
|
|
9
|
-
|
|
9
|
+
def compose(cls, update: Update, api: API) -> UpdateCute:
|
|
10
10
|
if isinstance(update, UpdateCute):
|
|
11
11
|
return update
|
|
12
12
|
return UpdateCute.from_update(update, api)
|
telegrinder/rules.py
CHANGED
telegrinder/tools/buttons.py
CHANGED
|
@@ -14,7 +14,7 @@ from telegrinder.types.objects import (
|
|
|
14
14
|
WebAppInfo,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
KeyboardButton = typing.TypeVar("KeyboardButton", bound="BaseButton")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@dataclasses.dataclass
|
|
@@ -27,11 +27,11 @@ class BaseButton:
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class RowButtons(typing.Generic[
|
|
31
|
-
buttons: list[
|
|
30
|
+
class RowButtons(typing.Generic[KeyboardButton]):
|
|
31
|
+
buttons: list[KeyboardButton]
|
|
32
32
|
auto_row: bool
|
|
33
33
|
|
|
34
|
-
def __init__(self, *buttons:
|
|
34
|
+
def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
|
|
35
35
|
self.buttons = list(buttons)
|
|
36
36
|
self.auto_row = auto_row
|
|
37
37
|
|
|
@@ -6,28 +6,28 @@ from fntypes.result import Result
|
|
|
6
6
|
from telegrinder.api import API
|
|
7
7
|
from telegrinder.bot.dispatch.context import Context
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Handler = typing.Callable[
|
|
9
|
+
Event = typing.TypeVar("Event")
|
|
10
|
+
Handler = typing.Callable[..., typing.Awaitable[typing.Any]]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class ABCErrorHandler(ABC, typing.Generic[
|
|
13
|
+
class ABCErrorHandler(ABC, typing.Generic[Event]):
|
|
14
14
|
@abstractmethod
|
|
15
15
|
def __call__(
|
|
16
16
|
self,
|
|
17
17
|
*args: typing.Any,
|
|
18
18
|
**kwargs: typing.Any,
|
|
19
19
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
|
|
20
|
-
"""Decorator for registering callback as
|
|
20
|
+
"""Decorator for registering callback as a catcher for the error handler."""
|
|
21
21
|
|
|
22
22
|
@abstractmethod
|
|
23
23
|
async def run(
|
|
24
24
|
self,
|
|
25
|
-
handler: Handler
|
|
26
|
-
event:
|
|
25
|
+
handler: Handler,
|
|
26
|
+
event: Event,
|
|
27
27
|
api: API,
|
|
28
28
|
ctx: Context,
|
|
29
29
|
) -> Result[typing.Any, typing.Any]:
|
|
30
|
-
"""Run error handler."""
|
|
30
|
+
"""Run the error handler."""
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
__all__ = ("ABCErrorHandler",)
|