maxapi-python 2.4.0__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.0.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +22 -12
- maxapi_python-2.5.0.dist-info/RECORD +195 -0
- {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +8 -2
- pymax/_data/apk_fingerprints.json +374 -17
- pymax/_data/rootca_ssl_rsa2022.crt +33 -0
- pymax/api/auth/service.py +15 -2
- 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 +21 -1
- pymax/api/chats/service.py +41 -0
- pymax/api/facade.py +2 -0
- pymax/api/messages/__init__.py +1 -1
- pymax/api/messages/enums.py +1 -0
- pymax/api/messages/payloads.py +56 -4
- pymax/api/messages/service.py +405 -27
- 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 +42 -1
- pymax/api/uploads/service.py +171 -69
- pymax/app.py +39 -8
- pymax/auth/__init__.py +2 -0
- pymax/auth/exceptions.py +9 -0
- pymax/auth/providers.py +6 -2
- pymax/auth/qr.py +11 -1
- pymax/auth/sms.py +11 -1
- pymax/base.py +146 -27
- pymax/client.py +39 -11
- pymax/client_web.py +15 -12
- pymax/config.py +101 -53
- 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/files/base.py +58 -1
- pymax/files/video.py +32 -40
- pymax/files/voice.py +18 -4
- 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/fingerprint/fingerprint.py +5 -22
- pymax/fingerprint/models.py +1 -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 +37 -5
- pymax/infra/message.py +204 -9
- pymax/infra/protocol.py +5 -1
- 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/session/__init__.py +1 -1
- pymax/session/models.py +30 -0
- pymax/session/protocol.py +33 -7
- pymax/session/store.py +73 -5
- pymax/transport/tcp.py +11 -3
- pymax/types/domain/__init__.py +23 -1
- pymax/types/domain/attachments/__init__.py +1 -1
- pymax/types/domain/attachments/call.py +7 -1
- pymax/types/domain/attachments/contact.py +3 -3
- pymax/types/domain/attachments/control.py +3 -0
- pymax/types/domain/attachments/enums.py +16 -0
- 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/attachments/video.py +3 -3
- pymax/types/domain/callback.py +19 -0
- pymax/types/domain/comment.py +24 -0
- pymax/types/domain/enums.py +7 -0
- pymax/types/domain/message.py +158 -9
- pymax/types/domain/story.py +126 -0
- pymax/types/events/reaction.py +28 -4
- pymax/versions/__init__.py +2 -0
- pymax/versions/catalog.py +104 -0
- pymax/versions/exceptions.py +10 -0
- maxapi_python-2.4.0.dist-info/RECORD +0 -161
- {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
pymax/client.py
CHANGED
|
@@ -7,12 +7,15 @@ from pymax.auth import (
|
|
|
7
7
|
SmsAuthFlow,
|
|
8
8
|
SmsCodeProvider,
|
|
9
9
|
)
|
|
10
|
+
from pymax.config import ClientConfig
|
|
10
11
|
from pymax.connection import ConnectionManager
|
|
11
12
|
from pymax.connection.readers import TCPReader
|
|
13
|
+
from pymax.dispatch import Router
|
|
12
14
|
from pymax.logging import configure_logging, get_logger
|
|
13
15
|
from pymax.protocol.tcp import TcpProtocol
|
|
14
16
|
from pymax.protocol.tcp.framing import TcpPacketFramer
|
|
15
17
|
from pymax.transport.tcp import TCPTransport
|
|
18
|
+
from pymax.versions.catalog import VersionCatalog
|
|
16
19
|
|
|
17
20
|
from .base import BaseClient
|
|
18
21
|
from .config import ExtraConfig
|
|
@@ -37,6 +40,15 @@ class Client(BaseClient["Client"]):
|
|
|
37
40
|
auth_flow: Полностью пользовательский сценарий авторизации.
|
|
38
41
|
sms_code_provider: Провайдер SMS-кода для стандартного ``SmsAuthFlow``.
|
|
39
42
|
password_provider: Провайдер пароля 2FA, если аккаунт его требует.
|
|
43
|
+
app_version: Версия Android-клиента для user-agent и fingerprint.
|
|
44
|
+
По умолчанию используется ``VersionCatalog.recommended()``.
|
|
45
|
+
catalog: Каталог fingerprints. Если не передан, используется
|
|
46
|
+
встроенный каталог. Каталог и версия разрешаются при
|
|
47
|
+
:meth:`connect` или :meth:`start`.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
VersionNotFoundError: При запуске, если ``app_version`` отсутствует в
|
|
51
|
+
итоговом каталоге.
|
|
40
52
|
"""
|
|
41
53
|
|
|
42
54
|
def __init__( # noqa: PLR0913
|
|
@@ -48,12 +60,15 @@ class Client(BaseClient["Client"]):
|
|
|
48
60
|
auth_flow: AuthFlow | None = None,
|
|
49
61
|
sms_code_provider: SmsCodeProvider | None = None,
|
|
50
62
|
password_provider: PasswordProvider | None = None,
|
|
63
|
+
app_version: str = VersionCatalog.RECOMMENDED_APP_VERSION,
|
|
64
|
+
catalog: VersionCatalog | None = None,
|
|
51
65
|
) -> None:
|
|
52
|
-
|
|
53
|
-
self.
|
|
54
|
-
self.
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
66
|
+
self.phone: str = phone
|
|
67
|
+
self.extra_config: ExtraConfig = extra_config or ExtraConfig()
|
|
68
|
+
self.session_name: str = session_name
|
|
69
|
+
self.work_dir: str = work_dir
|
|
70
|
+
self.app_version: str = app_version
|
|
71
|
+
self.catalog: VersionCatalog = catalog or VersionCatalog()
|
|
57
72
|
|
|
58
73
|
configure_logging(self.extra_config.log_level)
|
|
59
74
|
logger.debug(
|
|
@@ -65,17 +80,13 @@ class Client(BaseClient["Client"]):
|
|
|
65
80
|
self.extra_config.reconnect,
|
|
66
81
|
)
|
|
67
82
|
|
|
68
|
-
self._config = self._build_config(
|
|
69
|
-
phone=phone,
|
|
70
|
-
user_agent=(self.extra_config.user_agent or self.extra_config.generate_user_agent()),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
83
|
if auth_flow is None:
|
|
74
84
|
auth_flow = SmsAuthFlow(
|
|
75
85
|
sms_code_provider or ConsoleSmsCodeProvider(),
|
|
76
86
|
password_provider,
|
|
77
87
|
)
|
|
78
|
-
self.
|
|
88
|
+
self._auth_flow = auth_flow
|
|
89
|
+
self._router = Router()
|
|
79
90
|
|
|
80
91
|
logger.debug(
|
|
81
92
|
"client created transport=tcp host=%s port=%s",
|
|
@@ -83,6 +94,23 @@ class Client(BaseClient["Client"]):
|
|
|
83
94
|
self.extra_config.port,
|
|
84
95
|
)
|
|
85
96
|
|
|
97
|
+
async def _prepare_config(self) -> ClientConfig:
|
|
98
|
+
catalog = await self.catalog.load()
|
|
99
|
+
fingerprint = catalog.resolve(self.app_version)
|
|
100
|
+
|
|
101
|
+
user_agent = self.extra_config.user_agent or self.extra_config.generate_user_agent(
|
|
102
|
+
self.app_version, fingerprint.build_number
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
config = self._build_config(
|
|
106
|
+
phone=self.phone,
|
|
107
|
+
user_agent=user_agent,
|
|
108
|
+
version=self.app_version,
|
|
109
|
+
fingerprint=fingerprint,
|
|
110
|
+
)
|
|
111
|
+
config.restore_user_agent_from_session = self.extra_config.user_agent is None
|
|
112
|
+
return config
|
|
113
|
+
|
|
86
114
|
def _build_connection(self) -> ConnectionManager:
|
|
87
115
|
logger.debug(
|
|
88
116
|
"building tcp connection host=%s port=%s ssl=%s",
|
pymax/client_web.py
CHANGED
|
@@ -5,12 +5,13 @@ from pymax.auth.providers import ConsoleQrHandler, QrHandler
|
|
|
5
5
|
from pymax.auth.qr import QrAuthFlow
|
|
6
6
|
from pymax.connection import ConnectionManager
|
|
7
7
|
from pymax.connection.readers import WSReader
|
|
8
|
+
from pymax.dispatch import Router
|
|
8
9
|
from pymax.logging import configure_logging, get_logger
|
|
9
10
|
from pymax.protocol.tcp import TcpProtocol
|
|
10
11
|
from pymax.transport.websocket import WebSocketTransport
|
|
11
12
|
|
|
12
13
|
from .base import BaseClient
|
|
13
|
-
from .config import ExtraConfig
|
|
14
|
+
from .config import ClientConfig, ExtraConfig
|
|
14
15
|
|
|
15
16
|
logger = get_logger(__name__)
|
|
16
17
|
|
|
@@ -38,9 +39,9 @@ class WebClient(BaseClient["WebClient"]):
|
|
|
38
39
|
auth_flow: AuthFlow | None = None,
|
|
39
40
|
qr_provider: QrHandler | None = None,
|
|
40
41
|
) -> None:
|
|
41
|
-
self.extra_config = extra_config or ExtraConfig()
|
|
42
|
-
self.session_name = session_name
|
|
43
|
-
self.work_dir = work_dir
|
|
42
|
+
self.extra_config: ExtraConfig = extra_config or ExtraConfig()
|
|
43
|
+
self.session_name: str = session_name
|
|
44
|
+
self.work_dir: str = work_dir
|
|
44
45
|
|
|
45
46
|
configure_logging(self.extra_config.log_level)
|
|
46
47
|
logger.debug(
|
|
@@ -51,22 +52,24 @@ class WebClient(BaseClient["WebClient"]):
|
|
|
51
52
|
self.extra_config.reconnect,
|
|
52
53
|
)
|
|
53
54
|
|
|
54
|
-
self._config = self._build_config(
|
|
55
|
-
phone=None,
|
|
56
|
-
user_agent=(
|
|
57
|
-
self.extra_config.user_agent or self.extra_config.generate_web_user_agent()
|
|
58
|
-
),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
55
|
if auth_flow is None:
|
|
62
56
|
auth_flow = QrAuthFlow(qr_provider or ConsoleQrHandler())
|
|
63
|
-
self.
|
|
57
|
+
self._auth_flow = auth_flow
|
|
58
|
+
self._router = Router()
|
|
64
59
|
|
|
65
60
|
logger.debug(
|
|
66
61
|
"web client created transport=ws url=%s",
|
|
67
62
|
self.extra_config.url,
|
|
68
63
|
)
|
|
69
64
|
|
|
65
|
+
async def _prepare_config(self) -> ClientConfig:
|
|
66
|
+
user_agent = self.extra_config.user_agent or self.extra_config.generate_web_user_agent()
|
|
67
|
+
config = self._build_config(
|
|
68
|
+
phone=None, user_agent=user_agent, version=None, fingerprint=None
|
|
69
|
+
)
|
|
70
|
+
config.restore_user_agent_from_session = self.extra_config.user_agent is None
|
|
71
|
+
return config
|
|
72
|
+
|
|
70
73
|
def _build_connection(self) -> ConnectionManager:
|
|
71
74
|
logger.debug(
|
|
72
75
|
"building websocket connection url=%s",
|
pymax/config.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
1
|
+
import secrets
|
|
2
|
+
from random import choice, randint
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, ConfigDict, Field
|
|
5
5
|
|
|
@@ -8,8 +8,10 @@ from pymax.api.session.payloads import (
|
|
|
8
8
|
DEFAULT_WEB_HEADER_USER_AGENT,
|
|
9
9
|
MobileUserAgentPayload,
|
|
10
10
|
)
|
|
11
|
+
from pymax.fingerprint.models import ApkBuildFingerprint
|
|
11
12
|
from pymax.session import StoreProtocol
|
|
12
13
|
from pymax.types.domain.sync import SyncOverrides
|
|
14
|
+
from pymax.versions.catalog import VersionCatalog
|
|
13
15
|
|
|
14
16
|
MIN_PREFERRED_BUILD = 6712
|
|
15
17
|
APP_VERSIONS: tuple[tuple[str, int], ...] = (
|
|
@@ -47,50 +49,56 @@ APP_VERSIONS: tuple[tuple[str, int], ...] = (
|
|
|
47
49
|
("26.12.2", 6681),
|
|
48
50
|
("26.12.1", 6679),
|
|
49
51
|
("26.12.0", 6676),
|
|
50
|
-
("26.11.3", 6670),
|
|
51
|
-
("26.11.2", 6669),
|
|
52
|
-
("26.11.1", 6665),
|
|
53
|
-
("26.10.1", 6653),
|
|
54
|
-
("26.10.0", 6648),
|
|
55
|
-
("26.9.1", 6643),
|
|
52
|
+
# ("26.11.3", 6670), # Not supported TODO: delete maybe
|
|
53
|
+
# ("26.11.2", 6669),
|
|
54
|
+
# ("26.11.1", 6665),
|
|
55
|
+
# ("26.10.1", 6653),
|
|
56
|
+
# ("26.10.0", 6648),
|
|
57
|
+
# ("26.9.1", 6643),
|
|
56
58
|
)
|
|
57
59
|
ANDROID_DEVICES: tuple[tuple[str, str, str, str], ...] = (
|
|
58
|
-
("Samsung SM-A525F", "Android 13", "405dpi 405dpi 1080x2400", "arm64-v8a"),
|
|
59
|
-
("Samsung SM-A536B", "Android 14", "405dpi 405dpi 1080x2400", "arm64-v8a"),
|
|
60
|
-
("Samsung SM-A546E", "Android 14", "405dpi 405dpi 1080x2340", "arm64-v8a"),
|
|
61
|
-
("Samsung SM-G991B", "Android 14", "421dpi 421dpi 1080x2400", "arm64-v8a"),
|
|
62
|
-
("Samsung SM-G998B", "Android 13", "515dpi 515dpi 1440x3200", "arm64-v8a"),
|
|
63
|
-
("Samsung SM-S901B", "Android 14", "425dpi 425dpi 1080x2340", "arm64-v8a"),
|
|
64
|
-
("Samsung SM-S911B", "Android 14", "425dpi 425dpi 1080x2340", "arm64-v8a"),
|
|
65
|
-
("Xiaomi 2109119DG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
66
|
-
("Xiaomi 2201117TG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
67
|
-
("Xiaomi 2201123G", "Android 14", "526dpi 526dpi 1440x3200", "arm64-v8a"),
|
|
68
|
-
("Xiaomi 2210132G", "Android 14", "446dpi 446dpi 1220x2712", "arm64-v8a"),
|
|
69
60
|
(
|
|
70
|
-
"
|
|
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",
|
|
80
|
+
"Android 14",
|
|
81
|
+
"420dpi 420dpi 1080x2400",
|
|
82
|
+
"arm64-v8a",
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
"samsung SM-G991B",
|
|
71
86
|
"Android 14",
|
|
72
|
-
"
|
|
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",
|
|
73
100
|
"arm64-v8a",
|
|
74
101
|
),
|
|
75
|
-
("Redmi 2201116TG", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
76
|
-
("Redmi 22101316G", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
77
|
-
("Redmi 23021RAA2Y", "Android 14", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
78
|
-
("POCO 22011211G", "Android 13", "395dpi 395dpi 1080x2400", "arm64-v8a"),
|
|
79
|
-
("POCO 23049PCD8G", "Android 14", "446dpi 446dpi 1220x2712", "arm64-v8a"),
|
|
80
|
-
("Pixel 6", "Android 14", "411dpi 411dpi 1080x2400", "arm64-v8a"),
|
|
81
|
-
("Pixel 6a", "Android 14", "429dpi 429dpi 1080x2400", "arm64-v8a"),
|
|
82
|
-
("Pixel 7", "Android 14", "416dpi 416dpi 1080x2400", "arm64-v8a"),
|
|
83
|
-
("Pixel 7 Pro", "Android 14", "512dpi 512dpi 1440x3120", "arm64-v8a"),
|
|
84
|
-
("Pixel 8", "Android 14", "428dpi 428dpi 1080x2400", "arm64-v8a"),
|
|
85
|
-
("OnePlus NE2213", "Android 14", "525dpi 525dpi 1440x3216", "arm64-v8a"),
|
|
86
|
-
("OnePlus CPH2449", "Android 14", "451dpi 451dpi 1240x2772", "arm64-v8a"),
|
|
87
|
-
("realme RMX3085", "Android 13", "409dpi 409dpi 1080x2400", "arm64-v8a"),
|
|
88
|
-
("realme RMX3370", "Android 13", "409dpi 409dpi 1080x2400", "arm64-v8a"),
|
|
89
|
-
("realme RMX3630", "Android 13", "400dpi 400dpi 1080x2412", "arm64-v8a"),
|
|
90
|
-
("HUAWEI ELS-NX9", "Android 12", "441dpi 441dpi 1080x2340", "arm64-v8a"),
|
|
91
|
-
("HUAWEI VOG-L29", "Android 12", "398dpi 398dpi 1080x2340", "arm64-v8a"),
|
|
92
|
-
("HONOR RMO-NX1", "Android 13", "391dpi 391dpi 1080x2388", "arm64-v8a"),
|
|
93
|
-
("HONOR REA-NX9", "Android 13", "435dpi 435dpi 1200x2664", "arm64-v8a"),
|
|
94
102
|
)
|
|
95
103
|
LOCALE_TIMEZONES: tuple[tuple[str, str], ...] = (
|
|
96
104
|
("ru", "Europe/Moscow"),
|
|
@@ -104,17 +112,21 @@ LOCALE_TIMEZONES: tuple[tuple[str, str], ...] = (
|
|
|
104
112
|
("ru", "Asia/Yakutsk"),
|
|
105
113
|
("ru", "Asia/Vladivostok"),
|
|
106
114
|
)
|
|
107
|
-
WEB_APP_VERSION = "26.
|
|
115
|
+
WEB_APP_VERSION = "26.8.4"
|
|
108
116
|
WEB_SCREEN = "1080x1920 1.0x"
|
|
109
117
|
|
|
110
118
|
PREFERRED_VERSION = [version for version in APP_VERSIONS if version[1] >= MIN_PREFERRED_BUILD]
|
|
111
119
|
LEGACY_VERSIONS = [version for version in APP_VERSIONS if version[1] < MIN_PREFERRED_BUILD]
|
|
112
120
|
|
|
113
121
|
|
|
122
|
+
def generate_device_id() -> str:
|
|
123
|
+
return secrets.token_hex(8)
|
|
124
|
+
|
|
125
|
+
|
|
114
126
|
class DeviceConfig(BaseModel):
|
|
115
127
|
mt_instance_id: str
|
|
116
128
|
user_agent: MobileUserAgentPayload
|
|
117
|
-
device_id: str = Field(default_factory=
|
|
129
|
+
device_id: str = Field(default_factory=generate_device_id)
|
|
118
130
|
client_session_id: int = Field(default_factory=lambda: randint(1, 70))
|
|
119
131
|
|
|
120
132
|
|
|
@@ -136,6 +148,9 @@ class RegistrationConfig(BaseModel):
|
|
|
136
148
|
class ClientConfig(BaseModel):
|
|
137
149
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
138
150
|
|
|
151
|
+
app_version: str | None = VersionCatalog.RECOMMENDED_APP_VERSION
|
|
152
|
+
fingering: ApkBuildFingerprint | None
|
|
153
|
+
|
|
139
154
|
phone: str | None = None
|
|
140
155
|
work_dir: str = "."
|
|
141
156
|
session_name: str = "session.db"
|
|
@@ -143,17 +158,21 @@ class ClientConfig(BaseModel):
|
|
|
143
158
|
token: str | None = None
|
|
144
159
|
proxy: str | None = None
|
|
145
160
|
registration_config: RegistrationConfig | None = None
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
upload_timeout: int = 900
|
|
162
|
+
relogin: bool = True
|
|
163
|
+
host: str = "api2.oneme.ru"
|
|
148
164
|
port: int = 443
|
|
149
165
|
use_ssl: bool = True
|
|
166
|
+
persist_session: bool = True
|
|
150
167
|
|
|
168
|
+
password_max_attempts: int | None = None
|
|
151
169
|
protocol_version: int = 10
|
|
152
170
|
request_timeout: float = 30.0
|
|
153
171
|
log_level: str = "INFO"
|
|
154
172
|
telemetry: bool = False
|
|
155
173
|
|
|
156
174
|
interactive: bool = True
|
|
175
|
+
restore_user_agent_from_session: bool = True
|
|
157
176
|
|
|
158
177
|
store: StoreProtocol | None = None
|
|
159
178
|
|
|
@@ -181,13 +200,35 @@ class ExtraConfig(BaseModel):
|
|
|
181
200
|
proxy: Proxy URL для TCP- или WebSocket-транспорта.
|
|
182
201
|
reconnect: Переподключаться после сетевых ошибок.
|
|
183
202
|
reconnect_delay: Пауза перед reconnect.
|
|
184
|
-
|
|
203
|
+
upload_timeout: Общий timeout HTTP-загрузки файлов, видео, голосовых и
|
|
204
|
+
видеосообщений в секундах. По умолчанию ``900``; не применяется к
|
|
205
|
+
фотографиям и 60-секундному ожиданию обработки вложения. ``0`` и
|
|
206
|
+
отрицательные значения отключают общий timeout, но не отдельный
|
|
207
|
+
60-секундный socket/read timeout.
|
|
208
|
+
relogin: Автоматически сбрасывать сессию и повторять авторизацию, если
|
|
209
|
+
Max отозвал login token. По умолчанию ``True``.
|
|
210
|
+
password_max_attempts: Максимальное число попыток ввода пароля 2FA.
|
|
211
|
+
``None`` означает отсутствие лимита; ``0`` и отрицательные значения
|
|
212
|
+
сразу завершают авторизацию с ``PasswordAttemptsExceededError``.
|
|
213
|
+
persist_session: Сохранять token, device/user-agent и sync-state между
|
|
214
|
+
runtime в SQLite или переданном ``store``. По умолчанию ``True``.
|
|
215
|
+
При ``False`` используется отдельный ``InMemoryStore`` и значение
|
|
216
|
+
``store`` игнорируется; после пересоздания runtime, в том числе при
|
|
217
|
+
reconnect, сохраненная сессия недоступна.
|
|
218
|
+
device_id: Явный device ID. Если не передан, генерируется строка из
|
|
219
|
+
16 шестнадцатеричных символов.
|
|
185
220
|
device_type: Тип устройства для mobile user-agent.
|
|
186
|
-
user_agent: Полностью заданный user-agent payload.
|
|
187
|
-
|
|
221
|
+
user_agent: Полностью заданный user-agent payload. Явное значение имеет
|
|
222
|
+
приоритет над user-agent из сохраненной сессии. Автоматически
|
|
223
|
+
сгенерированный user-agent восстанавливает характеристики устройства
|
|
224
|
+
из сессии, сохраняя версию приложения и build number текущего запуска.
|
|
225
|
+
mt_instance_id: Instance ID устройства. По умолчанию генерируется
|
|
226
|
+
строка из 16 шестнадцатеричных символов.
|
|
188
227
|
request_timeout: Timeout API-запросов в секундах.
|
|
189
228
|
log_level: Уровень логов ``pymax``.
|
|
190
229
|
telemetry: Отправлять telemetry-события Max.
|
|
230
|
+
store: Пользовательское хранилище сессии. Используется только при
|
|
231
|
+
``persist_session=True``.
|
|
191
232
|
sync: Переопределения sync-маркеров для login.
|
|
192
233
|
|
|
193
234
|
Example:
|
|
@@ -210,18 +251,22 @@ class ExtraConfig(BaseModel):
|
|
|
210
251
|
token: str | None = None
|
|
211
252
|
registration_config: RegistrationConfig | None = None
|
|
212
253
|
|
|
213
|
-
host: str = "
|
|
254
|
+
host: str = "api2.oneme.ru"
|
|
214
255
|
port: int = 443
|
|
215
256
|
url: str = "wss://api.oneme.ru/websocket"
|
|
216
257
|
use_ssl: bool = True
|
|
217
258
|
proxy: str | None = None
|
|
218
259
|
reconnect: bool = True
|
|
219
260
|
reconnect_delay: float = 1.0
|
|
261
|
+
upload_timeout: int = 900
|
|
262
|
+
relogin: bool = True
|
|
263
|
+
password_max_attempts: int | None = None
|
|
264
|
+
persist_session: bool = True
|
|
220
265
|
|
|
221
266
|
device_id: str | None = None
|
|
222
267
|
device_type: DeviceType = DeviceType.ANDROID
|
|
223
268
|
user_agent: MobileUserAgentPayload | None = None
|
|
224
|
-
mt_instance_id: str = Field(default_factory=
|
|
269
|
+
mt_instance_id: str = Field(default_factory=generate_device_id)
|
|
225
270
|
|
|
226
271
|
request_timeout: float = 30.0
|
|
227
272
|
log_level: str = "INFO"
|
|
@@ -231,14 +276,17 @@ class ExtraConfig(BaseModel):
|
|
|
231
276
|
|
|
232
277
|
sync: SyncOverrides = Field(default_factory=SyncOverrides)
|
|
233
278
|
|
|
234
|
-
def generate_user_agent(self) -> MobileUserAgentPayload:
|
|
279
|
+
def generate_user_agent(self, app_version: str, build_number: int) -> MobileUserAgentPayload:
|
|
235
280
|
"""Создает mobile user-agent payload для TCP-клиента.
|
|
236
281
|
|
|
282
|
+
Args:
|
|
283
|
+
app_version: Версия Android-клиента Max.
|
|
284
|
+
build_number: Build number той же версии из ``VersionCatalog``.
|
|
285
|
+
|
|
237
286
|
Returns:
|
|
238
|
-
|
|
287
|
+
Случайная конфигурация Android-устройства с переданными версией и
|
|
288
|
+
build number.
|
|
239
289
|
"""
|
|
240
|
-
versions = PREFERRED_VERSION if random() < 0.9 else LEGACY_VERSIONS
|
|
241
|
-
app_version, build_number = choice(versions)
|
|
242
290
|
device_name, os_version, screen, arch = choice(ANDROID_DEVICES)
|
|
243
291
|
locale, timezone = choice(LOCALE_TIMEZONES)
|
|
244
292
|
|
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
|
)
|