maxapi-python 2.4.1__py3-none-any.whl → 2.5.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.
- {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +17 -9
- {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/RECORD +74 -45
- {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +5 -1
- pymax/_data/apk_fingerprints.json +272 -0
- pymax/api/auth/service.py +1 -0
- pymax/api/auth/types.py +3 -0
- pymax/api/bots/payloads.py +8 -0
- pymax/api/bots/service.py +13 -1
- pymax/api/chats/enums.py +1 -0
- pymax/api/chats/payloads.py +19 -0
- pymax/api/chats/service.py +34 -0
- pymax/api/facade.py +2 -0
- pymax/api/messages/enums.py +1 -0
- pymax/api/messages/payloads.py +47 -1
- pymax/api/messages/service.py +308 -24
- pymax/api/response.py +1 -1
- pymax/api/stories/__init__.py +2 -0
- pymax/api/stories/enums.py +19 -0
- pymax/api/stories/payloads.py +64 -0
- pymax/api/stories/service.py +198 -0
- pymax/api/uploads/payloads.py +10 -1
- pymax/api/uploads/service.py +94 -7
- pymax/app.py +16 -4
- pymax/auth/qr.py +11 -1
- pymax/base.py +70 -25
- pymax/client.py +0 -1
- pymax/config.py +38 -32
- pymax/di/__init__.py +2 -0
- pymax/di/context.py +18 -0
- pymax/di/resolver.py +38 -0
- pymax/dispatch/__init__.py +7 -0
- pymax/dispatch/dispatcher.py +143 -40
- pymax/dispatch/enums.py +1 -0
- pymax/dispatch/mapping.py +23 -3
- pymax/dispatch/middleware.py +32 -0
- pymax/dispatch/resolvers.py +4 -0
- pymax/dispatch/router.py +86 -34
- pymax/filters/__init__.py +15 -0
- pymax/filters/base.py +218 -0
- pymax/filters/containers/__init__.py +2 -0
- pymax/filters/containers/chat.py +240 -0
- pymax/filters/containers/message.py +147 -0
- pymax/filters/filters.py +211 -0
- pymax/filters/magic.py +13 -0
- pymax/filters/presets.py +88 -0
- pymax/filters/refs.py +56 -0
- pymax/formatting/markdown.py +55 -1
- pymax/fsm/__init__.py +3 -0
- pymax/fsm/context.py +30 -0
- pymax/fsm/protocol.py +14 -0
- pymax/fsm/state.py +70 -0
- pymax/fsm/storage.py +151 -0
- pymax/infra/base.py +2 -0
- pymax/infra/bots.py +20 -0
- pymax/infra/chat.py +28 -0
- pymax/infra/message.py +195 -6
- pymax/infra/stories.py +205 -0
- pymax/integrations/__init__.py +0 -0
- pymax/integrations/dishka.py +109 -0
- pymax/protocol/enums.py +2 -0
- pymax/protocol/tcp/payload.py +0 -2
- pymax/types/domain/__init__.py +23 -1
- pymax/types/domain/attachments/__init__.py +1 -1
- pymax/types/domain/attachments/keyboards/__init__.py +1 -1
- pymax/types/domain/attachments/keyboards/enums.py +12 -0
- pymax/types/domain/attachments/keyboards/inline.py +74 -3
- pymax/types/domain/callback.py +19 -0
- pymax/types/domain/comment.py +24 -0
- pymax/types/domain/message.py +91 -22
- pymax/types/domain/story.py +126 -0
- pymax/types/events/reaction.py +28 -4
- pymax/versions/catalog.py +1 -1
- {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
pymax/dispatch/dispatcher.py
CHANGED
|
@@ -3,11 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import inspect
|
|
5
5
|
from contextlib import suppress
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, get_type_hints
|
|
7
7
|
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from pymax.di import DiContext, DiResolver
|
|
11
|
+
from pymax.filters import Filter
|
|
8
12
|
from pymax.logging import get_logger
|
|
9
13
|
from pymax.protocol import InboundFrame
|
|
10
|
-
from pymax.types import Chat, MessageDeleteEvent
|
|
14
|
+
from pymax.types import Chat, MessageDeleteEvent, StoryPreview
|
|
11
15
|
from pymax.types.domain import Message
|
|
12
16
|
from pymax.types.events import (
|
|
13
17
|
MessageReadEvent,
|
|
@@ -17,7 +21,8 @@ from pymax.types.events import (
|
|
|
17
21
|
)
|
|
18
22
|
|
|
19
23
|
from .enums import EventType
|
|
20
|
-
from .mapping import EventMapper, EventResolver
|
|
24
|
+
from .mapping import DispatchEvent, EventMapper, EventResolver
|
|
25
|
+
from .middleware import Middleware, MiddlewareNext
|
|
21
26
|
from .router import (
|
|
22
27
|
DisconnectCallback,
|
|
23
28
|
DisconnectDecorator,
|
|
@@ -26,7 +31,6 @@ from .router import (
|
|
|
26
31
|
ErrorEntry,
|
|
27
32
|
ErrorScope,
|
|
28
33
|
ErrorSource,
|
|
29
|
-
FilterCallback,
|
|
30
34
|
HandlerCallback,
|
|
31
35
|
HandlerDecorator,
|
|
32
36
|
HandlerEntry,
|
|
@@ -45,16 +49,20 @@ if TYPE_CHECKING:
|
|
|
45
49
|
logger = get_logger(__name__)
|
|
46
50
|
|
|
47
51
|
ClientT = TypeVar("ClientT", bound="BaseClient")
|
|
52
|
+
EventT = TypeVar("EventT", bound=BaseModel)
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
class Dispatcher(Generic[ClientT]):
|
|
51
|
-
def __init__(self, app: App, root_router: Router[ClientT] | None = None) -> None:
|
|
56
|
+
def __init__(self, app: App[ClientT], root_router: Router[ClientT] | None = None) -> None:
|
|
52
57
|
self.root_router: Router[ClientT] = root_router or Router()
|
|
58
|
+
self.middlewares: list[Middleware[ClientT]] = self.root_router.middlewares
|
|
53
59
|
self.internal_router: Router[ClientT] = Router()
|
|
54
60
|
self.resolver = EventResolver()
|
|
55
61
|
self.mapper = EventMapper(app)
|
|
56
62
|
self.startup_tasks: list[asyncio.Task[Any]] = []
|
|
57
63
|
self.client: ClientT | None = None
|
|
64
|
+
self.app = app
|
|
65
|
+
self.di_resolver = DiResolver()
|
|
58
66
|
|
|
59
67
|
def bind_client(self, client: ClientT) -> None:
|
|
60
68
|
self.client = client
|
|
@@ -67,10 +75,16 @@ class Dispatcher(Generic[ClientT]):
|
|
|
67
75
|
len(router.children),
|
|
68
76
|
)
|
|
69
77
|
|
|
78
|
+
def add_middleware(self, middleware: Middleware) -> None:
|
|
79
|
+
if middleware not in self.middlewares: # TODO: make better comparison or just remove it
|
|
80
|
+
self.middlewares.append(middleware)
|
|
81
|
+
else:
|
|
82
|
+
logger.warning(f"Middleware {middleware.__class__.__name__} is already registered")
|
|
83
|
+
|
|
70
84
|
def on_internal(
|
|
71
85
|
self,
|
|
72
86
|
event: EventType,
|
|
73
|
-
*filters:
|
|
87
|
+
*filters: Filter[Any, ClientT],
|
|
74
88
|
) -> HandlerDecorator[Any, ClientT]:
|
|
75
89
|
logger.debug(
|
|
76
90
|
"registering internal handler event=%s filters=%s",
|
|
@@ -82,7 +96,7 @@ class Dispatcher(Generic[ClientT]):
|
|
|
82
96
|
def on(
|
|
83
97
|
self,
|
|
84
98
|
event: EventType,
|
|
85
|
-
*filters:
|
|
99
|
+
*filters: Filter[Any, ClientT],
|
|
86
100
|
) -> HandlerDecorator[Any, ClientT]:
|
|
87
101
|
logger.debug("registering handler event=%s filters=%s", event, len(filters))
|
|
88
102
|
return self.root_router.on(event, *filters)
|
|
@@ -96,57 +110,63 @@ class Dispatcher(Generic[ClientT]):
|
|
|
96
110
|
|
|
97
111
|
def on_message(
|
|
98
112
|
self,
|
|
99
|
-
*filters:
|
|
113
|
+
*filters: Filter[Message, ClientT],
|
|
100
114
|
) -> HandlerDecorator[Message, ClientT]:
|
|
101
115
|
logger.debug("registering message handler filters=%s", len(filters))
|
|
102
116
|
return self.root_router.on_message(*filters)
|
|
103
117
|
|
|
104
118
|
def on_message_edit(
|
|
105
119
|
self,
|
|
106
|
-
*filters:
|
|
120
|
+
*filters: Filter[Message, ClientT],
|
|
107
121
|
) -> HandlerDecorator[Message, ClientT]:
|
|
108
122
|
logger.debug("registering message edit handler filters=%s", len(filters))
|
|
109
123
|
return self.root_router.on_message_edit(*filters)
|
|
110
124
|
|
|
111
125
|
def on_message_delete(
|
|
112
126
|
self,
|
|
113
|
-
*filters:
|
|
127
|
+
*filters: Filter[MessageDeleteEvent, ClientT],
|
|
114
128
|
) -> HandlerDecorator[MessageDeleteEvent, ClientT]:
|
|
115
129
|
return self.root_router.on_message_delete(*filters)
|
|
116
130
|
|
|
117
131
|
def on_message_read(
|
|
118
132
|
self,
|
|
119
|
-
*filters:
|
|
133
|
+
*filters: Filter[MessageReadEvent, ClientT],
|
|
120
134
|
) -> HandlerDecorator[MessageReadEvent, ClientT]:
|
|
121
135
|
return self.root_router.on_message_read(*filters)
|
|
122
136
|
|
|
123
137
|
def on_typing(
|
|
124
138
|
self,
|
|
125
|
-
*filters:
|
|
139
|
+
*filters: Filter[TypingEvent, ClientT],
|
|
126
140
|
) -> HandlerDecorator[TypingEvent, ClientT]:
|
|
127
141
|
return self.root_router.on_typing(*filters)
|
|
128
142
|
|
|
129
143
|
def on_presence(
|
|
130
144
|
self,
|
|
131
|
-
*filters:
|
|
145
|
+
*filters: Filter[PresenceEvent, ClientT],
|
|
132
146
|
) -> HandlerDecorator[PresenceEvent, ClientT]:
|
|
133
147
|
return self.root_router.on_presence(*filters)
|
|
134
148
|
|
|
135
149
|
def on_reaction_update(
|
|
136
150
|
self,
|
|
137
|
-
*filters:
|
|
151
|
+
*filters: Filter[ReactionUpdateEvent, ClientT],
|
|
138
152
|
) -> HandlerDecorator[ReactionUpdateEvent, ClientT]:
|
|
139
153
|
return self.root_router.on_reaction_update(*filters)
|
|
140
154
|
|
|
141
155
|
def on_chat_update(
|
|
142
156
|
self,
|
|
143
|
-
*filters:
|
|
157
|
+
*filters: Filter[Chat, ClientT],
|
|
144
158
|
) -> HandlerDecorator[Chat, ClientT]:
|
|
145
159
|
return self.root_router.on_chat_update(*filters)
|
|
146
160
|
|
|
161
|
+
def on_story_update(
|
|
162
|
+
self,
|
|
163
|
+
*filters: Filter[StoryPreview, ClientT],
|
|
164
|
+
) -> HandlerDecorator[StoryPreview, ClientT]:
|
|
165
|
+
return self.root_router.on_story_update(*filters)
|
|
166
|
+
|
|
147
167
|
def on_raw(
|
|
148
168
|
self,
|
|
149
|
-
*filters:
|
|
169
|
+
*filters: Filter[InboundFrame, ClientT],
|
|
150
170
|
) -> HandlerDecorator[InboundFrame, ClientT]:
|
|
151
171
|
return self.root_router.on_raw(*filters)
|
|
152
172
|
|
|
@@ -234,27 +254,41 @@ class Dispatcher(Generic[ClientT]):
|
|
|
234
254
|
self.startup_tasks = []
|
|
235
255
|
|
|
236
256
|
async def dispatch(self, frame: InboundFrame) -> None:
|
|
237
|
-
|
|
257
|
+
try:
|
|
258
|
+
event_type = self.resolver.resolve(frame)
|
|
238
259
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
await self._dispatch_to_router(self.internal_router, event_type, event)
|
|
243
|
-
await self._dispatch_to_router(self.root_router, event_type, event)
|
|
244
|
-
else:
|
|
245
|
-
logger.debug(
|
|
246
|
-
"dispatching raw event only opcode=%s cmd=%s",
|
|
247
|
-
frame.opcode,
|
|
248
|
-
frame.cmd,
|
|
249
|
-
)
|
|
260
|
+
if event_type is not None:
|
|
261
|
+
logger.debug("dispatching event type=%s", event_type)
|
|
262
|
+
event = self.mapper.map(event_type, frame)
|
|
250
263
|
|
|
251
|
-
|
|
264
|
+
if event:
|
|
265
|
+
await self._dispatch_to_router(
|
|
266
|
+
self.internal_router,
|
|
267
|
+
event_type,
|
|
268
|
+
event,
|
|
269
|
+
apply_middleware=False,
|
|
270
|
+
)
|
|
271
|
+
if self.client:
|
|
272
|
+
async with self.app.fsm_storage.isolate(self.client, event):
|
|
273
|
+
await self._dispatch_to_router(self.root_router, event_type, event)
|
|
274
|
+
else:
|
|
275
|
+
await self._dispatch_to_router(self.root_router, event_type, event)
|
|
276
|
+
else:
|
|
277
|
+
logger.debug(
|
|
278
|
+
"dispatching raw event only opcode=%s cmd=%s",
|
|
279
|
+
frame.opcode,
|
|
280
|
+
frame.cmd,
|
|
281
|
+
)
|
|
282
|
+
finally:
|
|
283
|
+
await self._dispatch_to_router(self.root_router, EventType.RAW, frame)
|
|
252
284
|
|
|
253
285
|
async def _dispatch_to_router(
|
|
254
286
|
self,
|
|
255
287
|
router: Router[ClientT],
|
|
256
288
|
event_type: EventType,
|
|
257
|
-
event:
|
|
289
|
+
event: DispatchEvent,
|
|
290
|
+
*,
|
|
291
|
+
apply_middleware: bool = True,
|
|
258
292
|
) -> None:
|
|
259
293
|
for entry in router.handlers.get(event_type, []):
|
|
260
294
|
try:
|
|
@@ -264,24 +298,67 @@ class Dispatcher(Generic[ClientT]):
|
|
|
264
298
|
event_type,
|
|
265
299
|
_callback_name(entry.callback),
|
|
266
300
|
)
|
|
267
|
-
|
|
268
|
-
|
|
301
|
+
if apply_middleware:
|
|
302
|
+
await self._call_with_middleware(entry.callback, event)
|
|
303
|
+
else:
|
|
304
|
+
await self._call(entry.callback, event)
|
|
305
|
+
break
|
|
306
|
+
except Exception as e:
|
|
269
307
|
handled = await self.emit_error(e, event_type, event, router, entry)
|
|
270
308
|
if not handled:
|
|
271
309
|
raise
|
|
272
310
|
|
|
273
311
|
for child in router.children:
|
|
274
|
-
await self._dispatch_to_router(
|
|
312
|
+
await self._dispatch_to_router(
|
|
313
|
+
child,
|
|
314
|
+
event_type,
|
|
315
|
+
event,
|
|
316
|
+
apply_middleware=apply_middleware,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
async def _call_with_middleware(
|
|
320
|
+
self,
|
|
321
|
+
callback: HandlerCallback[DispatchEvent, ClientT],
|
|
322
|
+
event: DispatchEvent,
|
|
323
|
+
) -> Any:
|
|
324
|
+
|
|
325
|
+
async def call_handler(event: DispatchEvent) -> Any:
|
|
326
|
+
return await self._call(callback, event)
|
|
327
|
+
|
|
328
|
+
call_next: MiddlewareNext = call_handler
|
|
329
|
+
|
|
330
|
+
for middleware in reversed(self.middlewares):
|
|
331
|
+
next_ = call_next
|
|
332
|
+
|
|
333
|
+
async def wrapped(
|
|
334
|
+
event: DispatchEvent,
|
|
335
|
+
middleware: Middleware[ClientT] = middleware,
|
|
336
|
+
next_: MiddlewareNext = next_,
|
|
337
|
+
) -> Any:
|
|
338
|
+
if self.client is None:
|
|
339
|
+
raise RuntimeError("client is not bound")
|
|
340
|
+
|
|
341
|
+
return await middleware(
|
|
342
|
+
self.client,
|
|
343
|
+
event,
|
|
344
|
+
next_,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
call_next = wrapped
|
|
348
|
+
|
|
349
|
+
return await call_next(event)
|
|
275
350
|
|
|
276
351
|
async def _matches(
|
|
277
352
|
self,
|
|
278
|
-
entry: HandlerEntry[
|
|
279
|
-
event:
|
|
353
|
+
entry: HandlerEntry[DispatchEvent, ClientT],
|
|
354
|
+
event: DispatchEvent,
|
|
280
355
|
) -> bool:
|
|
281
356
|
for flt in entry.filters:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
357
|
+
if not self.client:
|
|
358
|
+
return False
|
|
359
|
+
filter_match = flt(event, self.client)
|
|
360
|
+
result = await flt.resolve_filter_result(filter_match)
|
|
361
|
+
|
|
285
362
|
if not result:
|
|
286
363
|
logger.debug(
|
|
287
364
|
"handler skipped by filter callback=%s",
|
|
@@ -290,11 +367,37 @@ class Dispatcher(Generic[ClientT]):
|
|
|
290
367
|
return False
|
|
291
368
|
return True
|
|
292
369
|
|
|
293
|
-
async def _call(
|
|
370
|
+
async def _call(
|
|
371
|
+
self, callback: HandlerCallback[DispatchEvent, ClientT], event: DispatchEvent
|
|
372
|
+
) -> Any:
|
|
294
373
|
if self.client is None:
|
|
295
374
|
raise RuntimeError("client is not bound")
|
|
296
375
|
|
|
297
|
-
|
|
376
|
+
signature = inspect.signature(callback)
|
|
377
|
+
bound = signature.bind_partial(event, self.client)
|
|
378
|
+
bound.apply_defaults()
|
|
379
|
+
|
|
380
|
+
missing_parameters = [
|
|
381
|
+
(name, parameter)
|
|
382
|
+
for name, parameter in signature.parameters.items()
|
|
383
|
+
if name not in bound.arguments
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
if missing_parameters:
|
|
387
|
+
hints = get_type_hints(callback, include_extras=True)
|
|
388
|
+
|
|
389
|
+
for name, parameter in missing_parameters:
|
|
390
|
+
annotation = hints.get(name, parameter.annotation)
|
|
391
|
+
|
|
392
|
+
ctx = DiContext(self.app, event, self.client)
|
|
393
|
+
result = await self.di_resolver.resolve(annotation, ctx)
|
|
394
|
+
|
|
395
|
+
if result is None:
|
|
396
|
+
raise ValueError(f"No resolver was found for parameter: `{parameter}`")
|
|
397
|
+
|
|
398
|
+
bound.arguments[name] = result
|
|
399
|
+
|
|
400
|
+
result = callback(*bound.args, **bound.kwargs)
|
|
298
401
|
|
|
299
402
|
if inspect.isawaitable(result):
|
|
300
403
|
return await result
|
pymax/dispatch/enums.py
CHANGED
pymax/dispatch/mapping.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING, TypeAlias
|
|
5
5
|
|
|
6
6
|
from pymax.api.binding import bind_api_model
|
|
7
7
|
from pymax.protocol import InboundFrame, Opcode
|
|
8
8
|
from pymax.protocol.enums import Command
|
|
9
9
|
from pymax.types import AudioUploadSignal, Chat, MessageDeleteEvent
|
|
10
|
-
from pymax.types.domain import Message
|
|
10
|
+
from pymax.types.domain import Message, StoryPreview
|
|
11
11
|
from pymax.types.events import (
|
|
12
12
|
FileUploadSignal,
|
|
13
13
|
MessageReadEvent,
|
|
@@ -26,6 +26,7 @@ from .resolvers import (
|
|
|
26
26
|
resolve_message_read,
|
|
27
27
|
resolve_presence,
|
|
28
28
|
resolve_reaction_update,
|
|
29
|
+
resolve_story_update,
|
|
29
30
|
resolve_typing,
|
|
30
31
|
)
|
|
31
32
|
|
|
@@ -33,6 +34,21 @@ if TYPE_CHECKING:
|
|
|
33
34
|
from pymax.app import App
|
|
34
35
|
|
|
35
36
|
Resolver = Callable[[InboundFrame], EventType | None]
|
|
37
|
+
DispatchEvent: TypeAlias = (
|
|
38
|
+
Message
|
|
39
|
+
| Chat
|
|
40
|
+
| MessageDeleteEvent
|
|
41
|
+
| MessageReadEvent
|
|
42
|
+
| TypingEvent
|
|
43
|
+
| PresenceEvent
|
|
44
|
+
| ReactionUpdateEvent
|
|
45
|
+
| StoryPreview
|
|
46
|
+
| VideoUploadSignal
|
|
47
|
+
| FileUploadSignal
|
|
48
|
+
| AudioUploadSignal
|
|
49
|
+
| InboundFrame
|
|
50
|
+
)
|
|
51
|
+
|
|
36
52
|
|
|
37
53
|
EVENT_MAP: dict[Opcode, Resolver] = {
|
|
38
54
|
Opcode.NOTIF_MESSAGE: resolve_message,
|
|
@@ -44,6 +60,8 @@ EVENT_MAP: dict[Opcode, Resolver] = {
|
|
|
44
60
|
Opcode.NOTIF_MARK: resolve_message_read,
|
|
45
61
|
Opcode.NOTIF_PRESENCE: resolve_presence,
|
|
46
62
|
Opcode.NOTIF_MSG_REACTIONS_CHANGED: resolve_reaction_update,
|
|
63
|
+
Opcode.NOTIF_MSG_YOU_REACTED: resolve_reaction_update,
|
|
64
|
+
Opcode.NOTIF_STORIES_UPDATE: resolve_story_update,
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
|
|
@@ -68,7 +86,7 @@ class EventMapper:
|
|
|
68
86
|
def __init__(self, app: App) -> None:
|
|
69
87
|
self.app = app
|
|
70
88
|
|
|
71
|
-
def map(self, event_type: EventType, frame: InboundFrame):
|
|
89
|
+
def map(self, event_type: EventType, frame: InboundFrame) -> DispatchEvent | None:
|
|
72
90
|
if frame.cmd != Command.REQUEST:
|
|
73
91
|
return None
|
|
74
92
|
|
|
@@ -96,6 +114,8 @@ class EventMapper:
|
|
|
96
114
|
return PresenceEvent.model_validate(frame.payload)
|
|
97
115
|
elif event_type == EventType.REACTION_UPDATE:
|
|
98
116
|
return ReactionUpdateEvent.model_validate(frame.payload)
|
|
117
|
+
elif event_type == EventType.STORY_UPDATE:
|
|
118
|
+
return StoryPreview.model_validate(frame.payload["storiesPreview"])
|
|
99
119
|
elif event_type == EventType.VIDEO_READY:
|
|
100
120
|
return VideoUploadSignal.model_validate(frame.payload)
|
|
101
121
|
elif event_type == EventType.FILE_READY:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeAlias, TypeVar
|
|
5
|
+
|
|
6
|
+
from .mapping import DispatchEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pymax.base import BaseClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ClientT_contra = TypeVar("ClientT_contra", bound="BaseClient", contravariant=True)
|
|
13
|
+
|
|
14
|
+
MiddlewareNext: TypeAlias = Callable[[DispatchEvent], Awaitable[Any]]
|
|
15
|
+
"""Следующий шаг middleware-цепочки, вызываемый как ``await call_next(event)``."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Middleware(Protocol, Generic[ClientT_contra]):
|
|
19
|
+
"""Асинхронная обертка выбранного обработчика события.
|
|
20
|
+
|
|
21
|
+
Реализуйте ``async __call__(client, event, call_next)`` и зарегистрируйте
|
|
22
|
+
экземпляр через ``client.add_middleware()``. Middleware запускаются после
|
|
23
|
+
фильтров в порядке регистрации для обработчиков всех подключенных роутеров.
|
|
24
|
+
Вызов ``await call_next(event)`` продолжает цепочку и возвращает ее результат.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
async def __call__(
|
|
28
|
+
self,
|
|
29
|
+
client: ClientT_contra,
|
|
30
|
+
event: DispatchEvent,
|
|
31
|
+
call_next: MiddlewareNext,
|
|
32
|
+
) -> Any: ...
|
pymax/dispatch/resolvers.py
CHANGED
|
@@ -36,6 +36,10 @@ def resolve_reaction_update(_: InboundFrame) -> EventType | None:
|
|
|
36
36
|
return EventType.REACTION_UPDATE
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def resolve_story_update(_: InboundFrame) -> EventType | None:
|
|
40
|
+
return EventType.STORY_UPDATE
|
|
41
|
+
|
|
42
|
+
|
|
39
43
|
def resolve_attach(frame: InboundFrame) -> EventType | None:
|
|
40
44
|
try:
|
|
41
45
|
FileUploadSignal.model_validate(frame.payload)
|