maxapi-python 2.3.0__py3-none-any.whl → 2.3.1__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.3.0.dist-info → maxapi_python-2.3.1.dist-info}/METADATA +2 -1
- {maxapi_python-2.3.0.dist-info → maxapi_python-2.3.1.dist-info}/RECORD +13 -13
- pymax/__init__.py +1 -1
- pymax/api/messages/payloads.py +21 -1
- pymax/api/messages/service.py +39 -0
- pymax/infra/message.py +27 -0
- pymax/protocol/tcp/compression.py +18 -0
- pymax/protocol/tcp/payload.py +20 -4
- pymax/protocol/tcp/protocol.py +5 -1
- pymax/types/domain/message.py +29 -2
- pymax/types/domain/user.py +6 -4
- {maxapi_python-2.3.0.dist-info → maxapi_python-2.3.1.dist-info}/WHEEL +0 -0
- {maxapi_python-2.3.0.dist-info → maxapi_python-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/MaxApiTeam/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/MaxApiTeam/PyMax
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pydantic>=2.10.0
|
|
|
31
31
|
Requires-Dist: python-socks[asyncio]>=2.8.1
|
|
32
32
|
Requires-Dist: qrcode>=8.2
|
|
33
33
|
Requires-Dist: websockets>=16.0
|
|
34
|
+
Requires-Dist: zstandard>=0.25.0
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
# PyMax
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pymax/__init__.py,sha256=
|
|
1
|
+
pymax/__init__.py,sha256=y6_pIn47iHEIkP4F4y7EL-qiIpAgJqs_jVKnOmooniY,1500
|
|
2
2
|
pymax/app.py,sha256=QQldPh1YYpFeKBOMfCQZA0BFW5bUQQO7hFBYtwpioZQ,10847
|
|
3
3
|
pymax/base.py,sha256=--6VwKUvqlocFAUbPWn_CfXQ-8CYfyCigXQ70ipXOgI,10500
|
|
4
4
|
pymax/client.py,sha256=ePDxR6wEeHJbk23wI_PkOVFQuyy5tQyckV-OmKEwz5U,3985
|
|
@@ -27,8 +27,8 @@ pymax/api/chats/payloads.py,sha256=qA46Q984FgHaXenPmvtotm7ZJOKM07aiP7dBYF_0gqQ,2
|
|
|
27
27
|
pymax/api/chats/service.py,sha256=jNQUlSdILR5afeTYFQmQ6mvx-rgQlWjhzmOWUA0vDcg,12064
|
|
28
28
|
pymax/api/messages/__init__.py,sha256=fw_uF6tgSbkxvqaSVE65wU0KWsu47u1CtR0Y4BbD-K0,36
|
|
29
29
|
pymax/api/messages/enums.py,sha256=tp5S2Q97a-FILcqpRhQiIRKdj0P_KSEBiCqWmb05dHs,368
|
|
30
|
-
pymax/api/messages/payloads.py,sha256=
|
|
31
|
-
pymax/api/messages/service.py,sha256=
|
|
30
|
+
pymax/api/messages/payloads.py,sha256=MQSaZD0kYbkojWNK6BJbwoS02ZjDX7eutFnJX3_L6_g,2712
|
|
31
|
+
pymax/api/messages/service.py,sha256=lGnciRoP9PkpgBDpkHQU1ukxNTwMJuGjHhM1EeSHWXU,13334
|
|
32
32
|
pymax/api/self/__init__.py,sha256=TfbqL4xLb5IMhbW8mlAK-AwVFqqPWMADogKZeWtkw0A,79
|
|
33
33
|
pymax/api/self/enums.py,sha256=iKEqPy44LyQKvAPfyhpkSXgmWWohxry73F0CVgtekwY,180
|
|
34
34
|
pymax/api/self/payloads.py,sha256=-SFqkNxC5ZLKnLty_Gyz_b_P3X1esu_7Urb4HjrQxM4,774
|
|
@@ -79,7 +79,7 @@ pymax/infra/auth.py,sha256=YuYH_NWNz8UfPyko6bv_cIzXDIvFeivDrxZCIpGoANU,3648
|
|
|
79
79
|
pymax/infra/base.py,sha256=sOo40Qkp14bE6CxVKppJhy3e0F5VhFPhnL3iBs8y5dA,368
|
|
80
80
|
pymax/infra/bots.py,sha256=IJs4ErFxjbMeiHfQgpDiDhtEu9ws-lIp8fb8I96szy8,1202
|
|
81
81
|
pymax/infra/chat.py,sha256=Jb5kzUnHtCg98glS4Jf-WEHIzZg4oIcS-MA8aGeJuKk,13884
|
|
82
|
-
pymax/infra/message.py,sha256=
|
|
82
|
+
pymax/infra/message.py,sha256=4AUp8EUQ46GhsJMgh7YmgogkBT0K4wUqEkIfrFT--oI,11737
|
|
83
83
|
pymax/infra/protocol.py,sha256=I2WrAeAgATuNSdK2gvHU-MhhxdRBfSMGMovF5HPFsQw,208
|
|
84
84
|
pymax/infra/self.py,sha256=h2GnzcCF2-FeY9-qBjtHjrSCx7elc4RjuCXPaewDkgg,4828
|
|
85
85
|
pymax/infra/user.py,sha256=wy6lElmJx7Ypjkk48kDrlLiL3V3p7pzO2MevsgCNy1k,4801
|
|
@@ -88,10 +88,10 @@ pymax/protocol/base.py,sha256=_bisk1BU_GSMSPIqfnizSCgm_qBrdbB5cJw6foAa2tc,294
|
|
|
88
88
|
pymax/protocol/enums.py,sha256=9y4kn9y2pKinaT_twdeW5MXVH3O-sJF6h0v5KbC9VkA,4376
|
|
89
89
|
pymax/protocol/models.py,sha256=kno-09OoPoLKBMixS9nrDkCbxcIwvU37RlZgaq5MfVo,584
|
|
90
90
|
pymax/protocol/tcp/__init__.py,sha256=ivIZZ-UoT_MiRIUWTLiyeKSBOzf9XztSprHPHgoWNuI,34
|
|
91
|
-
pymax/protocol/tcp/compression.py,sha256=
|
|
91
|
+
pymax/protocol/tcp/compression.py,sha256=JsW84MOjtUCJPxMfsfc4CX2bMH0D5sb_2mut189KHmU,3545
|
|
92
92
|
pymax/protocol/tcp/framing.py,sha256=qH2zNsJLfg5J2wNTcp1hPqsrWhZHajK-Ja7pdB_Gji0,1630
|
|
93
|
-
pymax/protocol/tcp/payload.py,sha256=
|
|
94
|
-
pymax/protocol/tcp/protocol.py,sha256
|
|
93
|
+
pymax/protocol/tcp/payload.py,sha256=roViz6uNrJvmEBukPwBPk462uFJMya48jEnLthZs-yU,4329
|
|
94
|
+
pymax/protocol/tcp/protocol.py,sha256=b5AAYQUOhq985Wc1pCPzt84pjGnVGfGxbdwRN0R91xk,2380
|
|
95
95
|
pymax/protocol/ws/__init__.py,sha256=oQK0h28B6gGItf7eHanWy_XsgUA_L9Lclnuz9ZQL9YI,33
|
|
96
96
|
pymax/protocol/ws/protocol.py,sha256=z6zzBQTlJfzwOmLsJYoYQ2L1AYpY5wYMJyPir_zfNBQ,913
|
|
97
97
|
pymax/session/__init__.py,sha256=natjnLjPjTvkfvSYtoiPxzmU44XJi0MpjWiOPD-On4g,100
|
|
@@ -118,13 +118,13 @@ pymax/types/domain/error.py,sha256=WZoDAXf5vt8O4VYy7iEHLGBfH6hZQZ1fVkTz9rVCTN0,5
|
|
|
118
118
|
pymax/types/domain/folder.py,sha256=Z9pXMzOTfxuhUfrJpLjl5LPnVXqqFsBVfKBQm4IbGWM,2225
|
|
119
119
|
pymax/types/domain/login.py,sha256=FBKpRhNPv6Zdjo6Wfl-lK8JpzpMx7zh84f1qrq6Z0og,1301
|
|
120
120
|
pymax/types/domain/member.py,sha256=8gUNn4fBS_S2ap45A40lRlrDXYFMoq0Kx-dh57hMWQc,490
|
|
121
|
-
pymax/types/domain/message.py,sha256=
|
|
121
|
+
pymax/types/domain/message.py,sha256=a1wdIVfRcLo30rUUQzBAyLqCGcPoc-6Uk9jcDg8oqHQ,16497
|
|
122
122
|
pymax/types/domain/name.py,sha256=qMIshIqgWkVuROxmiCRhwfJf8XtmhSBoEU6nXs_gEh0,523
|
|
123
123
|
pymax/types/domain/presence.py,sha256=lKvkK0uYwY0gTSEEkaVdI-tEpWjxLOtH7gp2dy5Uh-0,534
|
|
124
124
|
pymax/types/domain/profile.py,sha256=taN5PjlKp4EDypVhDx89vmBDpSKh-kT-ISpu1tR2cY8,471
|
|
125
125
|
pymax/types/domain/session.py,sha256=T0b0qjI65kNQOCgx3nIkaIqynD4eGKyqnscbIlPFVnQ,2002
|
|
126
126
|
pymax/types/domain/sync.py,sha256=lBDWo4ACLq2ov5XBccfnI8s_dsGEA7O4mIJVBSUpyrc,3248
|
|
127
|
-
pymax/types/domain/user.py,sha256=
|
|
127
|
+
pymax/types/domain/user.py,sha256=tpHBnatNXdajS8noMy8oox9qp8Yej9b7mkdtg7BpjQM,5514
|
|
128
128
|
pymax/types/domain/attachments/__init__.py,sha256=ZVSb7sSAwabSB8A8BWF_JbooPOMIP1r6gF_NUASgGGk,471
|
|
129
129
|
pymax/types/domain/attachments/audio.py,sha256=I_Qq1qQ9bXsEzWwxEbQbkiY8CEjSKqGqinDFYY_5DY8,1117
|
|
130
130
|
pymax/types/domain/attachments/call.py,sha256=RV-BFutymZFinzUiRdaIryqx6ubku2v6y6brc-qsHOQ,818
|
|
@@ -147,7 +147,7 @@ pymax/types/events/presence.py,sha256=77Cy352CdfIkbH6ZfLijHffF8gcMz-AZk5OCeESQeD
|
|
|
147
147
|
pymax/types/events/reaction.py,sha256=7kauScAH8O-K79xS6Q3b6Vi0KwRl4lGCKGeAMAH9sEU,672
|
|
148
148
|
pymax/types/events/typing.py,sha256=vDMgqYE0jz8dN2YfqsNaJLB8k7pMd6kaDEdR_KrLk5g,376
|
|
149
149
|
pymax/types/events/video.py,sha256=swBHYadmDS0SjLXGqVqRYsuMoVZ6MjD2aYR2fbM-AJc,104
|
|
150
|
-
maxapi_python-2.3.
|
|
151
|
-
maxapi_python-2.3.
|
|
152
|
-
maxapi_python-2.3.
|
|
153
|
-
maxapi_python-2.3.
|
|
150
|
+
maxapi_python-2.3.1.dist-info/METADATA,sha256=k5ItHuOsbXla9MpvlsqOWi7lJbV2u_T8wMotxOA2vDc,7309
|
|
151
|
+
maxapi_python-2.3.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
152
|
+
maxapi_python-2.3.1.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
|
|
153
|
+
maxapi_python-2.3.1.dist-info/RECORD,,
|
pymax/__init__.py
CHANGED
pymax/api/messages/payloads.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
|
|
@@ -46,6 +46,26 @@ class SendMessagePayload(CamelModel):
|
|
|
46
46
|
notify: bool = False
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class ForwardLink(CamelModel):
|
|
50
|
+
type: Literal["FORWARD"] = "FORWARD"
|
|
51
|
+
message_id: str
|
|
52
|
+
chat_id: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ForwardMessagePayloadMessage(CamelModel):
|
|
56
|
+
cid: int
|
|
57
|
+
link: ForwardLink
|
|
58
|
+
attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = Field(
|
|
59
|
+
default_factory=list
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ForwardMessagePayload(CamelModel):
|
|
64
|
+
chat_id: int
|
|
65
|
+
message: ForwardMessagePayloadMessage
|
|
66
|
+
notify: bool = True
|
|
67
|
+
|
|
68
|
+
|
|
49
69
|
class ChatHistoryPayload(CamelModel):
|
|
50
70
|
chat_id: int
|
|
51
71
|
forward: int
|
pymax/api/messages/service.py
CHANGED
|
@@ -36,6 +36,9 @@ from .payloads import (
|
|
|
36
36
|
ChatHistoryPayload,
|
|
37
37
|
DeleteMessagePayload,
|
|
38
38
|
EditMessagePayload,
|
|
39
|
+
ForwardLink,
|
|
40
|
+
ForwardMessagePayload,
|
|
41
|
+
ForwardMessagePayloadMessage,
|
|
39
42
|
GetFilePayload,
|
|
40
43
|
GetMessagesPayload,
|
|
41
44
|
GetReactionsPayload,
|
|
@@ -139,6 +142,42 @@ class MessageService:
|
|
|
139
142
|
logger.info("message sent chat_id=%s", chat_id)
|
|
140
143
|
return message
|
|
141
144
|
|
|
145
|
+
async def forward_message(
|
|
146
|
+
self,
|
|
147
|
+
chat_id: int,
|
|
148
|
+
message_id: int | str,
|
|
149
|
+
source_chat_id: int | None = None,
|
|
150
|
+
*,
|
|
151
|
+
notify: bool = True,
|
|
152
|
+
) -> Message | None:
|
|
153
|
+
source_chat_id = chat_id if source_chat_id is None else source_chat_id
|
|
154
|
+
logger.info(
|
|
155
|
+
"forwarding message source_chat_id=%s chat_id=%s message_id=%s",
|
|
156
|
+
source_chat_id,
|
|
157
|
+
chat_id,
|
|
158
|
+
message_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
frame = ForwardMessagePayload(
|
|
162
|
+
chat_id=chat_id,
|
|
163
|
+
message=ForwardMessagePayloadMessage(
|
|
164
|
+
cid=-self._next_cid(),
|
|
165
|
+
link=ForwardLink(
|
|
166
|
+
message_id=str(message_id),
|
|
167
|
+
chat_id=source_chat_id,
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
notify=notify,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
|
|
174
|
+
message = bind_api_model(
|
|
175
|
+
self.app,
|
|
176
|
+
require_payload_model(response, Message),
|
|
177
|
+
)
|
|
178
|
+
logger.info("message forwarded source_chat_id=%s chat_id=%s", source_chat_id, chat_id)
|
|
179
|
+
return message
|
|
180
|
+
|
|
142
181
|
async def get_messages(
|
|
143
182
|
self,
|
|
144
183
|
chat_id: int,
|
pymax/infra/message.py
CHANGED
|
@@ -62,6 +62,33 @@ class MessageMixin(IClientProtocol):
|
|
|
62
62
|
message_id=message_id,
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
+
async def forward_message(
|
|
66
|
+
self,
|
|
67
|
+
chat_id: int,
|
|
68
|
+
message_id: int | str,
|
|
69
|
+
source_chat_id: int | None = None,
|
|
70
|
+
*,
|
|
71
|
+
notify: bool = True,
|
|
72
|
+
) -> Message | None:
|
|
73
|
+
"""Пересылает существующее сообщение в чат.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
chat_id: ID целевого чата.
|
|
77
|
+
message_id: ID пересылаемого сообщения.
|
|
78
|
+
source_chat_id: ID исходного чата. Если не указан, используется
|
|
79
|
+
целевой чат.
|
|
80
|
+
notify: Отправить ли получателям push-уведомление.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Пересланное сообщение или ``None``, если сервер не вернул его.
|
|
84
|
+
"""
|
|
85
|
+
return await self._app.api.messages.forward_message(
|
|
86
|
+
chat_id=chat_id,
|
|
87
|
+
message_id=message_id,
|
|
88
|
+
source_chat_id=source_chat_id,
|
|
89
|
+
notify=notify,
|
|
90
|
+
)
|
|
91
|
+
|
|
65
92
|
async def get_messages(
|
|
66
93
|
self,
|
|
67
94
|
chat_id: int,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
3
|
+
import zstandard
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
class Lz4BlockCompression:
|
|
2
7
|
def decompress(self, src: bytes, max_output: int = 5 * 1024 * 1024) -> bytes:
|
|
3
8
|
dst = bytearray()
|
|
@@ -95,3 +100,16 @@ class Lz4BlockCompression:
|
|
|
95
100
|
dst.extend(src[lit_start : lit_start + lit_len])
|
|
96
101
|
|
|
97
102
|
return bytes(dst)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ZstdCompression:
|
|
106
|
+
def decompress(self, src: bytes, max_output: int = 5 * 1024 * 1024) -> bytes:
|
|
107
|
+
try:
|
|
108
|
+
with zstandard.ZstdDecompressor().stream_reader(BytesIO(src)) as reader:
|
|
109
|
+
result = reader.read(max_output + 1)
|
|
110
|
+
except zstandard.ZstdError as e:
|
|
111
|
+
raise ValueError("Zstd: failed to decompress payload") from e
|
|
112
|
+
|
|
113
|
+
if len(result) > max_output:
|
|
114
|
+
raise ValueError("Zstd: output too large")
|
|
115
|
+
return result
|
pymax/protocol/tcp/payload.py
CHANGED
|
@@ -5,7 +5,7 @@ import msgpack
|
|
|
5
5
|
|
|
6
6
|
from pymax.logging import get_logger
|
|
7
7
|
|
|
8
|
-
from .compression import Lz4BlockCompression
|
|
8
|
+
from .compression import Lz4BlockCompression, ZstdCompression
|
|
9
9
|
|
|
10
10
|
logger = get_logger(__name__)
|
|
11
11
|
|
|
@@ -70,9 +70,11 @@ class TcpPayloadDecoder:
|
|
|
70
70
|
*,
|
|
71
71
|
serializer: MsgpackPayloadCodec,
|
|
72
72
|
compression: Lz4BlockCompression | None = None,
|
|
73
|
+
zstd_compression: ZstdCompression | None = None,
|
|
73
74
|
) -> None:
|
|
74
75
|
self.serializer = serializer
|
|
75
76
|
self.compression = compression
|
|
77
|
+
self.zstd_compression = zstd_compression
|
|
76
78
|
|
|
77
79
|
def _normalize_keys(self, obj: Any) -> Any:
|
|
78
80
|
if isinstance(obj, dict):
|
|
@@ -97,12 +99,26 @@ class TcpPayloadDecoder:
|
|
|
97
99
|
if not payload_bytes:
|
|
98
100
|
return {}
|
|
99
101
|
|
|
100
|
-
if flags
|
|
102
|
+
if flags == 0xFF:
|
|
103
|
+
if self.zstd_compression is None:
|
|
104
|
+
raise ValueError("Zstd-compressed TCP payload without a decoder")
|
|
105
|
+
try:
|
|
106
|
+
payload_bytes = self.zstd_compression.decompress(payload_bytes)
|
|
107
|
+
logger.debug("tcp payload decompressed with Zstd")
|
|
108
|
+
except ValueError:
|
|
109
|
+
logger.debug("tcp Zstd payload decompression failed", exc_info=True)
|
|
110
|
+
raise
|
|
111
|
+
elif flags > 0x7F:
|
|
112
|
+
raise ValueError(f"invalid TCP compression factor: {flags}")
|
|
113
|
+
elif flags > 0:
|
|
114
|
+
if self.compression is None:
|
|
115
|
+
raise ValueError("LZ4-compressed TCP payload without a decoder")
|
|
101
116
|
try:
|
|
102
117
|
payload_bytes = self.compression.decompress(payload_bytes)
|
|
103
|
-
logger.debug("tcp payload decompressed
|
|
118
|
+
logger.debug("tcp payload decompressed cof=%s", flags)
|
|
104
119
|
except ValueError:
|
|
105
|
-
logger.debug("tcp payload
|
|
120
|
+
logger.debug("tcp payload decompression failed cof=%s", flags, exc_info=True)
|
|
121
|
+
raise
|
|
106
122
|
|
|
107
123
|
result = self.serializer.decode(payload_bytes)
|
|
108
124
|
return self._normalize_keys(result)
|
pymax/protocol/tcp/protocol.py
CHANGED
|
@@ -7,6 +7,7 @@ from .payload import (
|
|
|
7
7
|
Lz4BlockCompression,
|
|
8
8
|
MsgpackPayloadCodec,
|
|
9
9
|
TcpPayloadDecoder,
|
|
10
|
+
ZstdCompression,
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
logger = get_logger(__name__)
|
|
@@ -20,8 +21,11 @@ class TcpProtocol(BaseProtocol):
|
|
|
20
21
|
self.framer = TcpPacketFramer()
|
|
21
22
|
self.serializer = MsgpackPayloadCodec()
|
|
22
23
|
self.compression = Lz4BlockCompression()
|
|
24
|
+
self.zstd_compression = ZstdCompression()
|
|
23
25
|
self.payload_decoder = TcpPayloadDecoder(
|
|
24
|
-
serializer=self.serializer,
|
|
26
|
+
serializer=self.serializer,
|
|
27
|
+
compression=self.compression,
|
|
28
|
+
zstd_compression=self.zstd_compression,
|
|
25
29
|
)
|
|
26
30
|
|
|
27
31
|
def encode(self, frame: OutboundFrame) -> bytes:
|
pymax/types/domain/message.py
CHANGED
|
@@ -93,8 +93,9 @@ class Message(CamelModel):
|
|
|
93
93
|
|
|
94
94
|
Сообщения, полученные через клиент, обычно уже привязаны к сервису
|
|
95
95
|
сообщений. После этого можно вызывать удобные методы объекта:
|
|
96
|
-
:meth:`reply`, :meth:`answer`, :meth:`
|
|
97
|
-
:meth:`read`, :meth:`react`, :meth:`unreact` и
|
|
96
|
+
:meth:`reply`, :meth:`answer`, :meth:`forward`, :meth:`edit`, :meth:`pin`,
|
|
97
|
+
:meth:`delete`, :meth:`read`, :meth:`react`, :meth:`unreact` и
|
|
98
|
+
:meth:`get_reactions`.
|
|
98
99
|
|
|
99
100
|
Используйте ``Message`` в обработчиках ``on_message`` и при работе с
|
|
100
101
|
историей. Некоторые поля могут быть ``None``, потому что Max присылает
|
|
@@ -244,6 +245,32 @@ class Message(CamelModel):
|
|
|
244
245
|
notify=notify,
|
|
245
246
|
)
|
|
246
247
|
|
|
248
|
+
async def forward(
|
|
249
|
+
self,
|
|
250
|
+
chat_id: int,
|
|
251
|
+
*,
|
|
252
|
+
notify: bool = True,
|
|
253
|
+
) -> Message | None:
|
|
254
|
+
"""Пересылает это сообщение в другой чат.
|
|
255
|
+
|
|
256
|
+
:param chat_id: ID целевого чата.
|
|
257
|
+
:type chat_id: int
|
|
258
|
+
:param notify: Отправить ли получателям push-уведомление.
|
|
259
|
+
:type notify: bool
|
|
260
|
+
:returns: Пересланное сообщение или ``None``, если сервер его не вернул.
|
|
261
|
+
:rtype: Message | None
|
|
262
|
+
:raises RuntimeError: Если сообщение не привязано к сервису или не
|
|
263
|
+
содержит ``chat_id``.
|
|
264
|
+
"""
|
|
265
|
+
actions, source_chat_id = self._bound()
|
|
266
|
+
|
|
267
|
+
return await actions.forward_message(
|
|
268
|
+
chat_id=chat_id,
|
|
269
|
+
message_id=self.id,
|
|
270
|
+
source_chat_id=source_chat_id,
|
|
271
|
+
notify=notify,
|
|
272
|
+
)
|
|
273
|
+
|
|
247
274
|
async def pin(self, notify_pin: bool = True) -> bool:
|
|
248
275
|
"""Закрепляет это сообщение в чате.
|
|
249
276
|
|
pymax/types/domain/user.py
CHANGED
|
@@ -49,11 +49,11 @@ class User(CamelModel):
|
|
|
49
49
|
:ivar description: Описание профиля.
|
|
50
50
|
:vartype description: str | None
|
|
51
51
|
:ivar gender: Пол пользователя.
|
|
52
|
-
:vartype gender: str | None
|
|
52
|
+
:vartype gender: str | int | None
|
|
53
53
|
:ivar link: Ссылка на профиль.
|
|
54
54
|
:vartype link: str | None
|
|
55
55
|
:ivar web_app: Данные связанного web-приложения, если есть.
|
|
56
|
-
:vartype web_app: dict[str, Any] | None
|
|
56
|
+
:vartype web_app: dict[str, Any] | str | None
|
|
57
57
|
:ivar menu_button: Данные кнопки меню профиля, если есть.
|
|
58
58
|
:vartype menu_button: dict[str, Any] | None
|
|
59
59
|
"""
|
|
@@ -71,9 +71,11 @@ class User(CamelModel):
|
|
|
71
71
|
phone: int | None = None
|
|
72
72
|
status: str | None = None
|
|
73
73
|
description: str | None = None
|
|
74
|
-
gender
|
|
74
|
+
# Bots may send ``gender`` as a numeric code and ``web_app`` as a URL
|
|
75
|
+
# string instead of an object; accept these so profile parsing won't fail.
|
|
76
|
+
gender: str | int | None = None
|
|
75
77
|
link: str | None = None
|
|
76
|
-
web_app: dict[str, Any] | None = None
|
|
78
|
+
web_app: dict[str, Any] | str | None = None
|
|
77
79
|
menu_button: dict[str, Any] | None = None
|
|
78
80
|
|
|
79
81
|
_actions: UserService | None = PrivateAttr(default=None)
|
|
File without changes
|
|
File without changes
|