maxapi-python 0.1.2__py3-none-any.whl → 1.0.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-0.1.2.dist-info → maxapi_python-1.0.1.dist-info}/METADATA +3 -1
- maxapi_python-1.0.1.dist-info/RECORD +27 -0
- {maxapi_python-0.1.2.dist-info → maxapi_python-1.0.1.dist-info}/licenses/LICENSE +21 -21
- pymax/__init__.py +55 -55
- pymax/core.py +170 -156
- pymax/crud.py +99 -99
- pymax/exceptions.py +29 -20
- pymax/files.py +86 -85
- pymax/filters.py +38 -38
- pymax/interfaces.py +73 -67
- pymax/mixins/__init__.py +20 -18
- pymax/mixins/auth.py +81 -81
- pymax/mixins/channel.py +25 -25
- pymax/mixins/group.py +220 -220
- pymax/mixins/handler.py +60 -60
- pymax/mixins/message.py +293 -293
- pymax/mixins/self.py +38 -38
- pymax/mixins/telemetry.py +114 -0
- pymax/mixins/user.py +82 -82
- pymax/mixins/websocket.py +262 -242
- pymax/models.py +8 -8
- pymax/navigation.py +185 -0
- pymax/payloads.py +195 -175
- pymax/static.py +210 -210
- pymax/types.py +434 -432
- pymax/utils.py +38 -38
- maxapi_python-0.1.2.dist-info/RECORD +0 -25
- {maxapi_python-0.1.2.dist-info → maxapi_python-1.0.1.dist-info}/WHEEL +0 -0
pymax/mixins/message.py
CHANGED
@@ -1,293 +1,293 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
import aiohttp
|
4
|
-
from aiohttp import ClientSession
|
5
|
-
|
6
|
-
from pymax.files import File, Photo, Video
|
7
|
-
from pymax.interfaces import ClientProtocol
|
8
|
-
from pymax.payloads import (
|
9
|
-
AttachPhotoPayload,
|
10
|
-
DeleteMessagePayload,
|
11
|
-
EditMessagePayload,
|
12
|
-
FetchHistoryPayload,
|
13
|
-
PinMessagePayload,
|
14
|
-
ReplyLink,
|
15
|
-
SendMessagePayload,
|
16
|
-
SendMessagePayloadMessage,
|
17
|
-
UploadPhotoPayload,
|
18
|
-
)
|
19
|
-
from pymax.static import AttachType, Opcode
|
20
|
-
from pymax.types import Attach, Message
|
21
|
-
|
22
|
-
|
23
|
-
class MessageMixin(ClientProtocol):
|
24
|
-
async def _upload_photo(self, photo: Photo) -> None | Attach:
|
25
|
-
try:
|
26
|
-
self.logger.info("Uploading photo")
|
27
|
-
payload = UploadPhotoPayload().model_dump(by_alias=True)
|
28
|
-
|
29
|
-
data = await self._send_and_wait(
|
30
|
-
opcode=Opcode.PHOTO_UPLOAD,
|
31
|
-
payload=payload,
|
32
|
-
)
|
33
|
-
if error := data.get("payload", {}).get("error"):
|
34
|
-
self.logger.error("Upload photo error: %s", error)
|
35
|
-
return None
|
36
|
-
|
37
|
-
url = data.get("payload", {}).get("url")
|
38
|
-
if not url:
|
39
|
-
self.logger.error("No upload URL received")
|
40
|
-
return None
|
41
|
-
|
42
|
-
photo_data = photo.validate_photo()
|
43
|
-
if not photo_data:
|
44
|
-
self.logger.error("Photo validation failed")
|
45
|
-
return None
|
46
|
-
|
47
|
-
form = aiohttp.FormData()
|
48
|
-
form.add_field(
|
49
|
-
name="file",
|
50
|
-
value=await photo.read(),
|
51
|
-
filename=f"image.{photo_data[0]}",
|
52
|
-
content_type=photo_data[1],
|
53
|
-
)
|
54
|
-
|
55
|
-
async with (
|
56
|
-
ClientSession() as session,
|
57
|
-
session.post(
|
58
|
-
url=url,
|
59
|
-
data=form,
|
60
|
-
) as response,
|
61
|
-
):
|
62
|
-
if response.status != 200:
|
63
|
-
self.logger.error(f"Upload failed with status {response.status}")
|
64
|
-
return None
|
65
|
-
|
66
|
-
result = await response.json()
|
67
|
-
|
68
|
-
if not result.get("photos"):
|
69
|
-
self.logger.error("No photos in response")
|
70
|
-
return None
|
71
|
-
|
72
|
-
photo_data = next(iter(result["photos"].values()), None)
|
73
|
-
if not photo_data or "token" not in photo_data:
|
74
|
-
self.logger.error("No token in response")
|
75
|
-
return None
|
76
|
-
|
77
|
-
return Attach(
|
78
|
-
_type=AttachType.PHOTO,
|
79
|
-
photo_token=photo_data["token"],
|
80
|
-
)
|
81
|
-
|
82
|
-
except Exception as e:
|
83
|
-
self.logger.exception("Upload photo failed: %s", str(e))
|
84
|
-
return None
|
85
|
-
|
86
|
-
async def send_message(
|
87
|
-
self,
|
88
|
-
text: str,
|
89
|
-
chat_id: int,
|
90
|
-
notify: bool,
|
91
|
-
photo: Photo | None = None,
|
92
|
-
photos: list[Photo] | None = None,
|
93
|
-
reply_to: int | None = None,
|
94
|
-
) -> Message | None:
|
95
|
-
"""
|
96
|
-
Отправляет сообщение в чат.
|
97
|
-
"""
|
98
|
-
try:
|
99
|
-
self.logger.info("Sending message to chat_id=%s notify=%s", chat_id, notify)
|
100
|
-
if photos and photo:
|
101
|
-
self.logger.warning("Both photo and photos provided; using photos")
|
102
|
-
photo = None
|
103
|
-
attaches = []
|
104
|
-
if photo:
|
105
|
-
self.logger.info("Uploading photo for message")
|
106
|
-
attach = await self._upload_photo(photo)
|
107
|
-
if not attach or not attach.photo_token:
|
108
|
-
self.logger.error("Photo upload failed, message not sent")
|
109
|
-
return None
|
110
|
-
attaches = [
|
111
|
-
AttachPhotoPayload(photo_token=attach.photo_token).model_dump(
|
112
|
-
by_alias=True
|
113
|
-
)
|
114
|
-
]
|
115
|
-
elif photos:
|
116
|
-
self.logger.info("Uploading multiple photos for message")
|
117
|
-
for p in photos:
|
118
|
-
attach = await self._upload_photo(p)
|
119
|
-
if attach and attach.photo_token:
|
120
|
-
attaches.append(
|
121
|
-
AttachPhotoPayload(
|
122
|
-
photo_token=attach.photo_token
|
123
|
-
).model_dump(by_alias=True)
|
124
|
-
)
|
125
|
-
if not attaches:
|
126
|
-
self.logger.error("All photo uploads failed, message not sent")
|
127
|
-
return None
|
128
|
-
|
129
|
-
payload = SendMessagePayload(
|
130
|
-
chat_id=chat_id,
|
131
|
-
message=SendMessagePayloadMessage(
|
132
|
-
text=text,
|
133
|
-
cid=int(time.time() * 1000),
|
134
|
-
elements=[],
|
135
|
-
attaches=attaches,
|
136
|
-
link=ReplyLink(message_id=str(reply_to)) if reply_to else None,
|
137
|
-
),
|
138
|
-
notify=notify,
|
139
|
-
).model_dump(by_alias=True)
|
140
|
-
|
141
|
-
data = await self._send_and_wait(opcode=Opcode.MSG_SEND, payload=payload)
|
142
|
-
if error := data.get("payload", {}).get("error"):
|
143
|
-
self.logger.error("Send message error: %s", error)
|
144
|
-
print(data)
|
145
|
-
return None
|
146
|
-
msg = (
|
147
|
-
Message.from_dict(data["payload"]["message"])
|
148
|
-
if data.get("payload")
|
149
|
-
else None
|
150
|
-
)
|
151
|
-
self.logger.debug("send_message result: %r", msg)
|
152
|
-
return msg
|
153
|
-
except Exception:
|
154
|
-
self.logger.exception("Send message failed")
|
155
|
-
return None
|
156
|
-
|
157
|
-
async def edit_message(
|
158
|
-
self, chat_id: int, message_id: int, text: str
|
159
|
-
) -> Message | None:
|
160
|
-
"""
|
161
|
-
Редактирует сообщение.
|
162
|
-
"""
|
163
|
-
try:
|
164
|
-
self.logger.info(
|
165
|
-
"Editing message chat_id=%s message_id=%s", chat_id, message_id
|
166
|
-
)
|
167
|
-
payload = EditMessagePayload(
|
168
|
-
chat_id=chat_id,
|
169
|
-
message_id=message_id,
|
170
|
-
text=text,
|
171
|
-
elements=[],
|
172
|
-
attaches=[],
|
173
|
-
).model_dump(by_alias=True)
|
174
|
-
data = await self._send_and_wait(opcode=Opcode.MSG_EDIT, payload=payload)
|
175
|
-
if error := data.get("payload", {}).get("error"):
|
176
|
-
self.logger.error("Edit message error: %s", error)
|
177
|
-
msg = (
|
178
|
-
Message.from_dict(data["payload"]["message"])
|
179
|
-
if data.get("payload")
|
180
|
-
else None
|
181
|
-
)
|
182
|
-
self.logger.debug("edit_message result: %r", msg)
|
183
|
-
return msg
|
184
|
-
except Exception:
|
185
|
-
self.logger.exception("Edit message failed")
|
186
|
-
return None
|
187
|
-
|
188
|
-
async def delete_message(
|
189
|
-
self, chat_id: int, message_ids: list[int], for_me: bool
|
190
|
-
) -> bool:
|
191
|
-
"""
|
192
|
-
Удаляет сообщения.
|
193
|
-
"""
|
194
|
-
try:
|
195
|
-
self.logger.info(
|
196
|
-
"Deleting messages chat_id=%s ids=%s for_me=%s",
|
197
|
-
chat_id,
|
198
|
-
message_ids,
|
199
|
-
for_me,
|
200
|
-
)
|
201
|
-
|
202
|
-
payload = DeleteMessagePayload(
|
203
|
-
chat_id=chat_id, message_ids=message_ids, for_me=for_me
|
204
|
-
).model_dump(by_alias=True)
|
205
|
-
|
206
|
-
data = await self._send_and_wait(opcode=Opcode.MSG_DELETE, payload=payload)
|
207
|
-
if error := data.get("payload", {}).get("error"):
|
208
|
-
self.logger.error("Delete message error: %s", error)
|
209
|
-
return False
|
210
|
-
self.logger.debug("delete_message success")
|
211
|
-
return True
|
212
|
-
except Exception:
|
213
|
-
self.logger.exception("Delete message failed")
|
214
|
-
return False
|
215
|
-
|
216
|
-
async def pin_message(
|
217
|
-
self, chat_id: int, message_id: int, notify_pin: bool
|
218
|
-
) -> bool:
|
219
|
-
"""
|
220
|
-
Закрепляет сообщение.
|
221
|
-
|
222
|
-
Args:
|
223
|
-
chat_id (int): ID чата
|
224
|
-
message_id (int): ID сообщения
|
225
|
-
notify_pin (bool): Оповещать о закреплении
|
226
|
-
|
227
|
-
Returns:
|
228
|
-
bool: True, если сообщение закреплено
|
229
|
-
"""
|
230
|
-
try:
|
231
|
-
payload = PinMessagePayload(
|
232
|
-
chat_id=chat_id,
|
233
|
-
notify_pin=notify_pin,
|
234
|
-
pin_message_id=message_id,
|
235
|
-
).model_dump(by_alias=True)
|
236
|
-
|
237
|
-
data = await self._send_and_wait(opcode=Opcode.CHAT_UPDATE, payload=payload)
|
238
|
-
if error := data.get("payload", {}).get("error"):
|
239
|
-
self.logger.error("Pin message error: %s", error)
|
240
|
-
return False
|
241
|
-
self.logger.debug("pin_message success")
|
242
|
-
return True
|
243
|
-
except Exception:
|
244
|
-
self.logger.exception("Pin message failed")
|
245
|
-
return False
|
246
|
-
|
247
|
-
async def fetch_history(
|
248
|
-
self,
|
249
|
-
chat_id: int,
|
250
|
-
from_time: int | None = None,
|
251
|
-
forward: int = 0,
|
252
|
-
backward: int = 200,
|
253
|
-
) -> list[Message] | None:
|
254
|
-
"""
|
255
|
-
Получает историю сообщений чата.
|
256
|
-
"""
|
257
|
-
if from_time is None:
|
258
|
-
from_time = int(time.time() * 1000)
|
259
|
-
|
260
|
-
try:
|
261
|
-
self.logger.info(
|
262
|
-
"Fetching history chat_id=%s from=%s forward=%s backward=%s",
|
263
|
-
chat_id,
|
264
|
-
from_time,
|
265
|
-
forward,
|
266
|
-
backward,
|
267
|
-
)
|
268
|
-
|
269
|
-
payload = FetchHistoryPayload(
|
270
|
-
chat_id=chat_id,
|
271
|
-
from_time=from_time, # pyright: ignore[reportCallIssue] FIXME: Pydantic Field alias
|
272
|
-
forward=forward,
|
273
|
-
backward=backward,
|
274
|
-
).model_dump(by_alias=True)
|
275
|
-
|
276
|
-
self.logger.debug("Payload dict keys: %s", list(payload.keys()))
|
277
|
-
|
278
|
-
data = await self._send_and_wait(
|
279
|
-
opcode=Opcode.CHAT_HISTORY, payload=payload, timeout=10
|
280
|
-
)
|
281
|
-
|
282
|
-
if error := data.get("payload", {}).get("error"):
|
283
|
-
self.logger.error("Fetch history error: %s", error)
|
284
|
-
return None
|
285
|
-
|
286
|
-
messages = [
|
287
|
-
Message.from_dict(msg) for msg in data["payload"].get("messages", [])
|
288
|
-
]
|
289
|
-
self.logger.debug("History fetched: %d messages", len(messages))
|
290
|
-
return messages
|
291
|
-
except Exception:
|
292
|
-
self.logger.exception("Fetch history failed")
|
293
|
-
return None
|
1
|
+
import time
|
2
|
+
|
3
|
+
import aiohttp
|
4
|
+
from aiohttp import ClientSession
|
5
|
+
|
6
|
+
from pymax.files import File, Photo, Video
|
7
|
+
from pymax.interfaces import ClientProtocol
|
8
|
+
from pymax.payloads import (
|
9
|
+
AttachPhotoPayload,
|
10
|
+
DeleteMessagePayload,
|
11
|
+
EditMessagePayload,
|
12
|
+
FetchHistoryPayload,
|
13
|
+
PinMessagePayload,
|
14
|
+
ReplyLink,
|
15
|
+
SendMessagePayload,
|
16
|
+
SendMessagePayloadMessage,
|
17
|
+
UploadPhotoPayload,
|
18
|
+
)
|
19
|
+
from pymax.static import AttachType, Opcode
|
20
|
+
from pymax.types import Attach, Message
|
21
|
+
|
22
|
+
|
23
|
+
class MessageMixin(ClientProtocol):
|
24
|
+
async def _upload_photo(self, photo: Photo) -> None | Attach:
|
25
|
+
try:
|
26
|
+
self.logger.info("Uploading photo")
|
27
|
+
payload = UploadPhotoPayload().model_dump(by_alias=True)
|
28
|
+
|
29
|
+
data = await self._send_and_wait(
|
30
|
+
opcode=Opcode.PHOTO_UPLOAD,
|
31
|
+
payload=payload,
|
32
|
+
)
|
33
|
+
if error := data.get("payload", {}).get("error"):
|
34
|
+
self.logger.error("Upload photo error: %s", error)
|
35
|
+
return None
|
36
|
+
|
37
|
+
url = data.get("payload", {}).get("url")
|
38
|
+
if not url:
|
39
|
+
self.logger.error("No upload URL received")
|
40
|
+
return None
|
41
|
+
|
42
|
+
photo_data = photo.validate_photo()
|
43
|
+
if not photo_data:
|
44
|
+
self.logger.error("Photo validation failed")
|
45
|
+
return None
|
46
|
+
|
47
|
+
form = aiohttp.FormData()
|
48
|
+
form.add_field(
|
49
|
+
name="file",
|
50
|
+
value=await photo.read(),
|
51
|
+
filename=f"image.{photo_data[0]}",
|
52
|
+
content_type=photo_data[1],
|
53
|
+
)
|
54
|
+
|
55
|
+
async with (
|
56
|
+
ClientSession() as session,
|
57
|
+
session.post(
|
58
|
+
url=url,
|
59
|
+
data=form,
|
60
|
+
) as response,
|
61
|
+
):
|
62
|
+
if response.status != 200:
|
63
|
+
self.logger.error(f"Upload failed with status {response.status}")
|
64
|
+
return None
|
65
|
+
|
66
|
+
result = await response.json()
|
67
|
+
|
68
|
+
if not result.get("photos"):
|
69
|
+
self.logger.error("No photos in response")
|
70
|
+
return None
|
71
|
+
|
72
|
+
photo_data = next(iter(result["photos"].values()), None)
|
73
|
+
if not photo_data or "token" not in photo_data:
|
74
|
+
self.logger.error("No token in response")
|
75
|
+
return None
|
76
|
+
|
77
|
+
return Attach(
|
78
|
+
_type=AttachType.PHOTO,
|
79
|
+
photo_token=photo_data["token"],
|
80
|
+
)
|
81
|
+
|
82
|
+
except Exception as e:
|
83
|
+
self.logger.exception("Upload photo failed: %s", str(e))
|
84
|
+
return None
|
85
|
+
|
86
|
+
async def send_message(
|
87
|
+
self,
|
88
|
+
text: str,
|
89
|
+
chat_id: int,
|
90
|
+
notify: bool,
|
91
|
+
photo: Photo | None = None,
|
92
|
+
photos: list[Photo] | None = None,
|
93
|
+
reply_to: int | None = None,
|
94
|
+
) -> Message | None:
|
95
|
+
"""
|
96
|
+
Отправляет сообщение в чат.
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
self.logger.info("Sending message to chat_id=%s notify=%s", chat_id, notify)
|
100
|
+
if photos and photo:
|
101
|
+
self.logger.warning("Both photo and photos provided; using photos")
|
102
|
+
photo = None
|
103
|
+
attaches = []
|
104
|
+
if photo:
|
105
|
+
self.logger.info("Uploading photo for message")
|
106
|
+
attach = await self._upload_photo(photo)
|
107
|
+
if not attach or not attach.photo_token:
|
108
|
+
self.logger.error("Photo upload failed, message not sent")
|
109
|
+
return None
|
110
|
+
attaches = [
|
111
|
+
AttachPhotoPayload(photo_token=attach.photo_token).model_dump(
|
112
|
+
by_alias=True
|
113
|
+
)
|
114
|
+
]
|
115
|
+
elif photos:
|
116
|
+
self.logger.info("Uploading multiple photos for message")
|
117
|
+
for p in photos:
|
118
|
+
attach = await self._upload_photo(p)
|
119
|
+
if attach and attach.photo_token:
|
120
|
+
attaches.append(
|
121
|
+
AttachPhotoPayload(
|
122
|
+
photo_token=attach.photo_token
|
123
|
+
).model_dump(by_alias=True)
|
124
|
+
)
|
125
|
+
if not attaches:
|
126
|
+
self.logger.error("All photo uploads failed, message not sent")
|
127
|
+
return None
|
128
|
+
|
129
|
+
payload = SendMessagePayload(
|
130
|
+
chat_id=chat_id,
|
131
|
+
message=SendMessagePayloadMessage(
|
132
|
+
text=text,
|
133
|
+
cid=int(time.time() * 1000),
|
134
|
+
elements=[],
|
135
|
+
attaches=attaches,
|
136
|
+
link=ReplyLink(message_id=str(reply_to)) if reply_to else None,
|
137
|
+
),
|
138
|
+
notify=notify,
|
139
|
+
).model_dump(by_alias=True)
|
140
|
+
|
141
|
+
data = await self._send_and_wait(opcode=Opcode.MSG_SEND, payload=payload)
|
142
|
+
if error := data.get("payload", {}).get("error"):
|
143
|
+
self.logger.error("Send message error: %s", error)
|
144
|
+
print(data)
|
145
|
+
return None
|
146
|
+
msg = (
|
147
|
+
Message.from_dict(data["payload"]["message"])
|
148
|
+
if data.get("payload")
|
149
|
+
else None
|
150
|
+
)
|
151
|
+
self.logger.debug("send_message result: %r", msg)
|
152
|
+
return msg
|
153
|
+
except Exception:
|
154
|
+
self.logger.exception("Send message failed")
|
155
|
+
return None
|
156
|
+
|
157
|
+
async def edit_message(
|
158
|
+
self, chat_id: int, message_id: int, text: str
|
159
|
+
) -> Message | None:
|
160
|
+
"""
|
161
|
+
Редактирует сообщение.
|
162
|
+
"""
|
163
|
+
try:
|
164
|
+
self.logger.info(
|
165
|
+
"Editing message chat_id=%s message_id=%s", chat_id, message_id
|
166
|
+
)
|
167
|
+
payload = EditMessagePayload(
|
168
|
+
chat_id=chat_id,
|
169
|
+
message_id=message_id,
|
170
|
+
text=text,
|
171
|
+
elements=[],
|
172
|
+
attaches=[],
|
173
|
+
).model_dump(by_alias=True)
|
174
|
+
data = await self._send_and_wait(opcode=Opcode.MSG_EDIT, payload=payload)
|
175
|
+
if error := data.get("payload", {}).get("error"):
|
176
|
+
self.logger.error("Edit message error: %s", error)
|
177
|
+
msg = (
|
178
|
+
Message.from_dict(data["payload"]["message"])
|
179
|
+
if data.get("payload")
|
180
|
+
else None
|
181
|
+
)
|
182
|
+
self.logger.debug("edit_message result: %r", msg)
|
183
|
+
return msg
|
184
|
+
except Exception:
|
185
|
+
self.logger.exception("Edit message failed")
|
186
|
+
return None
|
187
|
+
|
188
|
+
async def delete_message(
|
189
|
+
self, chat_id: int, message_ids: list[int], for_me: bool
|
190
|
+
) -> bool:
|
191
|
+
"""
|
192
|
+
Удаляет сообщения.
|
193
|
+
"""
|
194
|
+
try:
|
195
|
+
self.logger.info(
|
196
|
+
"Deleting messages chat_id=%s ids=%s for_me=%s",
|
197
|
+
chat_id,
|
198
|
+
message_ids,
|
199
|
+
for_me,
|
200
|
+
)
|
201
|
+
|
202
|
+
payload = DeleteMessagePayload(
|
203
|
+
chat_id=chat_id, message_ids=message_ids, for_me=for_me
|
204
|
+
).model_dump(by_alias=True)
|
205
|
+
|
206
|
+
data = await self._send_and_wait(opcode=Opcode.MSG_DELETE, payload=payload)
|
207
|
+
if error := data.get("payload", {}).get("error"):
|
208
|
+
self.logger.error("Delete message error: %s", error)
|
209
|
+
return False
|
210
|
+
self.logger.debug("delete_message success")
|
211
|
+
return True
|
212
|
+
except Exception:
|
213
|
+
self.logger.exception("Delete message failed")
|
214
|
+
return False
|
215
|
+
|
216
|
+
async def pin_message(
|
217
|
+
self, chat_id: int, message_id: int, notify_pin: bool
|
218
|
+
) -> bool:
|
219
|
+
"""
|
220
|
+
Закрепляет сообщение.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
chat_id (int): ID чата
|
224
|
+
message_id (int): ID сообщения
|
225
|
+
notify_pin (bool): Оповещать о закреплении
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
bool: True, если сообщение закреплено
|
229
|
+
"""
|
230
|
+
try:
|
231
|
+
payload = PinMessagePayload(
|
232
|
+
chat_id=chat_id,
|
233
|
+
notify_pin=notify_pin,
|
234
|
+
pin_message_id=message_id,
|
235
|
+
).model_dump(by_alias=True)
|
236
|
+
|
237
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_UPDATE, payload=payload)
|
238
|
+
if error := data.get("payload", {}).get("error"):
|
239
|
+
self.logger.error("Pin message error: %s", error)
|
240
|
+
return False
|
241
|
+
self.logger.debug("pin_message success")
|
242
|
+
return True
|
243
|
+
except Exception:
|
244
|
+
self.logger.exception("Pin message failed")
|
245
|
+
return False
|
246
|
+
|
247
|
+
async def fetch_history(
|
248
|
+
self,
|
249
|
+
chat_id: int,
|
250
|
+
from_time: int | None = None,
|
251
|
+
forward: int = 0,
|
252
|
+
backward: int = 200,
|
253
|
+
) -> list[Message] | None:
|
254
|
+
"""
|
255
|
+
Получает историю сообщений чата.
|
256
|
+
"""
|
257
|
+
if from_time is None:
|
258
|
+
from_time = int(time.time() * 1000)
|
259
|
+
|
260
|
+
try:
|
261
|
+
self.logger.info(
|
262
|
+
"Fetching history chat_id=%s from=%s forward=%s backward=%s",
|
263
|
+
chat_id,
|
264
|
+
from_time,
|
265
|
+
forward,
|
266
|
+
backward,
|
267
|
+
)
|
268
|
+
|
269
|
+
payload = FetchHistoryPayload(
|
270
|
+
chat_id=chat_id,
|
271
|
+
from_time=from_time, # pyright: ignore[reportCallIssue] FIXME: Pydantic Field alias
|
272
|
+
forward=forward,
|
273
|
+
backward=backward,
|
274
|
+
).model_dump(by_alias=True)
|
275
|
+
|
276
|
+
self.logger.debug("Payload dict keys: %s", list(payload.keys()))
|
277
|
+
|
278
|
+
data = await self._send_and_wait(
|
279
|
+
opcode=Opcode.CHAT_HISTORY, payload=payload, timeout=10
|
280
|
+
)
|
281
|
+
|
282
|
+
if error := data.get("payload", {}).get("error"):
|
283
|
+
self.logger.error("Fetch history error: %s", error)
|
284
|
+
return None
|
285
|
+
|
286
|
+
messages = [
|
287
|
+
Message.from_dict(msg) for msg in data["payload"].get("messages", [])
|
288
|
+
]
|
289
|
+
self.logger.debug("History fetched: %d messages", len(messages))
|
290
|
+
return messages
|
291
|
+
except Exception:
|
292
|
+
self.logger.exception("Fetch history failed")
|
293
|
+
return None
|
pymax/mixins/self.py
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
-
from pymax.interfaces import ClientProtocol
|
2
|
-
from pymax.payloads import ChangeProfilePayload
|
3
|
-
from pymax.static import Opcode
|
4
|
-
|
5
|
-
|
6
|
-
class SelfMixin(ClientProtocol):
|
7
|
-
async def change_profile(
|
8
|
-
self,
|
9
|
-
first_name: str,
|
10
|
-
last_name: str | None = None,
|
11
|
-
description: str | None = None,
|
12
|
-
) -> bool:
|
13
|
-
"""
|
14
|
-
Изменяет профиль
|
15
|
-
|
16
|
-
Args:
|
17
|
-
first_name (str): Имя.
|
18
|
-
last_name (str | None, optional): Фамилия. Defaults to None.
|
19
|
-
description (str | None, optional): Описание. Defaults to None.
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
bool: True, если профиль изменен
|
23
|
-
"""
|
24
|
-
|
25
|
-
payload = ChangeProfilePayload(
|
26
|
-
first_name=first_name,
|
27
|
-
last_name=last_name,
|
28
|
-
description=description,
|
29
|
-
).model_dump(
|
30
|
-
by_alias=True,
|
31
|
-
exclude_none=True,
|
32
|
-
)
|
33
|
-
|
34
|
-
data = await self._send_and_wait(opcode=Opcode.PROFILE, payload=payload)
|
35
|
-
if error := data.get("payload", {}).get("error"):
|
36
|
-
self.logger.error("Change profile error: %s", error)
|
37
|
-
return False
|
38
|
-
return True
|
1
|
+
from pymax.interfaces import ClientProtocol
|
2
|
+
from pymax.payloads import ChangeProfilePayload
|
3
|
+
from pymax.static import Opcode
|
4
|
+
|
5
|
+
|
6
|
+
class SelfMixin(ClientProtocol):
|
7
|
+
async def change_profile(
|
8
|
+
self,
|
9
|
+
first_name: str,
|
10
|
+
last_name: str | None = None,
|
11
|
+
description: str | None = None,
|
12
|
+
) -> bool:
|
13
|
+
"""
|
14
|
+
Изменяет профиль
|
15
|
+
|
16
|
+
Args:
|
17
|
+
first_name (str): Имя.
|
18
|
+
last_name (str | None, optional): Фамилия. Defaults to None.
|
19
|
+
description (str | None, optional): Описание. Defaults to None.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
bool: True, если профиль изменен
|
23
|
+
"""
|
24
|
+
|
25
|
+
payload = ChangeProfilePayload(
|
26
|
+
first_name=first_name,
|
27
|
+
last_name=last_name,
|
28
|
+
description=description,
|
29
|
+
).model_dump(
|
30
|
+
by_alias=True,
|
31
|
+
exclude_none=True,
|
32
|
+
)
|
33
|
+
|
34
|
+
data = await self._send_and_wait(opcode=Opcode.PROFILE, payload=payload)
|
35
|
+
if error := data.get("payload", {}).get("error"):
|
36
|
+
self.logger.error("Change profile error: %s", error)
|
37
|
+
return False
|
38
|
+
return True
|