telegrinder 0.1.dev165__py3-none-any.whl → 0.1.dev167__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 +20 -0
- telegrinder/api/abc.py +1 -1
- telegrinder/api/api.py +8 -6
- telegrinder/api/error.py +2 -3
- telegrinder/bot/__init__.py +12 -0
- telegrinder/bot/bot.py +2 -2
- telegrinder/bot/cute_types/__init__.py +4 -0
- telegrinder/bot/cute_types/base.py +10 -10
- telegrinder/bot/cute_types/callback_query.py +1 -3
- telegrinder/bot/cute_types/chat_join_request.py +65 -0
- telegrinder/bot/cute_types/chat_member_updated.py +246 -0
- telegrinder/bot/cute_types/message.py +44 -38
- telegrinder/bot/cute_types/update.py +5 -4
- telegrinder/bot/cute_types/utils.py +40 -20
- telegrinder/bot/dispatch/__init__.py +8 -1
- telegrinder/bot/dispatch/composition.py +7 -7
- telegrinder/bot/dispatch/context.py +9 -10
- telegrinder/bot/dispatch/dispatch.py +30 -22
- telegrinder/bot/dispatch/handler/abc.py +1 -0
- telegrinder/bot/dispatch/handler/func.py +21 -5
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/abc.py +2 -4
- telegrinder/bot/dispatch/process.py +4 -3
- telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
- telegrinder/bot/dispatch/return_manager/abc.py +28 -20
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/message.py +8 -4
- telegrinder/bot/dispatch/view/__init__.py +8 -2
- telegrinder/bot/dispatch/view/abc.py +27 -23
- telegrinder/bot/dispatch/view/box.py +74 -11
- telegrinder/bot/dispatch/view/callback_query.py +1 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
- telegrinder/bot/dispatch/view/chat_member.py +26 -0
- telegrinder/bot/dispatch/view/message.py +23 -1
- telegrinder/bot/dispatch/view/raw.py +112 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +41 -26
- telegrinder/bot/dispatch/waiter_machine/middleware.py +14 -7
- telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -7
- telegrinder/bot/polling/polling.py +2 -4
- telegrinder/bot/rules/__init__.py +20 -12
- telegrinder/bot/rules/abc.py +0 -9
- telegrinder/bot/rules/adapter/event.py +29 -22
- telegrinder/bot/rules/callback_data.py +15 -18
- telegrinder/bot/rules/chat_join.py +47 -0
- telegrinder/bot/rules/enum_text.py +7 -2
- telegrinder/bot/rules/fuzzy.py +1 -2
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/is_from.py +39 -51
- telegrinder/bot/rules/markup.py +1 -2
- telegrinder/bot/rules/mention.py +1 -4
- telegrinder/bot/rules/message.py +17 -0
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/regex.py +1 -2
- telegrinder/bot/rules/rule_enum.py +1 -3
- telegrinder/bot/rules/start.py +7 -7
- telegrinder/bot/rules/text.py +2 -1
- telegrinder/bot/rules/update.py +16 -0
- telegrinder/bot/scenario/checkbox.py +5 -7
- telegrinder/client/aiohttp.py +5 -7
- telegrinder/model.py +37 -22
- telegrinder/modules.py +15 -33
- telegrinder/msgspec_utils.py +34 -35
- telegrinder/node/__init__.py +12 -12
- telegrinder/node/attachment.py +21 -7
- telegrinder/node/base.py +14 -13
- telegrinder/node/composer.py +5 -5
- telegrinder/node/container.py +1 -1
- telegrinder/node/message.py +3 -1
- telegrinder/node/rule.py +4 -4
- telegrinder/node/source.py +6 -2
- telegrinder/node/text.py +3 -1
- telegrinder/node/tools/generator.py +1 -1
- telegrinder/tools/__init__.py +3 -1
- telegrinder/tools/buttons.py +4 -6
- telegrinder/tools/error_handler/abc.py +1 -2
- telegrinder/tools/error_handler/error.py +3 -6
- telegrinder/tools/error_handler/error_handler.py +34 -24
- telegrinder/tools/formatting/html.py +9 -5
- telegrinder/tools/formatting/links.py +1 -3
- telegrinder/tools/formatting/spec_html_formats.py +1 -1
- telegrinder/tools/global_context/abc.py +3 -1
- telegrinder/tools/global_context/global_context.py +13 -31
- telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
- telegrinder/tools/i18n/base.py +4 -3
- telegrinder/tools/i18n/middleware/base.py +1 -3
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/limited_dict.py +27 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +18 -14
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/__init__.py +236 -0
- telegrinder/types/enums.py +34 -0
- telegrinder/types/methods.py +52 -47
- telegrinder/types/objects.py +533 -90
- telegrinder/verification_utils.py +4 -1
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev167.dist-info/RECORD +137 -0
- telegrinder-0.1.dev165.dist-info/RECORD +0 -128
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/WHEEL +0 -0
|
@@ -5,21 +5,25 @@ import typing
|
|
|
5
5
|
from telegrinder.api.abc import ABCAPI
|
|
6
6
|
from telegrinder.bot.dispatch.context import Context
|
|
7
7
|
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.tools.limited_dict import LimitedDict
|
|
9
|
+
from telegrinder.types import Update
|
|
8
10
|
|
|
9
11
|
from .middleware import WaiterMiddleware
|
|
10
12
|
from .short_state import Behaviour, EventModel, ShortState
|
|
11
13
|
|
|
12
|
-
Identificator: typing.TypeAlias = str | int
|
|
13
|
-
Storage: typing.TypeAlias = dict[str, dict[Identificator, "ShortState"]]
|
|
14
|
-
|
|
15
14
|
if typing.TYPE_CHECKING:
|
|
16
15
|
from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
|
|
17
16
|
|
|
17
|
+
T = typing.TypeVar("T")
|
|
18
|
+
|
|
19
|
+
Identificator: typing.TypeAlias = str | int
|
|
20
|
+
Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
class WaiterMachine:
|
|
20
24
|
def __init__(self) -> None:
|
|
21
25
|
self.storage: Storage = {}
|
|
22
|
-
|
|
26
|
+
|
|
23
27
|
def __repr__(self) -> str:
|
|
24
28
|
return "<{}: storage={!r}>".format(
|
|
25
29
|
self.__class__.__name__,
|
|
@@ -30,6 +34,7 @@ class WaiterMachine:
|
|
|
30
34
|
self,
|
|
31
35
|
state_view: "ABCStateView[EventModel]",
|
|
32
36
|
id: Identificator,
|
|
37
|
+
update: Update,
|
|
33
38
|
**context: typing.Any,
|
|
34
39
|
) -> None:
|
|
35
40
|
view_name = state_view.__class__.__name__
|
|
@@ -37,25 +42,25 @@ class WaiterMachine:
|
|
|
37
42
|
raise LookupError("No record of view {!r} found".format(view_name))
|
|
38
43
|
|
|
39
44
|
short_state = self.storage[view_name].pop(id, None)
|
|
40
|
-
if
|
|
45
|
+
if short_state is None:
|
|
41
46
|
raise LookupError(
|
|
42
47
|
"Waiter with identificator {} is not found for view {!r}".format(
|
|
43
|
-
id,
|
|
44
|
-
view_name,
|
|
48
|
+
id, view_name
|
|
45
49
|
)
|
|
46
50
|
)
|
|
47
51
|
|
|
48
52
|
waiters = typing.cast(
|
|
49
53
|
typing.Iterable[asyncio.Future[typing.Any]],
|
|
50
|
-
short_state.event._waiters # type: ignore
|
|
54
|
+
short_state.event._waiters, # type: ignore
|
|
51
55
|
)
|
|
52
56
|
for future in waiters:
|
|
53
57
|
future.cancel()
|
|
54
58
|
|
|
55
59
|
await self.call_behaviour(
|
|
56
60
|
state_view,
|
|
57
|
-
short_state.on_drop_behaviour,
|
|
58
61
|
short_state.event,
|
|
62
|
+
update,
|
|
63
|
+
behaviour=short_state.on_drop_behaviour,
|
|
59
64
|
**context,
|
|
60
65
|
)
|
|
61
66
|
|
|
@@ -66,21 +71,22 @@ class WaiterMachine:
|
|
|
66
71
|
*rules: ABCRule[EventModel],
|
|
67
72
|
default: Behaviour = None,
|
|
68
73
|
on_drop: Behaviour = None,
|
|
69
|
-
expiration: datetime.timedelta | int |
|
|
74
|
+
expiration: datetime.timedelta | int | None = None,
|
|
70
75
|
) -> tuple[EventModel, Context]:
|
|
71
76
|
if isinstance(expiration, int | float):
|
|
72
77
|
expiration = datetime.timedelta(seconds=expiration)
|
|
73
78
|
|
|
74
79
|
api: ABCAPI
|
|
75
80
|
key: Identificator
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
api, key = (
|
|
82
|
+
linked
|
|
83
|
+
if isinstance(linked, tuple)
|
|
84
|
+
else (linked.ctx_api, state_view.get_state_key(linked))
|
|
85
|
+
) # type: ignore
|
|
86
|
+
if not key:
|
|
87
|
+
raise RuntimeError("Unable to get state key.")
|
|
83
88
|
|
|
89
|
+
event = asyncio.Event()
|
|
84
90
|
short_state = ShortState(
|
|
85
91
|
key,
|
|
86
92
|
api,
|
|
@@ -90,33 +96,42 @@ class WaiterMachine:
|
|
|
90
96
|
default_behaviour=default,
|
|
91
97
|
on_drop_behaviour=on_drop,
|
|
92
98
|
)
|
|
93
|
-
|
|
94
99
|
view_name = state_view.__class__.__name__
|
|
95
100
|
if view_name not in self.storage:
|
|
96
101
|
state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
|
|
97
|
-
self.storage[view_name] =
|
|
102
|
+
self.storage[view_name] = LimitedDict()
|
|
98
103
|
|
|
99
104
|
self.storage[view_name][key] = short_state
|
|
100
|
-
|
|
101
105
|
await event.wait()
|
|
102
106
|
|
|
103
107
|
e, ctx = getattr(event, "context")
|
|
104
|
-
self.storage[view_name].pop(key)
|
|
105
|
-
|
|
108
|
+
self.storage[view_name].pop(key, None)
|
|
106
109
|
return e, ctx
|
|
107
110
|
|
|
108
111
|
async def call_behaviour(
|
|
109
112
|
self,
|
|
110
113
|
view: "ABCStateView[EventModel]",
|
|
111
|
-
behaviour: Behaviour,
|
|
112
114
|
event: asyncio.Event | EventModel,
|
|
115
|
+
update: Update,
|
|
116
|
+
behaviour: Behaviour[EventModel] | None = None,
|
|
113
117
|
**context: typing.Any,
|
|
114
118
|
) -> None:
|
|
119
|
+
# TODO: support param view as a behaviour
|
|
120
|
+
|
|
121
|
+
ctx = Context(**context)
|
|
122
|
+
|
|
123
|
+
if isinstance(event, asyncio.Event):
|
|
124
|
+
event, preset_ctx = typing.cast(
|
|
125
|
+
tuple[EventModel, Context],
|
|
126
|
+
getattr(event, "context"),
|
|
127
|
+
)
|
|
128
|
+
ctx.update(preset_ctx)
|
|
129
|
+
|
|
115
130
|
if behaviour is None:
|
|
116
131
|
return
|
|
117
|
-
# TODO: add behaviour check
|
|
118
|
-
# TODO: support view as a behaviour
|
|
119
|
-
await behaviour.run(event, context) # type: ignore
|
|
120
132
|
|
|
133
|
+
if await behaviour.check(event.api, update, ctx):
|
|
134
|
+
await behaviour.run(event, ctx)
|
|
135
|
+
|
|
121
136
|
|
|
122
137
|
__all__ = ("WaiterMachine",)
|
|
@@ -38,23 +38,24 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
38
38
|
if key is None:
|
|
39
39
|
raise RuntimeError("Unable to get state key.")
|
|
40
40
|
|
|
41
|
-
short_state:
|
|
41
|
+
short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
|
|
42
42
|
if not short_state:
|
|
43
43
|
return True
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
preset_context = Context(short_state=short_state)
|
|
45
46
|
if (
|
|
46
47
|
short_state.expiration_date is not None
|
|
47
48
|
and datetime.datetime.now() >= short_state.expiration_date
|
|
48
49
|
):
|
|
49
|
-
await self.machine.drop(self.view, short_state.key)
|
|
50
|
+
await self.machine.drop(self.view, short_state.key, ctx.raw_update, **preset_context.copy())
|
|
50
51
|
return True
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
handler = FuncHandler(
|
|
53
54
|
self.pass_runtime,
|
|
54
55
|
list(short_state.rules),
|
|
55
56
|
dataclass=None,
|
|
57
|
+
preset_context=preset_context,
|
|
56
58
|
)
|
|
57
|
-
handler.preset_context.set("short_state", short_state)
|
|
58
59
|
result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
|
|
59
60
|
|
|
60
61
|
if result is True:
|
|
@@ -63,14 +64,20 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
63
64
|
elif short_state.default_behaviour is not None:
|
|
64
65
|
await self.machine.call_behaviour(
|
|
65
66
|
self.view,
|
|
66
|
-
short_state.default_behaviour,
|
|
67
67
|
event,
|
|
68
|
+
ctx.raw_update,
|
|
69
|
+
behaviour=short_state.default_behaviour,
|
|
68
70
|
**handler.preset_context,
|
|
69
71
|
)
|
|
70
72
|
|
|
71
73
|
return False
|
|
72
74
|
|
|
73
|
-
async def pass_runtime(
|
|
75
|
+
async def pass_runtime(
|
|
76
|
+
self,
|
|
77
|
+
event: EventType,
|
|
78
|
+
short_state: "ShortState[EventType]",
|
|
79
|
+
ctx: Context,
|
|
80
|
+
) -> None:
|
|
74
81
|
setattr(short_state.event, "context", (event, ctx))
|
|
75
82
|
short_state.event.set()
|
|
76
83
|
|
|
@@ -7,12 +7,15 @@ from telegrinder.api import ABCAPI
|
|
|
7
7
|
from telegrinder.bot.cute_types import BaseCute
|
|
8
8
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
9
9
|
from telegrinder.bot.rules.abc import ABCRule
|
|
10
|
+
from telegrinder.model import Model
|
|
10
11
|
|
|
11
12
|
if typing.TYPE_CHECKING:
|
|
12
13
|
from .machine import Identificator
|
|
13
14
|
|
|
15
|
+
T = typing.TypeVar("T", bound=Model)
|
|
14
16
|
EventModel = typing.TypeVar("EventModel", bound=BaseCute)
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
Behaviour: typing.TypeAlias = ABCHandler[T] | None
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@dataclasses.dataclass
|
|
@@ -22,15 +25,15 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
22
25
|
event: asyncio.Event
|
|
23
26
|
rules: tuple[ABCRule[EventModel], ...]
|
|
24
27
|
_: dataclasses.KW_ONLY
|
|
25
|
-
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
29
|
+
default=None,
|
|
30
|
+
)
|
|
31
|
+
default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
32
|
+
on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
28
33
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False)
|
|
29
34
|
|
|
30
35
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
|
31
|
-
self.expiration_date = (
|
|
32
|
-
datetime.datetime.now() - expiration
|
|
33
|
-
) if expiration is not None else None
|
|
36
|
+
self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
__all__ = ("ShortState",)
|
|
@@ -33,7 +33,7 @@ class Polling(ABCPolling):
|
|
|
33
33
|
self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
|
|
34
34
|
self.offset = offset
|
|
35
35
|
self._stop = False
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
def __repr__(self) -> str:
|
|
38
38
|
return (
|
|
39
39
|
"<{}: with api={!r}, stopped={}, offset={}, allowed_updates={!r}, "
|
|
@@ -60,9 +60,7 @@ class Polling(ABCPolling):
|
|
|
60
60
|
|
|
61
61
|
if include_updates and exclude_updates:
|
|
62
62
|
allowed_updates = [
|
|
63
|
-
x
|
|
64
|
-
for x in allowed_updates
|
|
65
|
-
if x in include_updates and x not in exclude_updates
|
|
63
|
+
x for x in allowed_updates if x in include_updates and x not in exclude_updates
|
|
66
64
|
]
|
|
67
65
|
elif exclude_updates:
|
|
68
66
|
allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .abc import ABCRule, AndRule,
|
|
1
|
+
from .abc import ABCRule, AndRule, NotRule, OrRule
|
|
2
2
|
from .callback_data import (
|
|
3
3
|
CallbackDataEq,
|
|
4
4
|
CallbackDataJsonEq,
|
|
@@ -9,6 +9,12 @@ from .callback_data import (
|
|
|
9
9
|
CallbackQueryRule,
|
|
10
10
|
HasData,
|
|
11
11
|
)
|
|
12
|
+
from .chat_join import (
|
|
13
|
+
ChatJoinRequestRule,
|
|
14
|
+
HasInviteLink,
|
|
15
|
+
InviteLinkByCreator,
|
|
16
|
+
InviteLinkName,
|
|
17
|
+
)
|
|
12
18
|
from .command import Argument, Command
|
|
13
19
|
from .enum_text import EnumTextRule
|
|
14
20
|
from .func import FuncRule
|
|
@@ -22,13 +28,10 @@ from .inline import (
|
|
|
22
28
|
)
|
|
23
29
|
from .integer import Integer, IntegerInRange
|
|
24
30
|
from .is_from import (
|
|
25
|
-
IsBasketballDice,
|
|
26
31
|
IsBot,
|
|
27
|
-
IsBowlingDice,
|
|
28
32
|
IsChat,
|
|
29
33
|
IsChatId,
|
|
30
|
-
|
|
31
|
-
IsDice,
|
|
34
|
+
IsDiceEmoji,
|
|
32
35
|
IsForum,
|
|
33
36
|
IsForward,
|
|
34
37
|
IsForwardType,
|
|
@@ -43,11 +46,13 @@ from .is_from import (
|
|
|
43
46
|
)
|
|
44
47
|
from .markup import Markup
|
|
45
48
|
from .mention import HasMention
|
|
49
|
+
from .message import MessageRule
|
|
46
50
|
from .message_entities import HasEntities, MessageEntities
|
|
47
51
|
from .regex import Regex
|
|
48
52
|
from .rule_enum import RuleEnum
|
|
49
53
|
from .start import StartCommand
|
|
50
54
|
from .text import HasText, Text, TextMessageRule
|
|
55
|
+
from .update import IsUpdate
|
|
51
56
|
|
|
52
57
|
__all__ = (
|
|
53
58
|
"ABCRule",
|
|
@@ -60,25 +65,29 @@ __all__ = (
|
|
|
60
65
|
"CallbackDataMarkup",
|
|
61
66
|
"CallbackQueryDataRule",
|
|
62
67
|
"CallbackQueryRule",
|
|
68
|
+
"ChatJoinRequestRule",
|
|
63
69
|
"Command",
|
|
64
70
|
"EnumTextRule",
|
|
65
71
|
"FuncRule",
|
|
66
72
|
"FuzzyText",
|
|
67
73
|
"HasData",
|
|
68
74
|
"HasEntities",
|
|
75
|
+
"HasInviteLink",
|
|
76
|
+
"HasLocation",
|
|
69
77
|
"HasMention",
|
|
70
78
|
"HasText",
|
|
79
|
+
"InlineQueryChatType",
|
|
80
|
+
"InlineQueryMarkup",
|
|
71
81
|
"InlineQueryRule",
|
|
72
82
|
"InlineQueryText",
|
|
73
83
|
"Integer",
|
|
74
84
|
"IntegerInRange",
|
|
75
|
-
"
|
|
85
|
+
"InviteLinkByCreator",
|
|
86
|
+
"InviteLinkName",
|
|
76
87
|
"IsBot",
|
|
77
|
-
"IsBowlingDice",
|
|
78
88
|
"IsChat",
|
|
79
89
|
"IsChatId",
|
|
80
|
-
"
|
|
81
|
-
"IsDice",
|
|
90
|
+
"IsDiceEmoji",
|
|
82
91
|
"IsForum",
|
|
83
92
|
"IsForward",
|
|
84
93
|
"IsForwardType",
|
|
@@ -88,14 +97,13 @@ __all__ = (
|
|
|
88
97
|
"IsPrivate",
|
|
89
98
|
"IsReply",
|
|
90
99
|
"IsSuperGroup",
|
|
100
|
+
"IsUpdate",
|
|
91
101
|
"IsUser",
|
|
92
102
|
"IsUserId",
|
|
93
|
-
"HasLocation",
|
|
94
|
-
"InlineQueryChatType",
|
|
95
|
-
"InlineQueryMarkup",
|
|
96
103
|
"Markup",
|
|
97
104
|
"MessageEntities",
|
|
98
105
|
"MessageRule",
|
|
106
|
+
"NotRule",
|
|
99
107
|
"OrRule",
|
|
100
108
|
"Regex",
|
|
101
109
|
"RuleEnum",
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -102,18 +102,9 @@ class NotRule(ABCRule[T]):
|
|
|
102
102
|
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
class MessageRule(ABCRule[Message], ABC, requires=[]):
|
|
106
|
-
adapter = EventAdapter("message", Message)
|
|
107
|
-
|
|
108
|
-
@abstractmethod
|
|
109
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
110
|
-
...
|
|
111
|
-
|
|
112
|
-
|
|
113
105
|
__all__ = (
|
|
114
106
|
"ABCRule",
|
|
115
107
|
"AndRule",
|
|
116
|
-
"MessageRule",
|
|
117
108
|
"NotRule",
|
|
118
109
|
"OrRule",
|
|
119
110
|
"with_caching_translations",
|
|
@@ -9,41 +9,48 @@ from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
|
9
9
|
from telegrinder.msgspec_utils import Nothing
|
|
10
10
|
from telegrinder.types.objects import Model, Update
|
|
11
11
|
|
|
12
|
-
EventT = typing.TypeVar("EventT", bound=Model)
|
|
13
12
|
CuteT = typing.TypeVar("CuteT", bound=BaseCute)
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
17
|
-
def __init__(self,
|
|
18
|
-
self.
|
|
19
|
-
self.
|
|
20
|
-
|
|
16
|
+
def __init__(self, event: str | type[Model], cute_model: type[CuteT]) -> None:
|
|
17
|
+
self.event = event
|
|
18
|
+
self.cute_model = cute_model
|
|
19
|
+
|
|
21
20
|
def __repr__(self) -> str:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
if isinstance(self.event, str):
|
|
22
|
+
raw_update_type = Update.__annotations__.get(self.event, "Unknown")
|
|
23
|
+
raw_update_type = (
|
|
24
|
+
typing.get_args(raw_update_type)[0].__forward_arg__
|
|
25
|
+
if typing.get_args(raw_update_type)
|
|
26
|
+
else raw_update_type
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
raw_update_type = self.event.__name__
|
|
28
30
|
return "<{}: adapt {} -> {}>".format(
|
|
29
31
|
self.__class__.__name__,
|
|
30
32
|
raw_update_type,
|
|
31
|
-
self.
|
|
33
|
+
self.cute_model.__name__,
|
|
32
34
|
)
|
|
33
35
|
|
|
34
36
|
async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
|
|
35
37
|
update_dct = update.to_dict()
|
|
36
|
-
if self.
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
if isinstance(self.event, str):
|
|
39
|
+
if self.event not in update_dct:
|
|
40
|
+
return Error(
|
|
41
|
+
AdapterError(f"Update is not of event type {self.event!r}."),
|
|
42
|
+
)
|
|
43
|
+
if update_dct[self.event] is Nothing:
|
|
44
|
+
return Error(
|
|
45
|
+
AdapterError(f"Update is not an {self.event!r}."),
|
|
46
|
+
)
|
|
47
|
+
return Ok(
|
|
48
|
+
self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
|
|
39
49
|
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return Ok(
|
|
45
|
-
self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api),
|
|
46
|
-
)
|
|
50
|
+
event = update_dct[update.update_type.unwrap()].unwrap()
|
|
51
|
+
if not update.update_type or not issubclass(event.__class__, self.event):
|
|
52
|
+
return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
|
|
53
|
+
return Ok(self.cute_model.from_update(event, bound_api=api))
|
|
47
54
|
|
|
48
55
|
|
|
49
56
|
__all__ = ("EventAdapter",)
|
|
@@ -14,16 +14,13 @@ from telegrinder.tools.buttons import DataclassInstance
|
|
|
14
14
|
from .abc import ABCRule
|
|
15
15
|
from .markup import Markup, PatternLike, check_string
|
|
16
16
|
|
|
17
|
-
T = typing.TypeVar("T")
|
|
18
|
-
|
|
19
|
-
Ref: typing.TypeAlias = typing.Annotated[T, ...]
|
|
20
17
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
21
18
|
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
22
19
|
MapDict: typing.TypeAlias = dict[
|
|
23
|
-
str, typing.Any | type[typing.Any] | Validator | list[
|
|
20
|
+
str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"
|
|
24
21
|
]
|
|
25
|
-
CallbackMap: typing.TypeAlias = list[tuple[str, typing.Any | type | Validator |
|
|
26
|
-
CallbackMapStrict: typing.TypeAlias = list[tuple[str, Validator |
|
|
22
|
+
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type | Validator | CallbackMap"]]
|
|
23
|
+
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
27
24
|
|
|
28
25
|
|
|
29
26
|
class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
@@ -52,20 +49,20 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
52
49
|
@classmethod
|
|
53
50
|
def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
|
|
54
51
|
"""Transforms MapDict to CallbackMap."""
|
|
55
|
-
|
|
52
|
+
|
|
56
53
|
callback_map = []
|
|
57
|
-
|
|
54
|
+
|
|
58
55
|
for k, v in mapping.items():
|
|
59
56
|
if isinstance(v, dict):
|
|
60
57
|
v = cls.transform_to_map(v)
|
|
61
58
|
callback_map.append((k, v))
|
|
62
|
-
|
|
59
|
+
|
|
63
60
|
return callback_map
|
|
64
61
|
|
|
65
62
|
@classmethod
|
|
66
63
|
def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
|
|
67
64
|
"""Transforms `CallbackMap` to `CallbackMapStrict`."""
|
|
68
|
-
|
|
65
|
+
|
|
69
66
|
callback_map_result = []
|
|
70
67
|
|
|
71
68
|
for key, value in callback_map:
|
|
@@ -78,21 +75,21 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
78
75
|
else:
|
|
79
76
|
validator = value
|
|
80
77
|
callback_map_result.append((key, validator))
|
|
81
|
-
|
|
78
|
+
|
|
82
79
|
return callback_map_result
|
|
83
80
|
|
|
84
81
|
@staticmethod
|
|
85
82
|
async def run_validator(value: typing.Any, validator: Validator) -> bool:
|
|
86
83
|
"""Run async or sync validator."""
|
|
87
|
-
|
|
84
|
+
|
|
88
85
|
with suppress(BaseException):
|
|
89
86
|
result = validator(value)
|
|
90
87
|
if inspect.isawaitable(result):
|
|
91
88
|
result = await result
|
|
92
89
|
return result # type: ignore
|
|
93
|
-
|
|
90
|
+
|
|
94
91
|
return False
|
|
95
|
-
|
|
92
|
+
|
|
96
93
|
@classmethod
|
|
97
94
|
async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
|
|
98
95
|
"""Matches callback_data with callback_map recursively."""
|
|
@@ -100,19 +97,19 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
100
97
|
for key, validator in callback_map:
|
|
101
98
|
if key not in callback_data:
|
|
102
99
|
return False
|
|
103
|
-
|
|
100
|
+
|
|
104
101
|
if isinstance(validator, list):
|
|
105
102
|
if not (
|
|
106
103
|
isinstance(callback_data[key], dict)
|
|
107
104
|
and await cls.match(callback_data[key], validator)
|
|
108
105
|
):
|
|
109
106
|
return False
|
|
110
|
-
|
|
107
|
+
|
|
111
108
|
elif not await cls.run_validator(callback_data[key], validator):
|
|
112
109
|
return False
|
|
113
110
|
|
|
114
111
|
return True
|
|
115
|
-
|
|
112
|
+
|
|
116
113
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
117
114
|
callback_data = event.decode_callback_data().unwrap_or_none()
|
|
118
115
|
if callback_data is None:
|
|
@@ -142,7 +139,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
|
142
139
|
class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
143
140
|
def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
|
|
144
141
|
self.model = model
|
|
145
|
-
|
|
142
|
+
|
|
146
143
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
147
144
|
with suppress(BaseException):
|
|
148
145
|
ctx.data = decoder.decode(event.data.unwrap().encode(), type=self.model)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.bot.rules.adapter import EventAdapter
|
|
7
|
+
|
|
8
|
+
from .abc import ABCRule
|
|
9
|
+
|
|
10
|
+
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChatJoinRequestRule(ABCRule[ChatJoinRequestCute], requires=[]):
|
|
14
|
+
adapter = EventAdapter("chat_join_request", ChatJoinRequest)
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HasInviteLink(ChatJoinRequestRule):
|
|
22
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
23
|
+
return bool(event.invite_link)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
27
|
+
def __init__(self, name: str, /) -> None:
|
|
28
|
+
self.name = name
|
|
29
|
+
|
|
30
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
31
|
+
return event.invite_link.unwrap().name.unwrap_or_none() == self.name
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
35
|
+
def __init__(self, creator_id: int, /) -> None:
|
|
36
|
+
self.creator_id = creator_id
|
|
37
|
+
|
|
38
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
39
|
+
return event.invite_link.unwrap().creator.id == self.creator_id
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = (
|
|
43
|
+
"ChatJoinRequestRule",
|
|
44
|
+
"HasInviteLink",
|
|
45
|
+
"InviteLinkByCreator",
|
|
46
|
+
"InviteLinkName",
|
|
47
|
+
)
|
|
@@ -6,13 +6,18 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
6
6
|
from .abc import Message
|
|
7
7
|
from .text import TextMessageRule
|
|
8
8
|
|
|
9
|
-
T = typing.TypeVar("T", bound=enum.Enum
|
|
9
|
+
T = typing.TypeVar("T", bound=enum.Enum)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class EnumTextRule(TextMessageRule, typing.Generic[T]):
|
|
13
13
|
def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
|
|
14
14
|
self.enum_t = enum_t
|
|
15
|
-
self.texts = list(
|
|
15
|
+
self.texts = list(
|
|
16
|
+
map(
|
|
17
|
+
lambda x: x.value.lower() if lower_case else x.value,
|
|
18
|
+
self.enum_t,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
16
21
|
|
|
17
22
|
def find(self, s: str) -> T:
|
|
18
23
|
for enumeration in self.enum_t:
|
telegrinder/bot/rules/fuzzy.py
CHANGED
|
@@ -15,8 +15,7 @@ class FuzzyText(TextMessageRule):
|
|
|
15
15
|
|
|
16
16
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
17
17
|
match = max(
|
|
18
|
-
difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio()
|
|
19
|
-
for text in self.texts
|
|
18
|
+
difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio() for text in self.texts
|
|
20
19
|
)
|
|
21
20
|
if match < self.min_ratio:
|
|
22
21
|
return False
|
telegrinder/bot/rules/inline.py
CHANGED
|
@@ -48,15 +48,15 @@ class InlineQueryText(InlineQueryRule):
|
|
|
48
48
|
class InlineQueryMarkup(InlineQueryRule):
|
|
49
49
|
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
50
50
|
self.patterns = Markup(patterns).patterns
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
async def check(self, query: InlineQuery, ctx: Context) -> bool:
|
|
53
53
|
return check_string(self.patterns, query.query, ctx)
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
__all__ = (
|
|
57
57
|
"HasLocation",
|
|
58
|
+
"InlineQueryChatType",
|
|
59
|
+
"InlineQueryMarkup",
|
|
58
60
|
"InlineQueryRule",
|
|
59
61
|
"InlineQueryText",
|
|
60
|
-
"InlineQueryMarkup",
|
|
61
|
-
"InlineQueryChatType",
|
|
62
62
|
)
|