maxapi-python 2.4.1__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 (74) hide show
  1. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +17 -9
  2. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/RECORD +74 -45
  3. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +5 -1
  5. pymax/_data/apk_fingerprints.json +272 -0
  6. pymax/api/auth/service.py +1 -0
  7. pymax/api/auth/types.py +3 -0
  8. pymax/api/bots/payloads.py +8 -0
  9. pymax/api/bots/service.py +13 -1
  10. pymax/api/chats/enums.py +1 -0
  11. pymax/api/chats/payloads.py +19 -0
  12. pymax/api/chats/service.py +34 -0
  13. pymax/api/facade.py +2 -0
  14. pymax/api/messages/enums.py +1 -0
  15. pymax/api/messages/payloads.py +47 -1
  16. pymax/api/messages/service.py +308 -24
  17. pymax/api/response.py +1 -1
  18. pymax/api/stories/__init__.py +2 -0
  19. pymax/api/stories/enums.py +19 -0
  20. pymax/api/stories/payloads.py +64 -0
  21. pymax/api/stories/service.py +198 -0
  22. pymax/api/uploads/payloads.py +10 -1
  23. pymax/api/uploads/service.py +94 -7
  24. pymax/app.py +16 -4
  25. pymax/auth/qr.py +11 -1
  26. pymax/base.py +70 -25
  27. pymax/client.py +0 -1
  28. pymax/config.py +38 -32
  29. pymax/di/__init__.py +2 -0
  30. pymax/di/context.py +18 -0
  31. pymax/di/resolver.py +38 -0
  32. pymax/dispatch/__init__.py +7 -0
  33. pymax/dispatch/dispatcher.py +143 -40
  34. pymax/dispatch/enums.py +1 -0
  35. pymax/dispatch/mapping.py +23 -3
  36. pymax/dispatch/middleware.py +32 -0
  37. pymax/dispatch/resolvers.py +4 -0
  38. pymax/dispatch/router.py +86 -34
  39. pymax/filters/__init__.py +15 -0
  40. pymax/filters/base.py +218 -0
  41. pymax/filters/containers/__init__.py +2 -0
  42. pymax/filters/containers/chat.py +240 -0
  43. pymax/filters/containers/message.py +147 -0
  44. pymax/filters/filters.py +211 -0
  45. pymax/filters/magic.py +13 -0
  46. pymax/filters/presets.py +88 -0
  47. pymax/filters/refs.py +56 -0
  48. pymax/formatting/markdown.py +55 -1
  49. pymax/fsm/__init__.py +3 -0
  50. pymax/fsm/context.py +30 -0
  51. pymax/fsm/protocol.py +14 -0
  52. pymax/fsm/state.py +70 -0
  53. pymax/fsm/storage.py +151 -0
  54. pymax/infra/base.py +2 -0
  55. pymax/infra/bots.py +20 -0
  56. pymax/infra/chat.py +28 -0
  57. pymax/infra/message.py +195 -6
  58. pymax/infra/stories.py +205 -0
  59. pymax/integrations/__init__.py +0 -0
  60. pymax/integrations/dishka.py +109 -0
  61. pymax/protocol/enums.py +2 -0
  62. pymax/protocol/tcp/payload.py +0 -2
  63. pymax/types/domain/__init__.py +23 -1
  64. pymax/types/domain/attachments/__init__.py +1 -1
  65. pymax/types/domain/attachments/keyboards/__init__.py +1 -1
  66. pymax/types/domain/attachments/keyboards/enums.py +12 -0
  67. pymax/types/domain/attachments/keyboards/inline.py +74 -3
  68. pymax/types/domain/callback.py +19 -0
  69. pymax/types/domain/comment.py +24 -0
  70. pymax/types/domain/message.py +91 -22
  71. pymax/types/domain/story.py +126 -0
  72. pymax/types/events/reaction.py +28 -4
  73. pymax/versions/catalog.py +1 -1
  74. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,15 @@
1
1
  from pymax.api.models import CamelModel
2
+ from pymax.types import ButtonType
2
3
 
3
4
 
4
5
  class RequestInitDataPayload(CamelModel):
5
6
  bot_id: int
6
7
  chat_id: int | None = None
7
8
  start_param: str | None = None
9
+
10
+
11
+ class SendCallbackPayload(CamelModel):
12
+ callback_id: str
13
+ type: ButtonType
14
+ payload: str | None = None
15
+ timestamp: int
pymax/api/bots/service.py CHANGED
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import time
3
4
  from typing import TYPE_CHECKING
4
5
 
6
+ from pymax.api.binding import bind_api_model
5
7
  from pymax.api.response import require_payload_model
6
8
  from pymax.logging import get_logger
7
9
  from pymax.protocol import Opcode
10
+ from pymax.types import ButtonType, CallbackResponse
8
11
  from pymax.types.domain import InitData
9
12
 
10
- from .payloads import RequestInitDataPayload
13
+ from .payloads import RequestInitDataPayload, SendCallbackPayload
11
14
 
12
15
  if TYPE_CHECKING:
13
16
  from pymax.app import App
@@ -29,3 +32,12 @@ class BotsService:
29
32
  frame = RequestInitDataPayload(bot_id=bot_id, chat_id=chat_id, start_param=start_param)
30
33
  response = await self.app.invoke(Opcode.WEB_APP_INIT_DATA, frame.to_payload())
31
34
  return require_payload_model(response, InitData)
35
+
36
+ async def send_callback(
37
+ self, callback_id: str, type: ButtonType, payload: str | None = None
38
+ ) -> CallbackResponse:
39
+ frame = SendCallbackPayload(
40
+ callback_id=callback_id, type=type, payload=payload, timestamp=int(time.time() * 1000)
41
+ )
42
+ response = await self.app.invoke(Opcode.MSG_SEND_CALLBACK, frame.to_payload())
43
+ return bind_api_model(self.app, require_payload_model(response, CallbackResponse))
pymax/api/chats/enums.py CHANGED
@@ -11,6 +11,7 @@ class ChatMemberOperation(str, Enum):
11
11
 
12
12
 
13
13
  class ChatOption(str, Enum):
14
+ COMMENTS = "COMMENTS"
14
15
  ONLY_OWNER_CAN_CHANGE_ICON_TITLE = "ONLY_OWNER_CAN_CHANGE_ICON_TITLE"
15
16
  ALL_CAN_PIN_MESSAGE = "ALL_CAN_PIN_MESSAGE"
16
17
  ONLY_ADMIN_CAN_ADD_MEMBER = "ONLY_ADMIN_CAN_ADD_MEMBER"
@@ -137,3 +137,22 @@ class AddAdminPayload(CamelModel):
137
137
  type: PermType = PermType.ADMIN
138
138
  operation: str = "add"
139
139
  permissions: int
140
+
141
+
142
+ class ChannelCommentsOptions(CamelModel):
143
+ comments: bool = Field(serialization_alias=ChatOption.COMMENTS.value)
144
+
145
+
146
+ class SetChannelCommentsPayload(CamelModel):
147
+ chat_id: int
148
+ options: ChannelCommentsOptions
149
+
150
+
151
+ class BlockCommentAuthorPayload(CamelModel):
152
+ chat_id: int
153
+ post_id: int
154
+ user_ids: list[int]
155
+ message_id: int
156
+ type: Literal["COMMENTS_BLACKLIST"] = "COMMENTS_BLACKLIST"
157
+ operation: ChatMemberOperation = ChatMemberOperation.ADD
158
+ clean_msg_period: int = 0
@@ -22,9 +22,11 @@ from pymax.types.domain import Chat, Member, Message
22
22
  from .enums import ChannelPermissions, ChatLinkPrefix, ChatMemberOperation, ChatPayloadKey
23
23
  from .payloads import (
24
24
  AddAdminPayload,
25
+ BlockCommentAuthorPayload,
25
26
  ChangeGroupProfilePayload,
26
27
  ChangeGroupSettingsOptions,
27
28
  ChangeGroupSettingsPayload,
29
+ ChannelCommentsOptions,
28
30
  CreateGroupAttach,
29
31
  CreateGroupMessage,
30
32
  CreateGroupPayload,
@@ -40,6 +42,7 @@ from .payloads import (
40
42
  LinkInfoPayload,
41
43
  RemoveUsersPayload,
42
44
  ReworkInviteLinkPayload,
45
+ SetChannelCommentsPayload,
43
46
  )
44
47
 
45
48
  if TYPE_CHECKING:
@@ -422,3 +425,34 @@ class ChatService:
422
425
  )
423
426
 
424
427
  await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
428
+
429
+ async def set_channel_comments(self, chat_id: int, enabled: bool) -> None:
430
+ frame = SetChannelCommentsPayload(
431
+ chat_id=chat_id,
432
+ options=ChannelCommentsOptions(comments=enabled),
433
+ )
434
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
435
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
436
+ if chat:
437
+ self._cache_chat(chat)
438
+
439
+ async def block_comment_author(
440
+ self,
441
+ chat_id: int,
442
+ post_id: int,
443
+ user_ids: list[int],
444
+ message_id: int,
445
+ clean_msg_period: int = 0,
446
+ ) -> bool:
447
+ frame = BlockCommentAuthorPayload(
448
+ chat_id=chat_id,
449
+ post_id=post_id,
450
+ user_ids=user_ids,
451
+ message_id=message_id,
452
+ clean_msg_period=clean_msg_period,
453
+ )
454
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
455
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
456
+ if chat:
457
+ self._cache_chat(chat)
458
+ return True
pymax/api/facade.py CHANGED
@@ -10,6 +10,7 @@ from .chats import ChatService
10
10
  from .messages import MessageService
11
11
  from .self import SelfService
12
12
  from .session import SessionService
13
+ from .stories import StoriesService
13
14
  from .uploads import UploadService
14
15
  from .users import UserService
15
16
 
@@ -31,4 +32,5 @@ class ApiFacade:
31
32
  self.session = SessionService(app)
32
33
  self.auth = AuthService(app)
33
34
  self.uploads = UploadService(app)
35
+ self.stories = StoriesService(app)
34
36
  logger.debug("api facade initialized")
@@ -16,3 +16,4 @@ class MessagePayloadKey(str, Enum):
16
16
  MESSAGES = "messages"
17
17
  REACTION_INFO = "reactionInfo"
18
18
  MESSAGES_REACTIONS = "messagesReactions"
19
+ COMMENTS_INFO_UPDATES = "commentsInfoUpdates"
@@ -18,6 +18,10 @@ class GetMessagesPayload(CamelModel):
18
18
  message_ids: list[int]
19
19
 
20
20
 
21
+ class GetCommentsPayload(GetMessagesPayload):
22
+ post_id: int
23
+
24
+
21
25
  class EditMessagePayload(CamelModel):
22
26
  chat_id: int
23
27
  message_id: int
@@ -28,6 +32,10 @@ class EditMessagePayload(CamelModel):
28
32
  )
29
33
 
30
34
 
35
+ class EditCommentPayload(EditMessagePayload):
36
+ post_id: int
37
+
38
+
31
39
  class ReplyLink(CamelModel):
32
40
  type: str = "REPLY" # TODO: enum?
33
41
  message_id: int
@@ -53,6 +61,10 @@ class SendMessagePayload(CamelModel):
53
61
  notify: bool = False
54
62
 
55
63
 
64
+ class SendCommentPayload(SendMessagePayload):
65
+ post_id: int
66
+
67
+
56
68
  class ForwardLink(CamelModel):
57
69
  type: Literal["FORWARD"] = "FORWARD"
58
70
  message_id: str
@@ -86,6 +98,10 @@ class ChatHistoryPayload(CamelModel):
86
98
  interactive: bool = False
87
99
 
88
100
 
101
+ class CommentsHistoryPayload(ChatHistoryPayload):
102
+ post_id: int
103
+
104
+
89
105
  class DeleteMessagePayload(CamelModel):
90
106
  chat_id: int
91
107
  message_ids: list[int]
@@ -134,7 +150,7 @@ class RemoveReactionPayload(CamelModel):
134
150
  class ReadMessagesPayload(CamelModel):
135
151
  type: ReadAction
136
152
  chat_id: int
137
- message_id: str | int # Сокет просит int а вс str
153
+ message_id: int
138
154
  mark: int
139
155
 
140
156
 
@@ -143,3 +159,33 @@ class VotePollPayload(CamelModel):
143
159
  message_id: int
144
160
  poll_id: int
145
161
  answers_ids: list[int]
162
+
163
+
164
+ class DeleteCommentPayload(DeleteMessagePayload):
165
+ post_id: int
166
+
167
+
168
+ class AddCommentReactionPayload(AddReactionPayload):
169
+ post_id: int
170
+
171
+
172
+ class RemoveCommentReactionPayload(RemoveReactionPayload):
173
+ post_id: int
174
+
175
+
176
+ class SubscribeCommentsPayload(CamelModel):
177
+ chat_id: int
178
+ post_id: int
179
+ subscribe: bool = True
180
+
181
+
182
+ class GetCommentsInfoPayload(CamelModel):
183
+ chat_id: int
184
+ post_ids: list[int]
185
+
186
+
187
+ class DeleteUserCommentsPayload(CamelModel):
188
+ chat_id: int
189
+ post_id: int
190
+ user_id: int
191
+ message_id: int
@@ -27,6 +27,7 @@ from pymax.formatting.markdown import Formatter
27
27
  from pymax.logging import get_logger
28
28
  from pymax.protocol import Opcode
29
29
  from pymax.types.domain import (
30
+ CommentsInfoUpdate,
30
31
  FileRequest,
31
32
  Message,
32
33
  Poll,
@@ -38,14 +39,21 @@ from pymax.types.domain import (
38
39
 
39
40
  from .enums import ItemType, MessagePayloadKey, ReadAction
40
41
  from .payloads import (
42
+ AddCommentReactionPayload,
41
43
  AddReactionPayload,
42
44
  ChatHistoryPayload,
45
+ CommentsHistoryPayload,
43
46
  DelayedAttributes,
47
+ DeleteCommentPayload,
44
48
  DeleteMessagePayload,
49
+ DeleteUserCommentsPayload,
50
+ EditCommentPayload,
45
51
  EditMessagePayload,
46
52
  ForwardLink,
47
53
  ForwardMessagePayload,
48
54
  ForwardMessagePayloadMessage,
55
+ GetCommentsInfoPayload,
56
+ GetCommentsPayload,
49
57
  GetFilePayload,
50
58
  GetMessagesPayload,
51
59
  GetReactionsPayload,
@@ -53,10 +61,13 @@ from .payloads import (
53
61
  PinMessagePayload,
54
62
  ReactionInfoPayload,
55
63
  ReadMessagesPayload,
64
+ RemoveCommentReactionPayload,
56
65
  RemoveReactionPayload,
57
66
  ReplyLink,
67
+ SendCommentPayload,
58
68
  SendMessagePayload,
59
69
  SendMessagePayloadMessage,
70
+ SubscribeCommentsPayload,
60
71
  VotePollPayload,
61
72
  )
62
73
 
@@ -85,6 +96,40 @@ class MessageService:
85
96
  logger.debug("generated message cid=%s", e)
86
97
  return e
87
98
 
99
+ async def _prepare_message(
100
+ self,
101
+ text: str | None,
102
+ reply_to: int | None,
103
+ attachments: SendAttachments,
104
+ *,
105
+ delayed_attributes: DelayedAttributes | None = None,
106
+ ) -> tuple[
107
+ SendMessagePayloadMessage,
108
+ list[
109
+ AttachPhotoPayload | VideoAttachPayload | AttachFilePayload | VoiceAttachPayload | Poll
110
+ ],
111
+ ]:
112
+ if not text and not attachments:
113
+ raise ValueError("Either text or attachments must be provided")
114
+
115
+ if text:
116
+ clean_text, elements = Formatter.format_markdown(text)
117
+ else:
118
+ clean_text, elements = None, []
119
+
120
+ attaches = await self._upload_attachments(attachments)
121
+
122
+ message = SendMessagePayloadMessage(
123
+ text=clean_text,
124
+ cid=self._next_cid(),
125
+ elements=elements,
126
+ attaches=attaches,
127
+ link=ReplyLink(message_id=reply_to) if reply_to else None,
128
+ delayed_attributes=delayed_attributes,
129
+ )
130
+
131
+ return message, attaches
132
+
88
133
  async def _upload_attachments(
89
134
  self, attachments: SendAttachments
90
135
  ) -> list[
@@ -202,32 +247,21 @@ class MessageService:
202
247
  ) -> Message:
203
248
  logger.info("sending message chat_id=%s text_len=%s", chat_id, len(text) if text else 0)
204
249
 
205
- if not text and not attachments:
206
- logger.error("send_message failed: no text or attachments provided")
207
- raise ValueError("Either text or attachments must be provided")
208
-
209
- if text:
210
- clean_text, elements = Formatter.format_markdown(text)
211
- else:
212
- clean_text, elements = None, []
213
-
214
- attaches = await self._upload_attachments(attachments)
250
+ message, attaches = await self._prepare_message(
251
+ text,
252
+ reply_to,
253
+ attachments,
254
+ delayed_attributes=DelayedAttributes(
255
+ time_to_fire=self._convert_time(send_at),
256
+ notify_sender=notify,
257
+ )
258
+ if send_at
259
+ else None,
260
+ )
215
261
 
216
262
  frame = SendMessagePayload(
217
263
  chat_id=chat_id,
218
- message=SendMessagePayloadMessage(
219
- text=clean_text,
220
- cid=self._next_cid(),
221
- elements=elements,
222
- attaches=attaches,
223
- link=ReplyLink(message_id=reply_to) if reply_to else None,
224
- delayed_attributes=DelayedAttributes(
225
- time_to_fire=self._convert_time(send_at),
226
- notify_sender=notify,
227
- )
228
- if send_at
229
- else None,
230
- ),
264
+ message=message,
231
265
  notify=notify,
232
266
  )
233
267
 
@@ -390,6 +424,33 @@ class MessageService:
390
424
  )
391
425
  return messages
392
426
 
427
+ async def fetch_comments(
428
+ self,
429
+ chat_id: int,
430
+ post_id: int,
431
+ from_: int | None = None,
432
+ backward: int = 30,
433
+ get_messages: bool = True,
434
+ forward: int = 0,
435
+ ) -> list[Message]:
436
+ frame = CommentsHistoryPayload(
437
+ chat_id=chat_id,
438
+ forward=forward,
439
+ backward=backward,
440
+ post_id=post_id,
441
+ from_=-1 if from_ is None else from_,
442
+ get_messages=get_messages,
443
+ )
444
+ response = await self.app.invoke(
445
+ Opcode.CHAT_HISTORY,
446
+ payload=frame.to_payload(),
447
+ )
448
+ messages = bind_api_models(
449
+ self.app,
450
+ parse_payload_list(response, MessagePayloadKey.MESSAGES, Message),
451
+ )
452
+ return messages
453
+
393
454
  async def delete_message(
394
455
  self,
395
456
  chat_id: int,
@@ -551,7 +612,7 @@ class MessageService:
551
612
 
552
613
  return None
553
614
 
554
- async def read_message(self, message_id: int | str, chat_id: int) -> ReadState:
615
+ async def read_message(self, message_id: int, chat_id: int) -> ReadState:
555
616
  logger.info(
556
617
  "marking message as read chat_id=%s message_id=%s",
557
618
  chat_id,
@@ -580,3 +641,226 @@ class MessageService:
580
641
  response = await self.app.invoke(Opcode.SEND_VOTE, frame.to_payload())
581
642
 
582
643
  return require_payload_item_model(response, "state", PollState)
644
+
645
+ async def send_comment(
646
+ self,
647
+ chat_id: int,
648
+ post_id: int,
649
+ text: str | None = None,
650
+ reply_to: int | None = None,
651
+ attachments: SendAttachments = None,
652
+ *,
653
+ notify: bool = True,
654
+ ) -> Message:
655
+ message, attaches = await self._prepare_message(text, reply_to, attachments)
656
+
657
+ frame = SendCommentPayload(
658
+ chat_id=chat_id,
659
+ post_id=post_id,
660
+ notify=notify,
661
+ message=message,
662
+ )
663
+
664
+ try:
665
+ response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
666
+ except ApiError as e:
667
+ if e.error == "attachment.not.ready":
668
+ await self._process_attachment_error(attaches)
669
+ response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
670
+ else:
671
+ raise
672
+
673
+ return bind_api_model(
674
+ self.app,
675
+ require_payload_model(response, Message),
676
+ )
677
+
678
+ async def get_comments(
679
+ self,
680
+ chat_id: int,
681
+ post_id: int,
682
+ message_ids: list[int],
683
+ ) -> list[Message]:
684
+ frame = GetCommentsPayload(
685
+ chat_id=chat_id,
686
+ post_id=post_id,
687
+ message_ids=message_ids,
688
+ )
689
+
690
+ response = await self.app.invoke(Opcode.MSG_GET, frame.to_payload())
691
+ messages = parse_payload_list(response, MessagePayloadKey.MESSAGES, Message)
692
+ for message in messages:
693
+ if message.chat_id is None:
694
+ message.chat_id = chat_id
695
+
696
+ return bind_api_models(self.app, messages)
697
+
698
+ async def get_comment(
699
+ self,
700
+ chat_id: int,
701
+ post_id: int,
702
+ message_id: int,
703
+ ) -> Message | None:
704
+ messages = await self.get_comments(chat_id, post_id, [message_id])
705
+ return messages[0] if messages else None
706
+
707
+ async def edit_comment(
708
+ self,
709
+ chat_id: int,
710
+ post_id: int,
711
+ message_id: int,
712
+ text: str | None = None,
713
+ attachments: SendAttachments = None,
714
+ ) -> Message:
715
+ if not text and not attachments:
716
+ logger.error("edit_comment failed: no text or attachments provided")
717
+ raise ValueError("Either text or attachments must be provided")
718
+
719
+ if text:
720
+ clean_text, elements = Formatter.format_markdown(text)
721
+ else:
722
+ clean_text, elements = None, []
723
+
724
+ attaches = await self._upload_attachments(attachments)
725
+
726
+ frame = EditCommentPayload(
727
+ chat_id=chat_id,
728
+ post_id=post_id,
729
+ message_id=message_id,
730
+ text=clean_text,
731
+ elements=elements,
732
+ attachments=attaches,
733
+ )
734
+ try:
735
+ response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
736
+ except ApiError as e:
737
+ if e.error == "attachment.not.ready":
738
+ await self._process_attachment_error(attaches)
739
+
740
+ response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
741
+ else:
742
+ raise
743
+
744
+ message = require_payload_item_model(
745
+ response,
746
+ MessagePayloadKey.MESSAGE,
747
+ Message,
748
+ )
749
+ if message.chat_id is None:
750
+ message.chat_id = chat_id
751
+
752
+ return bind_api_model(self.app, message)
753
+
754
+ async def delete_comment(
755
+ self,
756
+ chat_id: int,
757
+ post_id: int,
758
+ message_ids: list[int],
759
+ for_me: bool = False,
760
+ ) -> bool:
761
+ logger.info(
762
+ "deleting messages chat_id=%s ids=%s for_me=%s",
763
+ chat_id,
764
+ message_ids,
765
+ for_me,
766
+ )
767
+ frame = DeleteCommentPayload(
768
+ chat_id=chat_id,
769
+ post_id=post_id,
770
+ message_ids=message_ids,
771
+ for_me=for_me,
772
+ )
773
+
774
+ await self.app.invoke(Opcode.MSG_DELETE, frame.to_payload())
775
+ logger.info("messages deleted chat_id=%s count=%s", chat_id, len(message_ids))
776
+ return True
777
+
778
+ async def add_comment_reaction(
779
+ self,
780
+ chat_id: int,
781
+ post_id: int,
782
+ message_id: int,
783
+ reaction: str,
784
+ ) -> ReactionInfo | None:
785
+ logger.info(
786
+ "adding reaction chat_id=%s message_id=%s reaction=%s",
787
+ chat_id,
788
+ message_id,
789
+ reaction,
790
+ )
791
+ frame = AddCommentReactionPayload(
792
+ chat_id=chat_id,
793
+ post_id=post_id,
794
+ message_id=message_id,
795
+ reaction=ReactionInfoPayload(id=reaction),
796
+ )
797
+
798
+ response = await self.app.invoke(Opcode.MSG_REACTION, frame.to_payload())
799
+ reaction_info = payload_item(response, MessagePayloadKey.REACTION_INFO)
800
+ if reaction_info:
801
+ return ReactionInfo.model_validate(reaction_info)
802
+
803
+ return None
804
+
805
+ async def remove_comment_reaction(
806
+ self,
807
+ chat_id: int,
808
+ post_id: int,
809
+ message_id: int,
810
+ ) -> ReactionInfo | None:
811
+ logger.info(
812
+ "removing reaction chat_id=%s message_id=%s",
813
+ chat_id,
814
+ message_id,
815
+ )
816
+ frame = RemoveCommentReactionPayload(
817
+ chat_id=chat_id, post_id=post_id, message_id=message_id
818
+ )
819
+
820
+ response = await self.app.invoke(
821
+ Opcode.MSG_CANCEL_REACTION,
822
+ frame.to_payload(),
823
+ )
824
+ reaction_info = payload_item(response, MessagePayloadKey.REACTION_INFO)
825
+ if reaction_info:
826
+ return ReactionInfo.model_validate(reaction_info)
827
+
828
+ return None
829
+
830
+ async def subscribe_comments(
831
+ self,
832
+ chat_id: int,
833
+ post_id: int,
834
+ subscribe: bool = True,
835
+ ) -> None:
836
+ frame = SubscribeCommentsPayload(chat_id=chat_id, post_id=post_id, subscribe=subscribe)
837
+ await self.app.invoke(Opcode.CHAT_SUBSCRIBE, frame.to_payload())
838
+
839
+ async def get_comments_info(
840
+ self,
841
+ chat_id: int,
842
+ post_ids: list[int],
843
+ ) -> list[CommentsInfoUpdate]:
844
+ frame = GetCommentsInfoPayload(chat_id=chat_id, post_ids=post_ids)
845
+ response = await self.app.invoke(Opcode.MSG_GET_COMMENTS_INFO, frame.to_payload())
846
+ return parse_payload_list(
847
+ response,
848
+ MessagePayloadKey.COMMENTS_INFO_UPDATES,
849
+ CommentsInfoUpdate,
850
+ )
851
+
852
+ async def delete_user_comments(
853
+ self,
854
+ chat_id: int,
855
+ post_id: int,
856
+ user_id: int,
857
+ message_id: int,
858
+ ) -> bool:
859
+ frame = DeleteUserCommentsPayload(
860
+ chat_id=chat_id,
861
+ post_id=post_id,
862
+ user_id=user_id,
863
+ message_id=message_id,
864
+ )
865
+ await self.app.invoke(Opcode.MSG_DELETE_USER_COMMENTS, frame.to_payload())
866
+ return True
pymax/api/response.py CHANGED
@@ -60,7 +60,7 @@ def payload_item(
60
60
  if validation_type is None:
61
61
  return data
62
62
 
63
- return cast(Any, validation_type)(data)
63
+ return cast("Any", validation_type)(data)
64
64
 
65
65
 
66
66
  def require_payload_item(
@@ -0,0 +1,2 @@
1
+ from .enums import StoryStatsFilter
2
+ from .service import StoriesService
@@ -0,0 +1,19 @@
1
+ from enum import Enum
2
+
3
+
4
+ class StoryStatsFilter(int, Enum):
5
+ """Фильтр подробной статистики: просмотры (``0``) или реакции (``1``)."""
6
+
7
+ VIEWS = 0
8
+ REACTIONS = 1
9
+
10
+
11
+ class StoryPayloadKey(str, Enum):
12
+ STORIES = "stories"
13
+ PEER_STORIES = "peerStories"
14
+ STORY = "story"
15
+ STORIES_PREVIEWS = "storiesPreviews"
16
+ CURSOR = "cursor"
17
+ STORY_DETAILED_STATS = "storyDetailedStats"
18
+ MARKER = "marker"
19
+ SUCCESS = "success"