maxapi-python 1.1.4__py3-none-any.whl → 1.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxapi-python
3
- Version: 1.1.4
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>=11.0
20
+ Requires-Dist: websockets>=15.0
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
  <p align="center">
@@ -7,22 +7,22 @@ pymax/filters.py,sha256=fxC3Bl5AqrB8BSdN-CtiLWucbHCRA2IODpsOy2NagGU,1471
7
7
  pymax/interfaces.py,sha256=_D6iMQI74Gdtl6-HMoE1acRFRHeITgOZPVFpt5pvoow,2394
8
8
  pymax/models.py,sha256=PsPGbOkERxesZZltjNrmqhOfRcO44Is2ThbEToREcB8,201
9
9
  pymax/navigation.py,sha256=16c1_FZrw24uFlP6W5-F8OrEQE73bkQA3HSFqTdBtgo,5725
10
- pymax/payloads.py,sha256=yeBRxiMq6ixUQjMBBFcBDtBpYzqqfaEII3Z1kJq8pe8,3907
10
+ pymax/payloads.py,sha256=494RYjE3AjRbIrme1hcb2q5WNNcoZF13elqJeSVOW0Q,4158
11
11
  pymax/static.py,sha256=wwSV1ue5s5buqWz6TvCzjzN2ZWI-wITposTRvcS151g,4738
12
- pymax/types.py,sha256=pC87lOfRmM33ed2LY_S2c2KJTMvWJfrkTqerTp-dluk,18179
12
+ pymax/types.py,sha256=jaqhh-G2xMVVhpRCqRFat5BecPqNcZIROEGr6YICPFE,19158
13
13
  pymax/utils.py,sha256=F2TdoWfSwDLeh2uIcMIE_GTdXd7hU7gWti2i5P727bA,1364
14
14
  pymax/mixins/__init__.py,sha256=-PSMwTVioS-VTy-EGfV-epaKFLy58R4N2b-rX6wJf-M,649
15
15
  pymax/mixins/auth.py,sha256=vTNSZ6AunvDIMPQAvgYozpIZaCWMYiMDiabCBI7Sm6c,3079
16
16
  pymax/mixins/channel.py,sha256=Stnf63GPtlQnsMPVEC9P0oardEOz50I4DCXN5H5s1SM,823
17
17
  pymax/mixins/group.py,sha256=NFdq6VExTKR80tE9XzVBjuG_eRMKEdYSm2HZop5yPYE,7809
18
18
  pymax/mixins/handler.py,sha256=I1iNPaEgpvFnphaxV6liLwVaBCJ8sN6-h7908-_tPFk,2104
19
- pymax/mixins/message.py,sha256=fPQ69N-9O03BEsbhBQzHn6HTcHH3tWjR6VJtn_F2kzc,10099
19
+ pymax/mixins/message.py,sha256=sIDNpLfCPCPJNq9kZNbNTSnAIs810WI4-E9VpJeGcRU,13386
20
20
  pymax/mixins/self.py,sha256=V0gbkY3jfX9fnd7v06n4_s7P3HTPcS1KNPqUzA0vNi8,1169
21
21
  pymax/mixins/socket.py,sha256=UNMU1Fh9J3xDV76J2Mx8bVHXo0gcS81qocOw8MM0XXs,15643
22
22
  pymax/mixins/telemetry.py,sha256=0sQl6kvFVxobLthNAPNS9LzMrwwzZFA1xmOnvfiHWos,3522
23
- pymax/mixins/user.py,sha256=U-epgvLruTDHBCrLDE0N0iWeOypGE1_SU8cKD3TE90U,3045
23
+ pymax/mixins/user.py,sha256=VgAylqEeWMzgPaScakozZ1Wx0NwEnwVJLt1DtUbWaoM,4276
24
24
  pymax/mixins/websocket.py,sha256=L958bXTYh6U89jnmd0VWIUVtmJweUVFOMqjEuH8jWGo,10079
25
- maxapi_python-1.1.4.dist-info/METADATA,sha256=DziHH8WPX4Ed1xzJ8at5MEGp0kMcKORFGNlKW1bb_-0,5930
26
- maxapi_python-1.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- maxapi_python-1.1.4.dist-info/licenses/LICENSE,sha256=oe-AGp86WMKawV4KmqF28Q0m-kGAhPfAOPrEUm4MnVw,1064
28
- maxapi_python-1.1.4.dist-info/RECORD,,
25
+ maxapi_python-1.1.7.dist-info/METADATA,sha256=WRYdT7ZWOChOvAnNW6R5TK-gu8Q71MC46jL9j5fmBP0,5930
26
+ maxapi_python-1.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ maxapi_python-1.1.7.dist-info/licenses/LICENSE,sha256=oe-AGp86WMKawV4KmqF28Q0m-kGAhPfAOPrEUm4MnVw,1064
28
+ maxapi_python-1.1.7.dist-info/RECORD,,
pymax/mixins/message.py CHANGED
@@ -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
pymax/mixins/user.py CHANGED
@@ -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
pymax/payloads.py CHANGED
@@ -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
pymax/types.py CHANGED
@@ -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,