maxapi-python 1.1.10__tar.gz → 1.1.11__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.
Files changed (45) hide show
  1. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/FUNDING.yml +1 -1
  2. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/PKG-INFO +1 -1
  3. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/examples/example.py +20 -5
  4. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/pyproject.toml +1 -1
  5. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/handler.py +2 -1
  6. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/message.py +148 -1
  7. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/payloads.py +21 -0
  8. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/types.py +42 -2
  9. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  10. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  11. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
  12. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/pull_request_template.md +0 -0
  13. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.github/workflows/publish.yml +0 -0
  14. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/.gitignore +0 -0
  15. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/LICENSE +0 -0
  16. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/README.md +0 -0
  17. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/assets/icon.svg +0 -0
  18. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/assets/logo.svg +0 -0
  19. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/docs/api.md +0 -0
  20. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/docs/assets/icon.svg +0 -0
  21. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/docs/examples.md +0 -0
  22. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/docs/index.md +0 -0
  23. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/mkdocs.yml +0 -0
  24. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/ruff.toml +0 -0
  25. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/scripts/build.py +0 -0
  26. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/__init__.py +0 -0
  27. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/core.py +0 -0
  28. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/crud.py +0 -0
  29. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/exceptions.py +0 -0
  30. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/files.py +0 -0
  31. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/filters.py +0 -0
  32. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/interfaces.py +0 -0
  33. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/__init__.py +0 -0
  34. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/auth.py +0 -0
  35. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/channel.py +0 -0
  36. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/group.py +0 -0
  37. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/self.py +0 -0
  38. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/socket.py +0 -0
  39. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/telemetry.py +0 -0
  40. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/user.py +0 -0
  41. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/mixins/websocket.py +0 -0
  42. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/models.py +0 -0
  43. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/navigation.py +0 -0
  44. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/static.py +0 -0
  45. {maxapi_python-1.1.10 → maxapi_python-1.1.11}/src/pymax/utils.py +0 -0
@@ -12,4 +12,4 @@ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cl
12
12
  polar: # Replace with a single Polar username
13
13
  buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14
14
  thanks_dev: # Replace with a single thanks.dev username
15
- custom: ['https://www.donationalerts.com/r/pymax']
15
+ custom: ['']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxapi-python
3
- Version: 1.1.10
3
+ Version: 1.1.11
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
@@ -21,12 +21,27 @@ async def handle_message(message: Message) -> None:
21
21
  @client.on_start
22
22
  async def handle_start() -> None:
23
23
  print("Client started successfully!")
24
- sessions = await client.get_sessions()
25
- for session in sessions:
26
- print(session.client)
24
+ react_info = await client.add_reaction(
25
+ chat_id=0, message_id="115368067020359151", reaction="👍"
26
+ )
27
+ if react_info:
28
+ print("Reaction added!")
29
+ print(react_info.total_count)
30
+ react_info = await client.get_reactions(
31
+ chat_id=0, message_ids=["115368067020359151"]
32
+ )
33
+ if react_info:
34
+ print("Reactions fetched!")
35
+ for msg_id, info in react_info.items():
36
+ print(f"Message ID: {msg_id}, Total Reactions: {info.total_count}")
37
+ react_info = await client.remove_reaction(
38
+ chat_id=0, message_id="115368067020359151"
39
+ )
40
+ if react_info:
41
+ print("Reaction removed!")
42
+ print(react_info.total_count)
27
43
  # print(client.dialogs)
28
- chat = await client.join_group("join/sdfdfsfdf")
29
- print(chat.title)
44
+
30
45
  # if history:
31
46
  # for message in history:
32
47
  # if message.link:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "maxapi-python"
3
- version = "1.1.10"
3
+ version = "1.1.11"
4
4
  description = "Python wrapper для API мессенджера Max"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,4 +1,5 @@
1
- from typing import Any, Awaitable, Callable
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import Any
2
3
 
3
4
  from pymax.interfaces import ClientProtocol, Filter
4
5
  from pymax.types import Message
@@ -6,20 +6,30 @@ from aiohttp import ClientSession
6
6
  from pymax.files import File, Photo, Video
7
7
  from pymax.interfaces import ClientProtocol
8
8
  from pymax.payloads import (
9
+ AddReactionPayload,
9
10
  AttachPhotoPayload,
10
11
  DeleteMessagePayload,
11
12
  EditMessagePayload,
12
13
  FetchHistoryPayload,
13
14
  GetFilePayload,
15
+ GetReactionsPayload,
14
16
  GetVideoPayload,
15
17
  PinMessagePayload,
18
+ ReactionInfoPayload,
19
+ RemoveReactionPayload,
16
20
  ReplyLink,
17
21
  SendMessagePayload,
18
22
  SendMessagePayloadMessage,
19
23
  UploadPhotoPayload,
20
24
  )
21
25
  from pymax.static import AttachType, Opcode
22
- from pymax.types import Attach, FileRequest, Message, VideoRequest
26
+ from pymax.types import (
27
+ Attach,
28
+ FileRequest,
29
+ Message,
30
+ ReactionInfo,
31
+ VideoRequest,
32
+ )
23
33
 
24
34
 
25
35
  class MessageMixin(ClientProtocol):
@@ -375,3 +385,140 @@ class MessageMixin(ClientProtocol):
375
385
  except Exception:
376
386
  self.logger.exception("Get video error")
377
387
  return None
388
+
389
+ async def add_reaction(
390
+ self,
391
+ chat_id: int,
392
+ message_id: str,
393
+ reaction: str,
394
+ ) -> ReactionInfo | None:
395
+ """
396
+ Добавляет реакцию к сообщению.
397
+
398
+ Args:
399
+ chat_id (int): ID чата
400
+ message_id (int): ID сообщения
401
+ reaction (str): Реакция (эмодзи)
402
+
403
+ Returns:
404
+ ReactionInfo | None: Информация о реакции или None при ошибке.
405
+ """
406
+ try:
407
+ self.logger.info(
408
+ "Adding reaction to message chat_id=%s message_id=%s reaction=%s",
409
+ chat_id,
410
+ message_id,
411
+ reaction,
412
+ )
413
+
414
+ payload = AddReactionPayload(
415
+ chat_id=chat_id,
416
+ message_id=message_id,
417
+ reaction=ReactionInfoPayload(id=reaction),
418
+ ).model_dump(by_alias=True)
419
+
420
+ data = await self._send_and_wait(
421
+ opcode=Opcode.MSG_REACTION, payload=payload
422
+ )
423
+ if error := data.get("payload", {}).get("error"):
424
+ self.logger.error("Add reaction error: %s", error)
425
+ return None
426
+
427
+ self.logger.debug("add_reaction success")
428
+ return (
429
+ ReactionInfo.from_dict(data["payload"]["reactionInfo"])
430
+ if data.get("payload")
431
+ else None
432
+ )
433
+ except Exception:
434
+ self.logger.exception("Add reaction failed")
435
+ return None
436
+
437
+ async def get_reactions(
438
+ self, chat_id: int, message_ids: list[str]
439
+ ) -> dict[str, ReactionInfo] | None:
440
+ """
441
+ Получает реакции на сообщения.
442
+
443
+ Args:
444
+ chat_id (int): ID чата
445
+ message_ids (list[str]): Список ID сообщений
446
+
447
+ Returns:
448
+ dict[str, ReactionInfo] | None: Словарь с ID сообщений и информацией о реакциях или None при ошибке.
449
+ """
450
+ try:
451
+ self.logger.info(
452
+ "Getting reactions for messages chat_id=%s message_ids=%s",
453
+ chat_id,
454
+ message_ids,
455
+ )
456
+
457
+ payload = GetReactionsPayload(
458
+ chat_id=chat_id, message_ids=message_ids
459
+ ).model_dump(by_alias=True)
460
+
461
+ data = await self._send_and_wait(
462
+ opcode=Opcode.MSG_GET_REACTIONS, payload=payload
463
+ )
464
+ if error := data.get("payload", {}).get("error"):
465
+ self.logger.error("Get reactions error: %s", error)
466
+ return None
467
+
468
+ reactions = {}
469
+ for msg_id, reaction_data in (
470
+ data.get("payload", {}).get("messagesReactions", {}).items()
471
+ ):
472
+ reactions[msg_id] = ReactionInfo.from_dict(reaction_data)
473
+
474
+ self.logger.debug("get_reactions success")
475
+
476
+ return reactions
477
+
478
+ except Exception:
479
+ self.logger.exception("Get reactions failed")
480
+ return None
481
+
482
+ async def remove_reaction(
483
+ self,
484
+ chat_id: int,
485
+ message_id: str,
486
+ ) -> ReactionInfo | None:
487
+ """
488
+ Удаляет реакцию с сообщения.
489
+
490
+ Args:
491
+ chat_id (int): ID чата
492
+ message_id (str): ID сообщения
493
+
494
+ Returns:
495
+ ReactionInfo | None: Информация о реакции или None при ошибке.
496
+ """
497
+ try:
498
+ self.logger.info(
499
+ "Removing reaction from message chat_id=%s message_id=%s",
500
+ chat_id,
501
+ message_id,
502
+ )
503
+
504
+ payload = RemoveReactionPayload(
505
+ chat_id=chat_id,
506
+ message_id=message_id,
507
+ ).model_dump(by_alias=True)
508
+
509
+ data = await self._send_and_wait(
510
+ opcode=Opcode.MSG_CANCEL_REACTION, payload=payload
511
+ )
512
+ if error := data.get("payload", {}).get("error"):
513
+ self.logger.error("Remove reaction error: %s", error)
514
+ return None
515
+
516
+ self.logger.debug("remove_reaction success")
517
+ return (
518
+ ReactionInfo.from_dict(data["payload"]["reactionInfo"])
519
+ if data.get("payload")
520
+ else None
521
+ )
522
+ except Exception:
523
+ self.logger.exception("Remove reaction failed")
524
+ return None
@@ -213,3 +213,24 @@ class SearchByPhonePayload(CamelModel):
213
213
 
214
214
  class JoinGroupPayload(CamelModel):
215
215
  link: str
216
+
217
+
218
+ class ReactionInfoPayload(CamelModel):
219
+ reaction_type: str = "EMOJI"
220
+ id: str
221
+
222
+
223
+ class AddReactionPayload(CamelModel):
224
+ chat_id: int
225
+ message_id: str
226
+ reaction: ReactionInfoPayload
227
+
228
+
229
+ class GetReactionsPayload(CamelModel):
230
+ chat_id: int
231
+ message_ids: list[str]
232
+
233
+
234
+ class RemoveReactionPayload(CamelModel):
235
+ chat_id: int
236
+ message_id: str
@@ -317,13 +317,51 @@ class MessageLink:
317
317
  return f"MessageLink: {self.chat_id}/{self.message.id}"
318
318
 
319
319
 
320
+ class ReactionCounter:
321
+ def __init__(self, count: int, reaction: str) -> None:
322
+ self.count = count
323
+ self.reaction = reaction
324
+
325
+ @classmethod
326
+ def from_dict(cls, data: dict[str, Any]) -> "ReactionCounter":
327
+ return cls(count=data["count"], reaction=data["reaction"])
328
+
329
+ @override
330
+ def __repr__(self) -> str:
331
+ return f"ReactionCounter(count={self.count!r}, reaction={self.reaction!r})"
332
+
333
+ @override
334
+ def __str__(self) -> str:
335
+ return f"{self.reaction}: {self.count}"
336
+
337
+
338
+ class ReactionInfo:
339
+ def __init__(
340
+ self,
341
+ total_count: int,
342
+ counters: list[ReactionCounter],
343
+ your_reaction: str | None = None,
344
+ ) -> None:
345
+ self.total_count = total_count
346
+ self.counters = counters
347
+ self.your_reaction = your_reaction
348
+
349
+ @classmethod
350
+ def from_dict(cls, data: dict[str, Any]) -> "ReactionInfo":
351
+ return cls(
352
+ total_count=data.get("totalCount", 0),
353
+ counters=[ReactionCounter.from_dict(c) for c in data.get("counters", [])],
354
+ your_reaction=data.get("yourReaction"),
355
+ )
356
+
357
+
320
358
  class Message:
321
359
  def __init__(
322
360
  self,
323
361
  chat_id: int | None,
324
362
  sender: int | None,
325
363
  elements: list[Element] | None,
326
- reaction_info: dict[str, Any] | None,
364
+ reaction_info: ReactionInfo | None,
327
365
  options: int | None,
328
366
  id: int,
329
367
  time: int,
@@ -373,7 +411,9 @@ class Message:
373
411
  link=MessageLink.from_dict(message.get("link"))
374
412
  if message.get("link")
375
413
  else None,
376
- reaction_info=message.get("reactionInfo"),
414
+ reaction_info=ReactionInfo.from_dict(message.get("reactionInfo"))
415
+ if message.get("reactionInfo")
416
+ else None,
377
417
  )
378
418
 
379
419
  @override
File without changes
File without changes
File without changes