telegrinder 0.1.dev170__py3-none-any.whl → 0.2.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 +2 -2
- telegrinder/api/__init__.py +1 -2
- telegrinder/api/api.py +15 -6
- telegrinder/api/error.py +2 -1
- telegrinder/api/token.py +36 -0
- telegrinder/bot/__init__.py +12 -6
- telegrinder/bot/bot.py +18 -6
- telegrinder/bot/cute_types/__init__.py +7 -7
- telegrinder/bot/cute_types/base.py +122 -20
- telegrinder/bot/cute_types/callback_query.py +10 -6
- telegrinder/bot/cute_types/chat_join_request.py +4 -5
- telegrinder/bot/cute_types/chat_member_updated.py +4 -6
- telegrinder/bot/cute_types/inline_query.py +3 -4
- telegrinder/bot/cute_types/message.py +32 -21
- telegrinder/bot/cute_types/update.py +51 -4
- telegrinder/bot/cute_types/utils.py +3 -466
- telegrinder/bot/dispatch/__init__.py +10 -11
- telegrinder/bot/dispatch/abc.py +8 -5
- telegrinder/bot/dispatch/context.py +17 -8
- telegrinder/bot/dispatch/dispatch.py +71 -48
- telegrinder/bot/dispatch/handler/__init__.py +3 -3
- telegrinder/bot/dispatch/handler/abc.py +4 -4
- telegrinder/bot/dispatch/handler/func.py +46 -22
- telegrinder/bot/dispatch/handler/message_reply.py +6 -7
- telegrinder/bot/dispatch/middleware/__init__.py +1 -1
- telegrinder/bot/dispatch/middleware/abc.py +2 -2
- telegrinder/bot/dispatch/process.py +38 -19
- telegrinder/bot/dispatch/return_manager/__init__.py +4 -4
- telegrinder/bot/dispatch/return_manager/abc.py +3 -3
- telegrinder/bot/dispatch/return_manager/callback_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/message.py +1 -2
- telegrinder/bot/dispatch/view/__init__.py +8 -8
- telegrinder/bot/dispatch/view/abc.py +18 -16
- telegrinder/bot/dispatch/view/box.py +75 -64
- telegrinder/bot/dispatch/view/callback_query.py +1 -2
- telegrinder/bot/dispatch/view/chat_join_request.py +1 -2
- telegrinder/bot/dispatch/view/chat_member.py +16 -2
- telegrinder/bot/dispatch/view/inline_query.py +1 -2
- telegrinder/bot/dispatch/view/message.py +12 -5
- telegrinder/bot/dispatch/view/raw.py +9 -8
- telegrinder/bot/dispatch/waiter_machine/__init__.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/machine.py +12 -8
- telegrinder/bot/dispatch/waiter_machine/middleware.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/short_state.py +4 -3
- telegrinder/bot/polling/abc.py +1 -1
- telegrinder/bot/polling/polling.py +6 -6
- telegrinder/bot/rules/__init__.py +20 -20
- telegrinder/bot/rules/abc.py +57 -43
- telegrinder/bot/rules/adapter/__init__.py +5 -5
- telegrinder/bot/rules/adapter/abc.py +6 -3
- telegrinder/bot/rules/adapter/errors.py +2 -1
- telegrinder/bot/rules/adapter/event.py +28 -13
- telegrinder/bot/rules/adapter/node.py +28 -22
- telegrinder/bot/rules/adapter/raw_update.py +13 -5
- telegrinder/bot/rules/callback_data.py +4 -4
- telegrinder/bot/rules/chat_join.py +4 -4
- telegrinder/bot/rules/command.py +5 -7
- telegrinder/bot/rules/func.py +2 -2
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/integer.py +1 -2
- telegrinder/bot/rules/markup.py +5 -3
- telegrinder/bot/rules/message_entities.py +2 -2
- telegrinder/bot/rules/node.py +2 -2
- telegrinder/bot/rules/regex.py +1 -1
- telegrinder/bot/rules/rule_enum.py +1 -1
- telegrinder/bot/rules/text.py +1 -2
- telegrinder/bot/rules/update.py +1 -2
- telegrinder/bot/scenario/abc.py +2 -2
- telegrinder/bot/scenario/checkbox.py +3 -4
- telegrinder/bot/scenario/choice.py +1 -2
- telegrinder/model.py +89 -45
- telegrinder/modules.py +3 -3
- telegrinder/msgspec_utils.py +85 -57
- telegrinder/node/__init__.py +17 -10
- telegrinder/node/attachment.py +19 -16
- telegrinder/node/base.py +46 -22
- telegrinder/node/callback_query.py +5 -9
- telegrinder/node/command.py +6 -2
- telegrinder/node/composer.py +102 -77
- telegrinder/node/container.py +3 -3
- telegrinder/node/event.py +68 -0
- telegrinder/node/me.py +3 -0
- telegrinder/node/message.py +6 -10
- telegrinder/node/polymorphic.py +15 -10
- telegrinder/node/rule.py +20 -6
- telegrinder/node/scope.py +9 -1
- telegrinder/node/source.py +21 -11
- telegrinder/node/text.py +4 -4
- telegrinder/node/update.py +7 -4
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +59 -0
- telegrinder/tools/__init__.py +2 -2
- telegrinder/tools/buttons.py +5 -10
- telegrinder/tools/error_handler/abc.py +2 -2
- telegrinder/tools/error_handler/error.py +2 -0
- telegrinder/tools/error_handler/error_handler.py +6 -6
- telegrinder/tools/formatting/spec_html_formats.py +10 -10
- telegrinder/tools/global_context/__init__.py +2 -2
- telegrinder/tools/global_context/global_context.py +3 -3
- telegrinder/tools/global_context/telegrinder_ctx.py +4 -4
- telegrinder/tools/keyboard.py +3 -3
- telegrinder/tools/loop_wrapper/loop_wrapper.py +47 -13
- telegrinder/tools/magic.py +96 -18
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +2 -0
- telegrinder/types/methods.py +91 -15
- telegrinder/types/objects.py +49 -24
- telegrinder/verification_utils.py +1 -3
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/METADATA +2 -2
- telegrinder-0.2.0.dist-info/RECORD +145 -0
- telegrinder/api/abc.py +0 -73
- telegrinder-0.1.dev170.dist-info/RECORD +0 -143
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -2,16 +2,20 @@ import asyncio
|
|
|
2
2
|
import datetime
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
|
-
from telegrinder.api
|
|
5
|
+
from telegrinder.api import API
|
|
6
6
|
from telegrinder.bot.dispatch.context import Context
|
|
7
7
|
from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
|
|
8
|
+
from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
|
|
9
|
+
from telegrinder.bot.dispatch.waiter_machine.short_state import (
|
|
10
|
+
Behaviour,
|
|
11
|
+
EventModel,
|
|
12
|
+
ShortState,
|
|
13
|
+
ShortStateContext,
|
|
14
|
+
)
|
|
8
15
|
from telegrinder.bot.rules.abc import ABCRule
|
|
9
16
|
from telegrinder.tools.limited_dict import LimitedDict
|
|
10
17
|
from telegrinder.types import Update
|
|
11
18
|
|
|
12
|
-
from .middleware import WaiterMiddleware
|
|
13
|
-
from .short_state import Behaviour, EventModel, ShortState, ShortStateContext
|
|
14
|
-
|
|
15
19
|
if typing.TYPE_CHECKING:
|
|
16
20
|
from telegrinder.bot.dispatch import Dispatch
|
|
17
21
|
|
|
@@ -67,7 +71,7 @@ class WaiterMachine:
|
|
|
67
71
|
async def wait(
|
|
68
72
|
self,
|
|
69
73
|
state_view: "BaseStateView[EventModel]",
|
|
70
|
-
linked: EventModel | tuple[
|
|
74
|
+
linked: EventModel | tuple[API, Identificator],
|
|
71
75
|
*rules: ABCRule,
|
|
72
76
|
default: Behaviour[EventModel] | None = None,
|
|
73
77
|
on_drop: Behaviour[EventModel] | None = None,
|
|
@@ -77,7 +81,7 @@ class WaiterMachine:
|
|
|
77
81
|
if isinstance(expiration, int | float):
|
|
78
82
|
expiration = datetime.timedelta(seconds=expiration)
|
|
79
83
|
|
|
80
|
-
api:
|
|
84
|
+
api: API
|
|
81
85
|
key: Identificator
|
|
82
86
|
api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked)) # type: ignore
|
|
83
87
|
if not key:
|
|
@@ -123,7 +127,7 @@ class WaiterMachine:
|
|
|
123
127
|
|
|
124
128
|
ctx = Context(**context)
|
|
125
129
|
if await behaviour.check(event.api, update, ctx):
|
|
126
|
-
await behaviour.run(event, ctx)
|
|
130
|
+
await behaviour.run(event.api, event, ctx)
|
|
127
131
|
return True
|
|
128
132
|
|
|
129
133
|
return False
|
|
@@ -132,7 +136,7 @@ class WaiterMachine:
|
|
|
132
136
|
self,
|
|
133
137
|
views: typing.Iterable[ABCStateView[EventModel]],
|
|
134
138
|
absolutely_dead_time: datetime.timedelta = WEEK,
|
|
135
|
-
):
|
|
139
|
+
) -> None:
|
|
136
140
|
"""Clears storage.
|
|
137
141
|
|
|
138
142
|
:param absolutely_dead_time: timedelta when state can be forgotten.
|
|
@@ -83,7 +83,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
83
83
|
result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
|
|
84
84
|
|
|
85
85
|
if result is True:
|
|
86
|
-
await handler.run(event, ctx)
|
|
86
|
+
await handler.run(event.api, event, ctx)
|
|
87
87
|
|
|
88
88
|
elif short_state.default_behaviour is not None:
|
|
89
89
|
await self.machine.call_behaviour(
|
|
@@ -3,7 +3,7 @@ import dataclasses
|
|
|
3
3
|
import datetime
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import
|
|
6
|
+
from telegrinder.api import API
|
|
7
7
|
from telegrinder.bot.cute_types import BaseCute
|
|
8
8
|
from telegrinder.bot.dispatch.context import Context
|
|
9
9
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
@@ -24,10 +24,10 @@ class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
|
|
|
24
24
|
context: Context
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@dataclasses.dataclass
|
|
27
|
+
@dataclasses.dataclass(slots=True)
|
|
28
28
|
class ShortState(typing.Generic[EventModel]):
|
|
29
29
|
key: "Identificator"
|
|
30
|
-
ctx_api:
|
|
30
|
+
ctx_api: API
|
|
31
31
|
event: asyncio.Event
|
|
32
32
|
rules: tuple[ABCRule, ...]
|
|
33
33
|
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
@@ -38,6 +38,7 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
38
38
|
on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
|
|
39
39
|
exit_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
|
|
40
40
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
|
|
41
|
+
creation_date: datetime.datetime = dataclasses.field(init=False)
|
|
41
42
|
context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
|
|
42
43
|
|
|
43
44
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
telegrinder/bot/polling/abc.py
CHANGED
|
@@ -5,25 +5,25 @@ import aiohttp
|
|
|
5
5
|
import msgspec
|
|
6
6
|
from fntypes.result import Error, Ok
|
|
7
7
|
|
|
8
|
-
from telegrinder.api
|
|
8
|
+
from telegrinder.api import API
|
|
9
9
|
from telegrinder.api.error import InvalidTokenError
|
|
10
10
|
from telegrinder.bot.polling.abc import ABCPolling
|
|
11
11
|
from telegrinder.modules import logger
|
|
12
12
|
from telegrinder.msgspec_utils import decoder
|
|
13
|
-
from telegrinder.types import Update, UpdateType
|
|
13
|
+
from telegrinder.types.objects import Update, UpdateType
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Polling(ABCPolling):
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
|
-
api:
|
|
19
|
+
api: API,
|
|
20
20
|
*,
|
|
21
21
|
offset: int = 0,
|
|
22
22
|
reconnection_timeout: float = 5,
|
|
23
23
|
max_reconnetions: int = 10,
|
|
24
24
|
include_updates: set[str | UpdateType] | None = None,
|
|
25
25
|
exclude_updates: set[str | UpdateType] | None = None,
|
|
26
|
-
):
|
|
26
|
+
) -> None:
|
|
27
27
|
self.api = api
|
|
28
28
|
self.allowed_updates = self.get_allowed_updates(
|
|
29
29
|
include_updates=include_updates,
|
|
@@ -48,8 +48,8 @@ class Polling(ABCPolling):
|
|
|
48
48
|
self.reconnection_timeout,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
@staticmethod
|
|
51
52
|
def get_allowed_updates(
|
|
52
|
-
self,
|
|
53
53
|
*,
|
|
54
54
|
include_updates: set[str | UpdateType] | None = None,
|
|
55
55
|
exclude_updates: set[str | UpdateType] | None = None,
|
|
@@ -111,7 +111,7 @@ class Polling(ABCPolling):
|
|
|
111
111
|
exit(6)
|
|
112
112
|
else:
|
|
113
113
|
logger.warning(
|
|
114
|
-
"Server disconnected, waiting 5 seconds to
|
|
114
|
+
"Server disconnected, waiting 5 seconds to reconnect...",
|
|
115
115
|
)
|
|
116
116
|
reconn_counter += 1
|
|
117
117
|
await asyncio.sleep(self.reconnection_timeout)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .abc import ABCRule, AndRule, NotRule, OrRule
|
|
2
|
-
from .callback_data import (
|
|
1
|
+
from telegrinder.bot.rules.abc import ABCRule, AndRule, NotRule, OrRule
|
|
2
|
+
from telegrinder.bot.rules.callback_data import (
|
|
3
3
|
CallbackDataEq,
|
|
4
4
|
CallbackDataJsonEq,
|
|
5
5
|
CallbackDataJsonModel,
|
|
@@ -9,25 +9,25 @@ from .callback_data import (
|
|
|
9
9
|
CallbackQueryRule,
|
|
10
10
|
HasData,
|
|
11
11
|
)
|
|
12
|
-
from .chat_join import (
|
|
12
|
+
from telegrinder.bot.rules.chat_join import (
|
|
13
13
|
ChatJoinRequestRule,
|
|
14
14
|
HasInviteLink,
|
|
15
15
|
InviteLinkByCreator,
|
|
16
16
|
InviteLinkName,
|
|
17
17
|
)
|
|
18
|
-
from .command import Argument, Command
|
|
19
|
-
from .enum_text import EnumTextRule
|
|
20
|
-
from .func import FuncRule
|
|
21
|
-
from .fuzzy import FuzzyText
|
|
22
|
-
from .inline import (
|
|
18
|
+
from telegrinder.bot.rules.command import Argument, Command
|
|
19
|
+
from telegrinder.bot.rules.enum_text import EnumTextRule
|
|
20
|
+
from telegrinder.bot.rules.func import FuncRule
|
|
21
|
+
from telegrinder.bot.rules.fuzzy import FuzzyText
|
|
22
|
+
from telegrinder.bot.rules.inline import (
|
|
23
23
|
HasLocation,
|
|
24
24
|
InlineQueryChatType,
|
|
25
25
|
InlineQueryMarkup,
|
|
26
26
|
InlineQueryRule,
|
|
27
27
|
InlineQueryText,
|
|
28
28
|
)
|
|
29
|
-
from .integer import IntegerInRange, IsInteger
|
|
30
|
-
from .is_from import (
|
|
29
|
+
from telegrinder.bot.rules.integer import IntegerInRange, IsInteger
|
|
30
|
+
from telegrinder.bot.rules.is_from import (
|
|
31
31
|
IsBot,
|
|
32
32
|
IsChat,
|
|
33
33
|
IsChatId,
|
|
@@ -45,16 +45,16 @@ from .is_from import (
|
|
|
45
45
|
IsUser,
|
|
46
46
|
IsUserId,
|
|
47
47
|
)
|
|
48
|
-
from .markup import Markup
|
|
49
|
-
from .mention import HasMention
|
|
50
|
-
from .message import MessageRule
|
|
51
|
-
from .message_entities import HasEntities, MessageEntities
|
|
52
|
-
from .node import NodeRule
|
|
53
|
-
from .regex import Regex
|
|
54
|
-
from .rule_enum import RuleEnum
|
|
55
|
-
from .start import StartCommand
|
|
56
|
-
from .text import HasText, Text
|
|
57
|
-
from .update import IsUpdateType
|
|
48
|
+
from telegrinder.bot.rules.markup import Markup
|
|
49
|
+
from telegrinder.bot.rules.mention import HasMention
|
|
50
|
+
from telegrinder.bot.rules.message import MessageRule
|
|
51
|
+
from telegrinder.bot.rules.message_entities import HasEntities, MessageEntities
|
|
52
|
+
from telegrinder.bot.rules.node import NodeRule
|
|
53
|
+
from telegrinder.bot.rules.regex import Regex
|
|
54
|
+
from telegrinder.bot.rules.rule_enum import RuleEnum
|
|
55
|
+
from telegrinder.bot.rules.start import StartCommand
|
|
56
|
+
from telegrinder.bot.rules.text import HasText, Text
|
|
57
|
+
from telegrinder.bot.rules.update import IsUpdateType
|
|
58
58
|
|
|
59
59
|
__all__ = (
|
|
60
60
|
"ABCRule",
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from functools import cached_property
|
|
3
4
|
|
|
4
5
|
import typing_extensions as typing
|
|
5
6
|
|
|
@@ -8,12 +9,19 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
8
9
|
from telegrinder.bot.dispatch.process import check_rule
|
|
9
10
|
from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
|
|
10
11
|
from telegrinder.bot.rules.adapter.node import Event
|
|
11
|
-
from telegrinder.node.base import Node, is_node
|
|
12
|
-
from telegrinder.node.composer import NodeCollection
|
|
12
|
+
from telegrinder.node.base import Node, get_nodes, is_node
|
|
13
13
|
from telegrinder.tools.i18n.base import ABCTranslator
|
|
14
|
-
from telegrinder.tools.magic import
|
|
14
|
+
from telegrinder.tools.magic import (
|
|
15
|
+
cache_translation,
|
|
16
|
+
get_annotations,
|
|
17
|
+
get_cached_translation,
|
|
18
|
+
get_default_args,
|
|
19
|
+
)
|
|
15
20
|
from telegrinder.types.objects import Update as UpdateObject
|
|
16
21
|
|
|
22
|
+
if typing.TYPE_CHECKING:
|
|
23
|
+
from telegrinder.node.composer import NodeCollection
|
|
24
|
+
|
|
17
25
|
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
|
|
18
26
|
|
|
19
27
|
Message: typing.TypeAlias = MessageCute
|
|
@@ -34,50 +42,16 @@ def with_caching_translations(func: typing.Callable[..., typing.Any]):
|
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
37
|
-
adapter: ABCAdapter[UpdateObject, AdaptTo]
|
|
45
|
+
adapter: ABCAdapter[UpdateObject, AdaptTo]
|
|
38
46
|
requires: list["ABCRule"] = []
|
|
39
47
|
|
|
48
|
+
if not typing.TYPE_CHECKING:
|
|
49
|
+
adapter = RawUpdateAdapter()
|
|
50
|
+
|
|
40
51
|
@abstractmethod
|
|
41
52
|
async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
|
|
42
53
|
pass
|
|
43
54
|
|
|
44
|
-
def get_required_nodes(self) -> dict[str, type[Node]]:
|
|
45
|
-
return {k: v for k, v in get_annotations(self.check).items() if is_node(v)}
|
|
46
|
-
|
|
47
|
-
async def bounding_check(
|
|
48
|
-
self,
|
|
49
|
-
adapted_value: AdaptTo,
|
|
50
|
-
ctx: Context,
|
|
51
|
-
node_col: NodeCollection | None = None,
|
|
52
|
-
) -> bool:
|
|
53
|
-
kw = {}
|
|
54
|
-
node_col_values = node_col.values() if node_col is not None else {}
|
|
55
|
-
|
|
56
|
-
for i, (k, v) in enumerate(get_annotations(self.check).items()):
|
|
57
|
-
if (isinstance(adapted_value, Event) and not i) or (
|
|
58
|
-
isinstance(v, type) and isinstance(adapted_value, v)
|
|
59
|
-
):
|
|
60
|
-
kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
|
|
61
|
-
elif is_node(v):
|
|
62
|
-
assert k in node_col_values, "Node is undefined, error while bounding."
|
|
63
|
-
kw[k] = node_col_values[k]
|
|
64
|
-
elif k in ctx:
|
|
65
|
-
kw[k] = ctx[k]
|
|
66
|
-
elif v is Context:
|
|
67
|
-
kw[k] = ctx
|
|
68
|
-
else:
|
|
69
|
-
raise LookupError(
|
|
70
|
-
f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__name__}.check()', because it cannot be resolved."
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
return await self.check(**kw)
|
|
74
|
-
|
|
75
|
-
def optional(self) -> "ABCRule":
|
|
76
|
-
return self | Always()
|
|
77
|
-
|
|
78
|
-
def should_fail(self) -> "ABCRule":
|
|
79
|
-
return self & Never()
|
|
80
|
-
|
|
81
55
|
def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
|
|
82
56
|
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
83
57
|
|
|
@@ -128,6 +102,46 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
128
102
|
self.adapter,
|
|
129
103
|
)
|
|
130
104
|
|
|
105
|
+
@cached_property
|
|
106
|
+
def required_nodes(self) -> dict[str, type[Node]]:
|
|
107
|
+
return get_nodes(self.check)
|
|
108
|
+
|
|
109
|
+
def as_optional(self) -> "ABCRule":
|
|
110
|
+
return self | Always()
|
|
111
|
+
|
|
112
|
+
def should_fail(self) -> "ABCRule":
|
|
113
|
+
return self & Never()
|
|
114
|
+
|
|
115
|
+
async def bounding_check(
|
|
116
|
+
self,
|
|
117
|
+
adapted_value: AdaptTo,
|
|
118
|
+
ctx: Context,
|
|
119
|
+
node_col: "NodeCollection | None" = None,
|
|
120
|
+
) -> bool:
|
|
121
|
+
kw = {}
|
|
122
|
+
node_col_values = node_col.values if node_col is not None else {}
|
|
123
|
+
temp_ctx = get_default_args(self.check) | ctx
|
|
124
|
+
|
|
125
|
+
for i, (k, v) in enumerate(get_annotations(self.check).items()):
|
|
126
|
+
if (isinstance(adapted_value, Event) and not i) or (
|
|
127
|
+
isinstance(v, type) and isinstance(adapted_value, v)
|
|
128
|
+
):
|
|
129
|
+
kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
|
|
130
|
+
elif is_node(v):
|
|
131
|
+
assert k in node_col_values, "Node is undefined, error while bounding."
|
|
132
|
+
kw[k] = node_col_values[k]
|
|
133
|
+
elif k in temp_ctx:
|
|
134
|
+
kw[k] = temp_ctx[k]
|
|
135
|
+
elif v is Context:
|
|
136
|
+
kw[k] = ctx
|
|
137
|
+
else:
|
|
138
|
+
raise LookupError(
|
|
139
|
+
f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__qualname__}.check()', "
|
|
140
|
+
"because it cannot be resolved."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return await self.check(**kw)
|
|
144
|
+
|
|
131
145
|
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
132
146
|
return self
|
|
133
147
|
|
|
@@ -179,10 +193,10 @@ class Always(ABCRule):
|
|
|
179
193
|
|
|
180
194
|
__all__ = (
|
|
181
195
|
"ABCRule",
|
|
196
|
+
"Always",
|
|
182
197
|
"AndRule",
|
|
198
|
+
"Never",
|
|
183
199
|
"NotRule",
|
|
184
200
|
"OrRule",
|
|
185
201
|
"with_caching_translations",
|
|
186
|
-
"Never",
|
|
187
|
-
"Always",
|
|
188
202
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from .abc import ABCAdapter, Event
|
|
2
|
-
from .errors import AdapterError
|
|
3
|
-
from .event import EventAdapter
|
|
4
|
-
from .node import NodeAdapter
|
|
5
|
-
from .raw_update import RawUpdateAdapter
|
|
1
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
|
|
2
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
3
|
+
from telegrinder.bot.rules.adapter.event import EventAdapter
|
|
4
|
+
from telegrinder.bot.rules.adapter.node import NodeAdapter
|
|
5
|
+
from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
|
|
6
6
|
|
|
7
7
|
__all__ = (
|
|
8
8
|
"ABCAdapter",
|
|
@@ -4,7 +4,8 @@ import typing
|
|
|
4
4
|
|
|
5
5
|
from fntypes.result import Result
|
|
6
6
|
|
|
7
|
-
from telegrinder.api
|
|
7
|
+
from telegrinder.api import API
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
9
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
10
|
from telegrinder.model import Model
|
|
10
11
|
|
|
@@ -13,12 +14,14 @@ To = typing.TypeVar("To")
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class ABCAdapter(abc.ABC, typing.Generic[From, To]):
|
|
17
|
+
ADAPTED_VALUE_KEY: str | None = None
|
|
18
|
+
|
|
16
19
|
@abc.abstractmethod
|
|
17
|
-
async def adapt(self, api:
|
|
20
|
+
async def adapt(self, api: API, update: From, context: Context) -> Result[To, AdapterError]:
|
|
18
21
|
pass
|
|
19
22
|
|
|
20
23
|
|
|
21
|
-
@dataclasses.dataclass
|
|
24
|
+
@dataclasses.dataclass(slots=True)
|
|
22
25
|
class Event(typing.Generic[To]):
|
|
23
26
|
obj: To
|
|
24
27
|
|
|
@@ -2,8 +2,9 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from fntypes.result import Error, Ok, Result
|
|
4
4
|
|
|
5
|
-
from telegrinder.api
|
|
6
|
-
from telegrinder.bot.cute_types import BaseCute
|
|
5
|
+
from telegrinder.api import API
|
|
6
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
8
|
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
8
9
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
10
|
from telegrinder.msgspec_utils import Nothing
|
|
@@ -14,6 +15,8 @@ ToCute = typing.TypeVar("ToCute", bound=BaseCute)
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class EventAdapter(ABCAdapter[Update, ToCute]):
|
|
18
|
+
ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
|
|
19
|
+
|
|
17
20
|
def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
|
|
18
21
|
self.event = event
|
|
19
22
|
self.cute_model = cute_model
|
|
@@ -29,33 +32,45 @@ class EventAdapter(ABCAdapter[Update, ToCute]):
|
|
|
29
32
|
else:
|
|
30
33
|
raw_update_type = self.event.__name__
|
|
31
34
|
|
|
32
|
-
return "<{}: adapt {} -> {}>".format(
|
|
35
|
+
return "<{}: adapt Update -> {} -> {}>".format(
|
|
33
36
|
self.__class__.__name__,
|
|
34
37
|
raw_update_type,
|
|
35
38
|
self.cute_model.__name__,
|
|
36
39
|
)
|
|
37
40
|
|
|
38
|
-
async def adapt(self, api:
|
|
39
|
-
|
|
41
|
+
async def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
|
|
42
|
+
if self.ADAPTED_VALUE_KEY in context:
|
|
43
|
+
return Ok(context[self.ADAPTED_VALUE_KEY])
|
|
44
|
+
|
|
40
45
|
if isinstance(self.event, UpdateType):
|
|
41
46
|
if update.update_type != self.event:
|
|
42
47
|
return Error(
|
|
43
48
|
AdapterError(f"Update is not of event type {self.event!r}."),
|
|
44
49
|
)
|
|
45
50
|
|
|
46
|
-
if
|
|
51
|
+
if isinstance(event := getattr(update, self.event.value, Nothing), type(Nothing)):
|
|
47
52
|
return Error(
|
|
48
53
|
AdapterError(f"Update is not an {self.event!r}."),
|
|
49
54
|
)
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
56
|
+
event = event.unwrap()
|
|
57
|
+
|
|
58
|
+
if type(event) is self.cute_model:
|
|
59
|
+
adapted = event
|
|
60
|
+
else:
|
|
61
|
+
adapted = self.cute_model.from_update(event, bound_api=api)
|
|
62
|
+
else:
|
|
63
|
+
event = getattr(update, update.update_type.value).unwrap()
|
|
64
|
+
if not update.update_type or not issubclass(type(event), self.event):
|
|
65
|
+
return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
|
|
66
|
+
|
|
67
|
+
if type(event) is self.cute_model:
|
|
68
|
+
adapted = event
|
|
69
|
+
else:
|
|
70
|
+
adapted = self.cute_model.from_update(event, bound_api=api)
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
|
|
58
|
-
return Ok(self.cute_model.from_update(event, bound_api=api))
|
|
72
|
+
context[self.ADAPTED_VALUE_KEY] = adapted
|
|
73
|
+
return Ok(adapted) # type: ignore
|
|
59
74
|
|
|
60
75
|
|
|
61
76
|
__all__ = ("EventAdapter",)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
1
|
+
import typing_extensions as typing
|
|
3
2
|
from fntypes.result import Error, Ok, Result
|
|
4
3
|
|
|
5
|
-
from telegrinder.api
|
|
6
|
-
from telegrinder.bot.cute_types.update import UpdateCute
|
|
4
|
+
from telegrinder.api import API
|
|
7
5
|
from telegrinder.bot.dispatch.context import Context
|
|
8
6
|
from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
|
|
9
7
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
10
|
-
from telegrinder.
|
|
11
|
-
from telegrinder.node.composer import NodeSession,
|
|
8
|
+
from telegrinder.msgspec_utils import repr_type
|
|
9
|
+
from telegrinder.node.composer import NodeSession, compose_nodes
|
|
12
10
|
from telegrinder.types.objects import Update
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from telegrinder.node.base import Node
|
|
14
|
+
|
|
15
|
+
Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
|
|
@@ -19,24 +20,29 @@ class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
|
|
|
19
20
|
self.nodes = nodes
|
|
20
21
|
|
|
21
22
|
def __repr__(self) -> str:
|
|
22
|
-
return "<{}: adapt Update -> {}>".format(
|
|
23
|
+
return "<{}: adapt Update -> ({})>".format(
|
|
23
24
|
self.__class__.__name__,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
", ".join(repr_type(node) for node in self.nodes),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def adapt(
|
|
29
|
+
self,
|
|
30
|
+
api: API,
|
|
31
|
+
update: Update,
|
|
32
|
+
context: Context,
|
|
33
|
+
) -> Result[Event[tuple[*Ts]], AdapterError]:
|
|
34
|
+
result = await compose_nodes(
|
|
35
|
+
nodes={str(i): typing.cast(type["Node"], node) for i, node in enumerate(self.nodes)},
|
|
36
|
+
ctx=context,
|
|
37
|
+
data={Update: update, API: api},
|
|
26
38
|
)
|
|
27
39
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
node_sessions.append(await compose_node(node_t, update_cute, Context())) # type: ignore
|
|
35
|
-
except ComposeError:
|
|
36
|
-
for session in node_sessions:
|
|
37
|
-
await session.close(with_value=None)
|
|
38
|
-
return Error(AdapterError(f"Couldn't compose nodes, error on {node_t}"))
|
|
39
|
-
return Ok(Event(tuple(node_sessions))) # type: ignore
|
|
40
|
+
match result:
|
|
41
|
+
case Ok(collection):
|
|
42
|
+
sessions: list[NodeSession] = list(collection.sessions.values())
|
|
43
|
+
return Ok(Event(tuple(sessions))) # type: ignore
|
|
44
|
+
case Error(err):
|
|
45
|
+
return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
|
|
40
46
|
|
|
41
47
|
|
|
42
48
|
__all__ = ("NodeAdapter",)
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
from fntypes.result import Ok, Result
|
|
2
2
|
|
|
3
|
-
from telegrinder.api
|
|
3
|
+
from telegrinder.api import API
|
|
4
4
|
from telegrinder.bot.cute_types.update import UpdateCute
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
6
|
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
6
7
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
7
8
|
from telegrinder.types.objects import Update
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
|
|
12
|
+
ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
|
|
13
|
+
|
|
11
14
|
def __repr__(self) -> str:
|
|
12
15
|
return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
|
|
13
16
|
|
|
14
17
|
async def adapt(
|
|
15
18
|
self,
|
|
16
|
-
api:
|
|
19
|
+
api: API,
|
|
17
20
|
update: Update,
|
|
21
|
+
context: Context,
|
|
18
22
|
) -> Result[UpdateCute, AdapterError]:
|
|
19
|
-
if not
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
if self.ADAPTED_VALUE_KEY not in context:
|
|
24
|
+
context[self.ADAPTED_VALUE_KEY] = (
|
|
25
|
+
UpdateCute.from_update(update, api)
|
|
26
|
+
if not isinstance(update, UpdateCute)
|
|
27
|
+
else update
|
|
28
|
+
)
|
|
29
|
+
return Ok(context[self.ADAPTED_VALUE_KEY])
|
|
22
30
|
|
|
23
31
|
|
|
24
32
|
__all__ = ("RawUpdateAdapter",)
|
|
@@ -26,12 +26,12 @@ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
|
26
26
|
adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
|
|
27
27
|
|
|
28
28
|
@abc.abstractmethod
|
|
29
|
-
async def check(self, event: CallbackQuery,
|
|
29
|
+
async def check(self, event: CallbackQuery, context: Context) -> bool:
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class HasData(CallbackQueryRule):
|
|
34
|
-
async def check(self, event: CallbackQuery
|
|
34
|
+
async def check(self, event: CallbackQuery) -> bool:
|
|
35
35
|
return bool(event.data.unwrap_or_none())
|
|
36
36
|
|
|
37
37
|
|
|
@@ -122,7 +122,7 @@ class CallbackDataEq(CallbackQueryDataRule):
|
|
|
122
122
|
def __init__(self, value: str, /) -> None:
|
|
123
123
|
self.value = value
|
|
124
124
|
|
|
125
|
-
async def check(self, event: CallbackQuery
|
|
125
|
+
async def check(self, event: CallbackQuery) -> bool:
|
|
126
126
|
return event.data.unwrap() == self.value
|
|
127
127
|
|
|
128
128
|
|
|
@@ -130,7 +130,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
|
130
130
|
def __init__(self, d: dict[str, typing.Any], /) -> None:
|
|
131
131
|
self.d = d
|
|
132
132
|
|
|
133
|
-
async def check(self, event: CallbackQuery
|
|
133
|
+
async def check(self, event: CallbackQuery) -> bool:
|
|
134
134
|
return event.decode_callback_data().unwrap_or_none() == self.d
|
|
135
135
|
|
|
136
136
|
|
|
@@ -15,12 +15,12 @@ class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
|
|
|
15
15
|
adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
|
|
16
16
|
|
|
17
17
|
@abc.abstractmethod
|
|
18
|
-
async def check(self, event: ChatJoinRequest,
|
|
18
|
+
async def check(self, event: ChatJoinRequest, context: Context) -> bool:
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class HasInviteLink(ChatJoinRequestRule):
|
|
23
|
-
async def check(self, event: ChatJoinRequest
|
|
23
|
+
async def check(self, event: ChatJoinRequest) -> bool:
|
|
24
24
|
return bool(event.invite_link)
|
|
25
25
|
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
|
28
28
|
def __init__(self, name: str, /) -> None:
|
|
29
29
|
self.name = name
|
|
30
30
|
|
|
31
|
-
async def check(self, event: ChatJoinRequest
|
|
31
|
+
async def check(self, event: ChatJoinRequest) -> bool:
|
|
32
32
|
return event.invite_link.unwrap().name.unwrap_or_none() == self.name
|
|
33
33
|
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
|
36
36
|
def __init__(self, creator_id: int, /) -> None:
|
|
37
37
|
self.creator_id = creator_id
|
|
38
38
|
|
|
39
|
-
async def check(self, event: ChatJoinRequest
|
|
39
|
+
async def check(self, event: ChatJoinRequest) -> bool:
|
|
40
40
|
return event.invite_link.unwrap().creator.id == self.creator_id
|
|
41
41
|
|
|
42
42
|
|