telegrinder 0.3.0.post1__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +1 -1
- telegrinder/bot/cute_types/callback_query.py +2 -14
- telegrinder/bot/cute_types/chat_join_request.py +1 -1
- telegrinder/bot/cute_types/chat_member_updated.py +1 -1
- telegrinder/bot/cute_types/inline_query.py +1 -6
- telegrinder/bot/cute_types/message.py +1 -21
- telegrinder/bot/cute_types/update.py +1 -1
- telegrinder/bot/dispatch/abc.py +45 -3
- telegrinder/bot/dispatch/dispatch.py +8 -8
- telegrinder/bot/dispatch/handler/func.py +8 -10
- telegrinder/bot/dispatch/process.py +1 -1
- telegrinder/bot/dispatch/view/base.py +31 -20
- telegrinder/bot/dispatch/view/raw.py +20 -16
- telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/machine.py +24 -30
- telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
- telegrinder/bot/rules/abc.py +42 -7
- telegrinder/bot/rules/adapter/raw_update.py +1 -3
- telegrinder/bot/rules/callback_data.py +7 -7
- telegrinder/bot/rules/chat_join.py +5 -5
- telegrinder/bot/rules/command.py +1 -1
- telegrinder/bot/rules/enum_text.py +4 -1
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/inline.py +6 -7
- telegrinder/bot/rules/integer.py +1 -1
- telegrinder/bot/rules/is_from.py +20 -20
- telegrinder/bot/rules/markup.py +6 -3
- telegrinder/bot/rules/mention.py +1 -1
- telegrinder/bot/rules/message.py +2 -2
- telegrinder/bot/rules/message_entities.py +2 -2
- telegrinder/bot/rules/node.py +1 -1
- telegrinder/bot/rules/regex.py +1 -1
- telegrinder/bot/rules/start.py +2 -2
- telegrinder/bot/rules/text.py +6 -4
- telegrinder/bot/rules/update.py +1 -1
- telegrinder/bot/scenario/checkbox.py +9 -1
- telegrinder/msgspec_utils.py +11 -3
- telegrinder/node/attachment.py +6 -6
- telegrinder/node/base.py +17 -11
- telegrinder/node/callback_query.py +1 -1
- telegrinder/node/command.py +1 -1
- telegrinder/node/composer.py +5 -2
- telegrinder/node/container.py +1 -1
- telegrinder/node/event.py +1 -1
- telegrinder/node/message.py +1 -1
- telegrinder/node/polymorphic.py +6 -3
- telegrinder/node/rule.py +1 -1
- telegrinder/node/source.py +5 -7
- telegrinder/node/text.py +2 -2
- telegrinder/node/tools/generator.py +1 -2
- telegrinder/node/update.py +3 -3
- telegrinder/rules.py +2 -0
- telegrinder/tools/buttons.py +4 -4
- telegrinder/tools/error_handler/abc.py +7 -7
- telegrinder/tools/error_handler/error_handler.py +58 -47
- telegrinder/tools/formatting/html.py +0 -2
- telegrinder/tools/functional.py +3 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
- telegrinder/tools/i18n/__init__.py +1 -1
- telegrinder/tools/i18n/{base.py → abc.py} +0 -0
- telegrinder/tools/i18n/middleware/__init__.py +1 -1
- telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
- telegrinder/tools/i18n/simple.py +11 -12
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/magic.py +8 -4
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/METADATA +1 -1
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/RECORD +73 -73
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/LICENSE +0 -0
- {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/WHEEL +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from telegrinder.bot.cute_types import MessageCute as Message
|
|
2
2
|
from telegrinder.bot.dispatch.view import MessageView
|
|
3
|
-
|
|
4
|
-
from .hasher import Hasher
|
|
3
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
def from_chat_hash(chat_id: int) -> int:
|
|
@@ -12,9 +11,12 @@ def get_chat_from_event(event: Message) -> int:
|
|
|
12
11
|
return event.chat.id
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
def from_user_in_chat_hash(chat_and_user: tuple[int, int]) -> str:
|
|
15
|
+
return f"{chat_and_user[0]}_{chat_and_user[1]}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_user_in_chat_from_event(event: Message) -> tuple[int, int]:
|
|
19
|
+
return event.chat.id, event.from_user.id
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def from_user_hash(from_id: int) -> int:
|
|
@@ -25,23 +27,20 @@ def get_user_from_event(event: Message) -> int:
|
|
|
25
27
|
return event.from_user.id
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
MESSAGE_IN_CHAT = Hasher(
|
|
31
|
+
view_class=MessageView,
|
|
32
|
+
get_hash_from_data=from_chat_hash,
|
|
33
|
+
get_data_from_event=get_chat_from_event,
|
|
34
|
+
)
|
|
35
|
+
|
|
28
36
|
MESSAGE_FROM_USER = Hasher(
|
|
29
|
-
|
|
37
|
+
view_class=MessageView,
|
|
30
38
|
get_hash_from_data=from_user_hash,
|
|
31
39
|
get_data_from_event=get_user_from_event,
|
|
32
40
|
)
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
def from_user_in_chat_hash(chat_and_user: tuple[int, int]) -> str:
|
|
36
|
-
return f"{chat_and_user[0]}_{chat_and_user[1]}"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def get_user_in_chat_from_event(event: Message) -> tuple[int, int]:
|
|
40
|
-
return event.chat.id, event.from_user.id
|
|
41
|
-
|
|
42
|
-
|
|
43
42
|
MESSAGE_FROM_USER_IN_CHAT = Hasher(
|
|
44
|
-
|
|
43
|
+
view_class=MessageView,
|
|
45
44
|
get_hash_from_data=from_user_in_chat_hash,
|
|
46
45
|
get_data_from_event=get_user_in_chat_from_event,
|
|
47
46
|
)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from fntypes import Option
|
|
1
|
+
from fntypes.option import Option
|
|
2
2
|
|
|
3
3
|
from telegrinder.bot.dispatch.view import BaseStateView
|
|
4
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import ECHO, Event, Hasher
|
|
4
5
|
from telegrinder.tools.functional import from_optional
|
|
5
6
|
|
|
6
|
-
from .hasher import ECHO, Event, Hasher
|
|
7
|
-
|
|
8
7
|
|
|
9
8
|
class StateViewHasher(Hasher[Event, int]):
|
|
10
|
-
view: BaseStateView
|
|
9
|
+
view: BaseStateView[Event]
|
|
11
10
|
|
|
12
|
-
def __init__(self, view:
|
|
13
|
-
|
|
11
|
+
def __init__(self, view: BaseStateView[Event]) -> None:
|
|
12
|
+
self.view = view
|
|
13
|
+
super().__init__(view.__class__, get_hash_from_data=ECHO)
|
|
14
14
|
|
|
15
15
|
def get_data_from_event(self, event: Event) -> Option[int]:
|
|
16
16
|
return from_optional(self.view.get_state_key(event))
|
|
@@ -41,16 +41,25 @@ class WaiterMachine:
|
|
|
41
41
|
self.storage: Storage = {}
|
|
42
42
|
|
|
43
43
|
def __repr__(self) -> str:
|
|
44
|
-
return "<{}: max_storage_size={}, {}>".format(
|
|
44
|
+
return "<{}: max_storage_size={}, base_state_lifetime={!r}>".format(
|
|
45
45
|
self.__class__.__name__,
|
|
46
46
|
self.max_storage_size,
|
|
47
|
-
|
|
48
|
-
f"{view_name}: {len(self.storage[view_name].values())} shortstates"
|
|
49
|
-
for view_name in self.storage
|
|
50
|
-
)
|
|
51
|
-
or "empty",
|
|
47
|
+
self.base_state_lifetime,
|
|
52
48
|
)
|
|
53
49
|
|
|
50
|
+
async def drop_all(self) -> None:
|
|
51
|
+
"""Drops all waiters in storage."""
|
|
52
|
+
|
|
53
|
+
for hasher in self.storage:
|
|
54
|
+
for ident, short_state in self.storage[hasher].items():
|
|
55
|
+
if short_state.context:
|
|
56
|
+
await self.drop(
|
|
57
|
+
hasher,
|
|
58
|
+
ident,
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
short_state.cancel()
|
|
62
|
+
|
|
54
63
|
async def drop(
|
|
55
64
|
self,
|
|
56
65
|
hasher: Hasher[EventModel, HasherData],
|
|
@@ -66,7 +75,7 @@ class WaiterMachine:
|
|
|
66
75
|
short_state = self.storage[hasher].pop(waiter_id, None)
|
|
67
76
|
if short_state is None:
|
|
68
77
|
raise LookupError(
|
|
69
|
-
"Waiter with identificator {} is not found for hasher {!r}".format(waiter_id, hasher)
|
|
78
|
+
"Waiter with identificator {} is not found for hasher {!r}.".format(waiter_id, hasher)
|
|
70
79
|
)
|
|
71
80
|
|
|
72
81
|
if on_drop := short_state.actions.get("on_drop"):
|
|
@@ -74,19 +83,6 @@ class WaiterMachine:
|
|
|
74
83
|
|
|
75
84
|
short_state.cancel()
|
|
76
85
|
|
|
77
|
-
async def drop_all(self) -> None:
|
|
78
|
-
"""Drops all waiters in storage."""
|
|
79
|
-
|
|
80
|
-
for hasher in self.storage:
|
|
81
|
-
for ident, short_state in self.storage[hasher].items():
|
|
82
|
-
if short_state.context:
|
|
83
|
-
await self.drop(
|
|
84
|
-
hasher,
|
|
85
|
-
ident,
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
short_state.cancel()
|
|
89
|
-
|
|
90
86
|
async def wait_from_event(
|
|
91
87
|
self,
|
|
92
88
|
view: BaseStateView[EventModel],
|
|
@@ -97,11 +93,11 @@ class WaiterMachine:
|
|
|
97
93
|
lifetime: datetime.timedelta | float | None = None,
|
|
98
94
|
**actions: typing.Unpack[WaiterActions[EventModel]],
|
|
99
95
|
) -> ShortStateContext[EventModel]:
|
|
100
|
-
hasher = StateViewHasher(view
|
|
96
|
+
hasher = StateViewHasher(view)
|
|
101
97
|
return await self.wait(
|
|
102
98
|
hasher=hasher,
|
|
103
99
|
data=hasher.get_data_from_event(event).expect(
|
|
104
|
-
RuntimeError("Hasher couldn't create data from event.")
|
|
100
|
+
RuntimeError("Hasher couldn't create data from event."),
|
|
105
101
|
),
|
|
106
102
|
filter=filter,
|
|
107
103
|
release=release,
|
|
@@ -124,18 +120,18 @@ class WaiterMachine:
|
|
|
124
120
|
|
|
125
121
|
event = asyncio.Event()
|
|
126
122
|
short_state = ShortState[EventModel](
|
|
127
|
-
|
|
123
|
+
event,
|
|
124
|
+
actions,
|
|
128
125
|
release=release,
|
|
129
|
-
|
|
126
|
+
filter=filter,
|
|
130
127
|
lifetime=lifetime or self.base_state_lifetime,
|
|
131
|
-
actions=actions,
|
|
132
128
|
)
|
|
133
129
|
waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
|
|
134
130
|
|
|
135
131
|
if hasher not in self.storage:
|
|
136
132
|
if self.dispatch:
|
|
137
|
-
view: BaseView[EventModel] = self.dispatch.get_view(hasher.
|
|
138
|
-
RuntimeError(f"View {hasher.
|
|
133
|
+
view: BaseView[EventModel] = self.dispatch.get_view(hasher.view_class).expect(
|
|
134
|
+
RuntimeError(f"View {hasher.view_class.__name__!r} is not defined in dispatch.")
|
|
139
135
|
)
|
|
140
136
|
view.middlewares.insert(0, WaiterMiddleware(self, hasher))
|
|
141
137
|
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
@@ -148,9 +144,7 @@ class WaiterMachine:
|
|
|
148
144
|
assert short_state.context is not None
|
|
149
145
|
return short_state.context
|
|
150
146
|
|
|
151
|
-
async def clear_storage(
|
|
152
|
-
self,
|
|
153
|
-
) -> None:
|
|
147
|
+
async def clear_storage(self) -> None:
|
|
154
148
|
"""Clears storage."""
|
|
155
149
|
|
|
156
150
|
for hasher in self.storage:
|
|
@@ -27,10 +27,17 @@ class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
|
|
|
27
27
|
@dataclasses.dataclass(slots=True)
|
|
28
28
|
class ShortState(typing.Generic[EventModel]):
|
|
29
29
|
event: asyncio.Event
|
|
30
|
-
actions: "WaiterActions"
|
|
30
|
+
actions: "WaiterActions[EventModel]"
|
|
31
|
+
|
|
32
|
+
release: ABCRule | None = dataclasses.field(
|
|
33
|
+
default=None,
|
|
34
|
+
kw_only=True,
|
|
35
|
+
)
|
|
36
|
+
filter: ABCRule | None = dataclasses.field(
|
|
37
|
+
default=None,
|
|
38
|
+
kw_only=True,
|
|
39
|
+
)
|
|
31
40
|
|
|
32
|
-
release: ABCRule | None = None
|
|
33
|
-
filter: ABCRule | None = None
|
|
34
41
|
lifetime: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
35
42
|
default=None,
|
|
36
43
|
kw_only=True,
|
|
@@ -38,7 +45,6 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
38
45
|
|
|
39
46
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
|
|
40
47
|
creation_date: datetime.datetime = dataclasses.field(init=False)
|
|
41
|
-
|
|
42
48
|
context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
|
|
43
49
|
|
|
44
50
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -10,7 +10,7 @@ from telegrinder.bot.dispatch.process import check_rule
|
|
|
10
10
|
from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
|
|
11
11
|
from telegrinder.bot.rules.adapter.node import Event
|
|
12
12
|
from telegrinder.node.base import Node, get_nodes, is_node
|
|
13
|
-
from telegrinder.tools.i18n.
|
|
13
|
+
from telegrinder.tools.i18n.abc import ABCTranslator
|
|
14
14
|
from telegrinder.tools.magic import (
|
|
15
15
|
cache_translation,
|
|
16
16
|
get_annotations,
|
|
@@ -22,8 +22,9 @@ from telegrinder.types.objects import Update as UpdateObject
|
|
|
22
22
|
if typing.TYPE_CHECKING:
|
|
23
23
|
from telegrinder.node.composer import NodeCollection
|
|
24
24
|
|
|
25
|
-
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
|
|
25
|
+
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
|
|
26
26
|
|
|
27
|
+
CheckResult: typing.TypeAlias = bool | typing.Coroutine[typing.Any, typing.Any, bool]
|
|
27
28
|
Message: typing.TypeAlias = MessageCute
|
|
28
29
|
Update: typing.TypeAlias = UpdateCute
|
|
29
30
|
|
|
@@ -45,12 +46,42 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
45
46
|
adapter: ABCAdapter[UpdateObject, AdaptTo]
|
|
46
47
|
requires: list["ABCRule"] = []
|
|
47
48
|
|
|
48
|
-
if
|
|
49
|
+
if typing.TYPE_CHECKING:
|
|
50
|
+
|
|
51
|
+
@typing.overload
|
|
52
|
+
def check(self) -> CheckResult: ...
|
|
53
|
+
|
|
54
|
+
@typing.overload
|
|
55
|
+
def check(self, event: AdaptTo, /) -> CheckResult: ...
|
|
56
|
+
|
|
57
|
+
@typing.overload
|
|
58
|
+
def check(self, event: AdaptTo, ctx: Context, /) -> CheckResult: ...
|
|
59
|
+
|
|
60
|
+
@typing.overload
|
|
61
|
+
def check(
|
|
62
|
+
self,
|
|
63
|
+
event: AdaptTo,
|
|
64
|
+
ctx: Context,
|
|
65
|
+
/,
|
|
66
|
+
*args: typing.Any,
|
|
67
|
+
**kwargs: typing.Any,
|
|
68
|
+
) -> CheckResult: ...
|
|
69
|
+
|
|
70
|
+
@typing.overload
|
|
71
|
+
def check(self, event: AdaptTo, /, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
72
|
+
|
|
73
|
+
@typing.overload
|
|
74
|
+
def check(self, ctx: Context, /, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult:
|
|
78
|
+
pass
|
|
79
|
+
else:
|
|
49
80
|
adapter = RawUpdateAdapter()
|
|
50
81
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def check(self, *args, **kwargs):
|
|
84
|
+
pass
|
|
54
85
|
|
|
55
86
|
def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
|
|
56
87
|
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
@@ -141,7 +172,10 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
141
172
|
"because it cannot be resolved."
|
|
142
173
|
)
|
|
143
174
|
|
|
144
|
-
|
|
175
|
+
result = bound_check_rule(**kw) # type: ignore
|
|
176
|
+
if inspect.isawaitable(result):
|
|
177
|
+
result = await result
|
|
178
|
+
return result
|
|
145
179
|
|
|
146
180
|
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
147
181
|
return self
|
|
@@ -199,5 +233,6 @@ __all__ = (
|
|
|
199
233
|
"Never",
|
|
200
234
|
"NotRule",
|
|
201
235
|
"OrRule",
|
|
236
|
+
"CheckResult",
|
|
202
237
|
"with_caching_translations",
|
|
203
238
|
)
|
|
@@ -22,9 +22,7 @@ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
|
|
|
22
22
|
) -> Result[UpdateCute, AdapterError]:
|
|
23
23
|
if self.ADAPTED_VALUE_KEY not in context:
|
|
24
24
|
context[self.ADAPTED_VALUE_KEY] = (
|
|
25
|
-
UpdateCute.from_update(update, api)
|
|
26
|
-
if not isinstance(update, UpdateCute)
|
|
27
|
-
else update
|
|
25
|
+
UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
|
|
28
26
|
)
|
|
29
27
|
return Ok(context[self.ADAPTED_VALUE_KEY])
|
|
30
28
|
|
|
@@ -12,7 +12,7 @@ from telegrinder.model import decoder
|
|
|
12
12
|
from telegrinder.tools.buttons import DataclassInstance
|
|
13
13
|
from telegrinder.types.enums import UpdateType
|
|
14
14
|
|
|
15
|
-
from .abc import ABCRule
|
|
15
|
+
from .abc import ABCRule, CheckResult
|
|
16
16
|
from .markup import Markup, PatternLike, check_string
|
|
17
17
|
|
|
18
18
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
@@ -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
|
-
|
|
29
|
+
def check(self, event: CallbackQuery, context: Context) -> CheckResult:
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class HasData(CallbackQueryRule):
|
|
34
|
-
|
|
34
|
+
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
|
-
|
|
125
|
+
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
|
-
|
|
133
|
+
def check(self, event: CallbackQuery) -> bool:
|
|
134
134
|
return event.decode_callback_data().unwrap_or_none() == self.d
|
|
135
135
|
|
|
136
136
|
|
|
@@ -144,7 +144,7 @@ class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
|
144
144
|
self.model = model
|
|
145
145
|
self.alias = alias or "data"
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
148
148
|
with suppress(BaseException):
|
|
149
149
|
ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
|
|
150
150
|
return True
|
|
@@ -155,7 +155,7 @@ class CallbackDataMarkup(CallbackQueryDataRule):
|
|
|
155
155
|
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
156
156
|
self.patterns = Markup(patterns).patterns
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
159
159
|
return check_string(self.patterns, event.data.unwrap(), ctx)
|
|
160
160
|
|
|
161
161
|
|
|
@@ -6,7 +6,7 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
6
6
|
from telegrinder.bot.rules.adapter import EventAdapter
|
|
7
7
|
from telegrinder.types.enums import UpdateType
|
|
8
8
|
|
|
9
|
-
from .abc import ABCRule
|
|
9
|
+
from .abc import ABCRule, CheckResult
|
|
10
10
|
|
|
11
11
|
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
12
12
|
|
|
@@ -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
|
-
|
|
18
|
+
def check(self, event: ChatJoinRequest, context: Context) -> CheckResult:
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class HasInviteLink(ChatJoinRequestRule):
|
|
23
|
-
|
|
23
|
+
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
|
-
|
|
31
|
+
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
|
-
|
|
39
|
+
def check(self, event: ChatJoinRequest) -> bool:
|
|
40
40
|
return event.invite_link.unwrap().creator.id == self.creator_id
|
|
41
41
|
|
|
42
42
|
|
telegrinder/bot/rules/command.py
CHANGED
|
@@ -97,7 +97,7 @@ class Command(ABCRule):
|
|
|
97
97
|
|
|
98
98
|
return None
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
def check(self, command: CommandInfo, me: Me, src: Source, ctx: Context) -> bool:
|
|
101
101
|
name = self.remove_prefix(command.name)
|
|
102
102
|
if name is None:
|
|
103
103
|
return False
|
|
@@ -25,9 +25,12 @@ class EnumTextRule(ABCRule, typing.Generic[T]):
|
|
|
25
25
|
return enumeration
|
|
26
26
|
raise KeyError("Enumeration is undefined.")
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
def check(self, text: Text, ctx: Context) -> bool:
|
|
29
29
|
text = text.lower() # type: ignore
|
|
30
30
|
if text not in self.texts:
|
|
31
31
|
return False
|
|
32
32
|
ctx.enum_text = self.find(text)
|
|
33
33
|
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ("EnumTextRule",)
|
telegrinder/bot/rules/fuzzy.py
CHANGED
|
@@ -13,7 +13,7 @@ class FuzzyText(ABCRule):
|
|
|
13
13
|
self.texts = texts
|
|
14
14
|
self.min_ratio = min_ratio
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
def check(self, message_text: Text, ctx: Context) -> bool:
|
|
17
17
|
match = max(difflib.SequenceMatcher(a=message_text, b=text).ratio() for text in self.texts)
|
|
18
18
|
if match < self.min_ratio:
|
|
19
19
|
return False
|
telegrinder/bot/rules/inline.py
CHANGED
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
|
|
4
4
|
from telegrinder.bot.cute_types import InlineQueryCute
|
|
5
5
|
from telegrinder.bot.dispatch.context import Context
|
|
6
|
-
from telegrinder.bot.rules.abc import ABCRule
|
|
6
|
+
from telegrinder.bot.rules.abc import ABCRule, CheckResult
|
|
7
7
|
from telegrinder.bot.rules.adapter import EventAdapter
|
|
8
8
|
from telegrinder.types.enums import ChatType, UpdateType
|
|
9
9
|
|
|
@@ -16,12 +16,11 @@ class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
|
|
|
16
16
|
adapter: EventAdapter[InlineQuery] = EventAdapter(UpdateType.INLINE_QUERY, InlineQuery)
|
|
17
17
|
|
|
18
18
|
@abc.abstractmethod
|
|
19
|
-
|
|
20
|
-
...
|
|
19
|
+
def check(self, query: InlineQuery, ctx: Context) -> CheckResult: ...
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class HasLocation(InlineQueryRule):
|
|
24
|
-
|
|
23
|
+
def check(self, query: InlineQuery) -> bool:
|
|
25
24
|
return bool(query.location)
|
|
26
25
|
|
|
27
26
|
|
|
@@ -29,7 +28,7 @@ class InlineQueryChatType(InlineQueryRule):
|
|
|
29
28
|
def __init__(self, chat_type: ChatType, /) -> None:
|
|
30
29
|
self.chat_type = chat_type
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
def check(self, query: InlineQuery) -> bool:
|
|
33
32
|
return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
|
|
34
33
|
|
|
35
34
|
|
|
@@ -40,7 +39,7 @@ class InlineQueryText(InlineQueryRule):
|
|
|
40
39
|
]
|
|
41
40
|
self.lower_case = lower_case
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
def check(self, query: InlineQuery) -> bool:
|
|
44
43
|
return (query.query.lower() if self.lower_case else query.query) in self.texts
|
|
45
44
|
|
|
46
45
|
|
|
@@ -48,7 +47,7 @@ class InlineQueryMarkup(InlineQueryRule):
|
|
|
48
47
|
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
49
48
|
self.patterns = Markup(patterns).patterns
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
def check(self, query: InlineQuery, ctx: Context) -> bool:
|
|
52
51
|
return check_string(self.patterns, query.query, ctx)
|
|
53
52
|
|
|
54
53
|
|
telegrinder/bot/rules/integer.py
CHANGED
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -8,17 +8,17 @@ from .message import MessageRule
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class IsBot(ABCRule):
|
|
11
|
-
|
|
11
|
+
def check(self, user: UserSource) -> bool:
|
|
12
12
|
return user.is_bot
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class IsUser(ABCRule):
|
|
16
|
-
|
|
16
|
+
def check(self, user: UserSource) -> bool:
|
|
17
17
|
return not user.is_bot
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class IsPremium(ABCRule):
|
|
21
|
-
|
|
21
|
+
def check(self, user: UserSource) -> bool:
|
|
22
22
|
return user.is_premium.unwrap_or(False)
|
|
23
23
|
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ class IsLanguageCode(ABCRule):
|
|
|
26
26
|
def __init__(self, lang_codes: str | list[str], /) -> None:
|
|
27
27
|
self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
def check(self, user: UserSource) -> bool:
|
|
30
30
|
return user.language_code.unwrap_or_none() in self.lang_codes
|
|
31
31
|
|
|
32
32
|
|
|
@@ -34,12 +34,12 @@ class IsUserId(ABCRule):
|
|
|
34
34
|
def __init__(self, user_ids: int | list[int], /) -> None:
|
|
35
35
|
self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
def check(self, user: UserSource) -> bool:
|
|
38
38
|
return user.id in self.user_ids
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class IsForum(ABCRule):
|
|
42
|
-
|
|
42
|
+
def check(self, chat: ChatSource) -> bool:
|
|
43
43
|
return chat.is_forum.unwrap_or(False)
|
|
44
44
|
|
|
45
45
|
|
|
@@ -47,32 +47,32 @@ class IsChatId(ABCRule):
|
|
|
47
47
|
def __init__(self, chat_ids: int | list[int], /) -> None:
|
|
48
48
|
self.chat_ids = [chat_ids] if isinstance(chat_ids, int) else chat_ids
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
def check(self, chat: ChatSource) -> bool:
|
|
51
51
|
return chat.id in self.chat_ids
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class IsPrivate(ABCRule):
|
|
55
|
-
|
|
55
|
+
def check(self, chat: ChatSource) -> bool:
|
|
56
56
|
return chat.type == ChatType.PRIVATE
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class IsGroup(ABCRule):
|
|
60
|
-
|
|
60
|
+
def check(self, chat: ChatSource) -> bool:
|
|
61
61
|
return chat.type == ChatType.GROUP
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class IsSuperGroup(ABCRule):
|
|
65
|
-
|
|
65
|
+
def check(self, chat: ChatSource) -> bool:
|
|
66
66
|
return chat.type == ChatType.SUPERGROUP
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class IsChat(ABCRule):
|
|
70
|
-
|
|
70
|
+
def check(self, chat: ChatSource) -> bool:
|
|
71
71
|
return chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
class IsDice(MessageRule):
|
|
75
|
-
|
|
75
|
+
def check(self, message: Message) -> bool:
|
|
76
76
|
return bool(message.dice)
|
|
77
77
|
|
|
78
78
|
|
|
@@ -80,12 +80,12 @@ class IsDiceEmoji(MessageRule, requires=[IsDice()]):
|
|
|
80
80
|
def __init__(self, dice_emoji: DiceEmoji, /) -> None:
|
|
81
81
|
self.dice_emoji = dice_emoji
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
def check(self, message: Message) -> bool:
|
|
84
84
|
return message.dice.unwrap().emoji == self.dice_emoji
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class IsForward(MessageRule):
|
|
88
|
-
|
|
88
|
+
def check(self, message: Message) -> bool:
|
|
89
89
|
return bool(message.forward_origin)
|
|
90
90
|
|
|
91
91
|
|
|
@@ -93,32 +93,32 @@ class IsForwardType(MessageRule, requires=[IsForward()]):
|
|
|
93
93
|
def __init__(self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /) -> None:
|
|
94
94
|
self.fwd_type = fwd_type
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
def check(self, message: Message) -> bool:
|
|
97
97
|
return message.forward_origin.unwrap().v.type == self.fwd_type
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
class IsReply(MessageRule):
|
|
101
|
-
|
|
101
|
+
def check(self, message: Message) -> bool:
|
|
102
102
|
return bool(message.reply_to_message)
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
class IsSticker(MessageRule):
|
|
106
|
-
|
|
106
|
+
def check(self, message: Message) -> bool:
|
|
107
107
|
return bool(message.sticker)
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
class IsVideoNote(MessageRule):
|
|
111
|
-
|
|
111
|
+
def check(self, message: Message) -> bool:
|
|
112
112
|
return bool(message.video_note)
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class IsDocument(MessageRule):
|
|
116
|
-
|
|
116
|
+
def check(self, message: Message) -> bool:
|
|
117
117
|
return bool(message.document)
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
class IsPhoto(MessageRule):
|
|
121
|
-
|
|
121
|
+
def check(self, message: Message) -> bool:
|
|
122
122
|
return bool(message.photo)
|
|
123
123
|
|
|
124
124
|
|
telegrinder/bot/rules/markup.py
CHANGED
|
@@ -24,16 +24,19 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class Markup(ABCRule):
|
|
27
|
-
"""Markup Language. See [
|
|
27
|
+
"""Markup Language. See the [vbml documentation](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)."""
|
|
28
28
|
|
|
29
29
|
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
30
30
|
if not isinstance(patterns, list):
|
|
31
31
|
patterns = [patterns]
|
|
32
32
|
self.patterns = [
|
|
33
|
-
vbml.Pattern(pattern
|
|
33
|
+
vbml.Pattern(pattern, flags=global_ctx.vbml_pattern_flags)
|
|
34
|
+
if isinstance(pattern, str)
|
|
35
|
+
else pattern
|
|
36
|
+
for pattern in patterns
|
|
34
37
|
]
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
def check(self, text: Text, ctx: Context) -> bool:
|
|
37
40
|
return check_string(self.patterns, text, ctx)
|
|
38
41
|
|
|
39
42
|
|