telegrinder 0.2.1__py3-none-any.whl → 0.3.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 +45 -12
- telegrinder/bot/__init__.py +32 -4
- telegrinder/bot/cute_types/callback_query.py +60 -146
- telegrinder/bot/cute_types/chat_join_request.py +12 -16
- telegrinder/bot/cute_types/chat_member_updated.py +15 -105
- telegrinder/bot/cute_types/inline_query.py +5 -14
- telegrinder/bot/cute_types/message.py +623 -1238
- telegrinder/bot/dispatch/__init__.py +40 -3
- telegrinder/bot/dispatch/abc.py +8 -1
- telegrinder/bot/dispatch/context.py +2 -2
- telegrinder/bot/dispatch/dispatch.py +8 -1
- telegrinder/bot/dispatch/handler/__init__.py +17 -1
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -0
- telegrinder/bot/dispatch/handler/base.py +57 -0
- telegrinder/bot/dispatch/handler/document_reply.py +44 -0
- telegrinder/bot/dispatch/handler/func.py +3 -3
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +12 -35
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -0
- telegrinder/bot/dispatch/handler/video_reply.py +44 -0
- telegrinder/bot/dispatch/process.py +2 -2
- telegrinder/bot/dispatch/return_manager/abc.py +11 -8
- telegrinder/bot/dispatch/return_manager/callback_query.py +2 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +2 -2
- telegrinder/bot/dispatch/return_manager/message.py +3 -3
- telegrinder/bot/dispatch/view/__init__.py +2 -1
- telegrinder/bot/dispatch/view/abc.py +2 -181
- telegrinder/bot/dispatch/view/base.py +200 -0
- telegrinder/bot/dispatch/view/callback_query.py +3 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -2
- telegrinder/bot/dispatch/view/chat_member.py +2 -3
- telegrinder/bot/dispatch/view/inline_query.py +2 -2
- telegrinder/bot/dispatch/view/message.py +5 -4
- telegrinder/bot/dispatch/view/raw.py +4 -3
- telegrinder/bot/dispatch/waiter_machine/__init__.py +18 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +10 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +15 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +60 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +54 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +88 -101
- telegrinder/bot/dispatch/waiter_machine/middleware.py +23 -41
- telegrinder/bot/dispatch/waiter_machine/short_state.py +9 -9
- telegrinder/bot/polling/polling.py +5 -2
- telegrinder/bot/rules/__init__.py +3 -3
- telegrinder/bot/rules/abc.py +6 -5
- telegrinder/bot/rules/adapter/__init__.py +1 -1
- telegrinder/bot/rules/integer.py +1 -1
- telegrinder/bot/rules/is_from.py +19 -0
- telegrinder/bot/rules/state.py +9 -6
- telegrinder/bot/scenario/checkbox.py +5 -5
- telegrinder/bot/scenario/choice.py +2 -2
- telegrinder/client/aiohttp.py +5 -7
- telegrinder/model.py +6 -11
- telegrinder/modules.py +16 -25
- telegrinder/msgspec_json.py +1 -1
- telegrinder/msgspec_utils.py +56 -5
- telegrinder/node/base.py +2 -2
- telegrinder/node/composer.py +5 -9
- telegrinder/node/container.py +6 -1
- telegrinder/node/event.py +2 -0
- telegrinder/node/polymorphic.py +7 -7
- telegrinder/node/rule.py +6 -4
- telegrinder/node/scope.py +3 -3
- telegrinder/node/source.py +4 -2
- telegrinder/node/tools/generator.py +7 -6
- telegrinder/rules.py +2 -2
- telegrinder/tools/__init__.py +10 -10
- telegrinder/tools/functional.py +9 -0
- telegrinder/tools/keyboard.py +6 -1
- telegrinder/tools/loop_wrapper/loop_wrapper.py +4 -5
- telegrinder/tools/magic.py +17 -19
- telegrinder/tools/state_storage/__init__.py +3 -3
- telegrinder/tools/state_storage/abc.py +12 -10
- telegrinder/tools/state_storage/memory.py +9 -6
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/methods.py +10 -2
- telegrinder/types/objects.py +47 -5
- {telegrinder-0.2.1.dist-info → telegrinder-0.3.0.dist-info}/METADATA +4 -5
- telegrinder-0.3.0.dist-info/RECORD +164 -0
- telegrinder-0.2.1.dist-info/RECORD +0 -149
- {telegrinder-0.2.1.dist-info → telegrinder-0.3.0.dist-info}/LICENSE +0 -0
- {telegrinder-0.2.1.dist-info → telegrinder-0.3.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes import Option
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
6
|
+
from telegrinder.bot.dispatch.view.base import BaseView
|
|
7
|
+
from telegrinder.tools.functional import from_optional
|
|
8
|
+
|
|
9
|
+
Event = typing.TypeVar("Event", bound=BaseCute)
|
|
10
|
+
Data = typing.TypeVar("Data")
|
|
11
|
+
|
|
12
|
+
ECHO = lambda x: x
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Hasher(typing.Generic[Event, Data]):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
view: type[BaseView[Event]],
|
|
19
|
+
get_hash_from_data: typing.Callable[[Data], typing.Hashable | None] | None = None,
|
|
20
|
+
get_data_from_event: typing.Callable[[Event], Data | None] | None = None,
|
|
21
|
+
):
|
|
22
|
+
self.view = view
|
|
23
|
+
self._get_hash_from_data = get_hash_from_data
|
|
24
|
+
self._get_data_from_event = get_data_from_event
|
|
25
|
+
|
|
26
|
+
def get_name(self) -> str:
|
|
27
|
+
return f"{self.view.__class__.__name__}_{id(self)}"
|
|
28
|
+
|
|
29
|
+
def get_hash_from_data(self, data: Data) -> Option[typing.Hashable]:
|
|
30
|
+
if self._get_hash_from_data is None:
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
return from_optional(self._get_hash_from_data(data))
|
|
33
|
+
|
|
34
|
+
def get_data_from_event(self, event: Event) -> Option[Data]:
|
|
35
|
+
if not self._get_data_from_event:
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
return from_optional(self._get_data_from_event(event))
|
|
38
|
+
|
|
39
|
+
def get_hash_from_data_from_event(self, event: Event) -> Option[typing.Hashable]:
|
|
40
|
+
return self.get_data_from_event(event).and_then(self.get_hash_from_data) # type: ignore
|
|
41
|
+
|
|
42
|
+
def __hash__(self) -> int:
|
|
43
|
+
return hash(self.get_name())
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
return f"<Hasher {self.get_name()}>"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = ("Hasher",)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types import MessageCute as Message
|
|
2
|
+
from telegrinder.bot.dispatch.view import MessageView
|
|
3
|
+
|
|
4
|
+
from .hasher import Hasher
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def from_chat_hash(chat_id: int) -> int:
|
|
8
|
+
return chat_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_chat_from_event(event: Message) -> int:
|
|
12
|
+
return event.chat.id
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MESSAGE_IN_CHAT = Hasher(
|
|
16
|
+
view=MessageView, get_hash_from_data=from_chat_hash, get_data_from_event=get_chat_from_event
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def from_user_hash(from_id: int) -> int:
|
|
21
|
+
return from_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_user_from_event(event: Message) -> int:
|
|
25
|
+
return event.from_user.id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
MESSAGE_FROM_USER = Hasher(
|
|
29
|
+
view=MessageView,
|
|
30
|
+
get_hash_from_data=from_user_hash,
|
|
31
|
+
get_data_from_event=get_user_from_event,
|
|
32
|
+
)
|
|
33
|
+
|
|
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
|
+
MESSAGE_FROM_USER_IN_CHAT = Hasher(
|
|
44
|
+
view=MessageView,
|
|
45
|
+
get_hash_from_data=from_user_in_chat_hash,
|
|
46
|
+
get_data_from_event=get_user_in_chat_from_event,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = (
|
|
51
|
+
"MESSAGE_FROM_USER",
|
|
52
|
+
"MESSAGE_FROM_USER_IN_CHAT",
|
|
53
|
+
"MESSAGE_IN_CHAT",
|
|
54
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from fntypes import Option
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.view import BaseStateView
|
|
4
|
+
from telegrinder.tools.functional import from_optional
|
|
5
|
+
|
|
6
|
+
from .hasher import ECHO, Event, Hasher
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StateViewHasher(Hasher[Event, int]):
|
|
10
|
+
view: BaseStateView
|
|
11
|
+
|
|
12
|
+
def __init__(self, view: type[BaseStateView[Event]]):
|
|
13
|
+
super().__init__(view, get_hash_from_data=ECHO)
|
|
14
|
+
|
|
15
|
+
def get_data_from_event(self, event: Event) -> Option[int]:
|
|
16
|
+
return from_optional(self.view.get_state_key(event))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ("StateViewHasher",)
|
|
@@ -2,34 +2,42 @@ import asyncio
|
|
|
2
2
|
import datetime
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
|
-
from telegrinder.
|
|
6
|
-
from telegrinder.bot.dispatch.
|
|
7
|
-
from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
|
|
5
|
+
from telegrinder.bot.dispatch.abc import ABCDispatch
|
|
6
|
+
from telegrinder.bot.dispatch.view.base import BaseStateView, BaseView
|
|
8
7
|
from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
|
|
9
8
|
from telegrinder.bot.dispatch.waiter_machine.short_state import (
|
|
10
|
-
Behaviour,
|
|
11
9
|
EventModel,
|
|
12
10
|
ShortState,
|
|
13
11
|
ShortStateContext,
|
|
14
12
|
)
|
|
15
13
|
from telegrinder.bot.rules.abc import ABCRule
|
|
16
14
|
from telegrinder.tools.limited_dict import LimitedDict
|
|
17
|
-
from telegrinder.types import Update
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
from .actions import WaiterActions
|
|
17
|
+
from .hasher import Hasher, StateViewHasher
|
|
21
18
|
|
|
22
19
|
T = typing.TypeVar("T")
|
|
20
|
+
HasherData = typing.TypeVar("HasherData")
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
Storage: typing.TypeAlias = dict[
|
|
22
|
+
|
|
23
|
+
Storage: typing.TypeAlias = dict[
|
|
24
|
+
Hasher[EventModel, HasherData], LimitedDict[typing.Hashable, ShortState[EventModel]]
|
|
25
|
+
]
|
|
26
26
|
|
|
27
27
|
WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class WaiterMachine:
|
|
31
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
dispatch: ABCDispatch | None = None,
|
|
34
|
+
*,
|
|
35
|
+
max_storage_size: int = 1000,
|
|
36
|
+
base_state_lifetime: datetime.timedelta = WEEK,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.dispatch = dispatch
|
|
32
39
|
self.max_storage_size = max_storage_size
|
|
40
|
+
self.base_state_lifetime = base_state_lifetime
|
|
33
41
|
self.storage: Storage = {}
|
|
34
42
|
|
|
35
43
|
def __repr__(self) -> str:
|
|
@@ -45,144 +53,123 @@ class WaiterMachine:
|
|
|
45
53
|
|
|
46
54
|
async def drop(
|
|
47
55
|
self,
|
|
48
|
-
|
|
49
|
-
id:
|
|
50
|
-
event: EventModel,
|
|
51
|
-
update: Update,
|
|
56
|
+
hasher: Hasher[EventModel, HasherData],
|
|
57
|
+
id: HasherData,
|
|
52
58
|
**context: typing.Any,
|
|
53
59
|
) -> None:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
raise LookupError("No record of view {!r} found.".format(view_name))
|
|
60
|
+
if hasher not in self.storage:
|
|
61
|
+
raise LookupError("No record of hasher {!r} found.".format(hasher))
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
waiter_id: typing.Hashable = hasher.get_hash_from_data(id).expect(
|
|
64
|
+
RuntimeError("Couldn't create hash from data")
|
|
65
|
+
)
|
|
66
|
+
short_state = self.storage[hasher].pop(waiter_id, None)
|
|
59
67
|
if short_state is None:
|
|
60
|
-
raise LookupError(
|
|
68
|
+
raise LookupError(
|
|
69
|
+
"Waiter with identificator {} is not found for hasher {!r}".format(waiter_id, hasher)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if on_drop := short_state.actions.get("on_drop"):
|
|
73
|
+
on_drop(short_state, **context)
|
|
61
74
|
|
|
62
75
|
short_state.cancel()
|
|
63
|
-
await self.call_behaviour(
|
|
64
|
-
event,
|
|
65
|
-
update,
|
|
66
|
-
behaviour=short_state.on_drop_behaviour,
|
|
67
|
-
**context,
|
|
68
|
-
)
|
|
69
76
|
|
|
70
77
|
async def drop_all(self) -> None:
|
|
71
|
-
"""Drops all waiters in storage"""
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
"""Drops all waiters in storage."""
|
|
79
|
+
|
|
80
|
+
for hasher in self.storage:
|
|
81
|
+
for ident, short_state in self.storage[hasher].items():
|
|
74
82
|
if short_state.context:
|
|
75
83
|
await self.drop(
|
|
76
|
-
|
|
84
|
+
hasher,
|
|
77
85
|
ident,
|
|
78
|
-
short_state.context.event,
|
|
79
|
-
short_state.context.context.raw_update
|
|
80
86
|
)
|
|
81
87
|
else:
|
|
82
88
|
short_state.cancel()
|
|
83
89
|
|
|
84
|
-
async def
|
|
90
|
+
async def wait_from_event(
|
|
85
91
|
self,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
view: BaseStateView[EventModel],
|
|
93
|
+
event: EventModel,
|
|
94
|
+
*,
|
|
95
|
+
filter: ABCRule | None = None,
|
|
96
|
+
release: ABCRule | None = None,
|
|
97
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
98
|
+
**actions: typing.Unpack[WaiterActions],
|
|
93
99
|
) -> ShortStateContext[EventModel]:
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
hasher = StateViewHasher(view.__class__)
|
|
101
|
+
return await self.wait(
|
|
102
|
+
hasher=hasher,
|
|
103
|
+
data=hasher.get_data_from_event(event).expect(
|
|
104
|
+
RuntimeError("Hasher couldn't create data from event.")
|
|
105
|
+
),
|
|
106
|
+
filter=filter,
|
|
107
|
+
release=release,
|
|
108
|
+
lifetime=lifetime,
|
|
109
|
+
**actions,
|
|
110
|
+
)
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
async def wait(
|
|
113
|
+
self,
|
|
114
|
+
hasher: Hasher[EventModel, HasherData],
|
|
115
|
+
data: HasherData,
|
|
116
|
+
*,
|
|
117
|
+
filter: ABCRule | None = None,
|
|
118
|
+
release: ABCRule | None = None,
|
|
119
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
120
|
+
**actions: typing.Unpack[WaiterActions],
|
|
121
|
+
) -> ShortStateContext[EventModel]:
|
|
122
|
+
if isinstance(lifetime, int | float):
|
|
123
|
+
lifetime = datetime.timedelta(seconds=lifetime)
|
|
102
124
|
|
|
103
|
-
view_name = state_view.__class__.__name__
|
|
104
125
|
event = asyncio.Event()
|
|
105
126
|
short_state = ShortState[EventModel](
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
event,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
default_behaviour=default,
|
|
112
|
-
on_drop_behaviour=on_drop,
|
|
113
|
-
exit_behaviour=exit,
|
|
127
|
+
filter=filter,
|
|
128
|
+
release=release,
|
|
129
|
+
event=event,
|
|
130
|
+
lifetime=lifetime or self.base_state_lifetime,
|
|
131
|
+
actions=actions,
|
|
114
132
|
)
|
|
133
|
+
waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
|
|
115
134
|
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
if hasher not in self.storage:
|
|
136
|
+
if self.dispatch:
|
|
137
|
+
view: BaseView[EventModel] = self.dispatch.get_view(hasher.view).expect(
|
|
138
|
+
RuntimeError(f"View {hasher.view.__name__} is not defined in dispatch")
|
|
139
|
+
)
|
|
140
|
+
view.middlewares.insert(0, WaiterMiddleware(self, hasher))
|
|
141
|
+
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
119
142
|
|
|
120
|
-
if (deleted_short_state := self.storage[
|
|
143
|
+
if (deleted_short_state := self.storage[hasher].set(waiter_hash, short_state)) is not None:
|
|
121
144
|
deleted_short_state.cancel()
|
|
122
145
|
|
|
123
146
|
await event.wait()
|
|
124
|
-
self.storage[
|
|
147
|
+
self.storage[hasher].pop(waiter_hash, None)
|
|
125
148
|
assert short_state.context is not None
|
|
126
149
|
return short_state.context
|
|
127
150
|
|
|
128
|
-
async def call_behaviour(
|
|
129
|
-
self,
|
|
130
|
-
event: EventModel,
|
|
131
|
-
update: Update,
|
|
132
|
-
behaviour: Behaviour[EventModel] | None = None,
|
|
133
|
-
**context: typing.Any,
|
|
134
|
-
) -> bool:
|
|
135
|
-
# TODO: support view as a behaviour
|
|
136
|
-
|
|
137
|
-
if behaviour is None:
|
|
138
|
-
return False
|
|
139
|
-
|
|
140
|
-
ctx = Context(**context)
|
|
141
|
-
if await behaviour.check(event.api, update, ctx):
|
|
142
|
-
await behaviour.run(event.api, event, ctx)
|
|
143
|
-
return True
|
|
144
|
-
|
|
145
|
-
return False
|
|
146
|
-
|
|
147
151
|
async def clear_storage(
|
|
148
152
|
self,
|
|
149
|
-
views: typing.Iterable[ABCStateView[EventModel]],
|
|
150
|
-
absolutely_dead_time: datetime.timedelta = WEEK,
|
|
151
153
|
) -> None:
|
|
152
|
-
"""Clears storage.
|
|
154
|
+
"""Clears storage."""
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
for view in views:
|
|
158
|
-
view_name = view.__class__.__name__
|
|
156
|
+
for hasher in self.storage:
|
|
159
157
|
now = datetime.datetime.now()
|
|
160
|
-
for ident, short_state in self.storage.get(
|
|
158
|
+
for ident, short_state in self.storage.get(hasher, {}).copy().items():
|
|
161
159
|
if short_state.expiration_date is not None and now > short_state.expiration_date:
|
|
162
|
-
assert short_state.context # FIXME: why???
|
|
163
160
|
await self.drop(
|
|
164
|
-
|
|
161
|
+
hasher,
|
|
165
162
|
ident,
|
|
166
|
-
event=short_state.context.event,
|
|
167
|
-
update=short_state.context.context.raw_update,
|
|
168
163
|
force=True,
|
|
169
164
|
)
|
|
170
|
-
elif short_state.creation_date + absolutely_dead_time < now:
|
|
171
|
-
short_state.cancel()
|
|
172
|
-
del self.storage[view_name][short_state.key]
|
|
173
165
|
|
|
174
166
|
|
|
175
167
|
async def clear_wm_storage_worker(
|
|
176
168
|
wm: WaiterMachine,
|
|
177
|
-
dp: "Dispatch",
|
|
178
169
|
interval_seconds: int = 60,
|
|
179
|
-
absolutely_dead_time: datetime.timedelta = WEEK,
|
|
180
170
|
) -> typing.NoReturn:
|
|
181
171
|
while True:
|
|
182
|
-
await wm.clear_storage(
|
|
183
|
-
views=[view for view in dp.get_views().values() if isinstance(view, ABCStateView)],
|
|
184
|
-
absolutely_dead_time=absolutely_dead_time,
|
|
185
|
-
)
|
|
172
|
+
await wm.clear_storage()
|
|
186
173
|
await asyncio.sleep(interval_seconds)
|
|
187
174
|
|
|
188
175
|
|
|
@@ -5,8 +5,11 @@ from telegrinder.bot.cute_types.base import BaseCute
|
|
|
5
5
|
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
|
-
from telegrinder.bot.dispatch.
|
|
8
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
9
9
|
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
|
|
10
|
+
from telegrinder.modules import logger
|
|
11
|
+
|
|
12
|
+
from .hasher import Hasher
|
|
10
13
|
|
|
11
14
|
if typing.TYPE_CHECKING:
|
|
12
15
|
from .machine import WaiterMachine
|
|
@@ -19,27 +22,21 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
19
22
|
def __init__(
|
|
20
23
|
self,
|
|
21
24
|
machine: "WaiterMachine",
|
|
22
|
-
|
|
25
|
+
hasher: Hasher,
|
|
23
26
|
) -> None:
|
|
24
27
|
self.machine = machine
|
|
25
|
-
self.
|
|
28
|
+
self.hasher = hasher
|
|
26
29
|
|
|
27
30
|
async def pre(self, event: EventType, ctx: Context) -> bool:
|
|
28
|
-
if
|
|
29
|
-
raise RuntimeError(
|
|
30
|
-
"WaiterMiddleware cannot be used inside a view which doesn't "
|
|
31
|
-
"provide get_state_key (ABCStateView interface)."
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
view_name = self.view.__class__.__name__
|
|
35
|
-
if view_name not in self.machine.storage:
|
|
31
|
+
if self.hasher not in self.machine.storage:
|
|
36
32
|
return True
|
|
37
33
|
|
|
38
|
-
key = self.
|
|
34
|
+
key = self.hasher.get_hash_from_data_from_event(event)
|
|
39
35
|
if key is None:
|
|
40
|
-
|
|
36
|
+
logger.info(f"Unable to get hash from event with hasher {self.hasher}")
|
|
37
|
+
return True
|
|
41
38
|
|
|
42
|
-
short_state: "ShortState[EventType] | None" = self.machine.storage[
|
|
39
|
+
short_state: "ShortState[EventType] | None" = self.machine.storage[self.hasher].get(key.unwrap())
|
|
43
40
|
if not short_state:
|
|
44
41
|
return True
|
|
45
42
|
|
|
@@ -47,35 +44,24 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
47
44
|
if short_state.context is not None:
|
|
48
45
|
preset_context.update(short_state.context.context)
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ctx.raw_update,
|
|
56
|
-
**preset_context.copy(),
|
|
57
|
-
)
|
|
47
|
+
# Run filter rule
|
|
48
|
+
if short_state.filter and not await check_rule(
|
|
49
|
+
event.ctx_api, short_state.filter, ctx.raw_update, preset_context
|
|
50
|
+
):
|
|
51
|
+
logger.debug("Filter rule {!r} failed", short_state.filter)
|
|
58
52
|
return True
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
if short_state.exit_behaviour is not None and await self.machine.call_behaviour(
|
|
62
|
-
event,
|
|
63
|
-
ctx.raw_update,
|
|
64
|
-
behaviour=short_state.exit_behaviour,
|
|
65
|
-
**preset_context,
|
|
66
|
-
):
|
|
54
|
+
if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
|
|
67
55
|
await self.machine.drop(
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
event,
|
|
71
|
-
ctx.raw_update,
|
|
56
|
+
self.hasher,
|
|
57
|
+
self.hasher.get_data_from_event(event).unwrap(),
|
|
72
58
|
**preset_context.copy(),
|
|
73
59
|
)
|
|
74
60
|
return True
|
|
75
61
|
|
|
76
62
|
handler = FuncHandler(
|
|
77
63
|
self.pass_runtime,
|
|
78
|
-
|
|
64
|
+
[short_state.release] if short_state.release else [],
|
|
79
65
|
dataclass=None,
|
|
80
66
|
preset_context=preset_context,
|
|
81
67
|
)
|
|
@@ -84,13 +70,9 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
84
70
|
if result is True:
|
|
85
71
|
await handler.run(event.api, event, ctx)
|
|
86
72
|
|
|
87
|
-
elif short_state.
|
|
88
|
-
await
|
|
89
|
-
event,
|
|
90
|
-
ctx.raw_update,
|
|
91
|
-
behaviour=short_state.default_behaviour,
|
|
92
|
-
**handler.preset_context,
|
|
93
|
-
)
|
|
73
|
+
elif on_miss := short_state.actions.get("on_miss"): # noqa: SIM102
|
|
74
|
+
if await on_miss.check(event.ctx_api, ctx.raw_update, ctx):
|
|
75
|
+
await on_miss.run(event.ctx_api, event, ctx)
|
|
94
76
|
|
|
95
77
|
return False
|
|
96
78
|
|
|
@@ -3,7 +3,6 @@ import dataclasses
|
|
|
3
3
|
import datetime
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import API
|
|
7
6
|
from telegrinder.bot.cute_types import BaseCute
|
|
8
7
|
from telegrinder.bot.dispatch.context import Context
|
|
9
8
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
@@ -11,7 +10,8 @@ from telegrinder.bot.rules.abc import ABCRule
|
|
|
11
10
|
from telegrinder.model import Model
|
|
12
11
|
|
|
13
12
|
if typing.TYPE_CHECKING:
|
|
14
|
-
from .
|
|
13
|
+
from .actions import WaiterActions
|
|
14
|
+
|
|
15
15
|
|
|
16
16
|
T = typing.TypeVar("T", bound=Model)
|
|
17
17
|
EventModel = typing.TypeVar("EventModel", bound=BaseCute)
|
|
@@ -26,19 +26,19 @@ class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
|
|
|
26
26
|
|
|
27
27
|
@dataclasses.dataclass(slots=True)
|
|
28
28
|
class ShortState(typing.Generic[EventModel]):
|
|
29
|
-
key: "Identificator"
|
|
30
|
-
ctx_api: API
|
|
31
29
|
event: asyncio.Event
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
actions: "WaiterActions"
|
|
31
|
+
|
|
32
|
+
release: ABCRule | None = None
|
|
33
|
+
filter: ABCRule | None = None
|
|
34
|
+
lifetime: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
34
35
|
default=None,
|
|
35
36
|
kw_only=True,
|
|
36
37
|
)
|
|
37
|
-
|
|
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)
|
|
38
|
+
|
|
40
39
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
|
|
41
40
|
creation_date: datetime.datetime = dataclasses.field(init=False)
|
|
41
|
+
|
|
42
42
|
context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
|
|
43
43
|
|
|
44
44
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
|
@@ -5,7 +5,7 @@ import aiohttp
|
|
|
5
5
|
import msgspec
|
|
6
6
|
from fntypes.result import Error, Ok
|
|
7
7
|
|
|
8
|
-
from telegrinder.api import API
|
|
8
|
+
from telegrinder.api.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
|
|
@@ -72,7 +72,10 @@ class Polling(ABCPolling):
|
|
|
72
72
|
async def get_updates(self) -> msgspec.Raw | None:
|
|
73
73
|
raw_updates = await self.api.request_raw(
|
|
74
74
|
"getUpdates",
|
|
75
|
-
{
|
|
75
|
+
{
|
|
76
|
+
"offset": self.offset,
|
|
77
|
+
"allowed_updates": self.allowed_updates,
|
|
78
|
+
},
|
|
76
79
|
)
|
|
77
80
|
match raw_updates:
|
|
78
81
|
case Ok(value):
|
|
@@ -83,7 +83,6 @@ __all__ = (
|
|
|
83
83
|
"InlineQueryMarkup",
|
|
84
84
|
"InlineQueryRule",
|
|
85
85
|
"InlineQueryText",
|
|
86
|
-
"IsInteger",
|
|
87
86
|
"IntegerInRange",
|
|
88
87
|
"InviteLinkByCreator",
|
|
89
88
|
"InviteLinkName",
|
|
@@ -96,6 +95,7 @@ __all__ = (
|
|
|
96
95
|
"IsForward",
|
|
97
96
|
"IsForwardType",
|
|
98
97
|
"IsGroup",
|
|
98
|
+
"IsInteger",
|
|
99
99
|
"IsLanguageCode",
|
|
100
100
|
"IsPremium",
|
|
101
101
|
"IsPrivate",
|
|
@@ -107,13 +107,13 @@ __all__ = (
|
|
|
107
107
|
"Markup",
|
|
108
108
|
"MessageEntities",
|
|
109
109
|
"MessageRule",
|
|
110
|
+
"NodeRule",
|
|
110
111
|
"NotRule",
|
|
111
112
|
"OrRule",
|
|
112
113
|
"Regex",
|
|
113
114
|
"RuleEnum",
|
|
114
115
|
"StartCommand",
|
|
115
|
-
"Text",
|
|
116
|
-
"NodeRule",
|
|
117
116
|
"State",
|
|
118
117
|
"StateMeta",
|
|
118
|
+
"Text",
|
|
119
119
|
)
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -118,12 +118,13 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
118
118
|
ctx: Context,
|
|
119
119
|
node_col: "NodeCollection | None" = None,
|
|
120
120
|
) -> bool:
|
|
121
|
+
bound_check_rule = self.check
|
|
121
122
|
kw = {}
|
|
122
123
|
node_col_values = node_col.values if node_col is not None else {}
|
|
123
|
-
temp_ctx = get_default_args(
|
|
124
|
+
temp_ctx = get_default_args(bound_check_rule) | ctx
|
|
124
125
|
|
|
125
|
-
for i, (k, v) in enumerate(get_annotations(
|
|
126
|
-
if (isinstance(adapted_value, Event) and
|
|
126
|
+
for i, (k, v) in enumerate(get_annotations(bound_check_rule).items()):
|
|
127
|
+
if (isinstance(adapted_value, Event) and i == 0) or ( # First arg is Event
|
|
127
128
|
isinstance(v, type) and isinstance(adapted_value, v)
|
|
128
129
|
):
|
|
129
130
|
kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
|
|
@@ -140,14 +141,14 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
140
141
|
"because it cannot be resolved."
|
|
141
142
|
)
|
|
142
143
|
|
|
143
|
-
return await
|
|
144
|
+
return await bound_check_rule(**kw)
|
|
144
145
|
|
|
145
146
|
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
146
147
|
return self
|
|
147
148
|
|
|
148
149
|
|
|
149
150
|
class AndRule(ABCRule):
|
|
150
|
-
def __init__(self, *rules: ABCRule
|
|
151
|
+
def __init__(self, *rules: ABCRule) -> None:
|
|
151
152
|
self.rules = rules
|
|
152
153
|
|
|
153
154
|
async def check(self, event: Update, ctx: Context) -> bool:
|
telegrinder/bot/rules/integer.py
CHANGED