telegrinder 0.1.dev168__py3-none-any.whl → 0.1.dev170__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 +9 -3
- telegrinder/bot/__init__.py +7 -5
- telegrinder/bot/cute_types/base.py +12 -14
- telegrinder/bot/cute_types/callback_query.py +54 -43
- telegrinder/bot/cute_types/chat_join_request.py +8 -7
- telegrinder/bot/cute_types/chat_member_updated.py +23 -17
- telegrinder/bot/cute_types/inline_query.py +1 -1
- telegrinder/bot/cute_types/message.py +331 -183
- telegrinder/bot/cute_types/update.py +4 -8
- telegrinder/bot/cute_types/utils.py +1 -5
- telegrinder/bot/dispatch/__init__.py +2 -3
- telegrinder/bot/dispatch/abc.py +4 -0
- telegrinder/bot/dispatch/context.py +9 -4
- telegrinder/bot/dispatch/dispatch.py +35 -33
- telegrinder/bot/dispatch/handler/func.py +34 -13
- telegrinder/bot/dispatch/handler/message_reply.py +6 -3
- telegrinder/bot/dispatch/middleware/abc.py +4 -4
- telegrinder/bot/dispatch/process.py +40 -13
- telegrinder/bot/dispatch/return_manager/abc.py +12 -12
- telegrinder/bot/dispatch/return_manager/callback_query.py +1 -3
- telegrinder/bot/dispatch/return_manager/inline_query.py +1 -3
- telegrinder/bot/dispatch/view/abc.py +37 -45
- telegrinder/bot/dispatch/view/box.py +66 -50
- telegrinder/bot/dispatch/view/message.py +1 -5
- telegrinder/bot/dispatch/view/raw.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
- telegrinder/bot/dispatch/waiter_machine/machine.py +77 -35
- telegrinder/bot/dispatch/waiter_machine/middleware.py +31 -7
- telegrinder/bot/dispatch/waiter_machine/short_state.py +17 -8
- telegrinder/bot/polling/polling.py +4 -4
- telegrinder/bot/rules/__init__.py +9 -6
- telegrinder/bot/rules/abc.py +99 -22
- telegrinder/bot/rules/adapter/__init__.py +4 -1
- telegrinder/bot/rules/adapter/abc.py +11 -6
- telegrinder/bot/rules/adapter/errors.py +1 -2
- telegrinder/bot/rules/adapter/event.py +14 -9
- telegrinder/bot/rules/adapter/node.py +42 -0
- telegrinder/bot/rules/callback_data.py +4 -4
- telegrinder/bot/rules/chat_join.py +3 -2
- telegrinder/bot/rules/command.py +26 -14
- telegrinder/bot/rules/enum_text.py +5 -5
- telegrinder/bot/rules/func.py +6 -6
- telegrinder/bot/rules/fuzzy.py +5 -7
- telegrinder/bot/rules/inline.py +4 -5
- telegrinder/bot/rules/integer.py +10 -8
- telegrinder/bot/rules/is_from.py +63 -91
- telegrinder/bot/rules/markup.py +5 -5
- telegrinder/bot/rules/mention.py +4 -4
- telegrinder/bot/rules/message.py +1 -1
- telegrinder/bot/rules/node.py +27 -0
- telegrinder/bot/rules/regex.py +5 -5
- telegrinder/bot/rules/rule_enum.py +4 -4
- telegrinder/bot/rules/start.py +5 -5
- telegrinder/bot/rules/text.py +9 -13
- telegrinder/bot/rules/update.py +4 -4
- telegrinder/bot/scenario/__init__.py +3 -3
- telegrinder/bot/scenario/choice.py +2 -3
- telegrinder/model.py +51 -16
- telegrinder/modules.py +14 -6
- telegrinder/msgspec_utils.py +67 -23
- telegrinder/node/__init__.py +26 -8
- telegrinder/node/attachment.py +13 -9
- telegrinder/node/base.py +27 -14
- telegrinder/node/callback_query.py +18 -0
- telegrinder/node/command.py +29 -0
- telegrinder/node/composer.py +119 -30
- telegrinder/node/me.py +14 -0
- telegrinder/node/message.py +2 -4
- telegrinder/node/polymorphic.py +44 -0
- telegrinder/node/rule.py +26 -22
- telegrinder/node/scope.py +36 -0
- telegrinder/node/source.py +37 -10
- telegrinder/node/text.py +11 -5
- telegrinder/node/tools/__init__.py +2 -2
- telegrinder/node/tools/generator.py +6 -6
- telegrinder/tools/__init__.py +8 -13
- telegrinder/tools/buttons.py +23 -17
- telegrinder/tools/error_handler/error_handler.py +11 -14
- telegrinder/tools/formatting/__init__.py +0 -6
- telegrinder/tools/formatting/html.py +10 -12
- telegrinder/tools/formatting/links.py +0 -5
- telegrinder/tools/formatting/spec_html_formats.py +0 -11
- telegrinder/tools/global_context/abc.py +1 -3
- telegrinder/tools/global_context/global_context.py +6 -16
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/kb_set/yaml.py +1 -2
- telegrinder/tools/keyboard.py +7 -8
- telegrinder/tools/limited_dict.py +1 -1
- telegrinder/tools/loop_wrapper/loop_wrapper.py +6 -5
- telegrinder/tools/magic.py +27 -5
- telegrinder/types/__init__.py +20 -0
- telegrinder/types/enums.py +37 -31
- telegrinder/types/methods.py +608 -327
- telegrinder/types/objects.py +1139 -716
- {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/LICENSE +1 -1
- {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/METADATA +6 -5
- telegrinder-0.1.dev170.dist-info/RECORD +143 -0
- telegrinder/bot/dispatch/composition.py +0 -88
- telegrinder-0.1.dev168.dist-info/RECORD +0 -137
- {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/WHEEL +0 -0
|
@@ -6,6 +6,7 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
6
6
|
from telegrinder.bot.dispatch.handler.func import FuncHandler
|
|
7
7
|
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
|
|
8
8
|
from telegrinder.bot.dispatch.view.abc import ABCStateView
|
|
9
|
+
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
|
|
9
10
|
|
|
10
11
|
if typing.TYPE_CHECKING:
|
|
11
12
|
from .machine import WaiterMachine
|
|
@@ -27,7 +28,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
27
28
|
if not self.view or not hasattr(self.view, "get_state_key"):
|
|
28
29
|
raise RuntimeError(
|
|
29
30
|
"WaiterMiddleware cannot be used inside a view which doesn't "
|
|
30
|
-
"provide get_state_key (ABCStateView
|
|
31
|
+
"provide get_state_key (ABCStateView interface)."
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
view_name = self.view.__class__.__name__
|
|
@@ -41,13 +42,36 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
41
42
|
short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
|
|
42
43
|
if not short_state:
|
|
43
44
|
return True
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
preset_context = Context(short_state=short_state)
|
|
46
|
-
if
|
|
47
|
-
short_state.
|
|
48
|
-
|
|
47
|
+
if short_state.context is not None:
|
|
48
|
+
preset_context.update(short_state.context.context)
|
|
49
|
+
|
|
50
|
+
if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
|
|
51
|
+
await self.machine.drop(
|
|
52
|
+
self.view,
|
|
53
|
+
short_state.key,
|
|
54
|
+
event,
|
|
55
|
+
ctx.raw_update,
|
|
56
|
+
**preset_context.copy(),
|
|
57
|
+
)
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
# before running the handler we check if the user wants to exit waiting
|
|
61
|
+
if short_state.exit_behaviour is not None and await self.machine.call_behaviour(
|
|
62
|
+
self.view,
|
|
63
|
+
event,
|
|
64
|
+
ctx.raw_update,
|
|
65
|
+
behaviour=short_state.exit_behaviour,
|
|
66
|
+
**preset_context,
|
|
49
67
|
):
|
|
50
|
-
await self.machine.drop(
|
|
68
|
+
await self.machine.drop(
|
|
69
|
+
self.view,
|
|
70
|
+
short_state.key,
|
|
71
|
+
event,
|
|
72
|
+
ctx.raw_update,
|
|
73
|
+
**preset_context.copy(),
|
|
74
|
+
)
|
|
51
75
|
return True
|
|
52
76
|
|
|
53
77
|
handler = FuncHandler(
|
|
@@ -78,7 +102,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
78
102
|
short_state: "ShortState[EventType]",
|
|
79
103
|
ctx: Context,
|
|
80
104
|
) -> None:
|
|
81
|
-
|
|
105
|
+
short_state.context = ShortStateContext(event, ctx)
|
|
82
106
|
short_state.event.set()
|
|
83
107
|
|
|
84
108
|
|
|
@@ -5,6 +5,7 @@ import typing
|
|
|
5
5
|
|
|
6
6
|
from telegrinder.api import ABCAPI
|
|
7
7
|
from telegrinder.bot.cute_types import BaseCute
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
9
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
9
10
|
from telegrinder.bot.rules.abc import ABCRule
|
|
10
11
|
from telegrinder.model import Model
|
|
@@ -18,23 +19,31 @@ EventModel = typing.TypeVar("EventModel", bound=BaseCute)
|
|
|
18
19
|
Behaviour: typing.TypeAlias = ABCHandler[T] | None
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
|
|
23
|
+
event: EventModel
|
|
24
|
+
context: Context
|
|
25
|
+
|
|
26
|
+
|
|
21
27
|
@dataclasses.dataclass
|
|
22
28
|
class ShortState(typing.Generic[EventModel]):
|
|
23
29
|
key: "Identificator"
|
|
24
30
|
ctx_api: ABCAPI
|
|
25
31
|
event: asyncio.Event
|
|
26
|
-
rules: tuple[ABCRule
|
|
27
|
-
_: dataclasses.KW_ONLY
|
|
32
|
+
rules: tuple[ABCRule, ...]
|
|
28
33
|
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
29
34
|
default=None,
|
|
35
|
+
kw_only=True,
|
|
30
36
|
)
|
|
31
|
-
default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
32
|
-
on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
33
|
-
|
|
37
|
+
default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
|
|
38
|
+
on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
|
|
39
|
+
exit_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
|
|
40
|
+
expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
|
|
41
|
+
context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
|
|
34
42
|
|
|
35
43
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
|
36
|
-
self.
|
|
37
|
-
|
|
44
|
+
self.creation_date = datetime.datetime.now()
|
|
45
|
+
self.expiration_date = (self.creation_date + expiration) if expiration is not None else None
|
|
46
|
+
|
|
38
47
|
def cancel(self) -> None:
|
|
39
48
|
"""Cancel schedule waiters."""
|
|
40
49
|
|
|
@@ -46,4 +55,4 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
46
55
|
future.cancel()
|
|
47
56
|
|
|
48
57
|
|
|
49
|
-
__all__ = ("ShortState",)
|
|
58
|
+
__all__ = ("ShortState", "ShortStateContext")
|
|
@@ -97,7 +97,7 @@ class Polling(ABCPolling):
|
|
|
97
97
|
except InvalidTokenError as e:
|
|
98
98
|
logger.error(e)
|
|
99
99
|
self.stop()
|
|
100
|
-
exit(
|
|
100
|
+
exit(3)
|
|
101
101
|
except asyncio.CancelledError:
|
|
102
102
|
logger.info("Caught cancel, polling stopping...")
|
|
103
103
|
self.stop()
|
|
@@ -108,14 +108,14 @@ class Polling(ABCPolling):
|
|
|
108
108
|
self.max_reconnetions,
|
|
109
109
|
)
|
|
110
110
|
self.stop()
|
|
111
|
-
exit(
|
|
111
|
+
exit(6)
|
|
112
112
|
else:
|
|
113
113
|
logger.warning(
|
|
114
|
-
"Server disconnected, waiting 5 seconds to
|
|
114
|
+
"Server disconnected, waiting 5 seconds to reconnet...",
|
|
115
115
|
)
|
|
116
116
|
reconn_counter += 1
|
|
117
117
|
await asyncio.sleep(self.reconnection_timeout)
|
|
118
|
-
except aiohttp.ClientConnectorError:
|
|
118
|
+
except (aiohttp.ClientConnectorError, aiohttp.ClientOSError):
|
|
119
119
|
logger.error("Client connection failed, attempted to reconnect...")
|
|
120
120
|
await asyncio.sleep(self.reconnection_timeout)
|
|
121
121
|
except BaseException as e:
|
|
@@ -26,11 +26,12 @@ from .inline import (
|
|
|
26
26
|
InlineQueryRule,
|
|
27
27
|
InlineQueryText,
|
|
28
28
|
)
|
|
29
|
-
from .integer import
|
|
29
|
+
from .integer import IntegerInRange, IsInteger
|
|
30
30
|
from .is_from import (
|
|
31
31
|
IsBot,
|
|
32
32
|
IsChat,
|
|
33
33
|
IsChatId,
|
|
34
|
+
IsDice,
|
|
34
35
|
IsDiceEmoji,
|
|
35
36
|
IsForum,
|
|
36
37
|
IsForward,
|
|
@@ -48,11 +49,12 @@ from .markup import Markup
|
|
|
48
49
|
from .mention import HasMention
|
|
49
50
|
from .message import MessageRule
|
|
50
51
|
from .message_entities import HasEntities, MessageEntities
|
|
52
|
+
from .node import NodeRule
|
|
51
53
|
from .regex import Regex
|
|
52
54
|
from .rule_enum import RuleEnum
|
|
53
55
|
from .start import StartCommand
|
|
54
|
-
from .text import HasText, Text
|
|
55
|
-
from .update import
|
|
56
|
+
from .text import HasText, Text
|
|
57
|
+
from .update import IsUpdateType
|
|
56
58
|
|
|
57
59
|
__all__ = (
|
|
58
60
|
"ABCRule",
|
|
@@ -80,13 +82,14 @@ __all__ = (
|
|
|
80
82
|
"InlineQueryMarkup",
|
|
81
83
|
"InlineQueryRule",
|
|
82
84
|
"InlineQueryText",
|
|
83
|
-
"
|
|
85
|
+
"IsInteger",
|
|
84
86
|
"IntegerInRange",
|
|
85
87
|
"InviteLinkByCreator",
|
|
86
88
|
"InviteLinkName",
|
|
87
89
|
"IsBot",
|
|
88
90
|
"IsChat",
|
|
89
91
|
"IsChatId",
|
|
92
|
+
"IsDice",
|
|
90
93
|
"IsDiceEmoji",
|
|
91
94
|
"IsForum",
|
|
92
95
|
"IsForward",
|
|
@@ -97,7 +100,7 @@ __all__ = (
|
|
|
97
100
|
"IsPrivate",
|
|
98
101
|
"IsReply",
|
|
99
102
|
"IsSuperGroup",
|
|
100
|
-
"
|
|
103
|
+
"IsUpdateType",
|
|
101
104
|
"IsUser",
|
|
102
105
|
"IsUserId",
|
|
103
106
|
"Markup",
|
|
@@ -109,5 +112,5 @@ __all__ = (
|
|
|
109
112
|
"RuleEnum",
|
|
110
113
|
"StartCommand",
|
|
111
114
|
"Text",
|
|
112
|
-
"
|
|
115
|
+
"NodeRule",
|
|
113
116
|
)
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
import typing
|
|
3
2
|
from abc import ABC, abstractmethod
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
import typing_extensions as typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.cute_types import MessageCute, UpdateCute
|
|
6
7
|
from telegrinder.bot.dispatch.context import Context
|
|
7
8
|
from telegrinder.bot.dispatch.process import check_rule
|
|
8
|
-
from telegrinder.bot.rules.adapter import ABCAdapter,
|
|
9
|
+
from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
|
|
10
|
+
from telegrinder.bot.rules.adapter.node import Event
|
|
11
|
+
from telegrinder.node.base import Node, is_node
|
|
12
|
+
from telegrinder.node.composer import NodeCollection
|
|
9
13
|
from telegrinder.tools.i18n.base import ABCTranslator
|
|
10
|
-
from telegrinder.tools.magic import cache_translation, get_cached_translation
|
|
14
|
+
from telegrinder.tools.magic import cache_translation, get_annotations, get_cached_translation
|
|
11
15
|
from telegrinder.types.objects import Update as UpdateObject
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
|
|
14
18
|
|
|
15
19
|
Message: typing.TypeAlias = MessageCute
|
|
16
20
|
Update: typing.TypeAlias = UpdateCute
|
|
17
21
|
|
|
18
22
|
|
|
19
|
-
def with_caching_translations(func):
|
|
23
|
+
def with_caching_translations(func: typing.Callable[..., typing.Any]):
|
|
20
24
|
"""Should be used as decorator for .translate method. Caches rule translations."""
|
|
21
25
|
|
|
22
|
-
async def wrapper(self: "ABCRule
|
|
26
|
+
async def wrapper(self: "ABCRule", translator: ABCTranslator):
|
|
23
27
|
if translation := get_cached_translation(self, translator.locale):
|
|
24
28
|
return translation
|
|
25
29
|
translation = await func(self, translator)
|
|
@@ -29,15 +33,52 @@ def with_caching_translations(func):
|
|
|
29
33
|
return wrapper
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
class ABCRule(ABC, typing.Generic[
|
|
33
|
-
adapter: ABCAdapter[UpdateObject,
|
|
34
|
-
requires: list["ABCRule
|
|
36
|
+
class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
37
|
+
adapter: ABCAdapter[UpdateObject, AdaptTo] = RawUpdateAdapter() # type: ignore
|
|
38
|
+
requires: list["ABCRule"] = []
|
|
35
39
|
|
|
36
40
|
@abstractmethod
|
|
37
|
-
async def check(self, event:
|
|
41
|
+
async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
|
|
38
42
|
pass
|
|
39
43
|
|
|
40
|
-
def
|
|
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
|
+
def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
|
|
41
82
|
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
42
83
|
|
|
43
84
|
requirements = []
|
|
@@ -48,17 +89,41 @@ class ABCRule(ABC, typing.Generic[T]):
|
|
|
48
89
|
requirements.extend(requires or ())
|
|
49
90
|
cls.requires = list(dict.fromkeys(requirements))
|
|
50
91
|
|
|
51
|
-
def __and__(self, other: "ABCRule
|
|
92
|
+
def __and__(self, other: "ABCRule") -> "AndRule":
|
|
93
|
+
"""And Rule.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
rule = HasText() & HasCaption()
|
|
97
|
+
rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
|
|
98
|
+
```
|
|
99
|
+
"""
|
|
100
|
+
|
|
52
101
|
return AndRule(self, other)
|
|
53
102
|
|
|
54
|
-
def __or__(self, other: "ABCRule
|
|
103
|
+
def __or__(self, other: "ABCRule") -> "OrRule":
|
|
104
|
+
"""Or Rule.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
rule = HasText() | HasCaption()
|
|
108
|
+
rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
|
|
109
|
+
```
|
|
110
|
+
"""
|
|
111
|
+
|
|
55
112
|
return OrRule(self, other)
|
|
56
113
|
|
|
57
|
-
def
|
|
114
|
+
def __invert__(self) -> "NotRule":
|
|
115
|
+
"""Not Rule.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
rule = ~HasText()
|
|
119
|
+
rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
|
|
120
|
+
```
|
|
121
|
+
"""
|
|
122
|
+
|
|
58
123
|
return NotRule(self)
|
|
59
124
|
|
|
60
125
|
def __repr__(self) -> str:
|
|
61
|
-
return "<
|
|
126
|
+
return "<{}: adapter={!r}>".format(
|
|
62
127
|
self.__class__.__name__,
|
|
63
128
|
self.adapter,
|
|
64
129
|
)
|
|
@@ -67,8 +132,8 @@ class ABCRule(ABC, typing.Generic[T]):
|
|
|
67
132
|
return self
|
|
68
133
|
|
|
69
134
|
|
|
70
|
-
class AndRule(ABCRule
|
|
71
|
-
def __init__(self, *rules: ABCRule[
|
|
135
|
+
class AndRule(ABCRule):
|
|
136
|
+
def __init__(self, *rules: ABCRule[AdaptTo]) -> None:
|
|
72
137
|
self.rules = rules
|
|
73
138
|
|
|
74
139
|
async def check(self, event: Update, ctx: Context) -> bool:
|
|
@@ -80,8 +145,8 @@ class AndRule(ABCRule[T]):
|
|
|
80
145
|
return True
|
|
81
146
|
|
|
82
147
|
|
|
83
|
-
class OrRule(ABCRule
|
|
84
|
-
def __init__(self, *rules: ABCRule
|
|
148
|
+
class OrRule(ABCRule):
|
|
149
|
+
def __init__(self, *rules: ABCRule) -> None:
|
|
85
150
|
self.rules = rules
|
|
86
151
|
|
|
87
152
|
async def check(self, event: Update, ctx: Context) -> bool:
|
|
@@ -93,8 +158,8 @@ class OrRule(ABCRule[T]):
|
|
|
93
158
|
return False
|
|
94
159
|
|
|
95
160
|
|
|
96
|
-
class NotRule(ABCRule
|
|
97
|
-
def __init__(self, rule: ABCRule
|
|
161
|
+
class NotRule(ABCRule):
|
|
162
|
+
def __init__(self, rule: ABCRule) -> None:
|
|
98
163
|
self.rule = rule
|
|
99
164
|
|
|
100
165
|
async def check(self, event: Update, ctx: Context) -> bool:
|
|
@@ -102,10 +167,22 @@ class NotRule(ABCRule[T]):
|
|
|
102
167
|
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
103
168
|
|
|
104
169
|
|
|
170
|
+
class Never(ABCRule):
|
|
171
|
+
async def check(self) -> typing.Literal[False]:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Always(ABCRule):
|
|
176
|
+
async def check(self) -> typing.Literal[True]:
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
|
|
105
180
|
__all__ = (
|
|
106
181
|
"ABCRule",
|
|
107
182
|
"AndRule",
|
|
108
183
|
"NotRule",
|
|
109
184
|
"OrRule",
|
|
110
185
|
"with_caching_translations",
|
|
186
|
+
"Never",
|
|
187
|
+
"Always",
|
|
111
188
|
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
from .abc import ABCAdapter
|
|
1
|
+
from .abc import ABCAdapter, Event
|
|
2
2
|
from .errors import AdapterError
|
|
3
3
|
from .event import EventAdapter
|
|
4
|
+
from .node import NodeAdapter
|
|
4
5
|
from .raw_update import RawUpdateAdapter
|
|
5
6
|
|
|
6
7
|
__all__ = (
|
|
7
8
|
"ABCAdapter",
|
|
8
9
|
"AdapterError",
|
|
9
10
|
"EventAdapter",
|
|
11
|
+
"NodeAdapter",
|
|
10
12
|
"RawUpdateAdapter",
|
|
13
|
+
"Event",
|
|
11
14
|
)
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import dataclasses
|
|
2
3
|
import typing
|
|
3
4
|
|
|
4
5
|
from fntypes.result import Result
|
|
5
6
|
|
|
6
7
|
from telegrinder.api.abc import ABCAPI
|
|
7
|
-
from telegrinder.bot.cute_types import BaseCute
|
|
8
8
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
9
|
from telegrinder.model import Model
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
From = typing.TypeVar("From", bound=Model)
|
|
12
|
+
To = typing.TypeVar("To")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class ABCAdapter(abc.ABC, typing.Generic[
|
|
15
|
+
class ABCAdapter(abc.ABC, typing.Generic[From, To]):
|
|
16
16
|
@abc.abstractmethod
|
|
17
|
-
async def adapt(self, api: ABCAPI, update:
|
|
17
|
+
async def adapt(self, api: ABCAPI, update: From) -> Result[To, AdapterError]:
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
@dataclasses.dataclass
|
|
22
|
+
class Event(typing.Generic[To]):
|
|
23
|
+
obj: To
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = ("ABCAdapter", "Event")
|
|
@@ -7,13 +7,14 @@ from telegrinder.bot.cute_types import BaseCute
|
|
|
7
7
|
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
8
8
|
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
9
|
from telegrinder.msgspec_utils import Nothing
|
|
10
|
+
from telegrinder.types.enums import UpdateType
|
|
10
11
|
from telegrinder.types.objects import Model, Update
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
ToCute = typing.TypeVar("ToCute", bound=BaseCute)
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class EventAdapter(ABCAdapter[Update,
|
|
16
|
-
def __init__(self, event:
|
|
16
|
+
class EventAdapter(ABCAdapter[Update, ToCute]):
|
|
17
|
+
def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
|
|
17
18
|
self.event = event
|
|
18
19
|
self.cute_model = cute_model
|
|
19
20
|
|
|
@@ -27,27 +28,31 @@ class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
|
27
28
|
)
|
|
28
29
|
else:
|
|
29
30
|
raw_update_type = self.event.__name__
|
|
31
|
+
|
|
30
32
|
return "<{}: adapt {} -> {}>".format(
|
|
31
33
|
self.__class__.__name__,
|
|
32
34
|
raw_update_type,
|
|
33
35
|
self.cute_model.__name__,
|
|
34
36
|
)
|
|
35
37
|
|
|
36
|
-
async def adapt(self, api: ABCAPI, update: Update) -> Result[
|
|
38
|
+
async def adapt(self, api: ABCAPI, update: Update) -> Result[ToCute, AdapterError]:
|
|
37
39
|
update_dct = update.to_dict()
|
|
38
|
-
if isinstance(self.event,
|
|
39
|
-
if self.event
|
|
40
|
+
if isinstance(self.event, UpdateType):
|
|
41
|
+
if update.update_type != self.event:
|
|
40
42
|
return Error(
|
|
41
43
|
AdapterError(f"Update is not of event type {self.event!r}."),
|
|
42
44
|
)
|
|
43
|
-
|
|
45
|
+
|
|
46
|
+
if update_dct[self.event.value] is Nothing:
|
|
44
47
|
return Error(
|
|
45
48
|
AdapterError(f"Update is not an {self.event!r}."),
|
|
46
49
|
)
|
|
50
|
+
|
|
47
51
|
return Ok(
|
|
48
|
-
self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
|
|
52
|
+
self.cute_model.from_update(update_dct[self.event.value].unwrap(), bound_api=api),
|
|
49
53
|
)
|
|
50
|
-
|
|
54
|
+
|
|
55
|
+
event = update_dct[update.update_type.value].unwrap()
|
|
51
56
|
if not update.update_type or not issubclass(event.__class__, self.event):
|
|
52
57
|
return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
|
|
53
58
|
return Ok(self.cute_model.from_update(event, bound_api=api))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes.result import Error, Ok, Result
|
|
4
|
+
|
|
5
|
+
from telegrinder.api.abc import ABCAPI
|
|
6
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
|
|
9
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
10
|
+
from telegrinder.node.base import ComposeError
|
|
11
|
+
from telegrinder.node.composer import NodeSession, compose_node
|
|
12
|
+
from telegrinder.types.objects import Update
|
|
13
|
+
|
|
14
|
+
Ts = typing.TypeVarTuple("Ts")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
|
|
18
|
+
def __init__(self, *nodes: *Ts) -> None:
|
|
19
|
+
self.nodes = nodes
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return "<{}: adapt Update -> {}>".format(
|
|
23
|
+
self.__class__.__name__,
|
|
24
|
+
Update.__name__,
|
|
25
|
+
", ".join(node.__name__ for node in self.nodes), # type: ignore
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def adapt(self, api: ABCAPI, update: Update) -> Result[Event[tuple[*Ts]], AdapterError]:
|
|
29
|
+
update_cute = UpdateCute.from_update(update, api)
|
|
30
|
+
node_sessions: list[NodeSession] = []
|
|
31
|
+
for node_t in self.nodes:
|
|
32
|
+
try:
|
|
33
|
+
# FIXME: adapters should have context
|
|
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
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ("NodeAdapter",)
|
|
@@ -10,6 +10,7 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
10
10
|
from telegrinder.bot.rules.adapter import EventAdapter
|
|
11
11
|
from telegrinder.model import decoder
|
|
12
12
|
from telegrinder.tools.buttons import DataclassInstance
|
|
13
|
+
from telegrinder.types.enums import UpdateType
|
|
13
14
|
|
|
14
15
|
from .abc import ABCRule
|
|
15
16
|
from .markup import Markup, PatternLike, check_string
|
|
@@ -22,7 +23,7 @@ CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapSt
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
25
|
-
adapter = EventAdapter(
|
|
26
|
+
adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
|
|
26
27
|
|
|
27
28
|
@abc.abstractmethod
|
|
28
29
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
@@ -31,7 +32,7 @@ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
|
31
32
|
|
|
32
33
|
class HasData(CallbackQueryRule):
|
|
33
34
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
34
|
-
return bool(event.data
|
|
35
|
+
return bool(event.data.unwrap_or_none())
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
|
|
@@ -98,8 +99,7 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
98
99
|
|
|
99
100
|
if isinstance(validator, list):
|
|
100
101
|
if not (
|
|
101
|
-
isinstance(callback_data[key], dict)
|
|
102
|
-
and await cls.match(callback_data[key], validator)
|
|
102
|
+
isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
|
|
103
103
|
):
|
|
104
104
|
return False
|
|
105
105
|
|
|
@@ -4,14 +4,15 @@ import typing
|
|
|
4
4
|
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
5
|
from telegrinder.bot.dispatch.context import Context
|
|
6
6
|
from telegrinder.bot.rules.adapter import EventAdapter
|
|
7
|
+
from telegrinder.types.enums import UpdateType
|
|
7
8
|
|
|
8
9
|
from .abc import ABCRule
|
|
9
10
|
|
|
10
11
|
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class ChatJoinRequestRule(ABCRule[
|
|
14
|
-
adapter = EventAdapter(
|
|
14
|
+
class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
|
|
15
|
+
adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
|
|
15
16
|
|
|
16
17
|
@abc.abstractmethod
|
|
17
18
|
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|