telegrinder 1.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
|
|
4
|
+
import kungfu
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
7
|
+
from telegrinder.node import EventNode
|
|
8
|
+
from telegrinder.node.utils import is_node
|
|
9
|
+
from telegrinder.tools.member_descriptor_proxy import MemberDescriptorProxy, evaluate_operations
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Magic[R, **P = [R]](kungfu.F[R, P], ABCRule):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
field: R,
|
|
16
|
+
parent: typing.Any = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
if isinstance(field, MemberDescriptorProxy):
|
|
19
|
+
owner_cls, member_name, operations = field._objclass, field._member_name, field._operations
|
|
20
|
+
node = EventNode[owner_cls]
|
|
21
|
+
super().__init__(lambda obj: evaluate_operations(obj, member_name, operations)) # type: ignore
|
|
22
|
+
|
|
23
|
+
elif is_node(field):
|
|
24
|
+
node = field
|
|
25
|
+
super().__init__() # type: ignore
|
|
26
|
+
|
|
27
|
+
else:
|
|
28
|
+
node = parent
|
|
29
|
+
super().__init__(field) # type: ignore
|
|
30
|
+
|
|
31
|
+
self.node = node
|
|
32
|
+
self.required_nodes = dict(magic_value=node)
|
|
33
|
+
|
|
34
|
+
def check(self, magic_value: typing.Any) -> bool:
|
|
35
|
+
with suppress(kungfu.UnwrapError):
|
|
36
|
+
self(magic_value) # type: ignore
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
def new(self, f: typing.Any, /) -> typing.Any:
|
|
42
|
+
return Magic(f, parent=self.node)
|
|
43
|
+
|
|
44
|
+
if typing.TYPE_CHECKING:
|
|
45
|
+
|
|
46
|
+
def then[T](self, g: typing.Callable[[R], T], /) -> "Magic[T, P]": ...
|
|
47
|
+
|
|
48
|
+
def ensure(
|
|
49
|
+
self,
|
|
50
|
+
chk: typing.Callable[[R], bool],
|
|
51
|
+
error: typing.Callable[[R], BaseException] | BaseException | str | None = None,
|
|
52
|
+
) -> "Magic[R, P]": ...
|
|
53
|
+
|
|
54
|
+
def expect[T, Err](
|
|
55
|
+
self: kungfu.F[kungfu.Result[T, Err], P],
|
|
56
|
+
error: typing.Callable[[kungfu.Result[T, Err]], BaseException] | BaseException | str | None = None,
|
|
57
|
+
) -> "Magic[T, P]": ...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = ("Magic",)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import vbml
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.node.nodes.text import Caption, Text
|
|
9
|
+
from telegrinder.tools.global_context.builtin_context import TelegrinderContext
|
|
10
|
+
|
|
11
|
+
type PatternLike = str | vbml.Pattern
|
|
12
|
+
|
|
13
|
+
TELEGRINDER_CONTEXT: typing.Final = TelegrinderContext()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_string(patterns: typing.Iterable[vbml.Pattern], s: str, ctx: Context) -> bool:
|
|
17
|
+
for pattern in patterns:
|
|
18
|
+
match TELEGRINDER_CONTEXT.vbml_patcher.check(pattern, s):
|
|
19
|
+
case {**response}:
|
|
20
|
+
ctx |= response
|
|
21
|
+
return True
|
|
22
|
+
case True:
|
|
23
|
+
return True
|
|
24
|
+
case _:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Markup(ABCRule):
|
|
31
|
+
"""Markup Language. See the [vbml documentation](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
patterns: PatternLike | list[PatternLike],
|
|
36
|
+
/,
|
|
37
|
+
*,
|
|
38
|
+
flags: re.RegexFlag | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.patterns = [
|
|
41
|
+
vbml.Pattern(text=pattern, flags=flags or TELEGRINDER_CONTEXT.vbml_pattern_flags)
|
|
42
|
+
if isinstance(pattern, str)
|
|
43
|
+
else pattern
|
|
44
|
+
for pattern in ([patterns] if not isinstance(patterns, list) else patterns)
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def check(self, text: Text | Caption, ctx: Context) -> bool:
|
|
48
|
+
return check_string(self.patterns, text, ctx)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("Markup", "check_string")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.bot.cute_types.utils import MEDIA_TYPES
|
|
3
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IsMediaGroup(ABCRule):
|
|
7
|
+
def check(self, message: MessageCute) -> bool:
|
|
8
|
+
if not message.media_group_id:
|
|
9
|
+
return False
|
|
10
|
+
return message.content_type in MEDIA_TYPES
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ("IsMediaGroup",)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
3
|
+
from telegrinder.bot.rules.text import HasText
|
|
4
|
+
from telegrinder.types.enums import MessageEntityType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HasMention(ABCRule, requires=[HasText()]):
|
|
8
|
+
def check(self, message: MessageCute) -> bool:
|
|
9
|
+
entities = message.entities.unwrap_or_none()
|
|
10
|
+
if not entities:
|
|
11
|
+
return False
|
|
12
|
+
return any(entity.type == MessageEntityType.MENTION for entity in entities)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ("HasMention",)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
4
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
6
|
+
from telegrinder.types.enums import MessageEntityType
|
|
7
|
+
from telegrinder.types.objects import MessageEntity
|
|
8
|
+
|
|
9
|
+
type Entity = str | MessageEntityType
|
|
10
|
+
|
|
11
|
+
Message: typing.TypeAlias = MessageCute
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HasEntities(ABCRule):
|
|
15
|
+
def check(self, message: Message) -> bool:
|
|
16
|
+
return bool(message.entities)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MessageEntities(ABCRule, requires=[HasEntities()]):
|
|
20
|
+
def __init__(self, entities: Entity | list[Entity], /) -> None:
|
|
21
|
+
self.entities = [entities] if not isinstance(entities, list) else entities
|
|
22
|
+
|
|
23
|
+
def check(self, message: Message, ctx: Context) -> bool:
|
|
24
|
+
message_entities: list[MessageEntity] = []
|
|
25
|
+
for entity in message.entities.unwrap():
|
|
26
|
+
for entity_type in self.entities:
|
|
27
|
+
if entity_type == entity.type:
|
|
28
|
+
message_entities.append(entity)
|
|
29
|
+
|
|
30
|
+
if not message_entities:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
ctx.message_entities = message_entities
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ("HasEntities", "MessageEntities")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from kungfu.library.monad.result import Ok
|
|
4
|
+
from nodnod.agent.event_loop.agent import EventLoopAgent
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.node.compose import run_agent
|
|
9
|
+
from telegrinder.node.utils import as_node
|
|
10
|
+
|
|
11
|
+
if typing.TYPE_CHECKING:
|
|
12
|
+
from nodnod.agent.base import Agent
|
|
13
|
+
|
|
14
|
+
type Node = typing.Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NodeRule(ABCRule):
|
|
18
|
+
nodes: tuple[tuple[str | None, Node], ...]
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*nodes: Node | tuple[str, Node],
|
|
23
|
+
agent: type[Agent] | None = None,
|
|
24
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.agent = (agent or EventLoopAgent).build(nodes=set(map(as_node, nodes)))
|
|
27
|
+
self.nodes = tuple((x if isinstance(x, tuple) else (None, x)) for x in nodes)
|
|
28
|
+
self.roots = roots
|
|
29
|
+
|
|
30
|
+
async def check(self, context: Context) -> bool:
|
|
31
|
+
async with run_agent(self.agent, context, roots=self.roots) as result:
|
|
32
|
+
match result:
|
|
33
|
+
case Ok(scope):
|
|
34
|
+
for key, node in self.nodes:
|
|
35
|
+
if key is not None and node in scope:
|
|
36
|
+
context[key] = scope[node].value
|
|
37
|
+
|
|
38
|
+
return True
|
|
39
|
+
case _:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ("NodeRule",)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
|
|
4
|
+
import msgspec
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.bot.rules.markup import Markup, PatternLike, check_string
|
|
9
|
+
from telegrinder.msgspec_utils.json import loads
|
|
10
|
+
from telegrinder.node.nodes.payload import Payload, PayloadData
|
|
11
|
+
from telegrinder.tools.serialization.abc import ABCDataSerializer, ModelType
|
|
12
|
+
from telegrinder.tools.serialization.json_ser import JSONSerializer
|
|
13
|
+
from telegrinder.tools.serialization.utils import get_model_serializer
|
|
14
|
+
|
|
15
|
+
_ANY: typing.Final = object()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PayloadRule[Data](ABCRule):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
data_type: type[Data],
|
|
22
|
+
serializer: type[ABCDataSerializer[Data]] | None = None,
|
|
23
|
+
*,
|
|
24
|
+
alias: str | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.data_type = data_type
|
|
27
|
+
self.serializer = serializer or get_model_serializer(data_type) or JSONSerializer[typing.Any]
|
|
28
|
+
self.alias = alias or "data"
|
|
29
|
+
self.required_nodes = dict(payload=PayloadData[self.data_type, self.serializer])
|
|
30
|
+
|
|
31
|
+
def check(self, payload: PayloadData, context: Context) -> typing.Literal[True]:
|
|
32
|
+
context.set(self.alias, payload)
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PayloadModelRule[Model: ModelType](PayloadRule):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
model_t: type[Model],
|
|
40
|
+
/,
|
|
41
|
+
*,
|
|
42
|
+
payload: typing.Any = _ANY,
|
|
43
|
+
serializer: type[ABCDataSerializer[Model]] | None = None,
|
|
44
|
+
alias: str | None = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(model_t, serializer, alias=alias or "model")
|
|
47
|
+
self.payload = payload
|
|
48
|
+
|
|
49
|
+
def check(self, payload: PayloadData, context: Context) -> bool:
|
|
50
|
+
if self.payload is not _ANY and payload != self.payload:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
context.set(self.alias, payload)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PayloadEqRule(ABCRule):
|
|
58
|
+
def __init__(self, payloads: str | list[str], /) -> None:
|
|
59
|
+
self.payloads = [payloads] if isinstance(payloads, str) else payloads
|
|
60
|
+
|
|
61
|
+
def check(self, payload: Payload) -> bool:
|
|
62
|
+
return any(p == payload for p in self.payloads)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PayloadMarkupRule(ABCRule):
|
|
66
|
+
def __init__(self, pattern: PatternLike | list[PatternLike], /) -> None:
|
|
67
|
+
self.patterns = Markup(pattern).patterns
|
|
68
|
+
|
|
69
|
+
def check(self, payload: Payload, context: Context) -> bool:
|
|
70
|
+
return check_string(self.patterns, payload, context)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PayloadJsonEqRule(ABCRule):
|
|
74
|
+
def __init__(self, payload: dict[str, typing.Any], /) -> None:
|
|
75
|
+
self.payload = payload
|
|
76
|
+
|
|
77
|
+
def check(self, payload: Payload) -> bool:
|
|
78
|
+
with suppress(msgspec.DecodeError, msgspec.ValidationError):
|
|
79
|
+
return self.payload == loads(payload)
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = (
|
|
84
|
+
"PayloadEqRule",
|
|
85
|
+
"PayloadJsonEqRule",
|
|
86
|
+
"PayloadMarkupRule",
|
|
87
|
+
"PayloadModelRule",
|
|
88
|
+
"PayloadRule",
|
|
89
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
|
|
2
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
3
|
+
from telegrinder.types.enums import Currency
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PaymentInvoiceCurrency(ABCRule):
|
|
7
|
+
def __init__(self, currency: Currency, /) -> None:
|
|
8
|
+
self.currency = currency
|
|
9
|
+
|
|
10
|
+
def check(self, query: PreCheckoutQueryCute) -> bool:
|
|
11
|
+
return self.currency == query.currency
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("PaymentInvoiceCurrency",)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
6
|
+
from telegrinder.node.nodes.text import Caption, Text
|
|
7
|
+
|
|
8
|
+
type PatternLike = str | typing.Pattern[str]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Regex(ABCRule):
|
|
12
|
+
def __init__(self, regexp: PatternLike | list[PatternLike]) -> None:
|
|
13
|
+
self.regexp: list[re.Pattern[str]] = []
|
|
14
|
+
match regexp:
|
|
15
|
+
case re.Pattern() as pattern:
|
|
16
|
+
self.regexp.append(pattern)
|
|
17
|
+
case str(regex):
|
|
18
|
+
self.regexp.append(re.compile(regex))
|
|
19
|
+
case _:
|
|
20
|
+
self.regexp.extend(re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp)
|
|
21
|
+
|
|
22
|
+
def check(self, text: Text | Caption, ctx: Context) -> bool:
|
|
23
|
+
for regexp in self.regexp:
|
|
24
|
+
response = re.match(regexp, text)
|
|
25
|
+
if response is not None:
|
|
26
|
+
if matches := response.groupdict():
|
|
27
|
+
ctx |= matches
|
|
28
|
+
else:
|
|
29
|
+
ctx |= {"matches": response.groups() or (response.group(),)}
|
|
30
|
+
return True
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ("Regex",)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
|
+
from telegrinder.bot.rules.abc import ABCRule, check_rule
|
|
6
|
+
from telegrinder.bot.rules.func import FuncRule
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclasses.dataclass(slots=True)
|
|
10
|
+
class RuleEnumState:
|
|
11
|
+
name: str
|
|
12
|
+
rule: ABCRule
|
|
13
|
+
cls: type["RuleEnum"]
|
|
14
|
+
|
|
15
|
+
def __eq__(self, other: typing.Self) -> bool:
|
|
16
|
+
return self.cls == other.cls and self.name == other.name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RuleEnum(ABCRule):
|
|
20
|
+
__enum__: list[RuleEnumState]
|
|
21
|
+
|
|
22
|
+
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
23
|
+
new_attributes = set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
|
|
24
|
+
enum_lst: list[RuleEnumState] = []
|
|
25
|
+
|
|
26
|
+
self = cls.__new__(cls)
|
|
27
|
+
self.__init__()
|
|
28
|
+
|
|
29
|
+
for attribute_name in new_attributes:
|
|
30
|
+
rules = getattr(cls, attribute_name)
|
|
31
|
+
attribute = RuleEnumState(attribute_name, rules, cls)
|
|
32
|
+
|
|
33
|
+
setattr(
|
|
34
|
+
self,
|
|
35
|
+
attribute.name,
|
|
36
|
+
self & FuncRule(lambda _, ctx: self.must_be_state(ctx, attribute)), # type: ignore
|
|
37
|
+
)
|
|
38
|
+
enum_lst.append(attribute)
|
|
39
|
+
|
|
40
|
+
setattr(cls, "__enum__", enum_lst)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def save_state(cls, ctx: Context, enum: RuleEnumState) -> None:
|
|
44
|
+
ctx |= {cls.__name__ + "_state": enum}
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def check_state(cls, ctx: Context) -> RuleEnumState | None:
|
|
48
|
+
return ctx.get(cls.__name__ + "_state")
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def must_be_state(cls, ctx: Context, state: RuleEnumState) -> bool:
|
|
52
|
+
real_state = cls.check_state(ctx)
|
|
53
|
+
if not real_state:
|
|
54
|
+
return False
|
|
55
|
+
return real_state == state
|
|
56
|
+
|
|
57
|
+
async def check(self, ctx: Context) -> bool:
|
|
58
|
+
if self.check_state(ctx):
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
for enum in self.__enum__:
|
|
62
|
+
ctx_copy = ctx.copy()
|
|
63
|
+
if await check_rule(enum.rule, ctx_copy):
|
|
64
|
+
ctx |= ctx_copy
|
|
65
|
+
self.save_state(ctx, enum)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = ("RuleEnum", "RuleEnumState")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import kungfu
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.bot.rules.is_from import IsPrivate
|
|
9
|
+
from telegrinder.bot.rules.markup import Markup
|
|
10
|
+
from telegrinder.node.nodes.me import BotUsername
|
|
11
|
+
from telegrinder.node.nodes.message_entities import MessageEntities
|
|
12
|
+
from telegrinder.types.enums import MessageEntityType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StartCommand(
|
|
16
|
+
ABCRule,
|
|
17
|
+
requires=[
|
|
18
|
+
IsPrivate(),
|
|
19
|
+
Markup(
|
|
20
|
+
[
|
|
21
|
+
"/start <param>",
|
|
22
|
+
"/start",
|
|
23
|
+
"tg://resolve?domain=<bot_username>&start=<param>",
|
|
24
|
+
]
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
validator: typing.Callable[[str], typing.Any | None] | None = None,
|
|
31
|
+
*,
|
|
32
|
+
deep_link: bool | None = None,
|
|
33
|
+
decode_deep_link_param: bool = False,
|
|
34
|
+
param_required: bool = False,
|
|
35
|
+
alias: str | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.param_required = param_required
|
|
38
|
+
self.validator = validator
|
|
39
|
+
self.alias = alias
|
|
40
|
+
self.deep_link = deep_link
|
|
41
|
+
self.decode_deep_link_param = decode_deep_link_param
|
|
42
|
+
|
|
43
|
+
def check(
|
|
44
|
+
self,
|
|
45
|
+
bot_username: BotUsername,
|
|
46
|
+
message_entities: kungfu.Option[MessageEntities],
|
|
47
|
+
ctx: Context,
|
|
48
|
+
) -> bool:
|
|
49
|
+
if self.deep_link is not None and all(
|
|
50
|
+
(
|
|
51
|
+
message_entities.map(
|
|
52
|
+
lambda entities: entities and entities[0].type == MessageEntityType.BOT_COMMAND,
|
|
53
|
+
).unwrap_or(False),
|
|
54
|
+
self.deep_link and bot_username == ctx.get("bot_username"),
|
|
55
|
+
),
|
|
56
|
+
):
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
param: str | None = ctx.pop("param", None)
|
|
60
|
+
|
|
61
|
+
if param is not None and self.decode_deep_link_param:
|
|
62
|
+
param = base64.urlsafe_b64decode(param.encode()).decode()
|
|
63
|
+
|
|
64
|
+
validated_param = self.validator(param) if self.validator and param is not None else param
|
|
65
|
+
|
|
66
|
+
if self.param_required and validated_param is None:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
ctx.set(self.alias or "param", validated_param)
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = ("StartCommand",)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import enum
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
7
|
+
from telegrinder.node.nodes.source import Source
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from telegrinder.tools.state_storage.abc import ABCStateStorage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StateMeta(enum.Enum):
|
|
14
|
+
NO_STATE = enum.auto()
|
|
15
|
+
ANY = enum.auto()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass(frozen=True, slots=True, repr=False)
|
|
19
|
+
class State[Payload](ABCRule):
|
|
20
|
+
storage: "ABCStateStorage[Payload]"
|
|
21
|
+
key: str | StateMeta | enum.Enum
|
|
22
|
+
|
|
23
|
+
async def check(self, source: Source, ctx: Context) -> bool:
|
|
24
|
+
state = await self.storage.get(source.from_user.id)
|
|
25
|
+
if not state:
|
|
26
|
+
return self.key == StateMeta.NO_STATE
|
|
27
|
+
|
|
28
|
+
if self.key != StateMeta.ANY and self.key != state.unwrap().key:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
ctx.state = state.unwrap()
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ("State", "StateMeta")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from telegrinder import node
|
|
2
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
3
|
+
from telegrinder.bot.rules.node import NodeRule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HasText(NodeRule):
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
super().__init__(node.as_node(node.Text))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HasCaption(NodeRule):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
super().__init__(node.as_node(node.Caption))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Text(ABCRule):
|
|
17
|
+
def __init__(self, texts: str | list[str], /, *, ignore_case: bool = False) -> None:
|
|
18
|
+
if not isinstance(texts, list):
|
|
19
|
+
texts = [texts]
|
|
20
|
+
self.texts = set(texts) if not ignore_case else set(map(str.lower, texts))
|
|
21
|
+
self.ignore_case = ignore_case
|
|
22
|
+
|
|
23
|
+
def check(self, text: node.Text | node.Caption) -> bool:
|
|
24
|
+
return (text if not self.ignore_case else text.lower()) in self.texts
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ("HasCaption", "HasText", "Text")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
2
|
+
from telegrinder.types.enums import UpdateType
|
|
3
|
+
from telegrinder.types.objects import Update
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IsUpdateType(ABCRule):
|
|
7
|
+
def __init__(self, update_type: UpdateType, /) -> None:
|
|
8
|
+
self.update_type = update_type
|
|
9
|
+
|
|
10
|
+
def check(self, update: Update) -> bool:
|
|
11
|
+
return update.update_type == self.update_type
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("IsUpdateType",)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from telegrinder.api.api import API
|
|
6
|
+
from telegrinder.bot.dispatch.view.abc import ABCView
|
|
7
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ABCScenario(ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def wait(self, hasher: Hasher, view: ABCView, api: API) -> typing.Any:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("ABCScenario",)
|