maxapi-python 1.2.4__py3-none-any.whl → 2.0.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.0.0.dist-info/METADATA +217 -0
- maxapi_python-2.0.0.dist-info/RECORD +140 -0
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +50 -105
- pymax/api/__init__.py +17 -0
- pymax/api/auth/__init__.py +1 -0
- pymax/api/auth/enums.py +17 -0
- pymax/api/auth/payloads.py +129 -0
- pymax/api/auth/service.py +313 -0
- pymax/api/auth/types.py +13 -0
- pymax/api/chats/__init__.py +8 -0
- pymax/api/chats/enums.py +27 -0
- pymax/api/chats/payloads.py +103 -0
- pymax/api/chats/service.py +277 -0
- pymax/api/facade.py +32 -0
- pymax/api/messages/__init__.py +1 -0
- pymax/api/messages/enums.py +17 -0
- pymax/api/messages/payloads.py +92 -0
- pymax/api/messages/service.py +337 -0
- pymax/api/models.py +13 -0
- pymax/api/response.py +123 -0
- pymax/api/self/__init__.py +2 -0
- pymax/api/self/enums.py +11 -0
- pymax/api/self/payloads.py +41 -0
- pymax/api/self/service.py +142 -0
- pymax/api/session/__init__.py +1 -0
- pymax/api/session/enums.py +10 -0
- pymax/api/session/payloads.py +76 -0
- pymax/api/session/service.py +72 -0
- pymax/api/uploads/__init__.py +1 -0
- pymax/api/uploads/models.py +49 -0
- pymax/api/uploads/payloads.py +25 -0
- pymax/api/uploads/service.py +458 -0
- pymax/api/users/__init__.py +2 -0
- pymax/api/users/enums.py +12 -0
- pymax/api/users/payloads.py +16 -0
- pymax/api/users/service.py +124 -0
- pymax/app.py +273 -0
- pymax/auth/__init__.py +25 -0
- pymax/auth/base.py +37 -0
- pymax/auth/email.py +0 -0
- pymax/auth/models.py +5 -0
- pymax/auth/providers.py +127 -0
- pymax/auth/qr.py +135 -0
- pymax/auth/service.py +25 -0
- pymax/auth/sms.py +122 -0
- pymax/base.py +204 -0
- pymax/client.py +106 -0
- pymax/client_web.py +83 -0
- pymax/config.py +215 -0
- pymax/connection/__init__.py +1 -0
- pymax/connection/connection.py +205 -0
- pymax/connection/pending.py +46 -0
- pymax/connection/readers/__init__.py +2 -0
- pymax/connection/readers/base.py +6 -0
- pymax/connection/readers/tcp.py +29 -0
- pymax/connection/readers/ws.py +14 -0
- pymax/dispatch/__init__.py +10 -0
- pymax/dispatch/dispatcher.py +222 -0
- pymax/dispatch/enums.py +12 -0
- pymax/dispatch/mapping.py +73 -0
- pymax/dispatch/resolvers.py +52 -0
- pymax/dispatch/router.py +216 -0
- pymax/exceptions.py +22 -89
- pymax/files/__init__.py +9 -0
- pymax/files/base.py +82 -0
- pymax/files/file.py +76 -0
- pymax/files/photo.py +108 -0
- pymax/files/static.py +10 -0
- pymax/files/video.py +74 -0
- pymax/formatting/__init__.py +0 -0
- pymax/formatting/markdown.py +217 -0
- pymax/infra/__init__.py +1 -0
- pymax/infra/auth.py +55 -0
- pymax/infra/base.py +15 -0
- pymax/infra/chat.py +240 -0
- pymax/infra/message.py +252 -0
- pymax/infra/protocol.py +9 -0
- pymax/infra/self.py +139 -0
- pymax/infra/user.py +107 -0
- pymax/logging.py +129 -0
- pymax/protocol/__init__.py +11 -0
- pymax/protocol/base.py +13 -0
- pymax/{static/enum.py → protocol/enums.py} +36 -79
- pymax/protocol/models.py +33 -0
- pymax/protocol/tcp/__init__.py +1 -0
- pymax/protocol/tcp/compression.py +97 -0
- pymax/protocol/tcp/framing.py +68 -0
- pymax/protocol/tcp/payload.py +127 -0
- pymax/protocol/tcp/protocol.py +68 -0
- pymax/protocol/ws/__init__.py +1 -0
- pymax/protocol/ws/protocol.py +27 -0
- pymax/py.typed +0 -0
- pymax/routers.py +8 -0
- pymax/session/__init__.py +3 -0
- pymax/session/models.py +11 -0
- pymax/session/protocol.py +14 -0
- pymax/session/store.py +232 -0
- pymax/telemetry/__init__.py +3 -0
- pymax/telemetry/navigation.py +181 -0
- pymax/telemetry/payloads.py +142 -0
- pymax/telemetry/service.py +225 -0
- pymax/transport/__init__.py +0 -0
- pymax/transport/base.py +14 -0
- pymax/transport/tcp.py +93 -0
- pymax/transport/websocket.py +50 -0
- pymax/types/__init__.py +2 -0
- pymax/types/domain/__init__.py +11 -0
- pymax/types/domain/attachments/__init__.py +11 -0
- pymax/types/domain/attachments/audio.py +35 -0
- pymax/types/domain/attachments/call.py +26 -0
- pymax/types/domain/attachments/contact.py +32 -0
- pymax/types/domain/attachments/control.py +20 -0
- pymax/types/domain/attachments/enums.py +27 -0
- pymax/types/domain/attachments/file.py +56 -0
- pymax/types/domain/attachments/keyboards/__init__.py +1 -0
- pymax/types/domain/attachments/keyboards/inline.py +19 -0
- pymax/types/domain/attachments/photo.py +45 -0
- pymax/types/domain/attachments/share.py +29 -0
- pymax/types/domain/attachments/sticker.py +50 -0
- pymax/types/domain/attachments/video.py +90 -0
- pymax/types/domain/auth.py +161 -0
- pymax/types/domain/base.py +17 -0
- pymax/types/domain/chat.py +426 -0
- pymax/types/domain/element.py +24 -0
- pymax/types/domain/enums.py +24 -0
- pymax/types/domain/error.py +20 -0
- pymax/types/domain/folder.py +74 -0
- pymax/types/domain/login.py +35 -0
- pymax/types/domain/message.py +378 -0
- pymax/types/domain/name.py +20 -0
- pymax/types/domain/profile.py +15 -0
- pymax/types/domain/session.py +52 -0
- pymax/types/domain/sync.py +80 -0
- pymax/types/domain/user.py +117 -0
- pymax/types/events/__init__.py +3 -0
- pymax/types/events/file.py +5 -0
- pymax/types/events/message.py +37 -0
- pymax/types/events/video.py +5 -0
- maxapi_python-1.2.4.dist-info/METADATA +0 -205
- maxapi_python-1.2.4.dist-info/RECORD +0 -33
- pymax/core.py +0 -390
- pymax/crud.py +0 -96
- pymax/files.py +0 -138
- pymax/filters.py +0 -164
- pymax/formatter.py +0 -31
- pymax/formatting.py +0 -74
- pymax/interfaces.py +0 -552
- pymax/mixins/__init__.py +0 -40
- pymax/mixins/auth.py +0 -368
- pymax/mixins/channel.py +0 -130
- pymax/mixins/group.py +0 -458
- pymax/mixins/handler.py +0 -285
- pymax/mixins/message.py +0 -879
- pymax/mixins/scheduler.py +0 -28
- pymax/mixins/self.py +0 -259
- pymax/mixins/socket.py +0 -297
- pymax/mixins/telemetry.py +0 -112
- pymax/mixins/user.py +0 -219
- pymax/mixins/websocket.py +0 -142
- pymax/models.py +0 -8
- pymax/navigation.py +0 -187
- pymax/payloads.py +0 -367
- pymax/protocols.py +0 -123
- pymax/static/constant.py +0 -89
- pymax/types.py +0 -1220
- pymax/utils.py +0 -90
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Annotated, Any, TypeAlias
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, PrivateAttr, model_validator
|
|
6
|
+
|
|
7
|
+
from pymax.files import File, Photo, Video
|
|
8
|
+
from pymax.types.domain import (
|
|
9
|
+
AudioAttachment,
|
|
10
|
+
CallAttachment,
|
|
11
|
+
ContactAttachment,
|
|
12
|
+
ControlAttachment,
|
|
13
|
+
FileAttachment,
|
|
14
|
+
InlineKeyboardAttachment,
|
|
15
|
+
PhotoAttachment,
|
|
16
|
+
ShareAttachment,
|
|
17
|
+
StickerAttachment,
|
|
18
|
+
VideoAttachment,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .base import CamelModel
|
|
22
|
+
from .element import Element
|
|
23
|
+
from .enums import MessageStatus
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from pymax.api.messages.service import MessageService
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Attachment: TypeAlias = Annotated[
|
|
30
|
+
PhotoAttachment
|
|
31
|
+
| VideoAttachment
|
|
32
|
+
| FileAttachment
|
|
33
|
+
| ContactAttachment
|
|
34
|
+
| StickerAttachment
|
|
35
|
+
| AudioAttachment
|
|
36
|
+
| ControlAttachment
|
|
37
|
+
| InlineKeyboardAttachment
|
|
38
|
+
| ShareAttachment
|
|
39
|
+
| CallAttachment,
|
|
40
|
+
Field(discriminator="type"),
|
|
41
|
+
]
|
|
42
|
+
SendAttachment: TypeAlias = Photo | File | Video
|
|
43
|
+
SendAttachments: TypeAlias = list[SendAttachment] | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReactionCounter(CamelModel):
|
|
47
|
+
"""Счетчик одной реакции на сообщение.
|
|
48
|
+
|
|
49
|
+
:ivar count: Количество таких реакций.
|
|
50
|
+
:vartype count: int
|
|
51
|
+
:ivar reaction: ID emoji-реакции.
|
|
52
|
+
:vartype reaction: str
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
count: int
|
|
56
|
+
reaction: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ReactionInfo(CamelModel):
|
|
60
|
+
"""Сводная информация о реакциях на сообщение.
|
|
61
|
+
|
|
62
|
+
:ivar total_count: Общее количество реакций.
|
|
63
|
+
:vartype total_count: int
|
|
64
|
+
:ivar counters: Счетчики по каждому типу реакции.
|
|
65
|
+
:vartype counters: list[ReactionCounter]
|
|
66
|
+
:ivar your_reaction: Реакция текущего пользователя, если она есть.
|
|
67
|
+
:vartype your_reaction: str | None
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
total_count: int = 0
|
|
71
|
+
counters: list[ReactionCounter] = Field(default_factory=list)
|
|
72
|
+
your_reaction: str | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ReadState(CamelModel):
|
|
76
|
+
"""Состояние прочтения после отметки сообщения прочитанным.
|
|
77
|
+
|
|
78
|
+
:ivar unread: Количество непрочитанных сообщений.
|
|
79
|
+
:vartype unread: int
|
|
80
|
+
:ivar mark: Текущая отметка прочтения.
|
|
81
|
+
:vartype mark: int
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
unread: int
|
|
85
|
+
mark: int
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Message(CamelModel):
|
|
89
|
+
"""Сообщение Max с методами для действий над ним.
|
|
90
|
+
|
|
91
|
+
Сообщения, полученные через клиент, обычно уже привязаны к сервису
|
|
92
|
+
сообщений. После этого можно вызывать удобные методы объекта:
|
|
93
|
+
:meth:`reply`, :meth:`answer`, :meth:`pin`, :meth:`delete`,
|
|
94
|
+
:meth:`read`, :meth:`react`, :meth:`unreact` и :meth:`get_reactions`.
|
|
95
|
+
|
|
96
|
+
Используйте ``Message`` в обработчиках ``on_message`` и при работе с
|
|
97
|
+
историей. Некоторые поля могут быть ``None``, потому что Max присылает
|
|
98
|
+
разные payload-ы для разных событий.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
.. code-block:: python
|
|
102
|
+
|
|
103
|
+
from pymax import Client
|
|
104
|
+
|
|
105
|
+
@client.on_message()
|
|
106
|
+
async def on_message(message: Message, client: Client) -> None:
|
|
107
|
+
if message.chat_id is None:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
await message.answer("Получил сообщение")
|
|
111
|
+
|
|
112
|
+
:ivar id: ID сообщения.
|
|
113
|
+
:vartype id: int
|
|
114
|
+
:ivar chat_id: ID чата, если он есть в данных сообщения.
|
|
115
|
+
:vartype chat_id: int | None
|
|
116
|
+
:ivar sender: ID отправителя.
|
|
117
|
+
:vartype sender: int | None
|
|
118
|
+
:ivar text: Текст сообщения.
|
|
119
|
+
:vartype text: str
|
|
120
|
+
:ivar time: Время сообщения в формате Unix time.
|
|
121
|
+
:vartype time: int
|
|
122
|
+
:ivar type: Тип сообщения.
|
|
123
|
+
:vartype type: str
|
|
124
|
+
:ivar cid: Клиентский ID сообщения, если он есть в payload-е.
|
|
125
|
+
:vartype cid: int | None
|
|
126
|
+
:ivar attaches: Вложения сообщения.
|
|
127
|
+
:vartype attaches: list[Attachment]
|
|
128
|
+
:ivar stats: Дополнительная статистика сообщения от Max.
|
|
129
|
+
:vartype stats: dict[str, Any] | None
|
|
130
|
+
:ivar status: Статус доставки сообщения.
|
|
131
|
+
:vartype status: MessageStatus | None
|
|
132
|
+
:ivar reaction_info: Информация о реакциях на сообщение.
|
|
133
|
+
:vartype reaction_info: ReactionInfo | None
|
|
134
|
+
:ivar options: Дополнительные параметры сообщения от Max.
|
|
135
|
+
:vartype options: int | dict[str, Any] | None
|
|
136
|
+
:ivar prev_message_id: ID предыдущего сообщения.
|
|
137
|
+
:vartype prev_message_id: int | str | None
|
|
138
|
+
:ivar ttl: Признак сообщения с ограниченным временем жизни.
|
|
139
|
+
:vartype ttl: bool | None
|
|
140
|
+
:ivar unread: Количество непрочитанных сообщений.
|
|
141
|
+
:vartype unread: int | None
|
|
142
|
+
:ivar mark: Текущая отметка прочтения.
|
|
143
|
+
:vartype mark: int | None
|
|
144
|
+
:ivar elements: Форматированные элементы текста сообщения.
|
|
145
|
+
:vartype elements: list[Element]
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
id: int
|
|
149
|
+
chat_id: int | None = None
|
|
150
|
+
sender: int | None = None
|
|
151
|
+
text: str = ""
|
|
152
|
+
time: int
|
|
153
|
+
type: str
|
|
154
|
+
cid: int | None = None
|
|
155
|
+
attaches: list[Attachment] = Field(default_factory=list)
|
|
156
|
+
stats: dict[str, Any] | None = None
|
|
157
|
+
status: MessageStatus | None = None
|
|
158
|
+
reaction_info: ReactionInfo | None = None
|
|
159
|
+
options: int | dict[str, Any] | None = None
|
|
160
|
+
prev_message_id: int | str | None = None
|
|
161
|
+
ttl: bool | None = None
|
|
162
|
+
unread: int | None = None
|
|
163
|
+
mark: int | None = None
|
|
164
|
+
elements: list[Element] = Field(default_factory=list)
|
|
165
|
+
|
|
166
|
+
_actions: MessageService | None = PrivateAttr(default=None)
|
|
167
|
+
|
|
168
|
+
def bind(self, actions: MessageService) -> Message:
|
|
169
|
+
"""Привязывает сервис сообщений к объекту сообщения.
|
|
170
|
+
|
|
171
|
+
:param actions: Сервис сообщений для выполнения действий с сообщением.
|
|
172
|
+
:type actions: MessageService
|
|
173
|
+
:returns: Это же сообщение с привязанным сервисом.
|
|
174
|
+
:rtype: Message
|
|
175
|
+
"""
|
|
176
|
+
self._actions = actions
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
async def reply(
|
|
180
|
+
self,
|
|
181
|
+
text: str,
|
|
182
|
+
attachments: SendAttachments = None,
|
|
183
|
+
*,
|
|
184
|
+
notify: bool = True,
|
|
185
|
+
) -> Message | None:
|
|
186
|
+
"""Отправляет ответ на это сообщение в тот же чат.
|
|
187
|
+
|
|
188
|
+
:param text: Текст сообщения.
|
|
189
|
+
:type text: str
|
|
190
|
+
:param attachments: Файлы, фотографии или видео для отправки.
|
|
191
|
+
:type attachments: SendAttachments
|
|
192
|
+
:param notify: Отправить ли получателям push-уведомление.
|
|
193
|
+
:type notify: bool
|
|
194
|
+
:returns: Отправленное сообщение или ``None``, если сервер не вернул
|
|
195
|
+
его.
|
|
196
|
+
:rtype: Message | None
|
|
197
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
198
|
+
содержит ``chat_id``.
|
|
199
|
+
"""
|
|
200
|
+
actions, chat_id = self._bound()
|
|
201
|
+
|
|
202
|
+
return await actions.send_message(
|
|
203
|
+
chat_id=chat_id,
|
|
204
|
+
text=text,
|
|
205
|
+
reply_to=self.id,
|
|
206
|
+
attachments=attachments,
|
|
207
|
+
notify=notify,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def answer(
|
|
211
|
+
self,
|
|
212
|
+
text: str,
|
|
213
|
+
reply_to: int | None = None,
|
|
214
|
+
attachments: SendAttachments = None,
|
|
215
|
+
*,
|
|
216
|
+
notify: bool = True,
|
|
217
|
+
) -> Message | None:
|
|
218
|
+
"""Отправляет сообщение в тот же чат.
|
|
219
|
+
|
|
220
|
+
:param text: Текст сообщения.
|
|
221
|
+
:type text: str
|
|
222
|
+
:param reply_to: ID сообщения для ответа.
|
|
223
|
+
:type reply_to: int | None
|
|
224
|
+
:param attachments: Файлы, фотографии или видео для отправки.
|
|
225
|
+
:type attachments: SendAttachments
|
|
226
|
+
:param notify: Отправить ли получателям push-уведомление.
|
|
227
|
+
:type notify: bool
|
|
228
|
+
:returns: Отправленное сообщение или ``None``, если сервер не вернул
|
|
229
|
+
его.
|
|
230
|
+
:rtype: Message | None
|
|
231
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
232
|
+
содержит ``chat_id``.
|
|
233
|
+
"""
|
|
234
|
+
actions, chat_id = self._bound()
|
|
235
|
+
|
|
236
|
+
return await actions.send_message(
|
|
237
|
+
chat_id=chat_id,
|
|
238
|
+
text=text,
|
|
239
|
+
reply_to=reply_to,
|
|
240
|
+
attachments=attachments,
|
|
241
|
+
notify=notify,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
async def pin(self, notify_pin: bool = True) -> bool:
|
|
245
|
+
"""Закрепляет это сообщение в чате.
|
|
246
|
+
|
|
247
|
+
:param notify_pin: Отправить ли уведомление о закреплении.
|
|
248
|
+
:type notify_pin: bool
|
|
249
|
+
:returns: ``True``, если сервер принял запрос.
|
|
250
|
+
:rtype: bool
|
|
251
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
252
|
+
содержит ``chat_id``.
|
|
253
|
+
"""
|
|
254
|
+
actions, chat_id = self._bound()
|
|
255
|
+
|
|
256
|
+
return await actions.pin_message(
|
|
257
|
+
chat_id=chat_id,
|
|
258
|
+
message_id=self.id,
|
|
259
|
+
notify_pin=notify_pin,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
async def delete(self, for_me: bool = False) -> bool:
|
|
263
|
+
"""Удаляет это сообщение.
|
|
264
|
+
|
|
265
|
+
:param for_me: Удалить сообщение только для текущего аккаунта.
|
|
266
|
+
:type for_me: bool
|
|
267
|
+
:returns: ``True``, если сервер принял запрос.
|
|
268
|
+
:rtype: bool
|
|
269
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
270
|
+
содержит ``chat_id``.
|
|
271
|
+
"""
|
|
272
|
+
actions, chat_id = self._bound()
|
|
273
|
+
|
|
274
|
+
return await actions.delete_message(
|
|
275
|
+
chat_id=chat_id,
|
|
276
|
+
message_ids=[self.id],
|
|
277
|
+
for_me=for_me,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
async def read(self) -> ReadState:
|
|
281
|
+
"""Отмечает это сообщение как прочитанное.
|
|
282
|
+
|
|
283
|
+
:returns: Состояние прочтения.
|
|
284
|
+
:rtype: ReadState
|
|
285
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
286
|
+
содержит ``chat_id``.
|
|
287
|
+
"""
|
|
288
|
+
actions, chat_id = self._bound()
|
|
289
|
+
|
|
290
|
+
return await actions.read_message(
|
|
291
|
+
message_id=self.id,
|
|
292
|
+
chat_id=chat_id,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def react(self, reaction: str) -> ReactionInfo | None:
|
|
296
|
+
"""Добавляет или заменяет свою реакцию на этом сообщении.
|
|
297
|
+
|
|
298
|
+
:param reaction: ID emoji-реакции, поддержанный сервером.
|
|
299
|
+
:type reaction: str
|
|
300
|
+
:returns: Обновленные реакции или ``None``, если сервер их не вернул.
|
|
301
|
+
:rtype: ReactionInfo | None
|
|
302
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
303
|
+
содержит ``chat_id``.
|
|
304
|
+
"""
|
|
305
|
+
actions, chat_id = self._bound()
|
|
306
|
+
|
|
307
|
+
return await actions.add_reaction(
|
|
308
|
+
chat_id=chat_id,
|
|
309
|
+
message_id=str(self.id), # :C
|
|
310
|
+
reaction=reaction,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
async def unreact(self) -> ReactionInfo | None:
|
|
314
|
+
"""Удаляет свою реакцию с этого сообщения.
|
|
315
|
+
|
|
316
|
+
:returns: Обновленные реакции или ``None``, если сервер их не вернул.
|
|
317
|
+
:rtype: ReactionInfo | None
|
|
318
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
319
|
+
содержит ``chat_id``.
|
|
320
|
+
"""
|
|
321
|
+
actions, chat_id = self._bound()
|
|
322
|
+
|
|
323
|
+
return await actions.remove_reaction(
|
|
324
|
+
chat_id=chat_id,
|
|
325
|
+
message_id=str(self.id), # :C x2
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
async def get_reactions(self) -> dict[str, ReactionInfo] | None:
|
|
329
|
+
"""Возвращает реакции для этого сообщения.
|
|
330
|
+
|
|
331
|
+
:returns: ``{message_id: ReactionInfo}`` или ``None``, если реакций
|
|
332
|
+
нет в ответе сервера.
|
|
333
|
+
:rtype: dict[str, ReactionInfo] | None
|
|
334
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
335
|
+
содержит ``chat_id``.
|
|
336
|
+
"""
|
|
337
|
+
actions, chat_id = self._bound()
|
|
338
|
+
|
|
339
|
+
return await actions.get_reactions(
|
|
340
|
+
chat_id=chat_id,
|
|
341
|
+
message_ids=[str(self.id)], # :C x3
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def _bound(self) -> tuple[MessageService, int]:
|
|
345
|
+
if self._actions is None:
|
|
346
|
+
raise RuntimeError("Message is not bound to a client.")
|
|
347
|
+
|
|
348
|
+
if self.chat_id is None:
|
|
349
|
+
raise RuntimeError("Message does not contain chat_id.")
|
|
350
|
+
|
|
351
|
+
return self._actions, self.chat_id
|
|
352
|
+
|
|
353
|
+
@model_validator(mode="before")
|
|
354
|
+
@classmethod
|
|
355
|
+
def _unwrap_message_event(cls, value: Any) -> Any:
|
|
356
|
+
"""Преобразует данные события сообщения в данные сообщения.
|
|
357
|
+
|
|
358
|
+
:param value: Значение, переданное в валидатор модели.
|
|
359
|
+
:type value: Any
|
|
360
|
+
:returns: Данные сообщения или исходное значение, если преобразование
|
|
361
|
+
не требуется.
|
|
362
|
+
:rtype: Any
|
|
363
|
+
"""
|
|
364
|
+
if not isinstance(value, dict):
|
|
365
|
+
return value
|
|
366
|
+
|
|
367
|
+
message = value.get("message")
|
|
368
|
+
if not isinstance(message, dict):
|
|
369
|
+
return value
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
**message,
|
|
373
|
+
"chatId": value.get("chatId"),
|
|
374
|
+
"prevMessageId": value.get("prevMessageId"),
|
|
375
|
+
"ttl": value.get("ttl"),
|
|
376
|
+
"unread": value.get("unread"),
|
|
377
|
+
"mark": value.get("mark"),
|
|
378
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .base import CamelModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Name(CamelModel):
|
|
5
|
+
"""Имя пользователя.
|
|
6
|
+
|
|
7
|
+
:ivar name: Полное отображаемое имя.
|
|
8
|
+
:vartype name: str | None
|
|
9
|
+
:ivar first_name: Имя.
|
|
10
|
+
:vartype first_name: str | None
|
|
11
|
+
:ivar last_name: Фамилия.
|
|
12
|
+
:vartype last_name: str | None
|
|
13
|
+
:ivar type: Тип имени.
|
|
14
|
+
:vartype type: str | None
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name: str | None = None
|
|
18
|
+
first_name: str | None = None
|
|
19
|
+
last_name: str | None = None
|
|
20
|
+
type: str | None = None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import CamelModel
|
|
2
|
+
from .user import User
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Profile(CamelModel):
|
|
6
|
+
"""Профиль текущего аккаунта.
|
|
7
|
+
|
|
8
|
+
:ivar contact: Контакт текущего аккаунта.
|
|
9
|
+
:vartype contact: User
|
|
10
|
+
:ivar profile_options: Дополнительные флаги профиля текущего аккаунта.
|
|
11
|
+
:vartype profile_options: list[int] | None
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
contact: User
|
|
15
|
+
profile_options: list[int] | None = None
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .base import CamelModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Session(CamelModel):
|
|
7
|
+
"""Активная сессия аккаунта.
|
|
8
|
+
|
|
9
|
+
:ivar id: ID сессии.
|
|
10
|
+
:vartype id: int | str | None
|
|
11
|
+
:ivar device_id: ID устройства.
|
|
12
|
+
:vartype device_id: str | None
|
|
13
|
+
:ivar current: Является ли сессия текущей.
|
|
14
|
+
:vartype current: bool | None
|
|
15
|
+
:ivar user_agent: User-Agent сессии.
|
|
16
|
+
:vartype user_agent: str | None
|
|
17
|
+
:ivar app_version: Версия приложения.
|
|
18
|
+
:vartype app_version: str | None
|
|
19
|
+
:ivar device_name: Название устройства.
|
|
20
|
+
:vartype device_name: str | None
|
|
21
|
+
:ivar device_type: Тип устройства.
|
|
22
|
+
:vartype device_type: str | None
|
|
23
|
+
:ivar platform: Платформа устройства.
|
|
24
|
+
:vartype platform: str | None
|
|
25
|
+
:ivar ip: IP-адрес сессии.
|
|
26
|
+
:vartype ip: str | None
|
|
27
|
+
:ivar location: Локация сессии.
|
|
28
|
+
:vartype location: str | None
|
|
29
|
+
:ivar created: Время создания в формате Unix time.
|
|
30
|
+
:vartype created: int | None
|
|
31
|
+
:ivar updated: Время обновления в формате Unix time.
|
|
32
|
+
:vartype updated: int | None
|
|
33
|
+
:ivar last_activity: Время последней активности в формате Unix time.
|
|
34
|
+
:vartype last_activity: int | None
|
|
35
|
+
:ivar options: Дополнительные параметры сессии от Max.
|
|
36
|
+
:vartype options: dict[str, Any] | list[Any] | None
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
id: int | str | None = None
|
|
40
|
+
device_id: str | None = None
|
|
41
|
+
current: bool | None = None
|
|
42
|
+
user_agent: str | None = None
|
|
43
|
+
app_version: str | None = None
|
|
44
|
+
device_name: str | None = None
|
|
45
|
+
device_type: str | None = None
|
|
46
|
+
platform: str | None = None
|
|
47
|
+
ip: str | None = None
|
|
48
|
+
location: str | None = None
|
|
49
|
+
created: int | None = None
|
|
50
|
+
updated: int | None = None
|
|
51
|
+
last_activity: int | None = None
|
|
52
|
+
options: dict[str, Any] | list[Any] | None = None
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
DEFAULT_CONFIG_HASH = (
|
|
4
|
+
"00000000-0000000000000000-00000000-"
|
|
5
|
+
"0000000000000000-0000000000000000-0-"
|
|
6
|
+
"0000000000000000-00000000"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
ConfigHash = str | int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SyncState(BaseModel):
|
|
13
|
+
"""Полное сохраненное состояние синхронизации Max.
|
|
14
|
+
|
|
15
|
+
Обычно PyMax управляет этим объектом сам и хранит его в файле сессии.
|
|
16
|
+
Пользователю чаще нужен ``SyncOverrides`` для временного сброса одного или
|
|
17
|
+
нескольких маркеров.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
chats_sync: Маркер sync для списка чатов.
|
|
21
|
+
contacts_sync: Маркер sync для контактов.
|
|
22
|
+
drafts_sync: Маркер sync для черновиков.
|
|
23
|
+
presence_sync: Маркер sync для presence.
|
|
24
|
+
config_hash: Хеш конфигурации Max.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
.. code-block:: python
|
|
28
|
+
|
|
29
|
+
from pymax import SyncState
|
|
30
|
+
|
|
31
|
+
state = SyncState(chats_sync=-1)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
chats_sync: int = -1
|
|
35
|
+
contacts_sync: int = -1
|
|
36
|
+
drafts_sync: int = -1
|
|
37
|
+
presence_sync: int = -1
|
|
38
|
+
config_hash: ConfigHash = DEFAULT_CONFIG_HASH
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SyncOverrides(BaseModel):
|
|
42
|
+
"""Частичные переопределения состояния синхронизации.
|
|
43
|
+
|
|
44
|
+
:ivar chats_sync: Новая метка синхронизации чатов.
|
|
45
|
+
:vartype chats_sync: int | None
|
|
46
|
+
:ivar contacts_sync: Новая метка синхронизации контактов.
|
|
47
|
+
:vartype contacts_sync: int | None
|
|
48
|
+
:ivar drafts_sync: Новая метка синхронизации черновиков.
|
|
49
|
+
:vartype drafts_sync: int | None
|
|
50
|
+
:ivar presence_sync: Новая метка синхронизации присутствия.
|
|
51
|
+
:vartype presence_sync: int | None
|
|
52
|
+
:ivar config_hash: Новый хеш конфигурации.
|
|
53
|
+
:vartype config_hash: ConfigHash | None
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
chats_sync: int | None = None
|
|
57
|
+
contacts_sync: int | None = None
|
|
58
|
+
drafts_sync: int | None = None
|
|
59
|
+
presence_sync: int | None = None
|
|
60
|
+
config_hash: ConfigHash | None = None
|
|
61
|
+
|
|
62
|
+
def resolve(self, saved: SyncState) -> SyncState:
|
|
63
|
+
"""Собирает полное состояние из переопределений и сохраненных данных.
|
|
64
|
+
|
|
65
|
+
:param saved: Сохраненное состояние синхронизации.
|
|
66
|
+
:type saved: SyncState
|
|
67
|
+
:returns: Полное состояние синхронизации.
|
|
68
|
+
:rtype: SyncState
|
|
69
|
+
"""
|
|
70
|
+
return SyncState(
|
|
71
|
+
chats_sync=(self.chats_sync if self.chats_sync is not None else saved.chats_sync),
|
|
72
|
+
contacts_sync=(
|
|
73
|
+
self.contacts_sync if self.contacts_sync is not None else saved.contacts_sync
|
|
74
|
+
),
|
|
75
|
+
drafts_sync=(self.drafts_sync if self.drafts_sync is not None else saved.drafts_sync),
|
|
76
|
+
presence_sync=(
|
|
77
|
+
self.presence_sync if self.presence_sync is not None else saved.presence_sync
|
|
78
|
+
),
|
|
79
|
+
config_hash=(self.config_hash if self.config_hash is not None else saved.config_hash),
|
|
80
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, PrivateAttr
|
|
6
|
+
|
|
7
|
+
from .base import CamelModel
|
|
8
|
+
from .name import Name
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pymax.api.users.service import UserService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class User(CamelModel):
|
|
15
|
+
"""Контакт или пользователь Max.
|
|
16
|
+
|
|
17
|
+
:ivar id: ID пользователя.
|
|
18
|
+
:vartype id: int
|
|
19
|
+
:ivar account_status: Статус аккаунта в кодировке Max.
|
|
20
|
+
:vartype account_status: int | None
|
|
21
|
+
:ivar base_raw_url: Исходный URL аватара.
|
|
22
|
+
:vartype base_raw_url: str | None
|
|
23
|
+
:ivar base_url: URL аватара.
|
|
24
|
+
:vartype base_url: str | None
|
|
25
|
+
:ivar names: Имена пользователя.
|
|
26
|
+
:vartype names: list[Name]
|
|
27
|
+
:ivar options: Дополнительные флаги контакта.
|
|
28
|
+
:vartype options: list[str]
|
|
29
|
+
:ivar photo_id: ID фотографии профиля.
|
|
30
|
+
:vartype photo_id: int | None
|
|
31
|
+
:ivar update_time: Время обновления в формате Unix time.
|
|
32
|
+
:vartype update_time: int | None
|
|
33
|
+
:ivar description: Описание профиля.
|
|
34
|
+
:vartype description: str | None
|
|
35
|
+
:ivar gender: Пол пользователя.
|
|
36
|
+
:vartype gender: str | None
|
|
37
|
+
:ivar link: Ссылка на профиль.
|
|
38
|
+
:vartype link: str | None
|
|
39
|
+
:ivar web_app: Данные связанного web-приложения, если есть.
|
|
40
|
+
:vartype web_app: dict[str, Any] | None
|
|
41
|
+
:ivar menu_button: Данные кнопки меню профиля, если есть.
|
|
42
|
+
:vartype menu_button: dict[str, Any] | None
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
id: int
|
|
46
|
+
account_status: int | None = None
|
|
47
|
+
base_raw_url: str | None = None
|
|
48
|
+
base_url: str | None = None
|
|
49
|
+
names: list[Name] = Field(default_factory=list)
|
|
50
|
+
options: list[str] = Field(default_factory=list)
|
|
51
|
+
photo_id: int | None = None
|
|
52
|
+
update_time: int | None = None
|
|
53
|
+
description: str | None = None
|
|
54
|
+
gender: str | None = None
|
|
55
|
+
link: str | None = None
|
|
56
|
+
web_app: dict[str, Any] | None = None
|
|
57
|
+
menu_button: dict[str, Any] | None = None
|
|
58
|
+
|
|
59
|
+
_actions: UserService | None = PrivateAttr(default=None)
|
|
60
|
+
|
|
61
|
+
def bind(self, actions: UserService) -> "User":
|
|
62
|
+
"""Привязывает сервис пользователей к объекту пользователя."""
|
|
63
|
+
self._actions = actions
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
async def add_contact(self) -> "User":
|
|
67
|
+
"""Добавляет пользователя в контакты.
|
|
68
|
+
|
|
69
|
+
Метод использует ID текущего пользователя и вызывает API через
|
|
70
|
+
привязанный сервис пользователей.
|
|
71
|
+
|
|
72
|
+
:raises RuntimeError: Если объект пользователя не привязан к клиенту.
|
|
73
|
+
:return: Обновленный объект пользователя.
|
|
74
|
+
:rtype: User
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
actions = self._bound()
|
|
78
|
+
|
|
79
|
+
return await actions.add_contact(self.id)
|
|
80
|
+
|
|
81
|
+
async def remove_contact(self) -> bool:
|
|
82
|
+
"""Удаляет пользователя из контактов.
|
|
83
|
+
|
|
84
|
+
Метод использует ID текущего пользователя и вызывает API через
|
|
85
|
+
привязанный сервис пользователей.
|
|
86
|
+
|
|
87
|
+
:raises RuntimeError: Если объект пользователя не привязан к клиенту.
|
|
88
|
+
:return: ``True``, если удаление прошло успешно.
|
|
89
|
+
:rtype: bool
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
actions = self._bound()
|
|
93
|
+
|
|
94
|
+
return await actions.remove_contact(self.id)
|
|
95
|
+
|
|
96
|
+
def get_chat_id(self, user_id: int) -> int:
|
|
97
|
+
"""Возвращает ID личного чата между двумя пользователями.
|
|
98
|
+
|
|
99
|
+
ID чата строится из ID переданного пользователя и ID текущего
|
|
100
|
+
пользователя.
|
|
101
|
+
|
|
102
|
+
:param user_id: ID второго пользователя.
|
|
103
|
+
:type user_id: int
|
|
104
|
+
:raises RuntimeError: Если объект пользователя не привязан к клиенту.
|
|
105
|
+
:return: ID личного чата.
|
|
106
|
+
:rtype: int
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
actions = self._bound()
|
|
110
|
+
|
|
111
|
+
return actions.get_chat_id(user_id, self.id)
|
|
112
|
+
|
|
113
|
+
def _bound(self) -> UserService:
|
|
114
|
+
if self._actions is None:
|
|
115
|
+
raise RuntimeError("Class is not bound to a client")
|
|
116
|
+
|
|
117
|
+
return self._actions
|