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.
Files changed (168) hide show
  1. maxapi_python-2.0.0.dist-info/METADATA +217 -0
  2. maxapi_python-2.0.0.dist-info/RECORD +140 -0
  3. {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +50 -105
  5. pymax/api/__init__.py +17 -0
  6. pymax/api/auth/__init__.py +1 -0
  7. pymax/api/auth/enums.py +17 -0
  8. pymax/api/auth/payloads.py +129 -0
  9. pymax/api/auth/service.py +313 -0
  10. pymax/api/auth/types.py +13 -0
  11. pymax/api/chats/__init__.py +8 -0
  12. pymax/api/chats/enums.py +27 -0
  13. pymax/api/chats/payloads.py +103 -0
  14. pymax/api/chats/service.py +277 -0
  15. pymax/api/facade.py +32 -0
  16. pymax/api/messages/__init__.py +1 -0
  17. pymax/api/messages/enums.py +17 -0
  18. pymax/api/messages/payloads.py +92 -0
  19. pymax/api/messages/service.py +337 -0
  20. pymax/api/models.py +13 -0
  21. pymax/api/response.py +123 -0
  22. pymax/api/self/__init__.py +2 -0
  23. pymax/api/self/enums.py +11 -0
  24. pymax/api/self/payloads.py +41 -0
  25. pymax/api/self/service.py +142 -0
  26. pymax/api/session/__init__.py +1 -0
  27. pymax/api/session/enums.py +10 -0
  28. pymax/api/session/payloads.py +76 -0
  29. pymax/api/session/service.py +72 -0
  30. pymax/api/uploads/__init__.py +1 -0
  31. pymax/api/uploads/models.py +49 -0
  32. pymax/api/uploads/payloads.py +25 -0
  33. pymax/api/uploads/service.py +458 -0
  34. pymax/api/users/__init__.py +2 -0
  35. pymax/api/users/enums.py +12 -0
  36. pymax/api/users/payloads.py +16 -0
  37. pymax/api/users/service.py +124 -0
  38. pymax/app.py +273 -0
  39. pymax/auth/__init__.py +25 -0
  40. pymax/auth/base.py +37 -0
  41. pymax/auth/email.py +0 -0
  42. pymax/auth/models.py +5 -0
  43. pymax/auth/providers.py +127 -0
  44. pymax/auth/qr.py +135 -0
  45. pymax/auth/service.py +25 -0
  46. pymax/auth/sms.py +122 -0
  47. pymax/base.py +204 -0
  48. pymax/client.py +106 -0
  49. pymax/client_web.py +83 -0
  50. pymax/config.py +215 -0
  51. pymax/connection/__init__.py +1 -0
  52. pymax/connection/connection.py +205 -0
  53. pymax/connection/pending.py +46 -0
  54. pymax/connection/readers/__init__.py +2 -0
  55. pymax/connection/readers/base.py +6 -0
  56. pymax/connection/readers/tcp.py +29 -0
  57. pymax/connection/readers/ws.py +14 -0
  58. pymax/dispatch/__init__.py +10 -0
  59. pymax/dispatch/dispatcher.py +222 -0
  60. pymax/dispatch/enums.py +12 -0
  61. pymax/dispatch/mapping.py +73 -0
  62. pymax/dispatch/resolvers.py +52 -0
  63. pymax/dispatch/router.py +216 -0
  64. pymax/exceptions.py +22 -89
  65. pymax/files/__init__.py +9 -0
  66. pymax/files/base.py +82 -0
  67. pymax/files/file.py +76 -0
  68. pymax/files/photo.py +108 -0
  69. pymax/files/static.py +10 -0
  70. pymax/files/video.py +74 -0
  71. pymax/formatting/__init__.py +0 -0
  72. pymax/formatting/markdown.py +217 -0
  73. pymax/infra/__init__.py +1 -0
  74. pymax/infra/auth.py +55 -0
  75. pymax/infra/base.py +15 -0
  76. pymax/infra/chat.py +240 -0
  77. pymax/infra/message.py +252 -0
  78. pymax/infra/protocol.py +9 -0
  79. pymax/infra/self.py +139 -0
  80. pymax/infra/user.py +107 -0
  81. pymax/logging.py +129 -0
  82. pymax/protocol/__init__.py +11 -0
  83. pymax/protocol/base.py +13 -0
  84. pymax/{static/enum.py → protocol/enums.py} +36 -79
  85. pymax/protocol/models.py +33 -0
  86. pymax/protocol/tcp/__init__.py +1 -0
  87. pymax/protocol/tcp/compression.py +97 -0
  88. pymax/protocol/tcp/framing.py +68 -0
  89. pymax/protocol/tcp/payload.py +127 -0
  90. pymax/protocol/tcp/protocol.py +68 -0
  91. pymax/protocol/ws/__init__.py +1 -0
  92. pymax/protocol/ws/protocol.py +27 -0
  93. pymax/py.typed +0 -0
  94. pymax/routers.py +8 -0
  95. pymax/session/__init__.py +3 -0
  96. pymax/session/models.py +11 -0
  97. pymax/session/protocol.py +14 -0
  98. pymax/session/store.py +232 -0
  99. pymax/telemetry/__init__.py +3 -0
  100. pymax/telemetry/navigation.py +181 -0
  101. pymax/telemetry/payloads.py +142 -0
  102. pymax/telemetry/service.py +225 -0
  103. pymax/transport/__init__.py +0 -0
  104. pymax/transport/base.py +14 -0
  105. pymax/transport/tcp.py +93 -0
  106. pymax/transport/websocket.py +50 -0
  107. pymax/types/__init__.py +2 -0
  108. pymax/types/domain/__init__.py +11 -0
  109. pymax/types/domain/attachments/__init__.py +11 -0
  110. pymax/types/domain/attachments/audio.py +35 -0
  111. pymax/types/domain/attachments/call.py +26 -0
  112. pymax/types/domain/attachments/contact.py +32 -0
  113. pymax/types/domain/attachments/control.py +20 -0
  114. pymax/types/domain/attachments/enums.py +27 -0
  115. pymax/types/domain/attachments/file.py +56 -0
  116. pymax/types/domain/attachments/keyboards/__init__.py +1 -0
  117. pymax/types/domain/attachments/keyboards/inline.py +19 -0
  118. pymax/types/domain/attachments/photo.py +45 -0
  119. pymax/types/domain/attachments/share.py +29 -0
  120. pymax/types/domain/attachments/sticker.py +50 -0
  121. pymax/types/domain/attachments/video.py +90 -0
  122. pymax/types/domain/auth.py +161 -0
  123. pymax/types/domain/base.py +17 -0
  124. pymax/types/domain/chat.py +426 -0
  125. pymax/types/domain/element.py +24 -0
  126. pymax/types/domain/enums.py +24 -0
  127. pymax/types/domain/error.py +20 -0
  128. pymax/types/domain/folder.py +74 -0
  129. pymax/types/domain/login.py +35 -0
  130. pymax/types/domain/message.py +378 -0
  131. pymax/types/domain/name.py +20 -0
  132. pymax/types/domain/profile.py +15 -0
  133. pymax/types/domain/session.py +52 -0
  134. pymax/types/domain/sync.py +80 -0
  135. pymax/types/domain/user.py +117 -0
  136. pymax/types/events/__init__.py +3 -0
  137. pymax/types/events/file.py +5 -0
  138. pymax/types/events/message.py +37 -0
  139. pymax/types/events/video.py +5 -0
  140. maxapi_python-1.2.4.dist-info/METADATA +0 -205
  141. maxapi_python-1.2.4.dist-info/RECORD +0 -33
  142. pymax/core.py +0 -390
  143. pymax/crud.py +0 -96
  144. pymax/files.py +0 -138
  145. pymax/filters.py +0 -164
  146. pymax/formatter.py +0 -31
  147. pymax/formatting.py +0 -74
  148. pymax/interfaces.py +0 -552
  149. pymax/mixins/__init__.py +0 -40
  150. pymax/mixins/auth.py +0 -368
  151. pymax/mixins/channel.py +0 -130
  152. pymax/mixins/group.py +0 -458
  153. pymax/mixins/handler.py +0 -285
  154. pymax/mixins/message.py +0 -879
  155. pymax/mixins/scheduler.py +0 -28
  156. pymax/mixins/self.py +0 -259
  157. pymax/mixins/socket.py +0 -297
  158. pymax/mixins/telemetry.py +0 -112
  159. pymax/mixins/user.py +0 -219
  160. pymax/mixins/websocket.py +0 -142
  161. pymax/models.py +0 -8
  162. pymax/navigation.py +0 -187
  163. pymax/payloads.py +0 -367
  164. pymax/protocols.py +0 -123
  165. pymax/static/constant.py +0 -89
  166. pymax/types.py +0 -1220
  167. pymax/utils.py +0 -90
  168. {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
@@ -0,0 +1,3 @@
1
+ from .file import FileUploadSignal
2
+ from .message import MessageDeleteEvent
3
+ from .video import VideoUploadSignal
@@ -0,0 +1,5 @@
1
+ from pymax.types.domain.base import CamelModel
2
+
3
+
4
+ class FileUploadSignal(CamelModel):
5
+ file_id: int