maxapi-python 2.4.0__py3-none-any.whl → 2.5.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 (101) hide show
  1. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +22 -12
  2. maxapi_python-2.5.0.dist-info/RECORD +195 -0
  3. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +8 -2
  5. pymax/_data/apk_fingerprints.json +374 -17
  6. pymax/_data/rootca_ssl_rsa2022.crt +33 -0
  7. pymax/api/auth/service.py +15 -2
  8. pymax/api/auth/types.py +3 -0
  9. pymax/api/bots/payloads.py +8 -0
  10. pymax/api/bots/service.py +13 -1
  11. pymax/api/chats/enums.py +1 -0
  12. pymax/api/chats/payloads.py +21 -1
  13. pymax/api/chats/service.py +41 -0
  14. pymax/api/facade.py +2 -0
  15. pymax/api/messages/__init__.py +1 -1
  16. pymax/api/messages/enums.py +1 -0
  17. pymax/api/messages/payloads.py +56 -4
  18. pymax/api/messages/service.py +405 -27
  19. pymax/api/response.py +1 -1
  20. pymax/api/stories/__init__.py +2 -0
  21. pymax/api/stories/enums.py +19 -0
  22. pymax/api/stories/payloads.py +64 -0
  23. pymax/api/stories/service.py +198 -0
  24. pymax/api/uploads/payloads.py +42 -1
  25. pymax/api/uploads/service.py +171 -69
  26. pymax/app.py +39 -8
  27. pymax/auth/__init__.py +2 -0
  28. pymax/auth/exceptions.py +9 -0
  29. pymax/auth/providers.py +6 -2
  30. pymax/auth/qr.py +11 -1
  31. pymax/auth/sms.py +11 -1
  32. pymax/base.py +146 -27
  33. pymax/client.py +39 -11
  34. pymax/client_web.py +15 -12
  35. pymax/config.py +101 -53
  36. pymax/di/__init__.py +2 -0
  37. pymax/di/context.py +18 -0
  38. pymax/di/resolver.py +38 -0
  39. pymax/dispatch/__init__.py +7 -0
  40. pymax/dispatch/dispatcher.py +143 -40
  41. pymax/dispatch/enums.py +1 -0
  42. pymax/dispatch/mapping.py +23 -3
  43. pymax/dispatch/middleware.py +32 -0
  44. pymax/dispatch/resolvers.py +4 -0
  45. pymax/dispatch/router.py +86 -34
  46. pymax/files/base.py +58 -1
  47. pymax/files/video.py +32 -40
  48. pymax/files/voice.py +18 -4
  49. pymax/filters/__init__.py +15 -0
  50. pymax/filters/base.py +218 -0
  51. pymax/filters/containers/__init__.py +2 -0
  52. pymax/filters/containers/chat.py +240 -0
  53. pymax/filters/containers/message.py +147 -0
  54. pymax/filters/filters.py +211 -0
  55. pymax/filters/magic.py +13 -0
  56. pymax/filters/presets.py +88 -0
  57. pymax/filters/refs.py +56 -0
  58. pymax/fingerprint/fingerprint.py +5 -22
  59. pymax/fingerprint/models.py +1 -0
  60. pymax/formatting/markdown.py +55 -1
  61. pymax/fsm/__init__.py +3 -0
  62. pymax/fsm/context.py +30 -0
  63. pymax/fsm/protocol.py +14 -0
  64. pymax/fsm/state.py +70 -0
  65. pymax/fsm/storage.py +151 -0
  66. pymax/infra/base.py +2 -0
  67. pymax/infra/bots.py +20 -0
  68. pymax/infra/chat.py +37 -5
  69. pymax/infra/message.py +204 -9
  70. pymax/infra/protocol.py +5 -1
  71. pymax/infra/stories.py +205 -0
  72. pymax/integrations/__init__.py +0 -0
  73. pymax/integrations/dishka.py +109 -0
  74. pymax/protocol/enums.py +2 -0
  75. pymax/protocol/tcp/payload.py +0 -2
  76. pymax/session/__init__.py +1 -1
  77. pymax/session/models.py +30 -0
  78. pymax/session/protocol.py +33 -7
  79. pymax/session/store.py +73 -5
  80. pymax/transport/tcp.py +11 -3
  81. pymax/types/domain/__init__.py +23 -1
  82. pymax/types/domain/attachments/__init__.py +1 -1
  83. pymax/types/domain/attachments/call.py +7 -1
  84. pymax/types/domain/attachments/contact.py +3 -3
  85. pymax/types/domain/attachments/control.py +3 -0
  86. pymax/types/domain/attachments/enums.py +16 -0
  87. pymax/types/domain/attachments/keyboards/__init__.py +1 -1
  88. pymax/types/domain/attachments/keyboards/enums.py +12 -0
  89. pymax/types/domain/attachments/keyboards/inline.py +74 -3
  90. pymax/types/domain/attachments/video.py +3 -3
  91. pymax/types/domain/callback.py +19 -0
  92. pymax/types/domain/comment.py +24 -0
  93. pymax/types/domain/enums.py +7 -0
  94. pymax/types/domain/message.py +158 -9
  95. pymax/types/domain/story.py +126 -0
  96. pymax/types/events/reaction.py +28 -4
  97. pymax/versions/__init__.py +2 -0
  98. pymax/versions/catalog.py +104 -0
  99. pymax/versions/exceptions.py +10 -0
  100. maxapi_python-2.4.0.dist-info/RECORD +0 -161
  101. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -10,8 +10,8 @@ from .enums import AttachmentType
10
10
  class ContactAttachment(CamelModel):
11
11
  """Контактное вложение сообщения.
12
12
 
13
- :ivar contact_id: ID контакта.
14
- :vartype contact_id: int
13
+ :ivar contact_id: ID контакта, если Max его прислал.
14
+ :vartype contact_id: int | None
15
15
  :ivar first_name: Имя контакта.
16
16
  :vartype first_name: str | None
17
17
  :ivar last_name: Фамилия контакта.
@@ -24,7 +24,7 @@ class ContactAttachment(CamelModel):
24
24
  :vartype type: Literal[AttachmentType.CONTACT]
25
25
  """
26
26
 
27
- contact_id: int
27
+ contact_id: int | None = None
28
28
  first_name: str | None = None
29
29
  last_name: str | None = None
30
30
  name: str | None = None
@@ -14,7 +14,10 @@ class ControlAttachment(CamelModel):
14
14
  :vartype type: Literal[AttachmentType.CONTROL]
15
15
  :ivar event: Событие управления.
16
16
  :vartype event: str
17
+ :ivar title: Заголовок служебного события, если он есть в payload-е.
18
+ :vartype title: str | None
17
19
  """
18
20
 
19
21
  type: Literal[AttachmentType.CONTROL] = Field(alias="_type")
20
22
  event: str
23
+ title: str | None = None
@@ -42,3 +42,19 @@ class PollFlags(IntFlag):
42
42
  FLAG_SETTINGS_CLOSED = 8
43
43
  FLAG_SETTINGS_QUIZ = 16
44
44
  FLAG_SETTINGS_CAN_FORWARD = 32
45
+
46
+
47
+ class CallType(str, Enum):
48
+ """Тип звонка во входящем ``CallAttachment``."""
49
+
50
+ AUDIO = "AUDIO"
51
+ VIDEO = "VIDEO"
52
+
53
+
54
+ class HangupType(str, Enum):
55
+ """Причина завершения звонка во входящем ``CallAttachment``."""
56
+
57
+ MISSED = "MISSED"
58
+ REJECTED = "REJECTED"
59
+ CANCELED = "CANCELED"
60
+ HUNGUP = "HUNGUP"
@@ -1 +1 @@
1
- from .inline import InlineKeyboardAttachment
1
+ from .inline import Button, ButtonType, InlineKeyboardAttachment
@@ -0,0 +1,12 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ButtonType(str, Enum):
5
+ CALLBACK = "CALLBACK"
6
+ LINK = "LINK"
7
+ CHAT = "CHAT"
8
+ REQUEST_GEO_LOCATION = "REQUEST_GEO_LOCATION"
9
+ REQUEST_CONTACT = "REQUEST_CONTACT"
10
+ OPEN_APP = "OPEN_APP"
11
+ MESSAGE = "MESSAGE"
12
+ CLIPBOARD = "CLIPBOARD"
@@ -1,10 +1,78 @@
1
- from typing import Any, Literal
1
+ from typing import Annotated, Literal, TypeAlias
2
2
 
3
3
  from pydantic import Field
4
4
 
5
5
  from pymax.types.domain.attachments import AttachmentType
6
6
  from pymax.types.domain.base import CamelModel
7
7
 
8
+ from .enums import ButtonType
9
+
10
+
11
+ class BaseButton(CamelModel):
12
+ text: str
13
+
14
+
15
+ class CallbackButton(BaseButton):
16
+ type: Literal[ButtonType.CALLBACK] = ButtonType.CALLBACK
17
+ text: str
18
+ payload: str
19
+ intent: str
20
+
21
+
22
+ class LinkButton(BaseButton):
23
+ type: Literal[ButtonType.LINK] = ButtonType.LINK
24
+ url: str
25
+
26
+
27
+ class ChatButton(BaseButton):
28
+ type: Literal[ButtonType.CHAT] = ButtonType.CHAT
29
+ payload: str
30
+
31
+
32
+ class GeoLocationButton(BaseButton):
33
+ type: Literal[ButtonType.REQUEST_GEO_LOCATION] = ButtonType.REQUEST_GEO_LOCATION
34
+ quick: bool | None = None
35
+
36
+
37
+ class RequestContactButton(BaseButton):
38
+ type: Literal[ButtonType.REQUEST_CONTACT] = ButtonType.REQUEST_CONTACT
39
+
40
+
41
+ class OpenAppButton(BaseButton):
42
+ type: Literal[ButtonType.OPEN_APP] = ButtonType.OPEN_APP
43
+ contact_id: int | None = None
44
+ payload: str | None = None
45
+ web_app: str | None = None
46
+
47
+
48
+ class MessageButton(BaseButton):
49
+ type: Literal[ButtonType.MESSAGE] = ButtonType.MESSAGE
50
+
51
+
52
+ class ClipboardButton(BaseButton):
53
+ type: Literal[ButtonType.CLIPBOARD] = ButtonType.CLIPBOARD
54
+ payload: str
55
+
56
+
57
+ Button: TypeAlias = Annotated[
58
+ CallbackButton
59
+ | ChatButton
60
+ | LinkButton
61
+ | RequestContactButton
62
+ | GeoLocationButton
63
+ | OpenAppButton
64
+ | MessageButton
65
+ | ClipboardButton,
66
+ Field(discriminator="type"),
67
+ ]
68
+ ButtonRow: TypeAlias = list[Button]
69
+
70
+
71
+ class InlineKeyboard(CamelModel):
72
+ """Inline-клавиатура со списком строк кнопок в ``buttons``."""
73
+
74
+ buttons: list[ButtonRow]
75
+
8
76
 
9
77
  class InlineKeyboardAttachment(CamelModel):
10
78
  """Вложение inline-клавиатуры.
@@ -12,8 +80,11 @@ class InlineKeyboardAttachment(CamelModel):
12
80
  :ivar type: Тип вложения.
13
81
  :vartype type: Literal[AttachmentType.INLINE_KEYBOARD]
14
82
  :ivar keyboard: Данные inline-клавиатуры.
15
- :vartype keyboard: dict[str, Any]
83
+ :vartype keyboard: InlineKeyboard
84
+ :ivar callback_id: ID клавиатуры для ``client.send_callback()``.
85
+ :vartype callback_id: str
16
86
  """
17
87
 
18
88
  type: Literal[AttachmentType.INLINE_KEYBOARD] = Field(alias="_type")
19
- keyboard: dict[str, Any]
89
+ keyboard: InlineKeyboard
90
+ callback_id: str
@@ -32,8 +32,8 @@ class VideoAttachment(CamelModel):
32
32
  :vartype video_id: int
33
33
  :ivar duration: Длительность видео.
34
34
  :vartype duration: int | None
35
- :ivar preview_data: Данные превью.
36
- :vartype preview_data: bytes
35
+ :ivar preview_data: Данные превью, если Max их прислал.
36
+ :vartype preview_data: bytes | None
37
37
  :ivar type: Тип вложения.
38
38
  :vartype type: Literal[AttachmentType.VIDEO]
39
39
  :ivar thumbnail: URL миниатюры.
@@ -48,7 +48,7 @@ class VideoAttachment(CamelModel):
48
48
  width: int
49
49
  video_id: int
50
50
  duration: int | None = None
51
- preview_data: bytes
51
+ preview_data: bytes | None = None
52
52
  type: Literal[AttachmentType.VIDEO] = Field(alias="_type")
53
53
  thumbnail: str
54
54
  token: str
@@ -0,0 +1,19 @@
1
+ from .base import CamelModel
2
+ from .chat import Chat
3
+ from .message import Message
4
+
5
+
6
+ class CallbackResponse(CamelModel):
7
+ """Результат нажатия inline-кнопки бота.
8
+
9
+ ``success`` показывает результат операции. Необязательные ``message`` и
10
+ ``chat`` содержат дополнительные данные ответа Max и поддерживают методы
11
+ действий, если ответ получен через клиент.
12
+ """
13
+
14
+ success: bool
15
+ unread: int
16
+ mark: int
17
+ message: Message | None = None
18
+ chat: Chat | None = None
19
+ chat_access_token: str | None = None
@@ -0,0 +1,24 @@
1
+ from .base import CamelModel
2
+
3
+
4
+ class CommentsInfo(CamelModel):
5
+ """Счетчики комментариев поста.
6
+
7
+ :ivar total_count: Общее количество комментариев.
8
+ :vartype total_count: int
9
+ """
10
+
11
+ total_count: int = 0
12
+
13
+
14
+ class CommentsInfoUpdate(CamelModel):
15
+ """Обновление счетчиков комментариев поста.
16
+
17
+ :ivar post_id: ID поста.
18
+ :vartype post_id: int
19
+ :ivar comments_info: Счетчики комментариев поста, если Max их прислал.
20
+ :vartype comments_info: CommentsInfo | None
21
+ """
22
+
23
+ post_id: int
24
+ comments_info: CommentsInfo | None
@@ -22,3 +22,10 @@ class MessageStatus(str, Enum):
22
22
 
23
23
  EDITED = "EDITED"
24
24
  REMOVED = "REMOVED"
25
+
26
+
27
+ class LinkType(str, Enum):
28
+ """Тип связи входящего сообщения с исходным сообщением."""
29
+
30
+ REPLY = "REPLY"
31
+ FORWARD = "FORWARD"
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Sequence
4
- from typing import TYPE_CHECKING, Annotated, Any, TypeAlias
4
+ from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeAlias
5
5
 
6
6
  from pydantic import Field, PrivateAttr, model_validator
7
7
 
@@ -24,10 +24,10 @@ from pymax.types.domain import (
24
24
 
25
25
  from .base import CamelModel
26
26
  from .element import Element
27
- from .enums import MessageStatus
27
+ from .enums import AccessType, LinkType, MessageStatus
28
28
 
29
29
  if TYPE_CHECKING:
30
- from pymax.api.messages.service import MessageService
30
+ from pymax.api.messages import DateTimeUnion, MessageService
31
31
 
32
32
 
33
33
  KnownAttachment: TypeAlias = Annotated[
@@ -47,6 +47,12 @@ KnownAttachment: TypeAlias = Annotated[
47
47
  Attachment: TypeAlias = KnownAttachment | UnknownAttachment
48
48
  SendAttachment: TypeAlias = Photo | File | Video | Poll | Voice | VideoNote
49
49
  SendAttachments: TypeAlias = Sequence[SendAttachment] | None
50
+ Link: TypeAlias = Annotated["ReplyLink | ForwardLink", Field(discriminator="type")]
51
+
52
+
53
+ class MessageLink(CamelModel):
54
+ message: Message
55
+ chat_id: int
50
56
 
51
57
 
52
58
  class ReactionCounter(CamelModel):
@@ -91,6 +97,48 @@ class ReadState(CamelModel):
91
97
  mark: int
92
98
 
93
99
 
100
+ class DelayedAttributes(CamelModel):
101
+ time_to_fire: int
102
+ notify_sender: bool
103
+ notify_opponents: bool
104
+
105
+
106
+ class ReplyLink(MessageLink):
107
+ """Источник сообщения, отправленного как reply.
108
+
109
+ :ivar message: Сообщение, на которое ответили.
110
+ :vartype message: Message
111
+ :ivar chat_id: ID чата исходного сообщения.
112
+ :vartype chat_id: int
113
+ """
114
+
115
+ type: Literal[LinkType.REPLY] = LinkType.REPLY
116
+ message: Message
117
+ chat_id: int
118
+
119
+
120
+ class ForwardLink(MessageLink):
121
+ """Источник пересланного сообщения.
122
+
123
+ Помимо исходного сообщения содержит доступные в payload-е имя, ссылку,
124
+ тип доступа и иконку исходного чата. Для скрытого источника эти метаданные
125
+ могут быть ``None``.
126
+
127
+ :ivar message: Исходное сообщение.
128
+ :vartype message: Message
129
+ :ivar chat_id: ID исходного чата.
130
+ :vartype chat_id: int
131
+ """
132
+
133
+ type: Literal[LinkType.FORWARD] = LinkType.FORWARD
134
+ message: Message
135
+ chat_id: int
136
+ chat_name: str | None = None
137
+ chat_link: str | None = None
138
+ chat_access_type: AccessType | None = None
139
+ chat_icon_url: str | None = None
140
+
141
+
94
142
  class Message(CamelModel):
95
143
  """Сообщение Max с методами для действий над ним.
96
144
 
@@ -120,6 +168,8 @@ class Message(CamelModel):
120
168
  :vartype id: int
121
169
  :ivar chat_id: ID чата, если он есть в данных сообщения.
122
170
  :vartype chat_id: int | None
171
+ :ivar post_id: ID поста канала, если сообщение является комментарием.
172
+ :vartype post_id: int | None
123
173
  :ivar sender: ID отправителя.
124
174
  :vartype sender: int | None
125
175
  :ivar text: Текст сообщения.
@@ -130,7 +180,8 @@ class Message(CamelModel):
130
180
  :vartype type: str
131
181
  :ivar cid: Клиентский ID сообщения, если он есть в payload-е.
132
182
  :vartype cid: int | None
133
- :ivar attaches: Вложения сообщения.
183
+ :ivar attaches: Вложения сообщения. Неизвестный ``_type`` представлен как
184
+ ``UnknownAttachment`` с сохраненными дополнительными полями.
134
185
  :vartype attaches: list[Attachment]
135
186
  :ivar stats: Дополнительная статистика сообщения от Max.
136
187
  :vartype stats: dict[str, Any] | None
@@ -150,10 +201,17 @@ class Message(CamelModel):
150
201
  :vartype mark: int | None
151
202
  :ivar elements: Форматированные элементы текста сообщения.
152
203
  :vartype elements: list[Element]
204
+ :ivar delayed_attributes: Время срабатывания в миллисекундах и настройки
205
+ уведомлений для отложенного сообщения; ``None`` для обычного.
206
+ :vartype delayed_attributes: DelayedAttributes | None
207
+ :ivar link: Исходное сообщение и чат для reply или forward; ``None`` для
208
+ обычного сообщения без связи.
209
+ :vartype link: ReplyLink | ForwardLink | None
153
210
  """
154
211
 
155
212
  id: int
156
213
  chat_id: int | None = None
214
+ post_id: int | None = None
157
215
  sender: int | None = None
158
216
  text: str = ""
159
217
  time: int
@@ -169,6 +227,8 @@ class Message(CamelModel):
169
227
  unread: int | None = None
170
228
  mark: int | None = None
171
229
  elements: list[Element] = Field(default_factory=list)
230
+ delayed_attributes: DelayedAttributes | None = None
231
+ link: Link | None = None
172
232
 
173
233
  _actions: MessageService | None = PrivateAttr(default=None)
174
234
 
@@ -189,8 +249,9 @@ class Message(CamelModel):
189
249
  attachments: SendAttachments = None,
190
250
  *,
191
251
  notify: bool = True,
252
+ send_at: DateTimeUnion | None = None,
192
253
  ) -> Message:
193
- """Отправляет ответ на это сообщение в тот же чат.
254
+ """Отправляет ответ в тот же чат или ветку комментариев.
194
255
 
195
256
  :param text: Текст сообщения.
196
257
  :type text: str | None
@@ -198,19 +259,43 @@ class Message(CamelModel):
198
259
  :type attachments: SendAttachments
199
260
  :param notify: Отправить ли получателям push-уведомление.
200
261
  :type notify: bool
262
+ :param send_at: Абсолютный ``datetime``, относительный ``timedelta``
263
+ или Unix time в секундах. ``None`` и ``0`` отправляют сразу.
264
+ :type send_at: DateTimeUnion | None
201
265
  :returns: Отправленное сообщение.
202
266
  :rtype: Message
203
267
  :raises RuntimeError: Если сообщение не привязано к сервису или не
204
268
  содержит ``chat_id``.
269
+ :raises ValueError: Если для комментария задана отложенная отправка.
205
270
  """
206
271
  actions, chat_id = self._bound()
207
272
 
273
+ if self.post_id is not None:
274
+ if send_at:
275
+ raise ValueError("Scheduled comments are not supported")
276
+
277
+ message = await actions.send_comment(
278
+ chat_id=chat_id,
279
+ post_id=self.post_id,
280
+ text=text,
281
+ reply_to=self.id,
282
+ attachments=attachments,
283
+ notify=notify,
284
+ )
285
+ if message.chat_id is None:
286
+ message.chat_id = chat_id
287
+ if message.post_id is None:
288
+ message.post_id = self.post_id
289
+
290
+ return message
291
+
208
292
  return await actions.send_message(
209
293
  chat_id=chat_id,
210
294
  text=text,
211
295
  reply_to=self.id,
212
296
  attachments=attachments,
213
297
  notify=notify,
298
+ send_at=send_at,
214
299
  )
215
300
 
216
301
  async def answer(
@@ -220,8 +305,9 @@ class Message(CamelModel):
220
305
  attachments: SendAttachments = None,
221
306
  *,
222
307
  notify: bool = True,
308
+ send_at: DateTimeUnion | None = None,
223
309
  ) -> Message:
224
- """Отправляет сообщение в тот же чат.
310
+ """Отправляет сообщение в тот же чат или ветку комментариев.
225
311
 
226
312
  :param text: Текст сообщения.
227
313
  :type text: str | None
@@ -231,19 +317,43 @@ class Message(CamelModel):
231
317
  :type attachments: SendAttachments
232
318
  :param notify: Отправить ли получателям push-уведомление.
233
319
  :type notify: bool
320
+ :param send_at: Абсолютный ``datetime``, относительный ``timedelta``
321
+ или Unix time в секундах. ``None`` и ``0`` отправляют сразу.
322
+ :type send_at: DateTimeUnion | None
234
323
  :returns: Отправленное сообщение.
235
324
  :rtype: Message
236
325
  :raises RuntimeError: Если сообщение не привязано к сервису или не
237
326
  содержит ``chat_id``.
327
+ :raises ValueError: Если для комментария задана отложенная отправка.
238
328
  """
239
329
  actions, chat_id = self._bound()
240
330
 
331
+ if self.post_id is not None:
332
+ if send_at:
333
+ raise ValueError("Scheduled comments are not supported")
334
+
335
+ message = await actions.send_comment(
336
+ chat_id=chat_id,
337
+ post_id=self.post_id,
338
+ text=text,
339
+ reply_to=reply_to,
340
+ attachments=attachments,
341
+ notify=notify,
342
+ )
343
+ if message.chat_id is None:
344
+ message.chat_id = chat_id
345
+ if message.post_id is None:
346
+ message.post_id = self.post_id
347
+
348
+ return message
349
+
241
350
  return await actions.send_message(
242
351
  chat_id=chat_id,
243
352
  text=text,
244
353
  reply_to=reply_to,
245
354
  attachments=attachments,
246
355
  notify=notify,
356
+ send_at=send_at,
247
357
  )
248
358
 
249
359
  async def forward(
@@ -308,6 +418,21 @@ class Message(CamelModel):
308
418
  """
309
419
  actions, chat_id = self._bound()
310
420
 
421
+ if self.post_id is not None:
422
+ message = await actions.edit_comment(
423
+ chat_id=chat_id,
424
+ post_id=self.post_id,
425
+ message_id=self.id,
426
+ text=text,
427
+ attachments=attachments,
428
+ )
429
+ if message.chat_id is None:
430
+ message.chat_id = chat_id
431
+ if message.post_id is None:
432
+ message.post_id = self.post_id
433
+
434
+ return message
435
+
311
436
  return await actions.edit_message(
312
437
  chat_id=chat_id,
313
438
  message_id=self.id,
@@ -327,6 +452,14 @@ class Message(CamelModel):
327
452
  """
328
453
  actions, chat_id = self._bound()
329
454
 
455
+ if self.post_id is not None:
456
+ return await actions.delete_comment(
457
+ chat_id=chat_id,
458
+ post_id=self.post_id,
459
+ message_ids=[self.id],
460
+ for_me=for_me,
461
+ )
462
+
330
463
  return await actions.delete_message(
331
464
  chat_id=chat_id,
332
465
  message_ids=[self.id],
@@ -360,9 +493,17 @@ class Message(CamelModel):
360
493
  """
361
494
  actions, chat_id = self._bound()
362
495
 
496
+ if self.post_id is not None:
497
+ return await actions.add_comment_reaction(
498
+ chat_id=chat_id,
499
+ post_id=self.post_id,
500
+ message_id=self.id,
501
+ reaction=reaction,
502
+ )
503
+
363
504
  return await actions.add_reaction(
364
505
  chat_id=chat_id,
365
- message_id=str(self.id), # :C
506
+ message_id=self.id,
366
507
  reaction=reaction,
367
508
  )
368
509
 
@@ -376,9 +517,16 @@ class Message(CamelModel):
376
517
  """
377
518
  actions, chat_id = self._bound()
378
519
 
520
+ if self.post_id is not None:
521
+ return await actions.remove_comment_reaction(
522
+ chat_id=chat_id,
523
+ post_id=self.post_id,
524
+ message_id=self.id,
525
+ )
526
+
379
527
  return await actions.remove_reaction(
380
528
  chat_id=chat_id,
381
- message_id=str(self.id), # :C x2
529
+ message_id=self.id,
382
530
  )
383
531
 
384
532
  async def get_reactions(self) -> dict[str, ReactionInfo] | None:
@@ -394,7 +542,7 @@ class Message(CamelModel):
394
542
 
395
543
  return await actions.get_reactions(
396
544
  chat_id=chat_id,
397
- message_ids=[str(self.id)], # :C x3
545
+ message_ids=[self.id],
398
546
  )
399
547
 
400
548
  def _bound(self) -> tuple[MessageService, int]:
@@ -427,6 +575,7 @@ class Message(CamelModel):
427
575
  return {
428
576
  **message,
429
577
  "chatId": value.get("chatId"),
578
+ "postId": value.get("postId"),
430
579
  "prevMessageId": value.get("prevMessageId"),
431
580
  "ttl": value.get("ttl"),
432
581
  "unread": value.get("unread"),
@@ -0,0 +1,126 @@
1
+ from typing import Annotated, Literal, TypeAlias
2
+
3
+ from pydantic import Field
4
+
5
+ from .attachments.enums import AttachmentType
6
+ from .base import CamelModel
7
+
8
+
9
+ class StoryCoordinates(CamelModel):
10
+ """Положение слоя сторис в долях ширины и высоты кадра."""
11
+
12
+ x: float
13
+ y: float
14
+ w: float
15
+ h: float
16
+ rotation: float = 0
17
+
18
+
19
+ class StoryClickableLink(CamelModel):
20
+ """Ссылка слоя сторис и результат ее проверки сервером."""
21
+
22
+ url: str
23
+ check_result: int | None = None
24
+
25
+
26
+ class StoryLayer(CamelModel):
27
+ """Слой сторис со ссылкой и координатами."""
28
+
29
+ type: int = 0
30
+ coordinates: StoryCoordinates
31
+ clickable_link: StoryClickableLink
32
+
33
+
34
+ class StoryPhotoMedia(CamelModel):
35
+ """Фото сторис, полученное от Max, без обязательного токена загрузки."""
36
+
37
+ base_url: str
38
+ height: int
39
+ width: int
40
+ photo_id: int
41
+ preview_data: bytes | None = None
42
+ type: Literal[AttachmentType.PHOTO] = Field(alias="_type")
43
+
44
+
45
+ class StoryVideoMedia(CamelModel):
46
+ """Видео сторис, полученное от Max.
47
+
48
+ В отличие от ``VideoAttachment``, не требует токен загрузки и
49
+ ``video_type``, которые используются при публикации.
50
+ """
51
+
52
+ height: int
53
+ width: int
54
+ video_id: int
55
+ duration: int | None = None
56
+ preview_data: bytes | None = None
57
+ type: Literal[AttachmentType.VIDEO] = Field(alias="_type")
58
+ thumbnail: str
59
+
60
+
61
+ StoryMedia: TypeAlias = Annotated[
62
+ StoryPhotoMedia | StoryVideoMedia,
63
+ Field(discriminator="type"),
64
+ ]
65
+
66
+
67
+ class Story(CamelModel):
68
+ """Сторис, возвращенная после публикации или при чтении.
69
+
70
+ :ivar id: ID сторис.
71
+ :ivar cid: Идентификатор публикации, сформированный клиентом.
72
+ :ivar media: Фото или видео сторис.
73
+ :ivar layers: Слои поверх кадра.
74
+ :ivar settings: Настройки сторис в формате Max.
75
+ :ivar expiration: Время жизни сторис в миллисекундах.
76
+ """
77
+
78
+ id: int
79
+ cid: int | None = None
80
+ media: StoryMedia
81
+ layers: list[StoryLayer] = Field(default_factory=list)
82
+ settings: int
83
+ expiration: int
84
+
85
+
86
+ class StoryOwner(CamelModel):
87
+ """Владелец сторис.
88
+
89
+ :ivar owner_id: ID владельца, а не отдельной сторис.
90
+ :ivar type: Числовой тип владельца из Max. ``0`` — пользователь.
91
+ """
92
+
93
+ owner_id: int
94
+ type: int = 0
95
+
96
+
97
+ class StoryPreview(CamelModel):
98
+ """Сводная информация о сторис владельца в ленте и обновлениях.
99
+
100
+ ID отдельных сторис в превью нет. Для их получения передайте
101
+ ``owner.owner_id`` и ``owner.type`` в ``client.get_stories()``.
102
+
103
+ :ivar owner: Владелец сторис.
104
+ :ivar total_count: Общее количество сторис владельца.
105
+ :ivar read_count: Количество просмотренных сторис владельца.
106
+ """
107
+
108
+ owner: StoryOwner
109
+ total_count: int = 0
110
+ read_count: int = 0
111
+ last_story_expiration_time: int = 0
112
+ settings: int = 0
113
+ update_time: int = 0
114
+
115
+
116
+ class StoryDetailedStats(CamelModel):
117
+ """Запись подробной статистики просмотра или реакции на сторис.
118
+
119
+ Типизировано только ``user_id``. Дополнительные поля доступны в
120
+ ``model_extra`` с исходными именами ответа Max, без преобразования
121
+ camelCase. Поле ``reaction``, если оно пришло, остается словарем.
122
+
123
+ :ivar user_id: ID пользователя.
124
+ """
125
+
126
+ user_id: int