maxapi-python 1.1.4__tar.gz → 1.1.7__tar.gz
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.1.4 → maxapi_python-1.1.7}/.gitignore +4 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/PKG-INFO +2 -2
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/examples/example.py +21 -36
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/pyproject.toml +2 -2
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/message.py +94 -2
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/user.py +36 -1
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/payloads.py +16 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/types.py +42 -1
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/.github/FUNDING.yml +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/.github/workflows/publish.yml +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/LICENSE +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/README.md +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/assets/icon.svg +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/assets/logo.svg +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/docs/api.md +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/docs/assets/icon.svg +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/docs/examples.md +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/docs/index.md +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/mkdocs.yml +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/ruff.toml +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/scripts/build.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/__init__.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/core.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/crud.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/exceptions.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/files.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/filters.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/interfaces.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/__init__.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/auth.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/channel.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/group.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/handler.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/self.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/socket.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/telemetry.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/mixins/websocket.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/models.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/navigation.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/static.py +0 -0
- {maxapi_python-1.1.4 → maxapi_python-1.1.7}/src/pymax/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: maxapi-python
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.7
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
5
5
|
Project-URL: Homepage, https://github.com/noxzion/PyMax
|
6
6
|
Project-URL: Repository, https://github.com/noxzion/PyMax
|
@@ -17,7 +17,7 @@ Requires-Dist: aiohttp>=3.12.15
|
|
17
17
|
Requires-Dist: lz4>=4.4.4
|
18
18
|
Requires-Dist: msgpack>=1.1.1
|
19
19
|
Requires-Dist: sqlmodel>=0.0.24
|
20
|
-
Requires-Dist: websockets>=
|
20
|
+
Requires-Dist: websockets>=15.0
|
21
21
|
Description-Content-Type: text/markdown
|
22
22
|
|
23
23
|
<p align="center">
|
@@ -13,31 +13,6 @@ client = MaxClient(phone=phone, work_dir="cache")
|
|
13
13
|
# client = SocketMaxClient(phone=phone, work_dir="cache")
|
14
14
|
|
15
15
|
|
16
|
-
async def main() -> None:
|
17
|
-
for chat in client.chats:
|
18
|
-
print(chat.title)
|
19
|
-
|
20
|
-
message = await client.send_message(
|
21
|
-
"Hello from MaxClient!", chat.id, notify=True
|
22
|
-
)
|
23
|
-
|
24
|
-
await asyncio.sleep(5)
|
25
|
-
message = await client.edit_message(
|
26
|
-
chat.id, message.id, "Hello from MaxClient! (edited)"
|
27
|
-
)
|
28
|
-
await asyncio.sleep(5)
|
29
|
-
|
30
|
-
await client.delete_message(chat.id, [message.id], for_me=False)
|
31
|
-
|
32
|
-
for dialog in client.dialogs:
|
33
|
-
print(dialog.last_message.text)
|
34
|
-
|
35
|
-
for channel in client.channels:
|
36
|
-
print(channel.title)
|
37
|
-
|
38
|
-
await client.close()
|
39
|
-
|
40
|
-
|
41
16
|
@client.on_message(filter=Filter(chat_id=0))
|
42
17
|
async def handle_message(message: Message) -> None:
|
43
18
|
print(str(message.sender) + ": " + message.text)
|
@@ -46,17 +21,27 @@ async def handle_message(message: Message) -> None:
|
|
46
21
|
@client.on_start
|
47
22
|
async def handle_start() -> None:
|
48
23
|
print("Client started successfully!")
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
24
|
+
# print(client.dialogs)
|
25
|
+
# history = await client.fetch_history(chat_id=0)
|
26
|
+
# if history:
|
27
|
+
# for message in history:
|
28
|
+
# if message.attaches:
|
29
|
+
# for attach in message.attaches:
|
30
|
+
# if attach.type == AttachType.VIDEO:
|
31
|
+
# print(message)
|
32
|
+
# vid = await client.get_video_by_id(
|
33
|
+
# chat_id=0,
|
34
|
+
# video_id=attach.video_id,
|
35
|
+
# message_id=message.id,
|
36
|
+
# )
|
37
|
+
# print(vid.url)
|
38
|
+
# elif attach.type == AttachType.FILE:
|
39
|
+
# file = await client.get_file_by_id(
|
40
|
+
# chat_id=0,
|
41
|
+
# file_id=attach.file_id,
|
42
|
+
# message_id=message.id,
|
43
|
+
# )
|
44
|
+
# print(file.url)
|
60
45
|
# print(client.me.names[0].first_name)
|
61
46
|
# user = await client.get_user(client.me.id)
|
62
47
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "maxapi-python"
|
3
|
-
version = "1.1.
|
3
|
+
version = "1.1.7"
|
4
4
|
description = "Python wrapper для API мессенджера Max"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
16
16
|
]
|
17
17
|
dependencies = [
|
18
18
|
"sqlmodel>=0.0.24",
|
19
|
-
"websockets>=
|
19
|
+
"websockets>=15.0",
|
20
20
|
"msgpack>=1.1.1",
|
21
21
|
"lz4>=4.4.4",
|
22
22
|
"aiohttp>=3.12.15",
|
@@ -10,6 +10,8 @@ from pymax.payloads import (
|
|
10
10
|
DeleteMessagePayload,
|
11
11
|
EditMessagePayload,
|
12
12
|
FetchHistoryPayload,
|
13
|
+
GetFilePayload,
|
14
|
+
GetVideoPayload,
|
13
15
|
PinMessagePayload,
|
14
16
|
ReplyLink,
|
15
17
|
SendMessagePayload,
|
@@ -17,7 +19,7 @@ from pymax.payloads import (
|
|
17
19
|
UploadPhotoPayload,
|
18
20
|
)
|
19
21
|
from pymax.static import AttachType, Opcode
|
20
|
-
from pymax.types import Attach, Message
|
22
|
+
from pymax.types import Attach, FileRequest, Message, VideoRequest
|
21
23
|
|
22
24
|
|
23
25
|
class MessageMixin(ClientProtocol):
|
@@ -141,7 +143,6 @@ class MessageMixin(ClientProtocol):
|
|
141
143
|
data = await self._send_and_wait(opcode=Opcode.MSG_SEND, payload=payload)
|
142
144
|
if error := data.get("payload", {}).get("error"):
|
143
145
|
self.logger.error("Send message error: %s", error)
|
144
|
-
print(data)
|
145
146
|
return None
|
146
147
|
msg = Message.from_dict(data["payload"]) if data.get("payload") else None
|
147
148
|
self.logger.debug("send_message result: %r", msg)
|
@@ -283,3 +284,94 @@ class MessageMixin(ClientProtocol):
|
|
283
284
|
except Exception:
|
284
285
|
self.logger.exception("Fetch history failed")
|
285
286
|
return None
|
287
|
+
|
288
|
+
async def get_video_by_id(
|
289
|
+
self,
|
290
|
+
chat_id: int,
|
291
|
+
message_id: int,
|
292
|
+
video_id: int,
|
293
|
+
) -> VideoRequest | None:
|
294
|
+
"""
|
295
|
+
Получает видео
|
296
|
+
|
297
|
+
Args:
|
298
|
+
chat_id (int): ID чата
|
299
|
+
message_id (int): ID сообщения
|
300
|
+
video_id (int): ID видео
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
external (str): Странная ссылка из апи
|
304
|
+
cache (bool): True, если видео кэшировано
|
305
|
+
url (str): Ссылка на видео
|
306
|
+
"""
|
307
|
+
try:
|
308
|
+
self.logger.info("Getting video_id=%s message_id=%s", video_id, message_id)
|
309
|
+
|
310
|
+
if self.is_connected and self._socket is not None:
|
311
|
+
payload = GetVideoPayload(
|
312
|
+
chat_id=chat_id, message_id=message_id, video_id=video_id
|
313
|
+
).model_dump(by_alias=True)
|
314
|
+
else:
|
315
|
+
payload = GetVideoPayload(
|
316
|
+
chat_id=chat_id, message_id=str(message_id), video_id=video_id
|
317
|
+
).model_dump(by_alias=True)
|
318
|
+
|
319
|
+
data = await self._send_and_wait(opcode=Opcode.VIDEO_PLAY, payload=payload)
|
320
|
+
|
321
|
+
if error := data.get("payload", {}).get("error"):
|
322
|
+
self.logger.error("Get video error: %s", error)
|
323
|
+
return
|
324
|
+
|
325
|
+
video = (
|
326
|
+
VideoRequest.from_dict(data["payload"]) if data.get("payload") else None
|
327
|
+
)
|
328
|
+
self.logger.debug("result: %r", video)
|
329
|
+
return video
|
330
|
+
except Exception:
|
331
|
+
self.logger.exception("Get video error")
|
332
|
+
return None
|
333
|
+
|
334
|
+
async def get_file_by_id(
|
335
|
+
self,
|
336
|
+
chat_id: int,
|
337
|
+
message_id: int,
|
338
|
+
file_id: int,
|
339
|
+
) -> FileRequest | None:
|
340
|
+
"""
|
341
|
+
Получает файл
|
342
|
+
|
343
|
+
Args:
|
344
|
+
chat_id (int): ID чата
|
345
|
+
message_id (int): ID сообщения
|
346
|
+
file_id (int): ID видео
|
347
|
+
|
348
|
+
Returns:
|
349
|
+
unsafe (bool): Проверка файла на безопасность максом
|
350
|
+
url (str): Ссылка на скачивание файла
|
351
|
+
"""
|
352
|
+
try:
|
353
|
+
self.logger.info("Getting file_id=%s message_id=%s", file_id, message_id)
|
354
|
+
if self.is_connected and self._socket is not None:
|
355
|
+
payload = GetFilePayload(
|
356
|
+
chat_id=chat_id, message_id=message_id, file_id=file_id
|
357
|
+
).model_dump(by_alias=True)
|
358
|
+
else:
|
359
|
+
payload = GetFilePayload(
|
360
|
+
chat_id=chat_id, message_id=str(message_id), file_id=file_id
|
361
|
+
).model_dump(by_alias=True)
|
362
|
+
data = await self._send_and_wait(
|
363
|
+
opcode=Opcode.FILE_DOWNLOAD, payload=payload
|
364
|
+
)
|
365
|
+
|
366
|
+
if error := data.get("payload", {}).get("error"):
|
367
|
+
self.logger.error("Get file error: %s", error)
|
368
|
+
return
|
369
|
+
|
370
|
+
file = (
|
371
|
+
FileRequest.from_dict(data["payload"]) if data.get("payload") else None
|
372
|
+
)
|
373
|
+
self.logger.debug(" result: %r", file)
|
374
|
+
return file
|
375
|
+
except Exception:
|
376
|
+
self.logger.exception("Get video error")
|
377
|
+
return None
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from pymax.interfaces import ClientProtocol
|
2
|
-
from pymax.payloads import FetchContactsPayload
|
2
|
+
from pymax.payloads import FetchContactsPayload, SearchByPhonePayload
|
3
3
|
from pymax.static import Opcode
|
4
4
|
from pymax.types import User
|
5
5
|
|
@@ -80,3 +80,38 @@ class UserMixin(ClientProtocol):
|
|
80
80
|
except Exception:
|
81
81
|
self.logger.exception("Fetch users failed")
|
82
82
|
return []
|
83
|
+
|
84
|
+
async def search_by_phone(self, phone: str) -> User | None:
|
85
|
+
"""
|
86
|
+
Ищет пользователя по номеру телефона.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
phone (str): Номер телефона.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
User | None: Объект User или None при ошибке.
|
93
|
+
"""
|
94
|
+
try:
|
95
|
+
self.logger.info("Searching user by phone: %s", phone)
|
96
|
+
|
97
|
+
payload = SearchByPhonePayload(phone=phone).model_dump(by_alias=True)
|
98
|
+
|
99
|
+
data = await self._send_and_wait(
|
100
|
+
opcode=Opcode.CONTACT_INFO_BY_PHONE, payload=payload
|
101
|
+
)
|
102
|
+
if error := data.get("payload", {}).get("error"):
|
103
|
+
self.logger.error("Search by phone error: %s", error)
|
104
|
+
return None
|
105
|
+
|
106
|
+
user = (
|
107
|
+
User.from_dict(data["payload"]["contact"])
|
108
|
+
if data.get("payload")
|
109
|
+
else None
|
110
|
+
)
|
111
|
+
if user:
|
112
|
+
self._users[user.id] = user
|
113
|
+
self.logger.debug("Found user by phone: %s", user)
|
114
|
+
return user
|
115
|
+
except Exception:
|
116
|
+
self.logger.exception("Search by phone failed")
|
117
|
+
return None
|
@@ -193,3 +193,19 @@ class NavigationEventPayload(CamelModel):
|
|
193
193
|
|
194
194
|
class NavigationPayload(CamelModel):
|
195
195
|
events: list[NavigationEventPayload]
|
196
|
+
|
197
|
+
|
198
|
+
class GetVideoPayload(CamelModel):
|
199
|
+
chat_id: int
|
200
|
+
message_id: int | str
|
201
|
+
video_id: int
|
202
|
+
|
203
|
+
|
204
|
+
class GetFilePayload(CamelModel):
|
205
|
+
chat_id: int
|
206
|
+
message_id: str | int
|
207
|
+
file_id: int
|
208
|
+
|
209
|
+
|
210
|
+
class SearchByPhonePayload(CamelModel):
|
211
|
+
phone: str
|
@@ -167,6 +167,47 @@ class FileAttach:
|
|
167
167
|
return f"FileAttach: {self.file_id}"
|
168
168
|
|
169
169
|
|
170
|
+
class FileRequest:
|
171
|
+
def __init__(
|
172
|
+
self,
|
173
|
+
unsafe: bool,
|
174
|
+
url: str,
|
175
|
+
) -> None:
|
176
|
+
self.unsafe = unsafe
|
177
|
+
self.url = url
|
178
|
+
|
179
|
+
@classmethod
|
180
|
+
def from_dict(cls, data: dict[str, Any]) -> "FileRequest":
|
181
|
+
return cls(
|
182
|
+
unsafe=data["unsafe"],
|
183
|
+
url=data["url"],
|
184
|
+
)
|
185
|
+
|
186
|
+
|
187
|
+
class VideoRequest:
|
188
|
+
def __init__(
|
189
|
+
self,
|
190
|
+
external: str,
|
191
|
+
cache: bool,
|
192
|
+
url: str,
|
193
|
+
) -> None:
|
194
|
+
self.external = external
|
195
|
+
self.cache = cache
|
196
|
+
self.url = url
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def from_dict(cls, data: dict[str, Any]) -> "VideoRequest":
|
200
|
+
# listdata = list(data.values()) # Костыль ✅
|
201
|
+
url = [v for k, v in data.items() if k not in ("EXTERNAL", "cache")][
|
202
|
+
0
|
203
|
+
] # Еще больший костыль ✅
|
204
|
+
return cls(
|
205
|
+
external=data["EXTERNAL"],
|
206
|
+
cache=data["cache"],
|
207
|
+
url=url,
|
208
|
+
)
|
209
|
+
|
210
|
+
|
170
211
|
class Me:
|
171
212
|
def __init__(
|
172
213
|
self,
|
@@ -538,7 +579,7 @@ class User:
|
|
538
579
|
return f"User {self.id}: {', '.join(str(n) for n in self.names)}"
|
539
580
|
|
540
581
|
|
541
|
-
class Attach: # УБРАТЬ ГАДА!!!
|
582
|
+
class Attach: # УБРАТЬ ГАДА!!! или нет...
|
542
583
|
def __init__(
|
543
584
|
self,
|
544
585
|
_type: AttachType,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|