maxapi-python 2.1.3__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.
- {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/METADATA +3 -11
- {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/RECORD +58 -53
- pymax/__init__.py +18 -3
- pymax/api/auth/payloads.py +7 -0
- pymax/api/auth/service.py +33 -30
- pymax/api/binding.py +57 -0
- pymax/api/chats/service.py +34 -47
- pymax/api/messages/enums.py +1 -0
- pymax/api/messages/payloads.py +16 -1
- pymax/api/messages/service.py +85 -33
- pymax/api/models.py +4 -6
- pymax/api/response.py +2 -2
- pymax/api/self/service.py +17 -26
- pymax/api/session/payloads.py +2 -9
- pymax/api/session/service.py +1 -3
- pymax/api/uploads/payloads.py +3 -9
- pymax/api/uploads/service.py +33 -99
- pymax/api/users/service.py +8 -16
- pymax/app.py +2 -0
- pymax/auth/qr.py +3 -9
- pymax/auth/sms.py +23 -11
- pymax/base.py +38 -1
- pymax/client.py +2 -1
- pymax/client_web.py +1 -2
- pymax/config.py +42 -3
- pymax/connection/connection.py +2 -0
- pymax/connection/readers/tcp.py +1 -3
- pymax/dispatch/dispatcher.py +36 -18
- pymax/dispatch/enums.py +4 -0
- pymax/dispatch/mapping.py +34 -11
- pymax/dispatch/resolvers.py +18 -0
- pymax/dispatch/router.py +34 -0
- pymax/formatting/markdown.py +22 -13
- pymax/infra/chat.py +12 -0
- pymax/infra/message.py +74 -3
- pymax/logging.py +2 -0
- pymax/protocol/tcp/compression.py +1 -3
- pymax/protocol/tcp/framing.py +1 -3
- pymax/protocol/ws/protocol.py +3 -9
- pymax/session/protocol.py +2 -6
- pymax/session/store.py +8 -24
- pymax/telemetry/navigation.py +1 -3
- pymax/telemetry/service.py +5 -17
- pymax/transport/tcp.py +1 -3
- pymax/types/domain/attachments/unknown.py +1 -3
- pymax/types/domain/auth.py +24 -2
- pymax/types/domain/chat.py +38 -1
- pymax/types/domain/message.py +31 -1
- pymax/types/domain/presence.py +3 -3
- pymax/types/domain/sync.py +5 -21
- pymax/types/events/__init__.py +4 -0
- pymax/types/events/mark.py +23 -0
- pymax/types/events/message.py +57 -5
- pymax/types/events/presence.py +15 -0
- pymax/types/events/reaction.py +21 -0
- pymax/types/events/typing.py +14 -0
- {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/WHEEL +0 -0
- {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -30,8 +30,6 @@ class UnknownAttachment(CamelModel):
|
|
|
30
30
|
|
|
31
31
|
attachment_type = value.get("_type", value.get("type"))
|
|
32
32
|
if attachment_type in KNOWN_ATTACHMENT_TYPES:
|
|
33
|
-
raise ValueError(
|
|
34
|
-
"Known attachment type should be parsed by its own model"
|
|
35
|
-
)
|
|
33
|
+
raise ValueError("Known attachment type should be parsed by its own model")
|
|
36
34
|
|
|
37
35
|
return value
|
pymax/types/domain/auth.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from pydantic import BaseModel, Field
|
|
2
2
|
|
|
3
|
+
from pymax.api.auth.enums import AuthType
|
|
3
4
|
from pymax.api.models import CamelModel
|
|
4
5
|
|
|
6
|
+
from .profile import Profile
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
class StartAuthResponse(CamelModel):
|
|
7
10
|
"""Ответ на начало авторизации.
|
|
@@ -70,7 +73,7 @@ class CheckCodeResponse(CamelModel):
|
|
|
70
73
|
:vartype password_challenge: PasswordChallenge | None
|
|
71
74
|
"""
|
|
72
75
|
|
|
73
|
-
token_attrs: TokenAttrs = Field(default_factory=TokenAttrs)
|
|
76
|
+
token_attrs: TokenAttrs = Field(default_factory=lambda: TokenAttrs.model_validate({}))
|
|
74
77
|
password_challenge: PasswordChallenge | None = None
|
|
75
78
|
|
|
76
79
|
@property
|
|
@@ -103,7 +106,7 @@ class CheckPasswordResponse(CamelModel):
|
|
|
103
106
|
:vartype error: str | None
|
|
104
107
|
"""
|
|
105
108
|
|
|
106
|
-
token_attrs: TokenAttrs = Field(default_factory=TokenAttrs)
|
|
109
|
+
token_attrs: TokenAttrs = Field(default_factory=lambda: TokenAttrs.model_validate({}))
|
|
107
110
|
error: str | None = None
|
|
108
111
|
|
|
109
112
|
@property
|
|
@@ -159,3 +162,22 @@ class CheckQrResponse(CamelModel):
|
|
|
159
162
|
"""
|
|
160
163
|
|
|
161
164
|
status: QrStatus
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ConfirmRegistrationResponse(CamelModel):
|
|
168
|
+
"""Ответ Max после регистрации нового аккаунта.
|
|
169
|
+
|
|
170
|
+
:ivar user_token: Внутренний ID зарегистрированного пользователя.
|
|
171
|
+
:vartype user_token: int
|
|
172
|
+
:ivar profile: Профиль зарегистрированного аккаунта.
|
|
173
|
+
:vartype profile: Profile
|
|
174
|
+
:ivar token_type: Тип выданного токена.
|
|
175
|
+
:vartype token_type: AuthType
|
|
176
|
+
:ivar token: Токен входа для новой сессии.
|
|
177
|
+
:vartype token: str
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
user_token: int
|
|
181
|
+
profile: Profile
|
|
182
|
+
token_type: AuthType
|
|
183
|
+
token: str
|
pymax/types/domain/chat.py
CHANGED
|
@@ -19,7 +19,8 @@ class Chat(CamelModel):
|
|
|
19
19
|
|
|
20
20
|
Объекты чатов, полученные через клиент, обычно уже привязаны к сервисам
|
|
21
21
|
сообщений и чатов. После этого можно вызывать удобные методы объекта:
|
|
22
|
-
:meth:`answer`, :meth:`history`, :meth:`
|
|
22
|
+
:meth:`answer`, :meth:`history`, :meth:`get_message`,
|
|
23
|
+
:meth:`get_messages`, :meth:`leave`, :meth:`invite`,
|
|
23
24
|
:meth:`remove_users`, :meth:`pin_message`, :meth:`update_settings` и
|
|
24
25
|
:meth:`rework_invite_link`.
|
|
25
26
|
|
|
@@ -147,6 +148,10 @@ class Chat(CamelModel):
|
|
|
147
148
|
"""
|
|
148
149
|
self._message_actions = message_actions
|
|
149
150
|
self._chat_actions = chat_actions
|
|
151
|
+
if self.last_message is not None:
|
|
152
|
+
self.last_message.bind(message_actions)
|
|
153
|
+
if self.pinned_message is not None:
|
|
154
|
+
self.pinned_message.bind(message_actions)
|
|
150
155
|
return self
|
|
151
156
|
|
|
152
157
|
async def answer(
|
|
@@ -242,6 +247,38 @@ class Chat(CamelModel):
|
|
|
242
247
|
interactive=interactive,
|
|
243
248
|
)
|
|
244
249
|
|
|
250
|
+
async def get_message(self, message_id: int) -> Message | None:
|
|
251
|
+
"""Возвращает сообщение этого чата по ID.
|
|
252
|
+
|
|
253
|
+
:param message_id: ID сообщения.
|
|
254
|
+
:type message_id: int
|
|
255
|
+
:returns: Сообщение или ``None``, если сервер его не вернул.
|
|
256
|
+
:rtype: Message | None
|
|
257
|
+
:raises RuntimeError: Если чат не привязан к клиенту.
|
|
258
|
+
"""
|
|
259
|
+
actions, _ = self._bound()
|
|
260
|
+
|
|
261
|
+
return await actions.get_message(
|
|
262
|
+
chat_id=self.id,
|
|
263
|
+
message_id=message_id,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
async def get_messages(self, message_ids: list[int]) -> list[Message]:
|
|
267
|
+
"""Возвращает сообщения этого чата по ID.
|
|
268
|
+
|
|
269
|
+
:param message_ids: ID сообщений.
|
|
270
|
+
:type message_ids: list[int]
|
|
271
|
+
:returns: Список найденных сообщений.
|
|
272
|
+
:rtype: list[Message]
|
|
273
|
+
:raises RuntimeError: Если чат не привязан к клиенту.
|
|
274
|
+
"""
|
|
275
|
+
actions, _ = self._bound()
|
|
276
|
+
|
|
277
|
+
return await actions.get_messages(
|
|
278
|
+
chat_id=self.id,
|
|
279
|
+
message_ids=message_ids,
|
|
280
|
+
)
|
|
281
|
+
|
|
245
282
|
async def leave(self) -> None:
|
|
246
283
|
"""Выходит из группы или канала.
|
|
247
284
|
|
pymax/types/domain/message.py
CHANGED
|
@@ -92,7 +92,7 @@ class Message(CamelModel):
|
|
|
92
92
|
|
|
93
93
|
Сообщения, полученные через клиент, обычно уже привязаны к сервису
|
|
94
94
|
сообщений. После этого можно вызывать удобные методы объекта:
|
|
95
|
-
:meth:`reply`, :meth:`answer`, :meth:`pin`, :meth:`delete`,
|
|
95
|
+
:meth:`reply`, :meth:`answer`, :meth:`edit`, :meth:`pin`, :meth:`delete`,
|
|
96
96
|
:meth:`read`, :meth:`react`, :meth:`unreact` и :meth:`get_reactions`.
|
|
97
97
|
|
|
98
98
|
Используйте ``Message`` в обработчиках ``on_message`` и при работе с
|
|
@@ -261,6 +261,36 @@ class Message(CamelModel):
|
|
|
261
261
|
notify_pin=notify_pin,
|
|
262
262
|
)
|
|
263
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
|
+
|
|
264
294
|
async def delete(self, for_me: bool = False) -> bool:
|
|
265
295
|
"""Удаляет это сообщение.
|
|
266
296
|
|
pymax/types/domain/presence.py
CHANGED
|
@@ -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
|
pymax/types/domain/sync.py
CHANGED
|
@@ -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
|
)
|
pymax/types/events/__init__.py
CHANGED
|
@@ -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
|
pymax/types/events/message.py
CHANGED
|
@@ -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
|
|
File without changes
|
|
File without changes
|