maxapi-python 2.1.2__py3-none-any.whl → 2.2.0__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.
Files changed (66) hide show
  1. {maxapi_python-2.1.2.dist-info → maxapi_python-2.2.0.dist-info}/METADATA +3 -11
  2. {maxapi_python-2.1.2.dist-info → maxapi_python-2.2.0.dist-info}/RECORD +66 -60
  3. pymax/__init__.py +18 -3
  4. pymax/api/auth/payloads.py +7 -0
  5. pymax/api/auth/service.py +33 -30
  6. pymax/api/binding.py +57 -0
  7. pymax/api/chats/service.py +34 -47
  8. pymax/api/messages/enums.py +1 -0
  9. pymax/api/messages/payloads.py +16 -1
  10. pymax/api/messages/service.py +85 -33
  11. pymax/api/models.py +4 -6
  12. pymax/api/response.py +2 -2
  13. pymax/api/self/service.py +17 -26
  14. pymax/api/session/payloads.py +2 -9
  15. pymax/api/session/service.py +1 -3
  16. pymax/api/uploads/payloads.py +3 -9
  17. pymax/api/uploads/service.py +33 -99
  18. pymax/api/users/service.py +8 -16
  19. pymax/app.py +20 -4
  20. pymax/auth/qr.py +3 -9
  21. pymax/auth/sms.py +23 -11
  22. pymax/base.py +38 -1
  23. pymax/client.py +3 -5
  24. pymax/client_web.py +1 -2
  25. pymax/config.py +42 -3
  26. pymax/connection/connection.py +48 -19
  27. pymax/connection/readers/tcp.py +1 -3
  28. pymax/dispatch/dispatcher.py +36 -18
  29. pymax/dispatch/enums.py +4 -0
  30. pymax/dispatch/mapping.py +34 -11
  31. pymax/dispatch/resolvers.py +18 -0
  32. pymax/dispatch/router.py +34 -0
  33. pymax/files/photo.py +4 -2
  34. pymax/formatting/markdown.py +22 -13
  35. pymax/infra/chat.py +12 -0
  36. pymax/infra/message.py +74 -3
  37. pymax/logging.py +35 -3
  38. pymax/protocol/tcp/compression.py +1 -3
  39. pymax/protocol/tcp/framing.py +1 -3
  40. pymax/protocol/tcp/payload.py +22 -42
  41. pymax/protocol/tcp/protocol.py +2 -8
  42. pymax/protocol/ws/protocol.py +3 -9
  43. pymax/session/protocol.py +2 -6
  44. pymax/session/store.py +8 -24
  45. pymax/telemetry/navigation.py +1 -3
  46. pymax/telemetry/service.py +5 -17
  47. pymax/transport/tcp.py +1 -3
  48. pymax/types/domain/attachments/__init__.py +1 -0
  49. pymax/types/domain/attachments/audio.py +4 -4
  50. pymax/types/domain/attachments/enums.py +1 -0
  51. pymax/types/domain/attachments/unknown.py +35 -0
  52. pymax/types/domain/attachments/video.py +2 -2
  53. pymax/types/domain/auth.py +24 -2
  54. pymax/types/domain/chat.py +38 -1
  55. pymax/types/domain/element.py +3 -3
  56. pymax/types/domain/message.py +34 -2
  57. pymax/types/domain/presence.py +3 -3
  58. pymax/types/domain/sync.py +5 -21
  59. pymax/types/events/__init__.py +4 -0
  60. pymax/types/events/mark.py +23 -0
  61. pymax/types/events/message.py +57 -5
  62. pymax/types/events/presence.py +15 -0
  63. pymax/types/events/reaction.py +21 -0
  64. pymax/types/events/typing.py +14 -0
  65. {maxapi_python-2.1.2.dist-info → maxapi_python-2.2.0.dist-info}/WHEEL +0 -0
  66. {maxapi_python-2.1.2.dist-info → maxapi_python-2.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,6 +15,7 @@ from pymax.types.domain import (
15
15
  PhotoAttachment,
16
16
  ShareAttachment,
17
17
  StickerAttachment,
18
+ UnknownAttachment,
18
19
  VideoAttachment,
19
20
  )
20
21
 
@@ -26,7 +27,7 @@ if TYPE_CHECKING:
26
27
  from pymax.api.messages.service import MessageService
27
28
 
28
29
 
29
- Attachment: TypeAlias = Annotated[
30
+ KnownAttachment: TypeAlias = Annotated[
30
31
  PhotoAttachment
31
32
  | VideoAttachment
32
33
  | FileAttachment
@@ -39,6 +40,7 @@ Attachment: TypeAlias = Annotated[
39
40
  | CallAttachment,
40
41
  Field(discriminator="type"),
41
42
  ]
43
+ Attachment: TypeAlias = KnownAttachment | UnknownAttachment
42
44
  SendAttachment: TypeAlias = Photo | File | Video
43
45
  SendAttachments: TypeAlias = list[SendAttachment] | None
44
46
 
@@ -90,7 +92,7 @@ class Message(CamelModel):
90
92
 
91
93
  Сообщения, полученные через клиент, обычно уже привязаны к сервису
92
94
  сообщений. После этого можно вызывать удобные методы объекта:
93
- :meth:`reply`, :meth:`answer`, :meth:`pin`, :meth:`delete`,
95
+ :meth:`reply`, :meth:`answer`, :meth:`edit`, :meth:`pin`, :meth:`delete`,
94
96
  :meth:`read`, :meth:`react`, :meth:`unreact` и :meth:`get_reactions`.
95
97
 
96
98
  Используйте ``Message`` в обработчиках ``on_message`` и при работе с
@@ -259,6 +261,36 @@ class Message(CamelModel):
259
261
  notify_pin=notify_pin,
260
262
  )
261
263
 
264
+ async def edit(
265
+ self,
266
+ text: str,
267
+ attachment: SendAttachment | None = None,
268
+ attachments: SendAttachments = None,
269
+ ) -> Message:
270
+ """Редактирует текст и вложения этого сообщения.
271
+
272
+ :param text: Новый текст сообщения с поддержкой markdown.
273
+ :type text: str
274
+ :param attachment: Одно новое вложение.
275
+ :type attachment: SendAttachment | None
276
+ :param attachments: Список новых вложений. Имеет приоритет над
277
+ ``attachment``.
278
+ :type attachments: SendAttachments
279
+ :returns: Отредактированное сообщение.
280
+ :rtype: Message
281
+ :raises RuntimeError: Если сообщение не привязано к сервису или не
282
+ содержит ``chat_id``.
283
+ """
284
+ actions, chat_id = self._bound()
285
+
286
+ return await actions.edit_message(
287
+ chat_id=chat_id,
288
+ message_id=self.id,
289
+ text=text,
290
+ attachment=attachment,
291
+ attachments=attachments,
292
+ )
293
+
262
294
  async def delete(self, for_me: bool = False) -> bool:
263
295
  """Удаляет это сообщение.
264
296
 
@@ -7,9 +7,9 @@ class Presence(CamelModel):
7
7
  :ivar seen: Время последней активности в формате Unix time, если оно
8
8
  передано сервером.
9
9
  :vartype seen: int | None
10
- :ivar status: Код статуса присутствия Max.
11
- :vartype status: int
10
+ :ivar status: Код статуса присутствия Max, если он передан сервером.
11
+ :vartype status: int | None
12
12
  """
13
13
 
14
14
  seen: int | None = None
15
- status: int
15
+ status: int | None = None
@@ -68,29 +68,13 @@ class SyncOverrides(BaseModel):
68
68
  :rtype: SyncState
69
69
  """
70
70
  return SyncState(
71
- chats_sync=(
72
- self.chats_sync
73
- if self.chats_sync is not None
74
- else saved.chats_sync
75
- ),
71
+ chats_sync=(self.chats_sync if self.chats_sync is not None else saved.chats_sync),
76
72
  contacts_sync=(
77
- self.contacts_sync
78
- if self.contacts_sync is not None
79
- else saved.contacts_sync
80
- ),
81
- drafts_sync=(
82
- self.drafts_sync
83
- if self.drafts_sync is not None
84
- else saved.drafts_sync
73
+ self.contacts_sync if self.contacts_sync is not None else saved.contacts_sync
85
74
  ),
75
+ drafts_sync=(self.drafts_sync if self.drafts_sync is not None else saved.drafts_sync),
86
76
  presence_sync=(
87
- self.presence_sync
88
- if self.presence_sync is not None
89
- else saved.presence_sync
90
- ),
91
- config_hash=(
92
- self.config_hash
93
- if self.config_hash is not None
94
- else saved.config_hash
77
+ self.presence_sync if self.presence_sync is not None else saved.presence_sync
95
78
  ),
79
+ config_hash=(self.config_hash if self.config_hash is not None else saved.config_hash),
96
80
  )
@@ -1,3 +1,7 @@
1
1
  from .file import FileUploadSignal
2
+ from .mark import MessageReadEvent
2
3
  from .message import MessageDeleteEvent
4
+ from .presence import PresenceEvent
5
+ from .reaction import ReactionUpdateEvent
6
+ from .typing import TypingEvent
3
7
  from .video import VideoUploadSignal
@@ -0,0 +1,23 @@
1
+ from pymax.types.domain.base import CamelModel
2
+
3
+
4
+ class MessageReadEvent(CamelModel):
5
+ """Событие изменения отметки прочтения чата.
6
+
7
+ Handler ``on_message_read`` получает объект, когда пользователь отмечает
8
+ сообщения прочитанными или возвращает чат в непрочитанное состояние.
9
+
10
+ :ivar set_as_unread: Чат был явно отмечен непрочитанным.
11
+ :vartype set_as_unread: bool
12
+ :ivar chat_id: ID чата.
13
+ :vartype chat_id: int
14
+ :ivar user_id: ID пользователя, изменившего отметку.
15
+ :vartype user_id: int
16
+ :ivar mark: Временная отметка прочтения Max.
17
+ :vartype mark: int
18
+ """
19
+
20
+ set_as_unread: bool
21
+ chat_id: int
22
+ user_id: int
23
+ mark: int
@@ -1,15 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
- from pydantic import PrivateAttr
5
+ from pydantic import PrivateAttr, model_validator
6
6
 
7
+ from pymax.logging import get_logger
7
8
  from pymax.types.domain import Chat
8
9
  from pymax.types.domain.base import CamelModel
10
+ from pymax.types.domain.message import Message
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  from pymax.api.messages import MessageService
12
14
 
15
+ logger = get_logger(__name__)
16
+
13
17
 
14
18
  class MessageDeleteEvent(CamelModel):
15
19
  """Событие удаления сообщений.
@@ -17,20 +21,68 @@ class MessageDeleteEvent(CamelModel):
17
21
  Handler ``on_message_delete`` получает этот объект, когда Max сообщает об
18
22
  удалении одного или нескольких сообщений в чате.
19
23
 
20
- :ivar chat: Чат, в котором удалены сообщения.
21
- :vartype chat: Chat
22
24
  :ivar message_ids: ID удаленных сообщений.
23
25
  :vartype message_ids: list[int]
26
+ :ivar chat_id: ID чата.
27
+ :vartype chat_id: int
28
+ :ivar chat: Чат, если Max прислал полный объект.
29
+ :vartype chat: Chat | None
30
+ :ivar message: Удаленное сообщение для WebSocket-события.
31
+ :vartype message: Message | None
24
32
  :ivar ttl: Признак удаления из-за TTL, если Max его прислал.
25
33
  :vartype ttl: bool
26
34
  """
27
35
 
28
- chat: Chat
29
36
  message_ids: list[int]
37
+ chat_id: int
38
+ chat: Chat | None = None
39
+ message: Message | None = None
30
40
  ttl: bool = False
31
41
 
32
42
  _actions: MessageService | None = PrivateAttr(default=None)
33
43
 
44
+ @model_validator(mode="before")
45
+ @classmethod
46
+ def normalize_payload(cls, data: Any) -> Any:
47
+ # i really hate it cause of stupid web version thats send other type
48
+ # of payload (128, expect 142)
49
+ # TODO: impl it in the better way maybe
50
+
51
+ if not isinstance(data, dict):
52
+ return data
53
+
54
+ if "chat" in data: # case opcode == 142
55
+ chat = data["chat"]
56
+ chat_id = chat.get("id") if isinstance(chat, dict) else getattr(chat, "id", None)
57
+ message_ids = data.get("messageIds", data.get("message_ids"))
58
+ if chat_id is None or message_ids is None:
59
+ return data
60
+
61
+ return {
62
+ "chat": chat,
63
+ "ttl": data.get("ttl", False),
64
+ "messageIds": message_ids,
65
+ "chatId": chat_id,
66
+ }
67
+ if "message" in data: # case opcode == 128
68
+ message = data["message"]
69
+ message_id = (
70
+ message.get("id") if isinstance(message, dict) else getattr(message, "id", None)
71
+ )
72
+ chat_id = data.get("chatId", data.get("chat_id"))
73
+ if chat_id is None or message_id is None:
74
+ return data
75
+
76
+ return {
77
+ "chatId": chat_id,
78
+ "message": message,
79
+ "ttl": data.get("ttl", False),
80
+ "messageIds": [message_id],
81
+ }
82
+
83
+ logger.warning("Illegal state during MessageDeleteEvent validation. Starting fallback")
84
+ return data # stupid fallback but who cares. Still better than KeyError
85
+
34
86
  def bind(self, actions: MessageService) -> MessageDeleteEvent:
35
87
  """Привязывает сервис сообщений к событию удаления."""
36
88
  self._actions = actions
@@ -0,0 +1,15 @@
1
+ from pymax.types.domain.base import CamelModel
2
+ from pymax.types.domain.presence import Presence
3
+
4
+
5
+ class PresenceEvent(CamelModel):
6
+ """Событие изменения присутствия пользователя.
7
+
8
+ :ivar presence: Новое состояние присутствия.
9
+ :vartype presence: Presence
10
+ :ivar user_id: ID пользователя.
11
+ :vartype user_id: int
12
+ """
13
+
14
+ presence: Presence
15
+ user_id: int
@@ -0,0 +1,21 @@
1
+ from pymax.types.domain.base import CamelModel
2
+ from pymax.types.domain.message import ReactionCounter
3
+
4
+
5
+ class ReactionUpdateEvent(CamelModel):
6
+ """Событие обновления реакций сообщения.
7
+
8
+ :ivar message_id: ID сообщения.
9
+ :vartype message_id: str
10
+ :ivar chat_id: ID чата.
11
+ :vartype chat_id: int
12
+ :ivar counters: Счетчики реакций по типам.
13
+ :vartype counters: list[ReactionCounter]
14
+ :ivar total_count: Общее количество реакций.
15
+ :vartype total_count: int
16
+ """
17
+
18
+ message_id: str
19
+ chat_id: int
20
+ counters: list[ReactionCounter]
21
+ total_count: int
@@ -0,0 +1,14 @@
1
+ from pymax.types.domain.base import CamelModel
2
+
3
+
4
+ class TypingEvent(CamelModel):
5
+ """Событие набора текста пользователем.
6
+
7
+ :ivar chat_id: ID чата.
8
+ :vartype chat_id: int
9
+ :ivar user_id: ID пользователя, который набирает текст.
10
+ :vartype user_id: int
11
+ """
12
+
13
+ chat_id: int
14
+ user_id: int