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/base.py
CHANGED
|
@@ -4,9 +4,10 @@ import asyncio
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
6
6
|
|
|
7
|
-
from pymax.dispatch import ErrorScope, Router
|
|
7
|
+
from pymax.dispatch import ErrorScope, Middleware, Router
|
|
8
8
|
from pymax.dispatch.router import DisconnectDecorator, ErrorDecorator
|
|
9
9
|
from pymax.exceptions import ApiError
|
|
10
|
+
from pymax.filters import Filter
|
|
10
11
|
from pymax.fingerprint.models import ApkBuildFingerprint
|
|
11
12
|
from pymax.infra import BaseMixin
|
|
12
13
|
from pymax.logging import get_logger
|
|
@@ -19,7 +20,6 @@ if TYPE_CHECKING:
|
|
|
19
20
|
from pymax.auth import AuthFlow
|
|
20
21
|
from pymax.connection import ConnectionManager
|
|
21
22
|
from pymax.dispatch.router import (
|
|
22
|
-
FilterCallback,
|
|
23
23
|
HandlerDecorator,
|
|
24
24
|
StartDecorator,
|
|
25
25
|
)
|
|
@@ -30,6 +30,7 @@ if TYPE_CHECKING:
|
|
|
30
30
|
MessageReadEvent,
|
|
31
31
|
PresenceEvent,
|
|
32
32
|
ReactionUpdateEvent,
|
|
33
|
+
StoryPreview,
|
|
33
34
|
TypingEvent,
|
|
34
35
|
User,
|
|
35
36
|
)
|
|
@@ -125,7 +126,6 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
125
126
|
|
|
126
127
|
@abstractmethod
|
|
127
128
|
def _build_connection(self) -> ConnectionManager:
|
|
128
|
-
|
|
129
129
|
raise NotImplementedError
|
|
130
130
|
|
|
131
131
|
@abstractmethod
|
|
@@ -140,7 +140,10 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
140
140
|
return
|
|
141
141
|
|
|
142
142
|
self._connection = self._build_connection()
|
|
143
|
-
self.
|
|
143
|
+
if self.__app is not None:
|
|
144
|
+
self._app.bind_connection(self._connection)
|
|
145
|
+
else:
|
|
146
|
+
self._app = self._build_app()
|
|
144
147
|
|
|
145
148
|
def _init_runtime(self: ClientT, *, auth_flow: AuthFlow) -> None: # noqa: PYI019
|
|
146
149
|
self._connection = self._build_connection()
|
|
@@ -158,14 +161,16 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
158
161
|
app.dispatcher.bind_client(self)
|
|
159
162
|
return app
|
|
160
163
|
|
|
161
|
-
def
|
|
164
|
+
def _reset_connection(self: ClientT) -> None: # noqa: PYI019
|
|
162
165
|
self._connection = self._build_connection()
|
|
163
|
-
self._app
|
|
166
|
+
self._app.bind_connection(self._connection)
|
|
167
|
+
# self._app = self._build_app()
|
|
164
168
|
|
|
165
169
|
async def connect(self: ClientT) -> None: # noqa: PYI019
|
|
166
|
-
"""Подключает клиента один раз и
|
|
170
|
+
"""Подключает клиента один раз и запускает ``on_start``-обработчики.
|
|
167
171
|
|
|
168
|
-
|
|
172
|
+
Обработчики старта выполняются в отдельных задачах; метод не ждет
|
|
173
|
+
их завершения. После возврата соединение остается открытым. В отличие от
|
|
169
174
|
:meth:`start`, метод не ожидает закрытия соединения и не запускает
|
|
170
175
|
reconnect loop; завершите работу через :meth:`stop` или :meth:`close`.
|
|
171
176
|
"""
|
|
@@ -181,6 +186,9 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
181
186
|
await self.close()
|
|
182
187
|
raise
|
|
183
188
|
except (ConnectionError, EOFError, OSError, TimeoutError, ApiError) as e:
|
|
189
|
+
if self.__app is None:
|
|
190
|
+
raise
|
|
191
|
+
|
|
184
192
|
await self.close()
|
|
185
193
|
|
|
186
194
|
await self._app.dispatcher.emit_disconnect(
|
|
@@ -189,13 +197,18 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
189
197
|
self.extra_config.reconnect_delay,
|
|
190
198
|
)
|
|
191
199
|
raise
|
|
200
|
+
except Exception:
|
|
201
|
+
await self.close()
|
|
202
|
+
raise
|
|
192
203
|
|
|
193
204
|
async def start(self: ClientT) -> None: # noqa: PYI019
|
|
194
205
|
"""Запускает клиента и слушает события до закрытия соединения.
|
|
195
206
|
|
|
196
207
|
При сетевых ошибках повторяет подключение, если ``reconnect=True``.
|
|
197
208
|
Отозванный login token автоматически сбрасывается при ``relogin=True``.
|
|
209
|
+
Если новый token тоже отклонен, ошибка передается вызывающему коду.
|
|
198
210
|
"""
|
|
211
|
+
relogin_attempted = False
|
|
199
212
|
while True:
|
|
200
213
|
try:
|
|
201
214
|
await self._ensure_runtime()
|
|
@@ -204,26 +217,38 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
204
217
|
await self.close()
|
|
205
218
|
return
|
|
206
219
|
|
|
220
|
+
relogin_attempted = False
|
|
207
221
|
await self._app.dispatcher.emit_start(self)
|
|
208
222
|
await self._connection.wait_closed()
|
|
209
223
|
except asyncio.CancelledError:
|
|
210
224
|
await self.close()
|
|
211
225
|
raise
|
|
212
226
|
except ApiError as e:
|
|
213
|
-
if
|
|
227
|
+
if (
|
|
228
|
+
not self._app._is_invalid_login_token_error(e)
|
|
229
|
+
or not self.extra_config.relogin
|
|
230
|
+
or relogin_attempted
|
|
231
|
+
):
|
|
214
232
|
await self.close()
|
|
215
233
|
raise
|
|
216
234
|
|
|
217
235
|
logger.warning("login token was revoked; starting authentication again")
|
|
218
|
-
|
|
219
|
-
|
|
236
|
+
relogin_attempted = True
|
|
237
|
+
await self.relogin(start=False)
|
|
220
238
|
except (
|
|
221
239
|
ConnectionError,
|
|
222
240
|
EOFError,
|
|
223
241
|
OSError,
|
|
224
242
|
TimeoutError,
|
|
225
243
|
) as e:
|
|
226
|
-
|
|
244
|
+
if self.__app is None:
|
|
245
|
+
raise
|
|
246
|
+
|
|
247
|
+
if self.extra_config.reconnect:
|
|
248
|
+
await self._app.disconnect()
|
|
249
|
+
else:
|
|
250
|
+
await self.close()
|
|
251
|
+
|
|
227
252
|
await self._app.dispatcher.emit_disconnect(
|
|
228
253
|
e,
|
|
229
254
|
self.extra_config.reconnect,
|
|
@@ -239,7 +264,7 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
239
264
|
exc_info=True,
|
|
240
265
|
)
|
|
241
266
|
await asyncio.sleep(self.extra_config.reconnect_delay)
|
|
242
|
-
self.
|
|
267
|
+
self._reset_connection()
|
|
243
268
|
except Exception:
|
|
244
269
|
await self.close()
|
|
245
270
|
raise
|
|
@@ -270,63 +295,70 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
270
295
|
|
|
271
296
|
def on_message(
|
|
272
297
|
self,
|
|
273
|
-
*filters:
|
|
298
|
+
*filters: Filter[Message, ClientT],
|
|
274
299
|
) -> HandlerDecorator[Message, ClientT]:
|
|
275
300
|
"""Регистрирует обработчик новых сообщений."""
|
|
276
301
|
return self._router.on_message(*filters)
|
|
277
302
|
|
|
278
303
|
def on_message_edit(
|
|
279
304
|
self,
|
|
280
|
-
*filters:
|
|
305
|
+
*filters: Filter[Message, ClientT],
|
|
281
306
|
) -> HandlerDecorator[Message, ClientT]:
|
|
282
307
|
"""Регистрирует обработчик редактирования сообщений."""
|
|
283
308
|
return self._router.on_message_edit(*filters)
|
|
284
309
|
|
|
285
310
|
def on_message_delete(
|
|
286
311
|
self,
|
|
287
|
-
*filters:
|
|
312
|
+
*filters: Filter[MessageDeleteEvent, ClientT],
|
|
288
313
|
) -> HandlerDecorator[MessageDeleteEvent, ClientT]:
|
|
289
314
|
"""Регистрирует обработчик удаления сообщений."""
|
|
290
315
|
return self._router.on_message_delete(*filters)
|
|
291
316
|
|
|
292
317
|
def on_message_read(
|
|
293
318
|
self,
|
|
294
|
-
*filters:
|
|
319
|
+
*filters: Filter[MessageReadEvent, ClientT],
|
|
295
320
|
) -> HandlerDecorator[MessageReadEvent, ClientT]:
|
|
296
321
|
"""Регистрирует обработчик изменения отметки прочтения."""
|
|
297
322
|
return self._router.on_message_read(*filters)
|
|
298
323
|
|
|
299
324
|
def on_typing(
|
|
300
325
|
self,
|
|
301
|
-
*filters:
|
|
326
|
+
*filters: Filter[TypingEvent, ClientT],
|
|
302
327
|
) -> HandlerDecorator[TypingEvent, ClientT]:
|
|
303
328
|
"""Регистрирует обработчик набора текста."""
|
|
304
329
|
return self._router.on_typing(*filters)
|
|
305
330
|
|
|
306
331
|
def on_presence(
|
|
307
332
|
self,
|
|
308
|
-
*filters:
|
|
333
|
+
*filters: Filter[PresenceEvent, ClientT],
|
|
309
334
|
) -> HandlerDecorator[PresenceEvent, ClientT]:
|
|
310
335
|
"""Регистрирует обработчик изменения присутствия пользователя."""
|
|
311
336
|
return self._router.on_presence(*filters)
|
|
312
337
|
|
|
313
338
|
def on_reaction_update(
|
|
314
339
|
self,
|
|
315
|
-
*filters:
|
|
340
|
+
*filters: Filter[ReactionUpdateEvent, ClientT],
|
|
316
341
|
) -> HandlerDecorator[ReactionUpdateEvent, ClientT]:
|
|
317
342
|
"""Регистрирует обработчик обновления реакций сообщения."""
|
|
318
343
|
return self._router.on_reaction_update(*filters)
|
|
319
344
|
|
|
320
345
|
def on_chat_update(
|
|
321
346
|
self,
|
|
322
|
-
*filters:
|
|
347
|
+
*filters: Filter[Chat, ClientT],
|
|
323
348
|
) -> HandlerDecorator[Chat, ClientT]:
|
|
324
349
|
"""Регистрирует обработчик обновления чата."""
|
|
325
350
|
return self._router.on_chat_update(*filters)
|
|
326
351
|
|
|
352
|
+
def on_story_update(
|
|
353
|
+
self,
|
|
354
|
+
*filters: Filter[StoryPreview, ClientT],
|
|
355
|
+
) -> HandlerDecorator[StoryPreview, ClientT]:
|
|
356
|
+
"""Регистрирует обработчик обновления превью сторис владельца."""
|
|
357
|
+
return self._router.on_story_update(*filters)
|
|
358
|
+
|
|
327
359
|
def on_raw(
|
|
328
360
|
self,
|
|
329
|
-
*filters:
|
|
361
|
+
*filters: Filter[InboundFrame, ClientT],
|
|
330
362
|
) -> HandlerDecorator[InboundFrame, ClientT]:
|
|
331
363
|
"""Регистрирует обработчик исходных входящих frame-ов."""
|
|
332
364
|
return self._router.on_raw(*filters)
|
|
@@ -343,12 +375,23 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
343
375
|
"""Подключает дочерний router к root router клиента."""
|
|
344
376
|
self._router.include_router(router)
|
|
345
377
|
|
|
378
|
+
def add_middleware(self, middleware: Middleware[ClientT]) -> None:
|
|
379
|
+
"""Добавляет middleware в цепочку обработчиков клиента.
|
|
380
|
+
|
|
381
|
+
Middleware вызываются в порядке регистрации после проверки фильтров.
|
|
382
|
+
Регистрация доступна до запуска клиента и сохраняется при reconnect.
|
|
383
|
+
"""
|
|
384
|
+
if middleware not in self._router.middlewares:
|
|
385
|
+
self._router.middlewares.append(middleware)
|
|
386
|
+
else:
|
|
387
|
+
logger.warning(f"Middleware {middleware.__class__.__name__} is already registered")
|
|
388
|
+
|
|
346
389
|
async def relogin(self: ClientT, drop_config_token: bool = True, start: bool = True) -> None: # noqa: PYI019
|
|
347
390
|
"""Удаляет текущую локальную сессию и запускает авторизацию заново.
|
|
348
391
|
|
|
349
392
|
Args:
|
|
350
393
|
drop_config_token: Сбросить token, переданный через ``ExtraConfig``.
|
|
351
|
-
start: Сразу запустить клиента после сброса
|
|
394
|
+
start: Сразу запустить клиента после сброса сессии.
|
|
352
395
|
"""
|
|
353
396
|
store = self._app.store
|
|
354
397
|
session = self._app.session
|
|
@@ -357,13 +400,15 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
357
400
|
raise RuntimeError("Cannot relogin before session is loaded")
|
|
358
401
|
|
|
359
402
|
await store.delete_session(session.token)
|
|
360
|
-
await self.
|
|
403
|
+
await self._app.disconnect()
|
|
361
404
|
|
|
362
405
|
if drop_config_token:
|
|
363
406
|
self.extra_config.token = None
|
|
364
407
|
self._config.token = None
|
|
365
408
|
|
|
366
|
-
self.
|
|
409
|
+
self._reset_connection()
|
|
410
|
+
|
|
411
|
+
self._app.session = None
|
|
367
412
|
|
|
368
413
|
if start:
|
|
369
414
|
await self.start()
|
pymax/client.py
CHANGED
|
@@ -63,7 +63,6 @@ class Client(BaseClient["Client"]):
|
|
|
63
63
|
app_version: str = VersionCatalog.RECOMMENDED_APP_VERSION,
|
|
64
64
|
catalog: VersionCatalog | None = None,
|
|
65
65
|
) -> None:
|
|
66
|
-
|
|
67
66
|
self.phone: str = phone
|
|
68
67
|
self.extra_config: ExtraConfig = extra_config or ExtraConfig()
|
|
69
68
|
self.session_name: str = session_name
|
pymax/config.py
CHANGED
|
@@ -57,42 +57,48 @@ APP_VERSIONS: tuple[tuple[str, int], ...] = (
|
|
|
57
57
|
# ("26.9.1", 6643),
|
|
58
58
|
)
|
|
59
59
|
ANDROID_DEVICES: tuple[tuple[str, str, str, str], ...] = (
|
|
60
|
-
("Samsung SM-A525F", "Android 13", "405dpi 405dpi 1080x2400", "arm64-v8a"),
|
|
61
|
-
("Samsung SM-A536B", "Android 14", "405dpi 405dpi 1080x2400", "arm64-v8a"),
|
|
62
|
-
("Samsung SM-A546E", "Android 14", "405dpi 405dpi 1080x2340", "arm64-v8a"),
|
|
63
|
-
("Samsung SM-G991B", "Android 14", "421dpi 421dpi 1080x2400", "arm64-v8a"),
|
|
64
|
-
("Samsung SM-G998B", "Android 13", "515dpi 515dpi 1440x3200", "arm64-v8a"),
|
|
65
|
-
("Samsung SM-S901B", "Android 14", "425dpi 425dpi 1080x2340", "arm64-v8a"),
|
|
66
|
-
("Samsung SM-S911B", "Android 14", "425dpi 425dpi 1080x2340", "arm64-v8a"),
|
|
67
|
-
("Xiaomi 2109119DG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
68
|
-
("Xiaomi 2201117TG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
69
|
-
("Xiaomi 2201123G", "Android 14", "526dpi 526dpi 1440x3200", "arm64-v8a"),
|
|
70
|
-
("Xiaomi 2210132G", "Android 14", "446dpi 446dpi 1220x2712", "arm64-v8a"),
|
|
71
60
|
(
|
|
72
|
-
"
|
|
61
|
+
"Google Pixel 6a",
|
|
62
|
+
"Android 13",
|
|
63
|
+
"420dpi 420dpi 1080x2400",
|
|
64
|
+
"arm64-v8a",
|
|
65
|
+
),
|
|
66
|
+
(
|
|
67
|
+
"Google Pixel 7",
|
|
68
|
+
"Android 13",
|
|
69
|
+
"420dpi 420dpi 1080x2400",
|
|
70
|
+
"arm64-v8a",
|
|
71
|
+
),
|
|
72
|
+
(
|
|
73
|
+
"Google Pixel 7 Pro",
|
|
74
|
+
"Android 13",
|
|
75
|
+
"560dpi 560dpi 1440x3120",
|
|
76
|
+
"arm64-v8a",
|
|
77
|
+
),
|
|
78
|
+
(
|
|
79
|
+
"Google Pixel 8",
|
|
73
80
|
"Android 14",
|
|
74
|
-
"
|
|
81
|
+
"420dpi 420dpi 1080x2400",
|
|
82
|
+
"arm64-v8a",
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
"samsung SM-G991B",
|
|
86
|
+
"Android 14",
|
|
87
|
+
"xxhdpi 480dpi 1080x2400",
|
|
88
|
+
"arm64-v8a",
|
|
89
|
+
),
|
|
90
|
+
(
|
|
91
|
+
"samsung SM-S911B",
|
|
92
|
+
"Android 15",
|
|
93
|
+
"xxhdpi 480dpi 1080x2340",
|
|
94
|
+
"arm64-v8a",
|
|
95
|
+
),
|
|
96
|
+
(
|
|
97
|
+
"Xiaomi 2201117TG",
|
|
98
|
+
"Android 13",
|
|
99
|
+
"440dpi 440dpi 1080x2400",
|
|
75
100
|
"arm64-v8a",
|
|
76
101
|
),
|
|
77
|
-
("Redmi 2201116TG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
78
|
-
("Redmi 22101316G", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
79
|
-
("Redmi 23021RAA2Y", "Android 14", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
80
|
-
("POCO 22011211G", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
81
|
-
("POCO 23049PCD8G", "Android 14", "446dpi 446dpi 1220x2712", "arm64-v8a"),
|
|
82
|
-
("Pixel 6", "Android 14", "411dpi 411dpi 1080x2400", "arm64-v8a"),
|
|
83
|
-
("Pixel 6a", "Android 14", "429dpi 429dpi 1080x2400", "arm64-v8a"),
|
|
84
|
-
("Pixel 7", "Android 14", "416dpi 416dpi 1080x2400", "arm64-v8a"),
|
|
85
|
-
("Pixel 7 Pro", "Android 14", "512dpi 512dpi 1440x3120", "arm64-v8a"),
|
|
86
|
-
("Pixel 8", "Android 14", "428dpi 428dpi 1080x2400", "arm64-v8a"),
|
|
87
|
-
("OnePlus NE2213", "Android 14", "525dpi 525dpi 1440x3216", "arm64-v8a"),
|
|
88
|
-
("OnePlus CPH2449", "Android 14", "451dpi 451dpi 1240x2772", "arm64-v8a"),
|
|
89
|
-
("realme RMX3085", "Android 13", "409dpi 409dpi 1080x2400", "arm64-v8a"),
|
|
90
|
-
("realme RMX3370", "Android 13", "409dpi 409dpi 1080x2400", "arm64-v8a"),
|
|
91
|
-
("realme RMX3630", "Android 13", "400dpi 400dpi 1080x2412", "arm64-v8a"),
|
|
92
|
-
("HUAWEI ELS-NX9", "Android 12", "441dpi 441dpi 1080x2340", "arm64-v8a"),
|
|
93
|
-
("HUAWEI VOG-L29", "Android 12", "398dpi 398dpi 1080x2340", "arm64-v8a"),
|
|
94
|
-
("HONOR RMO-NX1", "Android 13", "391dpi 391dpi 1080x2388", "arm64-v8a"),
|
|
95
|
-
("HONOR REA-NX9", "Android 13", "435dpi 435dpi 1200x2664", "arm64-v8a"),
|
|
96
102
|
)
|
|
97
103
|
LOCALE_TIMEZONES: tuple[tuple[str, str], ...] = (
|
|
98
104
|
("ru", "Europe/Moscow"),
|
pymax/di/__init__.py
ADDED
pymax/di/context.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from pymax.app import App
|
|
8
|
+
from pymax.base import BaseClient
|
|
9
|
+
from pymax.dispatch.mapping import DispatchEvent
|
|
10
|
+
|
|
11
|
+
ClientT = TypeVar("ClientT", bound="BaseClient")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class DiContext(Generic[ClientT]):
|
|
16
|
+
app: App[ClientT]
|
|
17
|
+
event: DispatchEvent
|
|
18
|
+
client: ClientT
|
pymax/di/resolver.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from typing import Any, TypeAlias
|
|
6
|
+
|
|
7
|
+
from pymax.fsm import FSMContext
|
|
8
|
+
from pymax.types import Message
|
|
9
|
+
|
|
10
|
+
from .context import DiContext
|
|
11
|
+
|
|
12
|
+
logger = getLogger()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def resolve_fsm(ctx: DiContext) -> FSMContext | None:
|
|
16
|
+
if not isinstance(ctx.event, Message):
|
|
17
|
+
raise ValueError("Fsm is not currently working for non-message handlers.")
|
|
18
|
+
|
|
19
|
+
if not ctx.event.chat_id or not ctx.event.sender:
|
|
20
|
+
logger.warning("Not enough data in message event. Skipping fsm")
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
return ctx.app.fsm_storage.get_context(ctx.client, ctx.event)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Resolver: TypeAlias = Callable[[DiContext], Awaitable[Any]]
|
|
27
|
+
|
|
28
|
+
DI_MAP: dict[type, Resolver] = {FSMContext: resolve_fsm}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DiResolver:
|
|
32
|
+
async def resolve(self, annotation: Any, ctx: DiContext) -> None | Any:
|
|
33
|
+
resolver = DI_MAP.get(annotation)
|
|
34
|
+
|
|
35
|
+
if resolver is None:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
return await resolver(ctx)
|
pymax/dispatch/__init__.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from .dispatcher import Dispatcher
|
|
2
2
|
from .enums import EventType
|
|
3
|
+
from .mapping import DispatchEvent
|
|
4
|
+
from .middleware import Middleware, MiddlewareNext
|
|
3
5
|
from .router import (
|
|
4
6
|
ClientRouter,
|
|
5
7
|
DisconnectCallback,
|
|
6
8
|
DisconnectDecorator,
|
|
7
9
|
ErrorContext,
|
|
8
10
|
ErrorScope,
|
|
11
|
+
HandlerCallback,
|
|
9
12
|
Router,
|
|
10
13
|
)
|
|
11
14
|
|
|
@@ -13,9 +16,13 @@ __all__ = (
|
|
|
13
16
|
"ClientRouter",
|
|
14
17
|
"DisconnectCallback",
|
|
15
18
|
"DisconnectDecorator",
|
|
19
|
+
"DispatchEvent",
|
|
16
20
|
"Dispatcher",
|
|
17
21
|
"ErrorContext",
|
|
18
22
|
"ErrorScope",
|
|
19
23
|
"EventType",
|
|
24
|
+
"HandlerCallback",
|
|
25
|
+
"Middleware",
|
|
26
|
+
"MiddlewareNext",
|
|
20
27
|
"Router",
|
|
21
28
|
)
|