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.
- {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +22 -12
- maxapi_python-2.5.0.dist-info/RECORD +195 -0
- {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +8 -2
- pymax/_data/apk_fingerprints.json +374 -17
- pymax/_data/rootca_ssl_rsa2022.crt +33 -0
- pymax/api/auth/service.py +15 -2
- pymax/api/auth/types.py +3 -0
- pymax/api/bots/payloads.py +8 -0
- pymax/api/bots/service.py +13 -1
- pymax/api/chats/enums.py +1 -0
- pymax/api/chats/payloads.py +21 -1
- pymax/api/chats/service.py +41 -0
- pymax/api/facade.py +2 -0
- pymax/api/messages/__init__.py +1 -1
- pymax/api/messages/enums.py +1 -0
- pymax/api/messages/payloads.py +56 -4
- pymax/api/messages/service.py +405 -27
- pymax/api/response.py +1 -1
- pymax/api/stories/__init__.py +2 -0
- pymax/api/stories/enums.py +19 -0
- pymax/api/stories/payloads.py +64 -0
- pymax/api/stories/service.py +198 -0
- pymax/api/uploads/payloads.py +42 -1
- pymax/api/uploads/service.py +171 -69
- pymax/app.py +39 -8
- pymax/auth/__init__.py +2 -0
- pymax/auth/exceptions.py +9 -0
- pymax/auth/providers.py +6 -2
- pymax/auth/qr.py +11 -1
- pymax/auth/sms.py +11 -1
- pymax/base.py +146 -27
- pymax/client.py +39 -11
- pymax/client_web.py +15 -12
- pymax/config.py +101 -53
- pymax/di/__init__.py +2 -0
- pymax/di/context.py +18 -0
- pymax/di/resolver.py +38 -0
- pymax/dispatch/__init__.py +7 -0
- pymax/dispatch/dispatcher.py +143 -40
- pymax/dispatch/enums.py +1 -0
- pymax/dispatch/mapping.py +23 -3
- pymax/dispatch/middleware.py +32 -0
- pymax/dispatch/resolvers.py +4 -0
- pymax/dispatch/router.py +86 -34
- pymax/files/base.py +58 -1
- pymax/files/video.py +32 -40
- pymax/files/voice.py +18 -4
- pymax/filters/__init__.py +15 -0
- pymax/filters/base.py +218 -0
- pymax/filters/containers/__init__.py +2 -0
- pymax/filters/containers/chat.py +240 -0
- pymax/filters/containers/message.py +147 -0
- pymax/filters/filters.py +211 -0
- pymax/filters/magic.py +13 -0
- pymax/filters/presets.py +88 -0
- pymax/filters/refs.py +56 -0
- pymax/fingerprint/fingerprint.py +5 -22
- pymax/fingerprint/models.py +1 -0
- pymax/formatting/markdown.py +55 -1
- pymax/fsm/__init__.py +3 -0
- pymax/fsm/context.py +30 -0
- pymax/fsm/protocol.py +14 -0
- pymax/fsm/state.py +70 -0
- pymax/fsm/storage.py +151 -0
- pymax/infra/base.py +2 -0
- pymax/infra/bots.py +20 -0
- pymax/infra/chat.py +37 -5
- pymax/infra/message.py +204 -9
- pymax/infra/protocol.py +5 -1
- pymax/infra/stories.py +205 -0
- pymax/integrations/__init__.py +0 -0
- pymax/integrations/dishka.py +109 -0
- pymax/protocol/enums.py +2 -0
- pymax/protocol/tcp/payload.py +0 -2
- pymax/session/__init__.py +1 -1
- pymax/session/models.py +30 -0
- pymax/session/protocol.py +33 -7
- pymax/session/store.py +73 -5
- pymax/transport/tcp.py +11 -3
- pymax/types/domain/__init__.py +23 -1
- pymax/types/domain/attachments/__init__.py +1 -1
- pymax/types/domain/attachments/call.py +7 -1
- pymax/types/domain/attachments/contact.py +3 -3
- pymax/types/domain/attachments/control.py +3 -0
- pymax/types/domain/attachments/enums.py +16 -0
- pymax/types/domain/attachments/keyboards/__init__.py +1 -1
- pymax/types/domain/attachments/keyboards/enums.py +12 -0
- pymax/types/domain/attachments/keyboards/inline.py +74 -3
- pymax/types/domain/attachments/video.py +3 -3
- pymax/types/domain/callback.py +19 -0
- pymax/types/domain/comment.py +24 -0
- pymax/types/domain/enums.py +7 -0
- pymax/types/domain/message.py +158 -9
- pymax/types/domain/story.py +126 -0
- pymax/types/events/reaction.py +28 -4
- pymax/versions/__init__.py +2 -0
- pymax/versions/catalog.py +104 -0
- pymax/versions/exceptions.py +10 -0
- maxapi_python-2.4.0.dist-info/RECORD +0 -161
- {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
pymax/fsm/storage.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator
|
|
8
|
+
|
|
9
|
+
from pymax.fsm.context import FSMContext, FSMKey
|
|
10
|
+
from pymax.types.domain.message import Message
|
|
11
|
+
|
|
12
|
+
from .protocol import FSMStorage
|
|
13
|
+
from .state import State
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pymax.base import BaseClient
|
|
17
|
+
from pymax.dispatch import DispatchEvent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InMemoryFSMStorage:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.records: dict[FSMKey, InMemoryFSM] = {}
|
|
23
|
+
self.locks: dict[FSMKey, asyncio.Lock] = {}
|
|
24
|
+
|
|
25
|
+
def resolve_key(
|
|
26
|
+
self,
|
|
27
|
+
client: BaseClient,
|
|
28
|
+
event: DispatchEvent,
|
|
29
|
+
) -> FSMKey | None:
|
|
30
|
+
if not isinstance(event, Message):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
if event.chat_id is None or event.sender is None:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
return FSMKey(
|
|
37
|
+
client_id=hash(client),
|
|
38
|
+
user_id=event.sender,
|
|
39
|
+
chat_id=event.chat_id,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def get_context(
|
|
43
|
+
self,
|
|
44
|
+
client: BaseClient,
|
|
45
|
+
event: Message,
|
|
46
|
+
) -> FSMContext | None:
|
|
47
|
+
if not event.chat_id or not event.sender:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
key = self.resolve_key(client, event)
|
|
51
|
+
|
|
52
|
+
if not key:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
record = self.records.get(key)
|
|
56
|
+
if record is None:
|
|
57
|
+
record = InMemoryFSM()
|
|
58
|
+
self.records[key] = record
|
|
59
|
+
|
|
60
|
+
return FSMContext(storage=record, key=key)
|
|
61
|
+
|
|
62
|
+
@asynccontextmanager
|
|
63
|
+
async def isolate(
|
|
64
|
+
self,
|
|
65
|
+
client: BaseClient,
|
|
66
|
+
event: DispatchEvent,
|
|
67
|
+
) -> AsyncGenerator[None]:
|
|
68
|
+
key = self.resolve_key(client, event)
|
|
69
|
+
|
|
70
|
+
if key is None:
|
|
71
|
+
yield
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
lock = self.locks.setdefault(key, asyncio.Lock())
|
|
75
|
+
|
|
76
|
+
async with lock:
|
|
77
|
+
yield
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class FsmState:
|
|
82
|
+
value: str | None = None
|
|
83
|
+
created_at: datetime = datetime.now()
|
|
84
|
+
ttl: int | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class InMemoryFSM(FSMStorage):
|
|
88
|
+
"""Состояние и данные одного FSM-контекста в памяти процесса.
|
|
89
|
+
|
|
90
|
+
В обработчике это хранилище доступно как ``ctx.storage``.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
self.state: FsmState = FsmState()
|
|
95
|
+
self.data: dict[str, Any] = {}
|
|
96
|
+
self.lock = asyncio.Lock()
|
|
97
|
+
|
|
98
|
+
async def get_state(self) -> str | None:
|
|
99
|
+
"""Возвращает имя состояния или ``None``, если оно не задано или истек TTL.
|
|
100
|
+
|
|
101
|
+
Истечение TTL сбрасывает только состояние, сохраняя данные контекста.
|
|
102
|
+
"""
|
|
103
|
+
async with self.lock:
|
|
104
|
+
if self.state.ttl is None:
|
|
105
|
+
return self.state.value
|
|
106
|
+
|
|
107
|
+
expires_at = self.state.created_at.timestamp() + self.state.ttl
|
|
108
|
+
|
|
109
|
+
if datetime.now().timestamp() >= expires_at:
|
|
110
|
+
self.state = FsmState()
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
return self.state.value
|
|
114
|
+
|
|
115
|
+
async def set_state(self, state: str | State | None, ttl: int | None = None) -> None:
|
|
116
|
+
"""Устанавливает состояние, сохраняя данные контекста.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
state: Состояние, его строковое имя или ``None`` для сброса.
|
|
120
|
+
ttl: Срок действия в секундах с момента установки.
|
|
121
|
+
``None`` означает отсутствие срока действия.
|
|
122
|
+
"""
|
|
123
|
+
async with self.lock:
|
|
124
|
+
if state is None:
|
|
125
|
+
self.state = FsmState()
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
self.state.value = str(state)
|
|
129
|
+
self.state.ttl = ttl
|
|
130
|
+
self.state.created_at = datetime.now()
|
|
131
|
+
|
|
132
|
+
async def get_data(self) -> dict[str, Any]:
|
|
133
|
+
"""Возвращает поверхностную копию словаря данных контекста."""
|
|
134
|
+
async with self.lock:
|
|
135
|
+
return self.data.copy()
|
|
136
|
+
|
|
137
|
+
async def set_data(self, key: str, value: Any) -> None:
|
|
138
|
+
"""Записывает одно значение, сохраняя остальные ключи данных."""
|
|
139
|
+
async with self.lock:
|
|
140
|
+
self.data[key] = value
|
|
141
|
+
|
|
142
|
+
async def update_data(self, **data: Any) -> None:
|
|
143
|
+
"""Добавляет или перезаписывает переданные ключи данных."""
|
|
144
|
+
async with self.lock:
|
|
145
|
+
self.data.update(data)
|
|
146
|
+
|
|
147
|
+
async def clear(self) -> None:
|
|
148
|
+
"""Сбрасывает состояние и удаляет все данные текущего контекста."""
|
|
149
|
+
async with self.lock:
|
|
150
|
+
self.state = FsmState()
|
|
151
|
+
self.data.clear()
|
pymax/infra/base.py
CHANGED
|
@@ -3,6 +3,7 @@ from .bots import BotsMixin
|
|
|
3
3
|
from .chat import ChatMixin
|
|
4
4
|
from .message import MessageMixin
|
|
5
5
|
from .self import SelfMixin
|
|
6
|
+
from .stories import StoriesMixin
|
|
6
7
|
from .user import UserMixin
|
|
7
8
|
|
|
8
9
|
|
|
@@ -13,5 +14,6 @@ class BaseMixin(
|
|
|
13
14
|
MessageMixin,
|
|
14
15
|
BotsMixin,
|
|
15
16
|
AuthMixin,
|
|
17
|
+
StoriesMixin,
|
|
16
18
|
):
|
|
17
19
|
"""Собирает публичные API-методы клиента."""
|
pymax/infra/bots.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pymax.types import ButtonType, CallbackResponse
|
|
1
2
|
from pymax.types.domain import InitData
|
|
2
3
|
|
|
3
4
|
from .protocol import IClientProtocol
|
|
@@ -31,3 +32,22 @@ class BotsMixin(IClientProtocol):
|
|
|
31
32
|
chat_id=chat_id,
|
|
32
33
|
start_param=start_param,
|
|
33
34
|
)
|
|
35
|
+
|
|
36
|
+
async def send_callback(
|
|
37
|
+
self,
|
|
38
|
+
callback_id: str,
|
|
39
|
+
type: ButtonType,
|
|
40
|
+
payload: str | None = None,
|
|
41
|
+
) -> CallbackResponse:
|
|
42
|
+
"""Передает нажатие inline-кнопки бота.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
callback_id: ID из ``InlineKeyboardAttachment.callback_id``.
|
|
46
|
+
type: Тип нажатой кнопки.
|
|
47
|
+
payload: Данные кнопки, если они есть.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Результат нажатия. ``message`` и ``chat`` содержатся в нем,
|
|
51
|
+
только если Max вернул эти объекты.
|
|
52
|
+
"""
|
|
53
|
+
return await self._app.api.bots.send_callback(callback_id, type, payload)
|
pymax/infra/chat.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pymax.api.chats import ChannelPermissions
|
|
2
|
+
from pymax.files import Photo
|
|
2
3
|
from pymax.types import Chat, Member, Message
|
|
3
4
|
|
|
4
5
|
from .protocol import IClientProtocol
|
|
@@ -131,20 +132,23 @@ class ChatMixin(IClientProtocol):
|
|
|
131
132
|
async def change_group_profile(
|
|
132
133
|
self,
|
|
133
134
|
chat_id: int,
|
|
134
|
-
name: str | None,
|
|
135
|
+
name: str | None = None,
|
|
135
136
|
description: str | None = None,
|
|
137
|
+
photo: Photo | None = None,
|
|
136
138
|
) -> None:
|
|
137
|
-
"""Обновляет
|
|
139
|
+
"""Обновляет название, описание или фотографию группы.
|
|
140
|
+
|
|
141
|
+
``None`` исключает соответствующее поле из запроса. Фотография
|
|
142
|
+
автоматически загружается перед обновлением профиля.
|
|
138
143
|
|
|
139
144
|
Args:
|
|
140
145
|
chat_id: ID группы.
|
|
141
146
|
name: Новое название.
|
|
142
147
|
description: Новое описание.
|
|
148
|
+
photo: Новая фотография группы.
|
|
143
149
|
"""
|
|
144
150
|
await self._app.api.chats.change_group_profile(
|
|
145
|
-
chat_id=chat_id,
|
|
146
|
-
name=name,
|
|
147
|
-
description=description,
|
|
151
|
+
chat_id=chat_id, name=name, description=description, photo=photo
|
|
148
152
|
)
|
|
149
153
|
|
|
150
154
|
async def join_group(self, link: str) -> Chat:
|
|
@@ -410,3 +414,31 @@ class ChatMixin(IClientProtocol):
|
|
|
410
414
|
permissions: Непустой список прав администратора.
|
|
411
415
|
"""
|
|
412
416
|
return await self._app.api.chats.add_admin(chat_id, user_id, permissions)
|
|
417
|
+
|
|
418
|
+
async def set_channel_comments(self, chat_id: int, enabled: bool) -> None:
|
|
419
|
+
"""Включает или выключает комментарии канала."""
|
|
420
|
+
return await self._app.api.chats.set_channel_comments(
|
|
421
|
+
chat_id=chat_id,
|
|
422
|
+
enabled=enabled,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
async def block_comment_author(
|
|
426
|
+
self,
|
|
427
|
+
chat_id: int,
|
|
428
|
+
post_id: int,
|
|
429
|
+
user_ids: list[int],
|
|
430
|
+
message_id: int,
|
|
431
|
+
clean_msg_period: int = 0,
|
|
432
|
+
) -> bool:
|
|
433
|
+
"""Блокирует авторов в комментариях.
|
|
434
|
+
|
|
435
|
+
``message_id`` — ID комментария для модерации.
|
|
436
|
+
``clean_msg_period=0`` сохраняет комментарии, ``-1`` также удаляет их.
|
|
437
|
+
"""
|
|
438
|
+
return await self._app.api.chats.block_comment_author(
|
|
439
|
+
chat_id=chat_id,
|
|
440
|
+
post_id=post_id,
|
|
441
|
+
user_ids=user_ids,
|
|
442
|
+
message_id=message_id,
|
|
443
|
+
clean_msg_period=clean_msg_period,
|
|
444
|
+
)
|
pymax/infra/message.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from pymax.api.messages import DateTimeUnion
|
|
1
2
|
from pymax.api.messages.enums import ItemType
|
|
2
3
|
from pymax.api.messages.service import SendAttachments
|
|
3
4
|
from pymax.types import (
|
|
5
|
+
CommentsInfoUpdate,
|
|
4
6
|
FileRequest,
|
|
5
7
|
Message,
|
|
6
8
|
PollState,
|
|
@@ -23,6 +25,7 @@ class MessageMixin(IClientProtocol):
|
|
|
23
25
|
attachments: SendAttachments = None,
|
|
24
26
|
*,
|
|
25
27
|
notify: bool = True,
|
|
28
|
+
send_at: DateTimeUnion | None = None,
|
|
26
29
|
) -> Message:
|
|
27
30
|
"""Отправляет сообщение в чат.
|
|
28
31
|
|
|
@@ -32,6 +35,9 @@ class MessageMixin(IClientProtocol):
|
|
|
32
35
|
reply_to: ID сообщения для ответа.
|
|
33
36
|
attachments: Файлы, фотографии, видео или опросы для отправки.
|
|
34
37
|
notify: Отправить ли получателям push-уведомление.
|
|
38
|
+
send_at: Время отложенной отправки: ``datetime`` для абсолютного
|
|
39
|
+
времени, ``timedelta`` для задержки или Unix time в секундах
|
|
40
|
+
как ``int``. ``None`` и ``0`` отправляют сообщение сразу.
|
|
35
41
|
|
|
36
42
|
Returns:
|
|
37
43
|
Отправленное сообщение.
|
|
@@ -45,6 +51,7 @@ class MessageMixin(IClientProtocol):
|
|
|
45
51
|
reply_to,
|
|
46
52
|
attachments,
|
|
47
53
|
notify=notify,
|
|
54
|
+
send_at=send_at,
|
|
48
55
|
)
|
|
49
56
|
|
|
50
57
|
async def get_message(
|
|
@@ -272,7 +279,7 @@ class MessageMixin(IClientProtocol):
|
|
|
272
279
|
async def add_reaction(
|
|
273
280
|
self,
|
|
274
281
|
chat_id: int,
|
|
275
|
-
message_id:
|
|
282
|
+
message_id: int,
|
|
276
283
|
reaction: str,
|
|
277
284
|
) -> ReactionInfo | None:
|
|
278
285
|
"""Добавляет реакцию к сообщению.
|
|
@@ -294,7 +301,7 @@ class MessageMixin(IClientProtocol):
|
|
|
294
301
|
async def get_reactions(
|
|
295
302
|
self,
|
|
296
303
|
chat_id: int,
|
|
297
|
-
message_ids: list[
|
|
304
|
+
message_ids: list[int],
|
|
298
305
|
) -> dict[str, ReactionInfo] | None:
|
|
299
306
|
"""Возвращает реакции для нескольких сообщений.
|
|
300
307
|
|
|
@@ -314,7 +321,7 @@ class MessageMixin(IClientProtocol):
|
|
|
314
321
|
async def remove_reaction(
|
|
315
322
|
self,
|
|
316
323
|
chat_id: int,
|
|
317
|
-
message_id:
|
|
324
|
+
message_id: int,
|
|
318
325
|
) -> ReactionInfo | None:
|
|
319
326
|
"""Удаляет свою реакцию с сообщения.
|
|
320
327
|
|
|
@@ -330,15 +337,11 @@ class MessageMixin(IClientProtocol):
|
|
|
330
337
|
message_id=message_id,
|
|
331
338
|
)
|
|
332
339
|
|
|
333
|
-
async def read_message(self, message_id: int
|
|
340
|
+
async def read_message(self, message_id: int, chat_id: int) -> ReadState:
|
|
334
341
|
"""Отмечает сообщение как прочитанное.
|
|
335
342
|
|
|
336
|
-
У Max различается wire-формат ``message_id`` для отметки прочтения:
|
|
337
|
-
TCP-клиент ожидает ``int``, WebSocket-клиент - ``str``.
|
|
338
|
-
|
|
339
343
|
Args:
|
|
340
|
-
message_id: ID
|
|
341
|
-
``str`` для ``WebClient``.
|
|
344
|
+
message_id: ID сообщения как ``int`` для ``Client`` и ``WebClient``.
|
|
342
345
|
chat_id: ID чата.
|
|
343
346
|
|
|
344
347
|
Returns:
|
|
@@ -373,3 +376,195 @@ class MessageMixin(IClientProtocol):
|
|
|
373
376
|
poll_id=poll_id,
|
|
374
377
|
answer_ids=answer_ids,
|
|
375
378
|
)
|
|
379
|
+
|
|
380
|
+
async def send_comment(
|
|
381
|
+
self,
|
|
382
|
+
chat_id: int,
|
|
383
|
+
post_id: int,
|
|
384
|
+
text: str | None = None,
|
|
385
|
+
reply_to: int | None = None,
|
|
386
|
+
attachments: SendAttachments = None,
|
|
387
|
+
*,
|
|
388
|
+
notify: bool = True,
|
|
389
|
+
) -> Message:
|
|
390
|
+
"""Отправляет комментарий к посту канала.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
chat_id: ID канала.
|
|
394
|
+
post_id: ID поста в канале.
|
|
395
|
+
text: Текст комментария с поддержкой Markdown.
|
|
396
|
+
reply_to: ID комментария, на который нужно ответить.
|
|
397
|
+
attachments: Вложения комментария.
|
|
398
|
+
notify: Отправить уведомление о комментарии.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Опубликованный комментарий как ``Message``.
|
|
402
|
+
"""
|
|
403
|
+
return await self._app.api.messages.send_comment(
|
|
404
|
+
chat_id=chat_id,
|
|
405
|
+
post_id=post_id,
|
|
406
|
+
text=text,
|
|
407
|
+
reply_to=reply_to,
|
|
408
|
+
attachments=attachments,
|
|
409
|
+
notify=notify,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
async def fetch_comments(
|
|
413
|
+
self,
|
|
414
|
+
chat_id: int,
|
|
415
|
+
post_id: int,
|
|
416
|
+
from_: int | None = None,
|
|
417
|
+
backward: int = 30,
|
|
418
|
+
get_messages: bool = True,
|
|
419
|
+
forward: int = 0,
|
|
420
|
+
) -> list[Message]:
|
|
421
|
+
"""Возвращает страницу комментариев поста канала.
|
|
422
|
+
|
|
423
|
+
``chat_id`` задает канал, ``post_id`` — пост внутри него.
|
|
424
|
+
``backward`` и ``forward`` задают количество комментариев по сторонам
|
|
425
|
+
от ``from_``. При ``from_=None`` используется начальная позиция Max.
|
|
426
|
+
"""
|
|
427
|
+
return await self._app.api.messages.fetch_comments(
|
|
428
|
+
chat_id=chat_id,
|
|
429
|
+
post_id=post_id,
|
|
430
|
+
from_=from_,
|
|
431
|
+
backward=backward,
|
|
432
|
+
get_messages=get_messages,
|
|
433
|
+
forward=forward,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
async def get_comments(
|
|
437
|
+
self,
|
|
438
|
+
chat_id: int,
|
|
439
|
+
post_id: int,
|
|
440
|
+
message_ids: list[int],
|
|
441
|
+
) -> list[Message]:
|
|
442
|
+
"""Возвращает конкретные комментарии поста по их ID.
|
|
443
|
+
|
|
444
|
+
``chat_id`` задает канал, ``post_id`` — пост, а ``message_ids`` —
|
|
445
|
+
ID запрашиваемых комментариев.
|
|
446
|
+
"""
|
|
447
|
+
return await self._app.api.messages.get_comments(
|
|
448
|
+
chat_id=chat_id,
|
|
449
|
+
post_id=post_id,
|
|
450
|
+
message_ids=message_ids,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
async def get_comment(
|
|
454
|
+
self,
|
|
455
|
+
chat_id: int,
|
|
456
|
+
post_id: int,
|
|
457
|
+
message_id: int,
|
|
458
|
+
) -> Message | None:
|
|
459
|
+
"""Возвращает комментарий по ID или ``None``, если он не найден."""
|
|
460
|
+
return await self._app.api.messages.get_comment(
|
|
461
|
+
chat_id=chat_id,
|
|
462
|
+
post_id=post_id,
|
|
463
|
+
message_id=message_id,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
async def edit_comment(
|
|
467
|
+
self,
|
|
468
|
+
chat_id: int,
|
|
469
|
+
post_id: int,
|
|
470
|
+
message_id: int,
|
|
471
|
+
text: str | None = None,
|
|
472
|
+
attachments: SendAttachments = None,
|
|
473
|
+
) -> Message:
|
|
474
|
+
"""Редактирует текст и вложения комментария. Текст поддерживает markdown."""
|
|
475
|
+
return await self._app.api.messages.edit_comment(
|
|
476
|
+
chat_id=chat_id,
|
|
477
|
+
post_id=post_id,
|
|
478
|
+
message_id=message_id,
|
|
479
|
+
text=text,
|
|
480
|
+
attachments=attachments,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
async def delete_comment(
|
|
484
|
+
self,
|
|
485
|
+
chat_id: int,
|
|
486
|
+
post_id: int,
|
|
487
|
+
message_ids: list[int],
|
|
488
|
+
for_me: bool = False,
|
|
489
|
+
) -> bool:
|
|
490
|
+
"""Удаляет комментарии поста. По умолчанию удаляет для всех."""
|
|
491
|
+
return await self._app.api.messages.delete_comment(
|
|
492
|
+
chat_id=chat_id,
|
|
493
|
+
post_id=post_id,
|
|
494
|
+
message_ids=message_ids,
|
|
495
|
+
for_me=for_me,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
async def add_comment_reaction(
|
|
499
|
+
self,
|
|
500
|
+
chat_id: int,
|
|
501
|
+
post_id: int,
|
|
502
|
+
message_id: int,
|
|
503
|
+
reaction: str,
|
|
504
|
+
) -> ReactionInfo | None:
|
|
505
|
+
"""Добавляет или заменяет свою emoji-реакцию на комментарии."""
|
|
506
|
+
return await self._app.api.messages.add_comment_reaction(
|
|
507
|
+
chat_id=chat_id,
|
|
508
|
+
post_id=post_id,
|
|
509
|
+
message_id=message_id,
|
|
510
|
+
reaction=reaction,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
async def remove_comment_reaction(
|
|
514
|
+
self,
|
|
515
|
+
chat_id: int,
|
|
516
|
+
post_id: int,
|
|
517
|
+
message_id: int,
|
|
518
|
+
) -> ReactionInfo | None:
|
|
519
|
+
"""Удаляет свою реакцию с комментария."""
|
|
520
|
+
return await self._app.api.messages.remove_comment_reaction(
|
|
521
|
+
chat_id=chat_id,
|
|
522
|
+
post_id=post_id,
|
|
523
|
+
message_id=message_id,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
async def subscribe_comments(
|
|
527
|
+
self,
|
|
528
|
+
chat_id: int,
|
|
529
|
+
post_id: int,
|
|
530
|
+
subscribe: bool = True,
|
|
531
|
+
) -> None:
|
|
532
|
+
"""Подписывается на события ветки комментариев.
|
|
533
|
+
|
|
534
|
+
Для поддержания подписки повторяйте вызов раз в минуту.
|
|
535
|
+
``subscribe=False`` отменяет подписку.
|
|
536
|
+
"""
|
|
537
|
+
return await self._app.api.messages.subscribe_comments(
|
|
538
|
+
chat_id=chat_id,
|
|
539
|
+
post_id=post_id,
|
|
540
|
+
subscribe=subscribe,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
async def get_comments_info(
|
|
544
|
+
self,
|
|
545
|
+
chat_id: int,
|
|
546
|
+
post_ids: list[int],
|
|
547
|
+
) -> list[CommentsInfoUpdate]:
|
|
548
|
+
"""Возвращает список обновлений счетчиков комментариев постов."""
|
|
549
|
+
return await self._app.api.messages.get_comments_info(
|
|
550
|
+
chat_id=chat_id,
|
|
551
|
+
post_ids=post_ids,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
async def delete_user_comments(
|
|
555
|
+
self,
|
|
556
|
+
chat_id: int,
|
|
557
|
+
post_id: int,
|
|
558
|
+
user_id: int,
|
|
559
|
+
message_id: int,
|
|
560
|
+
) -> bool:
|
|
561
|
+
"""Удаляет комментарии автора в ветке указанного поста.
|
|
562
|
+
|
|
563
|
+
``message_id`` — ID комментария этого автора.
|
|
564
|
+
"""
|
|
565
|
+
return await self._app.api.messages.delete_user_comments(
|
|
566
|
+
chat_id=chat_id,
|
|
567
|
+
post_id=post_id,
|
|
568
|
+
user_id=user_id,
|
|
569
|
+
message_id=message_id,
|
|
570
|
+
)
|
pymax/infra/protocol.py
CHANGED