maxapi-python 1.1.3__py3-none-any.whl → 1.1.5__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.3
3
+ Version: 1.1.5
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">
@@ -3,26 +3,26 @@ pymax/core.py,sha256=6fqnP-eAgbUTM6F0Kj09TPJ3IOgvkHz5EsjJTI18r5A,7813
3
3
  pymax/crud.py,sha256=Mk-c87GItS91BlJu6INDbw1-ovXyoB2D9rXHK8voxpU,3207
4
4
  pymax/exceptions.py,sha256=msS11MD7qZPm0qZ6O8fobTm-GTldm2IA3uQLTX6eDxc,919
5
5
  pymax/files.py,sha256=Tpv-43gS7I4Pwlaimb8mZ2B-ZkF3aMsrLYT20NPaqhE,2656
6
- pymax/filters.py,sha256=EejNuJMmSBhw3bUqDoqXEnCnLjGy_sw5aH3Vynpxc0A,1306
6
+ 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=t-6xM3s8laQqBWyVdcQNV080sjfRsOtbQ-o6CKpAh20,4087
11
11
  pymax/static.py,sha256=wwSV1ue5s5buqWz6TvCzjzN2ZWI-wITposTRvcS151g,4738
12
- pymax/types.py,sha256=gQc-XYhx2zZ05FE9eIVkPqPh2rERxil96i2mTXdhEPE,17831
12
+ pymax/types.py,sha256=2huOPuGxwka_6s-Fw-7KbeAAoiFJrB7e07ArBKIRA6w,19020
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
- pymax/mixins/group.py,sha256=QJCd5MLYCVRrClcuAuRkLV3oylJRAOaGw0xUqFm2uXk,7820
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=0y7fO61zg9XG46SKxOPCOLMJW1LJBWfTi-PR3HjJfJo,10249
19
+ pymax/mixins/message.py,sha256=GtwhoZaX2JPadhE9wq7M0YHBV1V2jbBIFwtQVIWP8sY,12896
20
20
  pymax/mixins/self.py,sha256=V0gbkY3jfX9fnd7v06n4_s7P3HTPcS1KNPqUzA0vNi8,1169
21
- pymax/mixins/socket.py,sha256=sgGxX7FmLyO3M82Nu2L-n00dCn0W2fV5TnrCipiKhaE,15738
21
+ pymax/mixins/socket.py,sha256=UNMU1Fh9J3xDV76J2Mx8bVHXo0gcS81qocOw8MM0XXs,15643
22
22
  pymax/mixins/telemetry.py,sha256=0sQl6kvFVxobLthNAPNS9LzMrwwzZFA1xmOnvfiHWos,3522
23
23
  pymax/mixins/user.py,sha256=U-epgvLruTDHBCrLDE0N0iWeOypGE1_SU8cKD3TE90U,3045
24
- pymax/mixins/websocket.py,sha256=ndgcezJ1xqYCpaxc17rXxiXDQrTwTmtrgvx_d1H2Pcc,10094
25
- maxapi_python-1.1.3.dist-info/METADATA,sha256=P3lRNm2tS__wY5cMe3awWXIvKx8YC2SvmxYBSFGu-fA,5930
26
- maxapi_python-1.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- maxapi_python-1.1.3.dist-info/licenses/LICENSE,sha256=oe-AGp86WMKawV4KmqF28Q0m-kGAhPfAOPrEUm4MnVw,1064
28
- maxapi_python-1.1.3.dist-info/RECORD,,
24
+ pymax/mixins/websocket.py,sha256=L958bXTYh6U89jnmd0VWIUVtmJweUVFOMqjEuH8jWGo,10079
25
+ maxapi_python-1.1.5.dist-info/METADATA,sha256=k2lznWoJb4fgMJg_abFlOXE064a5_eZjbMDknokgZh8,5930
26
+ maxapi_python-1.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ maxapi_python-1.1.5.dist-info/licenses/LICENSE,sha256=oe-AGp86WMKawV4KmqF28Q0m-kGAhPfAOPrEUm4MnVw,1064
28
+ maxapi_python-1.1.5.dist-info/RECORD,,
pymax/filters.py CHANGED
@@ -5,6 +5,7 @@ from .types import Message
5
5
  class Filter:
6
6
  def __init__(
7
7
  self,
8
+ chat_id: int | None = None,
8
9
  user_id: int | None = None,
9
10
  text: list[str] | None = None,
10
11
  status: MessageStatus | str | None = None,
@@ -12,6 +13,7 @@ class Filter:
12
13
  text_contains: str | None = None,
13
14
  reaction_info: bool | None = None,
14
15
  ) -> None:
16
+ self.chat_id = chat_id
15
17
  self.user_id = user_id
16
18
  self.text = text
17
19
  self.status = status
@@ -20,6 +22,8 @@ class Filter:
20
22
  self.text_contains = text_contains
21
23
 
22
24
  def match(self, message: Message) -> bool:
25
+ if self.chat_id is not None and message.chat_id != self.chat_id:
26
+ return False
23
27
  if self.user_id is not None and message.sender != self.user_id:
24
28
  return False
25
29
  if self.text is not None and any(
pymax/mixins/group.py CHANGED
@@ -51,7 +51,7 @@ class GroupMixin(ClientProtocol):
51
51
  return None
52
52
 
53
53
  chat = Chat.from_dict(data["payload"]["chat"])
54
- message = Message.from_dict(data["payload"]["message"])
54
+ message = Message.from_dict(data["payload"])
55
55
 
56
56
  if chat:
57
57
  cached_chat = await self._get_chat(chat.id)
pymax/mixins/message.py CHANGED
@@ -15,9 +15,11 @@ from pymax.payloads import (
15
15
  SendMessagePayload,
16
16
  SendMessagePayloadMessage,
17
17
  UploadPhotoPayload,
18
+ GetVideoPayload,
19
+ GetFilePayload,
18
20
  )
19
21
  from pymax.static import AttachType, Opcode
20
- from pymax.types import Attach, Message
22
+ from pymax.types import Attach, Message, FileRequest, VideoRequest
21
23
 
22
24
 
23
25
  class MessageMixin(ClientProtocol):
@@ -143,11 +145,7 @@ class MessageMixin(ClientProtocol):
143
145
  self.logger.error("Send message error: %s", error)
144
146
  print(data)
145
147
  return None
146
- msg = (
147
- Message.from_dict(data["payload"]["message"])
148
- if data.get("payload")
149
- else None
150
- )
148
+ msg = Message.from_dict(data["payload"]) if data.get("payload") else None
151
149
  self.logger.debug("send_message result: %r", msg)
152
150
  return msg
153
151
  except Exception:
@@ -174,11 +172,7 @@ class MessageMixin(ClientProtocol):
174
172
  data = await self._send_and_wait(opcode=Opcode.MSG_EDIT, payload=payload)
175
173
  if error := data.get("payload", {}).get("error"):
176
174
  self.logger.error("Edit message error: %s", error)
177
- msg = (
178
- Message.from_dict(data["payload"]["message"])
179
- if data.get("payload")
180
- else None
181
- )
175
+ msg = Message.from_dict(data["payload"]) if data.get("payload") else None
182
176
  self.logger.debug("edit_message result: %r", msg)
183
177
  return msg
184
178
  except Exception:
@@ -291,3 +285,83 @@ class MessageMixin(ClientProtocol):
291
285
  except Exception:
292
286
  self.logger.exception("Fetch history failed")
293
287
  return None
288
+
289
+
290
+ async def get_video_by_id(
291
+ self,
292
+ chat_id: int,
293
+ message_id: int,
294
+ video_id: str,
295
+ ) -> VideoRequest | None:
296
+ """
297
+ Получает видео
298
+
299
+ Args:
300
+ chat_id (int): ID чата
301
+ message_id (int): ID сообщения
302
+ video_id (int): ID видео
303
+
304
+ Returns:
305
+ external (str): Странная ссылка из апи
306
+ cache (bool): True, если видео кэшировано
307
+ url (str): Ссылка на видео
308
+ """
309
+ try:
310
+ self.logger.info(
311
+ "Getting video_id=%s message_id=%s", video_id, message_id
312
+ )
313
+
314
+ payload = GetVideoPayload(
315
+ chat_id=chat_id,
316
+ message_id=message_id,
317
+ video_id=video_id
318
+ ).model_dump(by_alias=True)
319
+
320
+ data = await self._send_and_wait(opcode=Opcode.VIDEO_PLAY, payload=payload)
321
+
322
+ if error := data.get("payload", {}).get("error"):
323
+ self.logger.error("Get video error: %s", error)
324
+
325
+ video = VideoRequest.from_dict(data["payload"]) if data.get("payload") else None
326
+ self.logger.debug(" result: %r", video)
327
+ return video
328
+ except Exception:
329
+ self.logger.exception("Get video error")
330
+ return None
331
+
332
+ async def get_file_by_id(
333
+ self,
334
+ chat_id: int,
335
+ message_id: int,
336
+ file_id: str,
337
+ ) -> FileRequest | None:
338
+ """
339
+ Получает файл
340
+
341
+ Args:
342
+ chat_id (int): ID чата
343
+ message_id (int): ID сообщения
344
+ file_id (int): ID видео
345
+
346
+ Returns:
347
+ unsafe (bool): Проверка файла на безопасность максом
348
+ url (str): Ссылка на скачивание файла
349
+ """
350
+ try:
351
+ self.logger.info(
352
+ "Getting file_id=%s message_id=%s", file_id, message_id
353
+ )
354
+ payload = GetFilePayload(
355
+ chat_id=chat_id,
356
+ message_id=message_id,
357
+ file_id=file_id
358
+ ).model_dump(by_alias=True)
359
+ data = await self._send_and_wait(opcode=Opcode.FILE_DOWNLOAD, payload=payload)
360
+ if error := data.get("payload", {}).get("error"):
361
+ self.logger.error("Get file error: %s", error)
362
+ file = FileRequest.from_dict(data["payload"]) if data.get("payload") else None
363
+ self.logger.debug(" result: %r", file)
364
+ return file
365
+ except Exception:
366
+ self.logger.exception("Get video error")
367
+ return None
pymax/mixins/socket.py CHANGED
@@ -214,9 +214,7 @@ Socket connections may be unstable, SSL issues are possible.
214
214
  for handler, filter in self._on_message_handlers:
215
215
  payload = data_item.get("payload", {})
216
216
  msg_dict = (
217
- payload.get("message")
218
- if isinstance(payload, dict)
219
- else None
217
+ payload if isinstance(payload, dict) else None
220
218
  )
221
219
  msg = (
222
220
  Message.from_dict(msg_dict)
pymax/mixins/websocket.py CHANGED
@@ -118,7 +118,7 @@ class WebSocketMixin(ClientProtocol):
118
118
  try:
119
119
  for handler, filter in self._on_message_handlers:
120
120
  payload = data.get("payload", {})
121
- msg = Message.from_dict(payload.get("message"))
121
+ msg = Message.from_dict(payload)
122
122
  if msg:
123
123
  if msg.status:
124
124
  continue # TODO: заглушка! сделать отдельный хендлер
pymax/payloads.py CHANGED
@@ -193,3 +193,13 @@ class NavigationEventPayload(CamelModel):
193
193
 
194
194
  class NavigationPayload(CamelModel):
195
195
  events: list[NavigationEventPayload]
196
+
197
+ class GetVideoPayload(CamelModel):
198
+ chat_id: int
199
+ message_id: str
200
+ video_id: int
201
+
202
+ class GetFilePayload(CamelModel):
203
+ chat_id: int
204
+ message_id: str
205
+ file_id: int
pymax/types.py CHANGED
@@ -167,6 +167,46 @@ 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
+
201
+ listdata = list(data.values()) # Костыль ✅
202
+
203
+ return cls(
204
+ external=data["EXTERNAL"],
205
+ cache=data["cache"],
206
+ url=listdata[2],
207
+ )
208
+
209
+
170
210
  class Me:
171
211
  def __init__(
172
212
  self,
@@ -230,6 +270,7 @@ class Element:
230
270
  class Message:
231
271
  def __init__(
232
272
  self,
273
+ chat_id: int | None,
233
274
  sender: int | None,
234
275
  elements: list[Element] | None,
235
276
  reaction_info: dict[str, Any] | None,
@@ -241,6 +282,7 @@ class Message:
241
282
  type: MessageType | str,
242
283
  attaches: list[PhotoAttach | VideoAttach | FileAttach],
243
284
  ) -> None:
285
+ self.chat_id = chat_id
244
286
  self.sender = sender
245
287
  self.elements = elements
246
288
  self.options = options
@@ -254,8 +296,9 @@ class Message:
254
296
 
255
297
  @classmethod
256
298
  def from_dict(cls, data: dict[Any, Any]) -> "Message":
299
+ message = data["message"] if data.get("message") else data
257
300
  attaches = []
258
- for a in data.get("attaches", []):
301
+ for a in message.get("attaches", []):
259
302
  if a["_type"] == AttachType.PHOTO:
260
303
  attaches.append(PhotoAttach.from_dict(a))
261
304
  elif a["_type"] == AttachType.VIDEO:
@@ -263,16 +306,17 @@ class Message:
263
306
  elif a["_type"] == AttachType.FILE:
264
307
  attaches.append(FileAttach.from_dict(a))
265
308
  return cls(
266
- sender=data.get("sender"),
267
- elements=[Element.from_dict(e) for e in data.get("elements", [])],
268
- options=data.get("options"),
269
- id=data["id"],
270
- time=data["time"],
271
- text=data["text"],
272
- type=data["type"],
309
+ chat_id=data.get("chatId"),
310
+ sender=message.get("sender"),
311
+ elements=[Element.from_dict(e) for e in message.get("elements", [])],
312
+ options=message.get("options"),
313
+ id=message["id"],
314
+ time=message["time"],
315
+ text=message["text"],
316
+ type=message["type"],
273
317
  attaches=attaches,
274
- status=data.get("status"),
275
- reaction_info=data.get("reactionInfo"),
318
+ status=message.get("status"),
319
+ reaction_info=message.get("reactionInfo"),
276
320
  )
277
321
 
278
322
  @override
@@ -280,6 +324,7 @@ class Message:
280
324
  return (
281
325
  f"Message(id={self.id!r}, sender={self.sender!r}, text={self.text!r}, "
282
326
  f"type={self.type!r}, status={self.status!r}, elements={self.elements!r})"
327
+ f"attaches={self.attaches!r}, chat_id={self.chat_id!r}, time={self.time!r}, options={self.options!r}, reactionInfo={self.reactionInfo!r})"
283
328
  )
284
329
 
285
330
  @override
@@ -533,7 +578,7 @@ class User:
533
578
  return f"User {self.id}: {', '.join(str(n) for n in self.names)}"
534
579
 
535
580
 
536
- class Attach: # УБРАТЬ ГАДА!!!
581
+ class Attach: # УБРАТЬ ГАДА!!! или нет...
537
582
  def __init__(
538
583
  self,
539
584
  _type: AttachType,