maxapi-python 1.2.1__py3-none-any.whl → 1.2.2__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-1.2.1.dist-info → maxapi_python-1.2.2.dist-info}/METADATA +43 -6
- {maxapi_python-1.2.1.dist-info → maxapi_python-1.2.2.dist-info}/RECORD +8 -8
- pymax/core.py +1 -1
- pymax/mixins/websocket.py +7 -16
- pymax/payloads.py +29 -14
- pymax/static/constant.py +1 -1
- {maxapi_python-1.2.1.dist-info → maxapi_python-1.2.2.dist-info}/WHEEL +0 -0
- {maxapi_python-1.2.1.dist-info → maxapi_python-1.2.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/ink-developer/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/ink-developer/PyMax
|
|
@@ -19,6 +19,13 @@ Requires-Dist: msgpack>=1.1.1
|
|
|
19
19
|
Requires-Dist: qrcode>=8.2
|
|
20
20
|
Requires-Dist: sqlmodel>=0.0.24
|
|
21
21
|
Requires-Dist: websockets>=15.0
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: flake8; extra == 'test'
|
|
24
|
+
Requires-Dist: mypy; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
22
29
|
Description-Content-Type: text/markdown
|
|
23
30
|
|
|
24
31
|
<p align="center">
|
|
@@ -36,11 +43,6 @@ Description-Content-Type: text/markdown
|
|
|
36
43
|
<img src="https://img.shields.io/badge/packaging-uv-D7FF64.svg" alt="Packaging">
|
|
37
44
|
</p>
|
|
38
45
|
|
|
39
|
-
> [!IMPORTANT]
|
|
40
|
-
> (20.12.25) Из за резкого изменения апи большая часть библиотеки не работает.
|
|
41
|
-
Смотрите [новость](https://t.me/pymax_news/111)
|
|
42
|
-
>
|
|
43
|
-
> P.s добавил логин по qr в dev/1.2.1
|
|
44
46
|
|
|
45
47
|
---
|
|
46
48
|
> ⚠️ **Дисклеймер**
|
|
@@ -82,6 +84,41 @@ uv add -U maxapi-python
|
|
|
82
84
|
|
|
83
85
|
## Быстрый старт
|
|
84
86
|
|
|
87
|
+
### Аутентификация (`device_type`)
|
|
88
|
+
|
|
89
|
+
> [!IMPORTANT]
|
|
90
|
+
> Параметр `device_type` в `UserAgentPayload` **критически важен** для выбора способа авторизации:
|
|
91
|
+
|
|
92
|
+
**Вход по номеру телефона (DESKTOP):**
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from pymax import MaxClient
|
|
96
|
+
from pymax.payloads import UserAgentPayload
|
|
97
|
+
|
|
98
|
+
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
99
|
+
|
|
100
|
+
client = MaxClient(
|
|
101
|
+
phone="+79111111111",
|
|
102
|
+
work_dir="cache",
|
|
103
|
+
headers=ua,
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Вход через QR-код (WEB)** — токен совместим с веб-версией Max:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from pymax import MaxClient
|
|
111
|
+
from pymax.payloads import UserAgentPayload
|
|
112
|
+
|
|
113
|
+
ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")
|
|
114
|
+
|
|
115
|
+
client = MaxClient(
|
|
116
|
+
phone="+7911111111",
|
|
117
|
+
work_dir="cache",
|
|
118
|
+
headers=ua,
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
85
122
|
### Базовый пример использования
|
|
86
123
|
|
|
87
124
|
```python
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
pymax/__init__.py,sha256=6wUKKwsyxFpWG3b7kwptOvHd-w78C-ygw42iCDBYQvc,1915
|
|
2
|
-
pymax/core.py,sha256=
|
|
2
|
+
pymax/core.py,sha256=OXGNaQ0pDaf6Ofr1Fb9m7vh5ffpbiMyvUMM0EfwlnIQ,14907
|
|
3
3
|
pymax/crud.py,sha256=YC92TyhA2mr1tJCcfd-tvh8umtXKgqJfgiLo7nXUl3Q,3076
|
|
4
4
|
pymax/exceptions.py,sha256=nDUNx7bM-Yjugj-qfIllcrnwLg9JpZroYqfXapjYbMQ,3178
|
|
5
5
|
pymax/files.py,sha256=AvFIr34Desq2p4CNWXIngRqeyTBKMT98VmcnI-zvUU0,3462
|
|
@@ -9,7 +9,7 @@ pymax/formatting.py,sha256=XRtuXJGweuNZevJFdPxksDftIrfuMGEA-AOUc_v6IhQ,2484
|
|
|
9
9
|
pymax/interfaces.py,sha256=wKF1z1QRw8LcjvM9rzSHWXTK6gPb6sDt2UGiQLvyMf8,8790
|
|
10
10
|
pymax/models.py,sha256=PsPGbOkERxesZZltjNrmqhOfRcO44Is2ThbEToREcB8,201
|
|
11
11
|
pymax/navigation.py,sha256=4ia6RGY2pXMArboNhHkbWlWX7LtcYK1VGVXorPX0Pb4,5747
|
|
12
|
-
pymax/payloads.py,sha256
|
|
12
|
+
pymax/payloads.py,sha256=GuTLK6HYe_bLW3ardgpKeZ98f79j349tD_6B6EwkGww,7879
|
|
13
13
|
pymax/types.py,sha256=_ARcVXLGHyiGAJKYPd6EU9QDKzz4VwS6kjTu3YEH_u4,35523
|
|
14
14
|
pymax/mixins/__init__.py,sha256=5sXJME34S1EssuDETaN4DLRH7vhMw_Q3Jmay9myAIZM,775
|
|
15
15
|
pymax/mixins/auth.py,sha256=e90vIpEOwAjUxgYMYaG7R6jR_5t9rKsei_mTBQUirL4,14716
|
|
@@ -23,10 +23,10 @@ pymax/mixins/socket.py,sha256=tdHgd1NwWoEZhHCDd74XLOHFKUq-rladxhXV8Z_-APU,22860
|
|
|
23
23
|
pymax/mixins/telemetry.py,sha256=LWr68DNQkPhAjGRDYQ5lORXxC3Yw6M9E8sF0TCNISTE,3609
|
|
24
24
|
pymax/mixins/user.py,sha256=RSZd4t-aq8P2k3cVzNVWBkUf-_xTWILrBzwxLRgk1pw,9450
|
|
25
25
|
pymax/mixins/utils.py,sha256=s3FUf3i_wjn2Gbg5YY1rWZB-90ZEGrrcUuND_MqqSTE,853
|
|
26
|
-
pymax/mixins/websocket.py,sha256=
|
|
27
|
-
pymax/static/constant.py,sha256
|
|
26
|
+
pymax/mixins/websocket.py,sha256=GpdboEVWzyN1qLTcsgKZym6TlPnklcQuNeXJ5YKwg8c,17724
|
|
27
|
+
pymax/static/constant.py,sha256=nM0svv3VpsVxK-RqoADn9qsTdQvB-IYv0Sgv-bQcWs4,1116
|
|
28
28
|
pymax/static/enum.py,sha256=Hk0e6zSbGOJC_9Aw7gNXX3hcavnjzQfDyr8vjW22cFo,4648
|
|
29
|
-
maxapi_python-1.2.
|
|
30
|
-
maxapi_python-1.2.
|
|
31
|
-
maxapi_python-1.2.
|
|
32
|
-
maxapi_python-1.2.
|
|
29
|
+
maxapi_python-1.2.2.dist-info/METADATA,sha256=rgiQKdSqYAO743n6jWOy0F76jZyjaGMY7A6qUlHlk64,6753
|
|
30
|
+
maxapi_python-1.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
31
|
+
maxapi_python-1.2.2.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
|
|
32
|
+
maxapi_python-1.2.2.dist-info/RECORD,,
|
pymax/core.py
CHANGED
pymax/mixins/websocket.py
CHANGED
|
@@ -50,9 +50,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
50
50
|
payload=payload,
|
|
51
51
|
).model_dump(by_alias=True)
|
|
52
52
|
|
|
53
|
-
self.logger.debug(
|
|
54
|
-
"make_message opcode=%s cmd=%s seq=%s", opcode, cmd, self._seq
|
|
55
|
-
)
|
|
53
|
+
self.logger.debug("make_message opcode=%s cmd=%s seq=%s", opcode, cmd, self._seq)
|
|
56
54
|
return msg
|
|
57
55
|
|
|
58
56
|
async def _send_interactive_ping(self) -> None:
|
|
@@ -68,9 +66,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
68
66
|
self.logger.warning("Interactive ping failed", exc_info=True)
|
|
69
67
|
await asyncio.sleep(DEFAULT_PING_INTERVAL)
|
|
70
68
|
|
|
71
|
-
async def connect(
|
|
72
|
-
self, user_agent: UserAgentPayload | None = None
|
|
73
|
-
) -> dict[str, Any] | None:
|
|
69
|
+
async def connect(self, user_agent: UserAgentPayload | None = None) -> dict[str, Any] | None:
|
|
74
70
|
"""
|
|
75
71
|
Устанавливает соединение WebSocket с сервером и выполняет handshake.
|
|
76
72
|
|
|
@@ -173,9 +169,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
173
169
|
fut = self._file_upload_waiters.pop(id_, None)
|
|
174
170
|
if fut and not fut.done():
|
|
175
171
|
fut.set_result(data)
|
|
176
|
-
self.logger.debug(
|
|
177
|
-
"Fulfilled file upload waiter for %s=%s", key, id_
|
|
178
|
-
)
|
|
172
|
+
self.logger.debug("Fulfilled file upload waiter for %s=%s", key, id_)
|
|
179
173
|
|
|
180
174
|
async def _handle_message_notifications(self, data: dict) -> None:
|
|
181
175
|
if data.get("opcode") != Opcode.NOTIF_MESSAGE.value:
|
|
@@ -359,9 +353,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
359
353
|
)
|
|
360
354
|
return data
|
|
361
355
|
except Exception:
|
|
362
|
-
self.logger.exception(
|
|
363
|
-
"Send and wait failed (opcode=%s, seq=%s)", opcode, msg["seq"]
|
|
364
|
-
)
|
|
356
|
+
self.logger.exception("Send and wait failed (opcode=%s, seq=%s)", opcode, msg["seq"])
|
|
365
357
|
raise RuntimeError("Send and wait failed")
|
|
366
358
|
finally:
|
|
367
359
|
self._pending.pop(msg["seq"], None)
|
|
@@ -442,7 +434,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
442
434
|
else:
|
|
443
435
|
return float(2**retry_count)
|
|
444
436
|
|
|
445
|
-
async def _sync(self) -> None:
|
|
437
|
+
async def _sync(self, user_agent: UserAgentPayload) -> None:
|
|
446
438
|
self.logger.info("Starting initial sync")
|
|
447
439
|
|
|
448
440
|
payload = SyncPayload(
|
|
@@ -453,6 +445,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
453
445
|
presence_sync=0,
|
|
454
446
|
drafts_sync=0,
|
|
455
447
|
chats_count=40,
|
|
448
|
+
user_agent=user_agent,
|
|
456
449
|
).model_dump(by_alias=True)
|
|
457
450
|
try:
|
|
458
451
|
data = await self._send_and_wait(opcode=Opcode.LOGIN, payload=payload)
|
|
@@ -473,9 +466,7 @@ class WebSocketMixin(ClientProtocol):
|
|
|
473
466
|
self.logger.exception("Error parsing chat entry")
|
|
474
467
|
|
|
475
468
|
if raw_payload.get("profile", {}).get("contact"):
|
|
476
|
-
self.me = Me.from_dict(
|
|
477
|
-
raw_payload.get("profile", {}).get("contact", {})
|
|
478
|
-
)
|
|
469
|
+
self.me = Me.from_dict(raw_payload.get("profile", {}).get("contact", {}))
|
|
479
470
|
|
|
480
471
|
self.logger.info(
|
|
481
472
|
"Sync completed: dialogs=%d chats=%d channels=%d",
|
pymax/payloads.py
CHANGED
|
@@ -39,6 +39,20 @@ class BaseWebSocketMessage(BaseModel):
|
|
|
39
39
|
payload: dict[str, Any]
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class UserAgentPayload(CamelModel):
|
|
43
|
+
device_type: str = Field(default=DEFAULT_DEVICE_TYPE)
|
|
44
|
+
locale: str = Field(default=DEFAULT_LOCALE)
|
|
45
|
+
device_locale: str = Field(default=DEFAULT_DEVICE_LOCALE)
|
|
46
|
+
os_version: str = Field(default=DEFAULT_OS_VERSION)
|
|
47
|
+
device_name: str = Field(default=DEFAULT_DEVICE_NAME)
|
|
48
|
+
header_user_agent: str = Field(default=DEFAULT_USER_AGENT)
|
|
49
|
+
app_version: str = Field(default=DEFAULT_APP_VERSION)
|
|
50
|
+
screen: str = Field(default=DEFAULT_SCREEN)
|
|
51
|
+
timezone: str = Field(default=DEFAULT_TIMEZONE)
|
|
52
|
+
client_session_id: int = Field(default=DEFAULT_CLIENT_SESSION_ID)
|
|
53
|
+
build_number: int = Field(default=DEFAULT_BUILD_NUMBER)
|
|
54
|
+
|
|
55
|
+
|
|
42
56
|
class RequestCodePayload(CamelModel):
|
|
43
57
|
phone: str
|
|
44
58
|
type: AuthType = AuthType.START_AUTH
|
|
@@ -59,6 +73,21 @@ class SyncPayload(CamelModel):
|
|
|
59
73
|
presence_sync: int = 0
|
|
60
74
|
drafts_sync: int = 0
|
|
61
75
|
chats_count: int = 40
|
|
76
|
+
user_agent: UserAgentPayload = Field(
|
|
77
|
+
default_factory=lambda: UserAgentPayload(
|
|
78
|
+
device_type=DEFAULT_DEVICE_TYPE,
|
|
79
|
+
locale=DEFAULT_LOCALE,
|
|
80
|
+
device_locale=DEFAULT_DEVICE_LOCALE,
|
|
81
|
+
os_version=DEFAULT_OS_VERSION,
|
|
82
|
+
device_name=DEFAULT_DEVICE_NAME,
|
|
83
|
+
header_user_agent=DEFAULT_USER_AGENT,
|
|
84
|
+
app_version=DEFAULT_APP_VERSION,
|
|
85
|
+
screen=DEFAULT_SCREEN,
|
|
86
|
+
timezone=DEFAULT_TIMEZONE,
|
|
87
|
+
client_session_id=DEFAULT_CLIENT_SESSION_ID,
|
|
88
|
+
build_number=DEFAULT_BUILD_NUMBER,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
62
91
|
|
|
63
92
|
|
|
64
93
|
class ReplyLink(CamelModel):
|
|
@@ -276,20 +305,6 @@ class RemoveReactionPayload(CamelModel):
|
|
|
276
305
|
message_id: str
|
|
277
306
|
|
|
278
307
|
|
|
279
|
-
class UserAgentPayload(CamelModel):
|
|
280
|
-
device_type: str = Field(default=DEFAULT_DEVICE_TYPE)
|
|
281
|
-
locale: str = Field(default=DEFAULT_LOCALE)
|
|
282
|
-
device_locale: str = Field(default=DEFAULT_DEVICE_LOCALE)
|
|
283
|
-
os_version: str = Field(default=DEFAULT_OS_VERSION)
|
|
284
|
-
device_name: str = Field(default=DEFAULT_DEVICE_NAME)
|
|
285
|
-
header_user_agent: str = Field(default=DEFAULT_USER_AGENT)
|
|
286
|
-
app_version: str = Field(default=DEFAULT_APP_VERSION)
|
|
287
|
-
screen: str = Field(default=DEFAULT_SCREEN)
|
|
288
|
-
timezone: str = Field(default=DEFAULT_TIMEZONE)
|
|
289
|
-
client_session_id: int = Field(default=DEFAULT_CLIENT_SESSION_ID)
|
|
290
|
-
build_number: int = Field(default=DEFAULT_BUILD_NUMBER)
|
|
291
|
-
|
|
292
|
-
|
|
293
308
|
class ReworkInviteLinkPayload(CamelModel):
|
|
294
309
|
revoke_private_link: bool = True
|
|
295
310
|
chat_id: int
|
pymax/static/constant.py
CHANGED
|
@@ -9,7 +9,7 @@ WEBSOCKET_ORIGIN: Final[Origin] = Origin("https://web.max.ru")
|
|
|
9
9
|
HOST: Final[str] = "api.oneme.ru"
|
|
10
10
|
PORT: Final[int] = 443
|
|
11
11
|
DEFAULT_TIMEOUT: Final[float] = 20.0
|
|
12
|
-
DEFAULT_DEVICE_TYPE: Final[str] = "
|
|
12
|
+
DEFAULT_DEVICE_TYPE: Final[str] = "DESKTOP"
|
|
13
13
|
DEFAULT_LOCALE: Final[str] = "ru"
|
|
14
14
|
DEFAULT_DEVICE_LOCALE: Final[str] = "ru"
|
|
15
15
|
DEFAULT_DEVICE_NAME: Final[str] = "Chrome"
|
|
File without changes
|
|
File without changes
|