maxapi-python 1.2.2__tar.gz → 1.2.3__tar.gz
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-1.2.2 → maxapi_python-1.2.3}/PKG-INFO +1 -1
- maxapi_python-1.2.3/examples/example.py +74 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/pyproject.toml +1 -1
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/core.py +1 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/files.py +5 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/interfaces.py +1 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/group.py +31 -12
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/self.py +74 -9
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/socket.py +10 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/websocket.py +9 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/payloads.py +3 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/static/enum.py +1 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/types.py +57 -28
- maxapi_python-1.2.2/examples/example.py +0 -268
- maxapi_python-1.2.2/examples/flt_test.py +0 -51
- maxapi_python-1.2.2/examples/large_file_upload.py +0 -51
- maxapi_python-1.2.2/examples/reg.py +0 -34
- maxapi_python-1.2.2/examples/test.py +0 -20
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.coderabbit.yaml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/FUNDING.yml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/pull_request_template.md +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/workflows/publish.yml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.github/workflows/tests.yml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.gitignore +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/.pre-commit-config.yaml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/LICENSE +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/README.md +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/assets/icon.svg +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/assets/logo.svg +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/examples/telegram_bridge.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/mkdocs.yml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/pytest.ini +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/Makefile +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/build.sh +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/make.bat +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/_static/logo.svg +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/clients.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/conf.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/decorators.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/examples.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/guides.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/index.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/installation.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/quickstart.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/redocs/source/types.rst +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/ruff.toml +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/__init__.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/crud.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/exceptions.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/filters.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/formatter.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/formatting.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/__init__.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/auth.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/channel.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/handler.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/message.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/scheduler.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/telemetry.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/user.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/mixins/utils.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/models.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/navigation.py +0 -0
- {maxapi_python-1.2.2 → maxapi_python-1.2.3}/src/pymax/static/constant.py +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
import logging
|
|
4
|
+
from time import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import pymax
|
|
8
|
+
from pymax import MaxClient, Message, ReactionInfo, SocketMaxClient, filters
|
|
9
|
+
from pymax.files import File, Photo, Video
|
|
10
|
+
from pymax.filters import Filters
|
|
11
|
+
from pymax.payloads import UserAgentPayload
|
|
12
|
+
from pymax.static.enum import AttachType, Opcode
|
|
13
|
+
from pymax.types import Chat
|
|
14
|
+
|
|
15
|
+
phone = "+79991234567"
|
|
16
|
+
headers = UserAgentPayload(device_type="WEB")
|
|
17
|
+
|
|
18
|
+
client = MaxClient(
|
|
19
|
+
phone=phone,
|
|
20
|
+
work_dir="cache",
|
|
21
|
+
reconnect=False,
|
|
22
|
+
logger=None,
|
|
23
|
+
headers=headers,
|
|
24
|
+
)
|
|
25
|
+
client.logger.setLevel(logging.INFO)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@client.on_start
|
|
29
|
+
async def handle_start() -> None:
|
|
30
|
+
print(f"Client started as {client.me.names[0].first_name}!")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@client.on_raw_receive
|
|
34
|
+
async def handle_raw_receive(data: dict[str, Any]) -> None:
|
|
35
|
+
print(f"Raw data received: {data}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@client.on_reaction_change
|
|
39
|
+
async def handle_reaction_change(
|
|
40
|
+
message_id: str, chat_id: int, reaction_info: ReactionInfo
|
|
41
|
+
) -> None:
|
|
42
|
+
print(
|
|
43
|
+
f"Reaction changed on message {message_id} in chat {chat_id}: "
|
|
44
|
+
f"Total count: {reaction_info.total_count}, "
|
|
45
|
+
f"Your reaction: {reaction_info.your_reaction}, "
|
|
46
|
+
f"Counters: {reaction_info.counters[0].reaction}={reaction_info.counters[0].count}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@client.on_chat_update
|
|
51
|
+
async def handle_chat_update(chat: Chat) -> None:
|
|
52
|
+
print(f"Chat updated: {chat.id}, new title: {chat.title}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@client.on_message(Filters.chat(0) & Filters.text("hello"))
|
|
56
|
+
async def handle_message(message: Message) -> None:
|
|
57
|
+
print(f"New message in chat {message.chat_id} from {message.sender}: {message.text}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@client.on_message_edit()
|
|
61
|
+
async def handle_edited_message(message: Message) -> None:
|
|
62
|
+
print(f"Edited message in chat {message.chat_id}: {message.text}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@client.on_message_delete()
|
|
66
|
+
async def handle_deleted_message(message: Message) -> None:
|
|
67
|
+
print(f"Deleted message in chat {message.chat_id}: {message.id}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
try:
|
|
72
|
+
asyncio.run(client.start())
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
print("Client stopped by user")
|
|
@@ -116,6 +116,7 @@ class MaxClient(ApiMixin, WebSocketMixin, BaseClient):
|
|
|
116
116
|
self.dialogs: list[Dialog] = []
|
|
117
117
|
self.channels: list[Channel] = []
|
|
118
118
|
self.me: Me | None = None
|
|
119
|
+
self.contacts: list[User] = []
|
|
119
120
|
self._users: dict[int, User] = {}
|
|
120
121
|
|
|
121
122
|
self._work_dir: str = work_dir
|
|
@@ -46,6 +46,11 @@ class Photo(BaseFile):
|
|
|
46
46
|
} # FIXME: костыль ✅
|
|
47
47
|
|
|
48
48
|
def __init__(self, url: str | None = None, path: str | None = None) -> None:
|
|
49
|
+
if path:
|
|
50
|
+
self.file_name = Path(path).name
|
|
51
|
+
elif url:
|
|
52
|
+
self.file_name = Path(url).name
|
|
53
|
+
|
|
49
54
|
super().__init__(url, path)
|
|
50
55
|
|
|
51
56
|
def validate_photo(self) -> tuple[str, str] | None:
|
|
@@ -95,9 +95,7 @@ class GroupMixin(ClientProtocol):
|
|
|
95
95
|
operation="add",
|
|
96
96
|
).model_dump(by_alias=True)
|
|
97
97
|
|
|
98
|
-
data = await self._send_and_wait(
|
|
99
|
-
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
|
100
|
-
)
|
|
98
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload)
|
|
101
99
|
|
|
102
100
|
if data.get("payload", {}).get("error"):
|
|
103
101
|
MixinsUtils.handle_error(data)
|
|
@@ -155,9 +153,7 @@ class GroupMixin(ClientProtocol):
|
|
|
155
153
|
clean_msg_period=clean_msg_period,
|
|
156
154
|
).model_dump(by_alias=True)
|
|
157
155
|
|
|
158
|
-
data = await self._send_and_wait(
|
|
159
|
-
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
|
160
|
-
)
|
|
156
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload)
|
|
161
157
|
|
|
162
158
|
if data.get("payload", {}).get("error"):
|
|
163
159
|
MixinsUtils.handle_error(data)
|
|
@@ -293,6 +289,33 @@ class GroupMixin(ClientProtocol):
|
|
|
293
289
|
|
|
294
290
|
return chat
|
|
295
291
|
|
|
292
|
+
async def resolve_group_by_link(self, link: str) -> Chat | None:
|
|
293
|
+
"""
|
|
294
|
+
Разрешает группу по ссылке
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
link (str): Ссылка на группу.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Chat | None: Объект чата группы или None, если не найдено.
|
|
301
|
+
"""
|
|
302
|
+
proceed_link = self._process_chat_join_link(link)
|
|
303
|
+
if proceed_link is None:
|
|
304
|
+
raise ValueError("Invalid group link")
|
|
305
|
+
|
|
306
|
+
data = await self._send_and_wait(
|
|
307
|
+
opcode=Opcode.LINK_INFO,
|
|
308
|
+
payload={
|
|
309
|
+
"link": proceed_link,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if data.get("payload", {}).get("error"):
|
|
314
|
+
MixinsUtils.handle_error(data)
|
|
315
|
+
|
|
316
|
+
chat = Chat.from_dict(data["payload"].get("chat", {}))
|
|
317
|
+
return chat
|
|
318
|
+
|
|
296
319
|
async def rework_invite_link(self, chat_id: int) -> Chat:
|
|
297
320
|
"""
|
|
298
321
|
Пересоздает ссылку для приглашения в группу
|
|
@@ -329,14 +352,10 @@ class GroupMixin(ClientProtocol):
|
|
|
329
352
|
chat_id for chat_id in chat_ids if await self._get_chat(chat_id) is None
|
|
330
353
|
]
|
|
331
354
|
if missed_chat_ids:
|
|
332
|
-
payload = GetChatInfoPayload(chat_ids=missed_chat_ids).model_dump(
|
|
333
|
-
by_alias=True
|
|
334
|
-
)
|
|
355
|
+
payload = GetChatInfoPayload(chat_ids=missed_chat_ids).model_dump(by_alias=True)
|
|
335
356
|
else:
|
|
336
357
|
chats: list[Chat] = [
|
|
337
|
-
chat
|
|
338
|
-
for chat_id in chat_ids
|
|
339
|
-
if (chat := await self._get_chat(chat_id)) is not None
|
|
358
|
+
chat for chat_id in chat_ids if (chat := await self._get_chat(chat_id)) is not None
|
|
340
359
|
]
|
|
341
360
|
return chats
|
|
342
361
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
from http import HTTPStatus
|
|
1
3
|
from typing import Any
|
|
4
|
+
from urllib.parse import parse_qs, urlparse
|
|
2
5
|
from uuid import uuid4
|
|
3
6
|
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
4
9
|
from pymax.exceptions import Error
|
|
10
|
+
from pymax.files import Photo
|
|
5
11
|
from pymax.interfaces import ClientProtocol
|
|
6
12
|
from pymax.mixins.utils import MixinsUtils
|
|
7
13
|
from pymax.payloads import (
|
|
@@ -10,17 +16,60 @@ from pymax.payloads import (
|
|
|
10
16
|
DeleteFolderPayload,
|
|
11
17
|
GetFolderPayload,
|
|
12
18
|
UpdateFolderPayload,
|
|
19
|
+
UploadPayload,
|
|
13
20
|
)
|
|
14
21
|
from pymax.static.enum import Opcode
|
|
15
|
-
from pymax.types import Folder, FolderList, FolderUpdate
|
|
22
|
+
from pymax.types import Folder, FolderList, FolderUpdate, Me
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
class SelfMixin(ClientProtocol):
|
|
26
|
+
async def _request_photo_upload_url(self) -> str:
|
|
27
|
+
self.logger.info("Requesting profile photo upload URL")
|
|
28
|
+
|
|
29
|
+
data = await self._send_and_wait(
|
|
30
|
+
opcode=Opcode.PHOTO_UPLOAD,
|
|
31
|
+
payload=UploadPayload(profile=True).model_dump(by_alias=True),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if data.get("payload", {}).get("error"):
|
|
35
|
+
MixinsUtils.handle_error(data)
|
|
36
|
+
|
|
37
|
+
return data["payload"]["url"]
|
|
38
|
+
|
|
39
|
+
async def _upload_profile_photo(self, upload_url: str, photo: Photo) -> str:
|
|
40
|
+
self.logger.info("Uploading profile photo")
|
|
41
|
+
|
|
42
|
+
parsed_url = urlparse(upload_url)
|
|
43
|
+
photo_id = parse_qs(parsed_url.query)["photoIds"][0]
|
|
44
|
+
|
|
45
|
+
form = aiohttp.FormData()
|
|
46
|
+
form.add_field(
|
|
47
|
+
"file",
|
|
48
|
+
await photo.read(),
|
|
49
|
+
filename=photo.file_name,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async with (
|
|
53
|
+
aiohttp.ClientSession() as session,
|
|
54
|
+
session.post(upload_url, data=form) as response,
|
|
55
|
+
):
|
|
56
|
+
if response.status != HTTPStatus.OK:
|
|
57
|
+
raise Error(
|
|
58
|
+
"Failed to upload profile photo.", message="UploadError", title="Upload Error"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.logger.info("Upload successful")
|
|
62
|
+
data = await response.json()
|
|
63
|
+
return data["photos"][photo_id][
|
|
64
|
+
"token"
|
|
65
|
+
] # TODO: сделать нормальную типизацию и чекнинг ответа
|
|
66
|
+
|
|
19
67
|
async def change_profile(
|
|
20
68
|
self,
|
|
21
69
|
first_name: str,
|
|
22
70
|
last_name: str | None = None,
|
|
23
71
|
description: str | None = None,
|
|
72
|
+
photo: Photo | None = None,
|
|
24
73
|
) -> bool:
|
|
25
74
|
"""
|
|
26
75
|
Изменяет информацию профиля текущего пользователя.
|
|
@@ -35,20 +84,36 @@ class SelfMixin(ClientProtocol):
|
|
|
35
84
|
:rtype: bool
|
|
36
85
|
"""
|
|
37
86
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
87
|
+
if photo:
|
|
88
|
+
upload_url = await self._request_photo_upload_url()
|
|
89
|
+
photo_token = await self._upload_profile_photo(upload_url, photo)
|
|
90
|
+
|
|
91
|
+
payload = ChangeProfilePayload(
|
|
92
|
+
first_name=first_name,
|
|
93
|
+
last_name=last_name,
|
|
94
|
+
description=description,
|
|
95
|
+
photo_token=photo_token,
|
|
96
|
+
).model_dump(
|
|
97
|
+
by_alias=True,
|
|
98
|
+
exclude_none=True,
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
payload = ChangeProfilePayload(
|
|
102
|
+
first_name=first_name,
|
|
103
|
+
last_name=last_name,
|
|
104
|
+
description=description,
|
|
105
|
+
).model_dump(
|
|
106
|
+
by_alias=True,
|
|
107
|
+
exclude_none=True,
|
|
108
|
+
)
|
|
46
109
|
|
|
47
110
|
data = await self._send_and_wait(opcode=Opcode.PROFILE, payload=payload)
|
|
48
111
|
|
|
49
112
|
if data.get("payload", {}).get("error"):
|
|
50
113
|
MixinsUtils.handle_error(data)
|
|
51
114
|
|
|
115
|
+
self.me = Me.from_dict(data["payload"]["profile"]["contact"])
|
|
116
|
+
|
|
52
117
|
return True
|
|
53
118
|
|
|
54
119
|
async def create_folder(
|
|
@@ -28,6 +28,7 @@ from pymax.types import (
|
|
|
28
28
|
Message,
|
|
29
29
|
ReactionCounter,
|
|
30
30
|
ReactionInfo,
|
|
31
|
+
User,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
@@ -603,6 +604,15 @@ Socket connections may be unstable, SSL issues are possible.
|
|
|
603
604
|
self.channels.append(Channel.from_dict(raw_chat))
|
|
604
605
|
except Exception:
|
|
605
606
|
self.logger.exception("Error parsing chat entry (socket)")
|
|
607
|
+
|
|
608
|
+
for raw_user in raw_payload.get("contacts", []):
|
|
609
|
+
try:
|
|
610
|
+
user = User.from_dict(raw_user)
|
|
611
|
+
if user:
|
|
612
|
+
self.contacts.append(user)
|
|
613
|
+
except Exception:
|
|
614
|
+
self.logger.exception("Error parsing contact entry (socket)")
|
|
615
|
+
|
|
606
616
|
if raw_payload.get("profile", {}).get("contact"):
|
|
607
617
|
self.me = Me.from_dict(raw_payload.get("profile", {}).get("contact", {}))
|
|
608
618
|
self.logger.info(
|
|
@@ -27,6 +27,7 @@ from pymax.types import (
|
|
|
27
27
|
Message,
|
|
28
28
|
ReactionCounter,
|
|
29
29
|
ReactionInfo,
|
|
30
|
+
User,
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
|
|
@@ -465,6 +466,14 @@ class WebSocketMixin(ClientProtocol):
|
|
|
465
466
|
except Exception:
|
|
466
467
|
self.logger.exception("Error parsing chat entry")
|
|
467
468
|
|
|
469
|
+
for raw_user in raw_payload.get("contacts", []):
|
|
470
|
+
try:
|
|
471
|
+
user = User.from_dict(raw_user)
|
|
472
|
+
if user:
|
|
473
|
+
self.contacts.append(user)
|
|
474
|
+
except Exception:
|
|
475
|
+
self.logger.exception("Error parsing contact entry")
|
|
476
|
+
|
|
468
477
|
if raw_payload.get("profile", {}).get("contact"):
|
|
469
478
|
self.me = Me.from_dict(raw_payload.get("profile", {}).get("contact", {}))
|
|
470
479
|
|
|
@@ -97,6 +97,7 @@ class ReplyLink(CamelModel):
|
|
|
97
97
|
|
|
98
98
|
class UploadPayload(CamelModel):
|
|
99
99
|
count: int = 1
|
|
100
|
+
profile: bool = False
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
class AttachPhotoPayload(CamelModel):
|
|
@@ -168,6 +169,8 @@ class ChangeProfilePayload(CamelModel):
|
|
|
168
169
|
first_name: str
|
|
169
170
|
last_name: str | None = None
|
|
170
171
|
description: str | None = None
|
|
172
|
+
photo_token: str | None = None
|
|
173
|
+
avatar_type: str = "USER_AVATAR" # TODO: вынести гада в энам
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
class ResolveLinkPayload(CamelModel):
|
|
@@ -47,7 +47,7 @@ class Name:
|
|
|
47
47
|
def __init__(
|
|
48
48
|
self,
|
|
49
49
|
name: str | None,
|
|
50
|
-
first_name: None,
|
|
50
|
+
first_name: None | str,
|
|
51
51
|
last_name: str | None,
|
|
52
52
|
type: str | None,
|
|
53
53
|
) -> None:
|
|
@@ -90,16 +90,14 @@ class Names(Name):
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
92
92
|
name: str | None,
|
|
93
|
-
first_name: None,
|
|
93
|
+
first_name: None | str,
|
|
94
94
|
last_name: str | None,
|
|
95
95
|
type: str | None,
|
|
96
96
|
) -> None:
|
|
97
97
|
"""
|
|
98
98
|
Синоним для класса Name.
|
|
99
99
|
"""
|
|
100
|
-
super().__init__(
|
|
101
|
-
name=name, first_name=first_name, last_name=last_name, type=type
|
|
102
|
-
)
|
|
100
|
+
super().__init__(name=name, first_name=first_name, last_name=last_name, type=type)
|
|
103
101
|
|
|
104
102
|
|
|
105
103
|
class Contact:
|
|
@@ -219,7 +217,7 @@ class StickerAttach:
|
|
|
219
217
|
def __init__(
|
|
220
218
|
self,
|
|
221
219
|
author_type: str,
|
|
222
|
-
lottie_url: str,
|
|
220
|
+
lottie_url: str | None,
|
|
223
221
|
url: str,
|
|
224
222
|
sticker_id: int,
|
|
225
223
|
tags: list[str] | None,
|
|
@@ -248,7 +246,7 @@ class StickerAttach:
|
|
|
248
246
|
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
249
247
|
return cls(
|
|
250
248
|
author_type=data["authorType"],
|
|
251
|
-
lottie_url=data
|
|
249
|
+
lottie_url=data.get("lottieUrl"),
|
|
252
250
|
url=data["url"],
|
|
253
251
|
sticker_id=data["stickerId"],
|
|
254
252
|
tags=data.get("tags"),
|
|
@@ -443,9 +441,7 @@ class VideoAttach:
|
|
|
443
441
|
|
|
444
442
|
|
|
445
443
|
class FileAttach:
|
|
446
|
-
def __init__(
|
|
447
|
-
self, file_id: int, name: str, size: int, token: str, type: AttachType
|
|
448
|
-
) -> None:
|
|
444
|
+
def __init__(self, file_id: int, name: str, size: int, token: str, type: AttachType) -> None:
|
|
449
445
|
self.file_id = file_id
|
|
450
446
|
self.name = name
|
|
451
447
|
self.size = size
|
|
@@ -553,9 +549,7 @@ class Me:
|
|
|
553
549
|
|
|
554
550
|
|
|
555
551
|
class Element:
|
|
556
|
-
def __init__(
|
|
557
|
-
self, type: FormattingType | str, length: int, from_: int | None = None
|
|
558
|
-
) -> None:
|
|
552
|
+
def __init__(self, type: FormattingType | str, length: int, from_: int | None = None) -> None:
|
|
559
553
|
self.type = type
|
|
560
554
|
self.length = length
|
|
561
555
|
self.from_ = from_
|
|
@@ -566,9 +560,7 @@ class Element:
|
|
|
566
560
|
|
|
567
561
|
@override
|
|
568
562
|
def __repr__(self) -> str:
|
|
569
|
-
return (
|
|
570
|
-
f"Element(type={self.type!r}, length={self.length!r}, from_={self.from_!r})"
|
|
571
|
-
)
|
|
563
|
+
return f"Element(type={self.type!r}, length={self.length!r}, from_={self.from_!r})"
|
|
572
564
|
|
|
573
565
|
@override
|
|
574
566
|
def __str__(self) -> str:
|
|
@@ -591,7 +583,9 @@ class MessageLink:
|
|
|
591
583
|
|
|
592
584
|
@override
|
|
593
585
|
def __repr__(self) -> str:
|
|
594
|
-
return
|
|
586
|
+
return (
|
|
587
|
+
f"MessageLink(chat_id={self.chat_id!r}, message={self.message!r}, type={self.type!r})"
|
|
588
|
+
)
|
|
595
589
|
|
|
596
590
|
@override
|
|
597
591
|
def __str__(self) -> str:
|
|
@@ -636,6 +630,36 @@ class ReactionInfo:
|
|
|
636
630
|
)
|
|
637
631
|
|
|
638
632
|
|
|
633
|
+
class ContactAttach:
|
|
634
|
+
def __init__(
|
|
635
|
+
self, contact_id: int, first_name: str, last_name: str, name: str, photo_url: str
|
|
636
|
+
) -> None:
|
|
637
|
+
self.contact_id = contact_id
|
|
638
|
+
self.first_name = first_name
|
|
639
|
+
self.last_name = last_name
|
|
640
|
+
self.name = name
|
|
641
|
+
self.photo_url = photo_url
|
|
642
|
+
self.type = AttachType.CONTACT
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
646
|
+
return cls(
|
|
647
|
+
contact_id=data["contactId"],
|
|
648
|
+
first_name=data["firstName"],
|
|
649
|
+
last_name=data["lastName"],
|
|
650
|
+
name=data["name"],
|
|
651
|
+
photo_url=data["photoUrl"],
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
@override
|
|
655
|
+
def __repr__(self) -> str:
|
|
656
|
+
return f"ContactAttach(contact_id={self.contact_id!r}, first_name={self.first_name!r}, last_name={self.last_name!r}, name={self.name!r}, photo_url={self.photo_url!r})"
|
|
657
|
+
|
|
658
|
+
@override
|
|
659
|
+
def __str__(self) -> str:
|
|
660
|
+
return f"ContactAttach: {self.name}"
|
|
661
|
+
|
|
662
|
+
|
|
639
663
|
class Message:
|
|
640
664
|
def __init__(
|
|
641
665
|
self,
|
|
@@ -658,6 +682,7 @@ class Message:
|
|
|
658
682
|
| ControlAttach
|
|
659
683
|
| StickerAttach
|
|
660
684
|
| AudioAttach
|
|
685
|
+
| ContactAttach
|
|
661
686
|
]
|
|
662
687
|
| None
|
|
663
688
|
),
|
|
@@ -679,7 +704,13 @@ class Message:
|
|
|
679
704
|
def from_dict(cls, data: dict[Any, Any]) -> Self:
|
|
680
705
|
message = data["message"] if data.get("message") else data
|
|
681
706
|
attaches: list[
|
|
682
|
-
PhotoAttach
|
|
707
|
+
PhotoAttach
|
|
708
|
+
| VideoAttach
|
|
709
|
+
| FileAttach
|
|
710
|
+
| ControlAttach
|
|
711
|
+
| StickerAttach
|
|
712
|
+
| AudioAttach
|
|
713
|
+
| ContactAttach
|
|
683
714
|
] = []
|
|
684
715
|
for a in message.get("attaches", []):
|
|
685
716
|
if a["_type"] == AttachType.PHOTO:
|
|
@@ -694,6 +725,8 @@ class Message:
|
|
|
694
725
|
attaches.append(StickerAttach.from_dict(a))
|
|
695
726
|
elif a["_type"] == AttachType.AUDIO:
|
|
696
727
|
attaches.append(AudioAttach.from_dict(a))
|
|
728
|
+
elif a["_type"] == AttachType.CONTACT:
|
|
729
|
+
attaches.append(ContactAttach.from_dict(a))
|
|
697
730
|
link_value = message.get("link")
|
|
698
731
|
if isinstance(link_value, dict):
|
|
699
732
|
link = MessageLink.from_dict(link_value)
|
|
@@ -778,9 +811,7 @@ class Dialog:
|
|
|
778
811
|
join_time=data["joinTime"],
|
|
779
812
|
created=data["created"],
|
|
780
813
|
last_message=(
|
|
781
|
-
Message.from_dict(data["lastMessage"])
|
|
782
|
-
if data.get("lastMessage")
|
|
783
|
-
else None
|
|
814
|
+
Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
|
|
784
815
|
),
|
|
785
816
|
type=ChatType(data["type"]),
|
|
786
817
|
last_fire_delayed_error_time=data["lastFireDelayedErrorTime"],
|
|
@@ -865,14 +896,10 @@ class Chat:
|
|
|
865
896
|
@classmethod
|
|
866
897
|
def from_dict(cls, data: dict[Any, Any]) -> Self:
|
|
867
898
|
raw_admins = data.get("adminParticipants", {}) or {}
|
|
868
|
-
admin_participants: dict[int, dict[Any, Any]] = {
|
|
869
|
-
int(k): v for k, v in raw_admins.items()
|
|
870
|
-
}
|
|
899
|
+
admin_participants: dict[int, dict[Any, Any]] = {int(k): v for k, v in raw_admins.items()}
|
|
871
900
|
raw_participants = data.get("participants", {}) or {}
|
|
872
901
|
participants: dict[int, int] = {int(k): v for k, v in raw_participants.items()}
|
|
873
|
-
last_msg = (
|
|
874
|
-
Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
|
|
875
|
-
)
|
|
902
|
+
last_msg = Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
|
|
876
903
|
return cls(
|
|
877
904
|
participants_count=data.get("participantsCount", 0),
|
|
878
905
|
access=AccessType(data.get("access", AccessType.PUBLIC.value)),
|
|
@@ -1051,7 +1078,9 @@ class Session:
|
|
|
1051
1078
|
|
|
1052
1079
|
@override
|
|
1053
1080
|
def __str__(self) -> str:
|
|
1054
|
-
return
|
|
1081
|
+
return (
|
|
1082
|
+
f"Session: {self.client} from {self.location} at {self.time} (current={self.current})"
|
|
1083
|
+
)
|
|
1055
1084
|
|
|
1056
1085
|
|
|
1057
1086
|
class Folder:
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import datetime
|
|
3
|
-
import logging
|
|
4
|
-
from time import time
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
import pymax
|
|
8
|
-
from pymax import MaxClient, Message, ReactionInfo, SocketMaxClient
|
|
9
|
-
from pymax.files import File, Video
|
|
10
|
-
from pymax.payloads import UserAgentPayload
|
|
11
|
-
from pymax.static.enum import AttachType, Opcode
|
|
12
|
-
from pymax.types import Chat
|
|
13
|
-
|
|
14
|
-
phone = "+7903223111"
|
|
15
|
-
headers = UserAgentPayload(device_type="WEB")
|
|
16
|
-
|
|
17
|
-
client = MaxClient(
|
|
18
|
-
phone=phone,
|
|
19
|
-
work_dir="cache",
|
|
20
|
-
reconnect=False,
|
|
21
|
-
logger=None,
|
|
22
|
-
headers=headers,
|
|
23
|
-
)
|
|
24
|
-
client.logger.setLevel(logging.INFO)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@client.on_raw_receive
|
|
28
|
-
async def handle_raw_receive(data: dict[str, Any]) -> None:
|
|
29
|
-
print(f"Raw data received: {data}")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@client.task(seconds=10)
|
|
33
|
-
async def periodic_task() -> None:
|
|
34
|
-
# print(f"Periodic task executed at {datetime.datetime.now()}")
|
|
35
|
-
...
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@client.on_start
|
|
39
|
-
async def handle_start() -> None:
|
|
40
|
-
print(f"Client started as {client.me.names[0].first_name}!")
|
|
41
|
-
|
|
42
|
-
chat_id = -1
|
|
43
|
-
max_messages = 1000
|
|
44
|
-
messages = []
|
|
45
|
-
from_time = int(time() * 1000)
|
|
46
|
-
while len(messages) < max_messages:
|
|
47
|
-
r = await client.fetch_history(chat_id=chat_id, from_time=from_time, backward=30)
|
|
48
|
-
if not r:
|
|
49
|
-
break
|
|
50
|
-
from_time = r[0].time
|
|
51
|
-
messages.extend(r)
|
|
52
|
-
print(f"First message time: {from_time}, id: {r[0].id}, text: {r[0].text}")
|
|
53
|
-
print(f"Last message time: {from_time}, id: {r[-1].id}, text: {r[-1].text}")
|
|
54
|
-
print(f"Loaded {len(messages)}/{max_messages} messages...")
|
|
55
|
-
# channel = await client.resolve_channel_by_name("fm92")
|
|
56
|
-
# if channel:
|
|
57
|
-
# print(f"Resolved channel by name: {channel.title}, ID: {channel.id}")
|
|
58
|
-
# else:
|
|
59
|
-
# print("Channel not found by name.")
|
|
60
|
-
|
|
61
|
-
# channel = await client.join_channel(link)
|
|
62
|
-
# if channel:
|
|
63
|
-
# print(f"Joined channel: {channel.title}, ID: {channel.id}")
|
|
64
|
-
# else:
|
|
65
|
-
# print("Failed to join channel.")
|
|
66
|
-
# await client.send_message(
|
|
67
|
-
# "Hello! The client has started successfully.",
|
|
68
|
-
# chat_id=2265456546456,
|
|
69
|
-
# notify=True,
|
|
70
|
-
# )
|
|
71
|
-
# folder_update = await client.create_folder(
|
|
72
|
-
# title="My Folder",
|
|
73
|
-
# chat_include=[0],
|
|
74
|
-
# )
|
|
75
|
-
# print(f"Folder created: {folder_update.folder.title}")
|
|
76
|
-
# video_path = "tests2/test.mp4"
|
|
77
|
-
# video_file = Video(path=video_path)
|
|
78
|
-
|
|
79
|
-
# await client.send_message(
|
|
80
|
-
# text="Here is the video you requested.",
|
|
81
|
-
# chat_id=0,
|
|
82
|
-
# attachment=video_file,
|
|
83
|
-
# notify=True,
|
|
84
|
-
# )
|
|
85
|
-
# chat_id = -6970655
|
|
86
|
-
# for chat in client.chats:
|
|
87
|
-
# if chat.id == chat_id:
|
|
88
|
-
# print(f"Found chat: {chat.title}, ID: {chat.id}")
|
|
89
|
-
# members_count = chat.participants_count
|
|
90
|
-
# marker = 0
|
|
91
|
-
# member_list = []
|
|
92
|
-
# while len(member_list) < members_count:
|
|
93
|
-
# await asyncio.sleep(10)
|
|
94
|
-
# r = await client.load_members(
|
|
95
|
-
# chat_id=chat_id,
|
|
96
|
-
# marker=marker,
|
|
97
|
-
# count=200,
|
|
98
|
-
# )
|
|
99
|
-
# members, marker = r
|
|
100
|
-
# member_list.extend(members)
|
|
101
|
-
# print(f"Loaded {len(member_list)}/{members_count} members...")
|
|
102
|
-
# for member in member_list:
|
|
103
|
-
# print(
|
|
104
|
-
# f"Member {member.contact.names[0].first_name}, ID: {member.contact.id}"
|
|
105
|
-
# )
|
|
106
|
-
# r = await client.load_members(chat_id=chat_id, count=50)
|
|
107
|
-
# print(f"Loaded {len(r)} members from chat {chat_id}")
|
|
108
|
-
# member_list, marker = r
|
|
109
|
-
# for member in member_list:
|
|
110
|
-
# print(f"Member {member.contact.names[0].first_name}, ID: {member.contact.id}")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# @client.on_reaction_change
|
|
114
|
-
# async def handle_reaction_change(
|
|
115
|
-
# message_id: str, chat_id: int, reaction_info: ReactionInfo
|
|
116
|
-
# ) -> None:
|
|
117
|
-
# print(
|
|
118
|
-
# f"Reaction changed on message {message_id} in chat {chat_id}: "
|
|
119
|
-
# f"Total count: {reaction_info.total_count}, "
|
|
120
|
-
# f"Your reaction: {reaction_info.your_reaction}, "
|
|
121
|
-
# f"Counters: {reaction_info.counters[0].reaction}={reaction_info.counters[0].count}"
|
|
122
|
-
# )
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# @client.on_chat_update
|
|
126
|
-
# async def handle_chat_update(chat: Chat) -> None:
|
|
127
|
-
# print(f"Chat updated: {chat.id}, new title: {chat.title}")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@client.on_message()
|
|
131
|
-
async def handle_message(message: Message) -> None:
|
|
132
|
-
print(f"New message in chat {message.chat_id} from {message.sender}: {message.text}")
|
|
133
|
-
# if message.link and message.link.message.attaches:
|
|
134
|
-
# for attach in message.link.message.attaches:
|
|
135
|
-
# print(f"Link attach type: {attach.type}")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@client.on_message_edit()
|
|
139
|
-
async def handle_edited_message(message: Message) -> None:
|
|
140
|
-
print(f"Edited message in chat {message.chat_id}: {message.text}")
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@client.on_message_delete()
|
|
144
|
-
async def handle_deleted_message(message: Message) -> None:
|
|
145
|
-
print(f"Deleted message in chat {message.chat_id}: {message.id}")
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# async def login_flow_test():
|
|
149
|
-
# await client.connect()
|
|
150
|
-
# temp_token = await client.request_code(phone)
|
|
151
|
-
# code = input("Введите код: ").strip()
|
|
152
|
-
# await client.login_with_code(temp_token, code)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# asyncio.run(login_flow_test())
|
|
156
|
-
|
|
157
|
-
# @client.on_message(filter=Filter(chat_id=0))
|
|
158
|
-
# async def handle_message(message: Message) -> None:
|
|
159
|
-
# print(str(message.sender) + ": " + message.text)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# @client.on_message_edit()
|
|
163
|
-
# async def handle_edited_message(message: Message) -> None:
|
|
164
|
-
# print(f"Edited message in chat {message.chat_id}: {message.text}")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# @client.on_message_delete()
|
|
168
|
-
# async def handle_deleted_message(message: Message) -> None:
|
|
169
|
-
# print(f"Deleted message in chat {message.chat_id}: {message.id}")
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
# @client.on_start
|
|
173
|
-
# async def handle_start() -> None:
|
|
174
|
-
# print(f"Client started successfully at {datetime.datetime.now()}!")
|
|
175
|
-
# print(client.me.id)
|
|
176
|
-
|
|
177
|
-
# await client.send_message(
|
|
178
|
-
# "Hello, this is a test message sent upon client start!",
|
|
179
|
-
# chat_id=23424,
|
|
180
|
-
# notify=True,
|
|
181
|
-
# )
|
|
182
|
-
# file_path = "ruff.toml"
|
|
183
|
-
# file = File(path=file_path)
|
|
184
|
-
# msg = await client.send_message(
|
|
185
|
-
# text="Here is the file you requested.",
|
|
186
|
-
# chat_id=0,
|
|
187
|
-
# attachment=file,
|
|
188
|
-
# notify=True,
|
|
189
|
-
# )
|
|
190
|
-
# if msg:
|
|
191
|
-
# print(f"File sent successfully in message ID: {msg.id}")
|
|
192
|
-
# history = await client.fetch_history(chat_id=0)
|
|
193
|
-
# if history:
|
|
194
|
-
# for message in history:
|
|
195
|
-
# if message.attaches:
|
|
196
|
-
# for attach in message.attaches:
|
|
197
|
-
# if attach.type == AttachType.AUDIO:
|
|
198
|
-
# print(attach.url)
|
|
199
|
-
# chat = await client.rework_invite_link(chat_id=0)
|
|
200
|
-
# print(chat.link)
|
|
201
|
-
# text = """
|
|
202
|
-
# **123**
|
|
203
|
-
# *123*
|
|
204
|
-
# __123__
|
|
205
|
-
# ~~123~~
|
|
206
|
-
# """
|
|
207
|
-
# message = await client.send_message(text, chat_id=0, notify=True)
|
|
208
|
-
# react_info = await client.add_reaction(
|
|
209
|
-
# chat_id=0, message_id="115368067020359151", reaction="👍"
|
|
210
|
-
# )
|
|
211
|
-
# if react_info:
|
|
212
|
-
# print("Reaction added!")
|
|
213
|
-
# print(react_info.total_count)
|
|
214
|
-
# react_info = await client.get_reactions(
|
|
215
|
-
# chat_id=0, message_ids=["115368067020359151"]
|
|
216
|
-
# )
|
|
217
|
-
# if react_info:
|
|
218
|
-
# print("Reactions fetched!")
|
|
219
|
-
# for msg_id, info in react_info.items():
|
|
220
|
-
# print(f"Message ID: {msg_id}, Total Reactions: {info.total_count}")
|
|
221
|
-
# react_info = await client.remove_reaction(
|
|
222
|
-
# chat_id=0, message_id="115368067020359151"
|
|
223
|
-
# )
|
|
224
|
-
# if react_info:
|
|
225
|
-
# print("Reaction removed!")
|
|
226
|
-
# print(react_info.total_count)
|
|
227
|
-
# print(client.dialogs)
|
|
228
|
-
|
|
229
|
-
# if history:
|
|
230
|
-
# for message in history:
|
|
231
|
-
# if message.link:
|
|
232
|
-
# print(message.link.chat_id)
|
|
233
|
-
# print(message.link.message.text)
|
|
234
|
-
# for attach in message.attaches:
|
|
235
|
-
# if attach.type == AttachType.CONTROL:
|
|
236
|
-
# print(attach.event)
|
|
237
|
-
# print(attach.extra)
|
|
238
|
-
# if attach.type == AttachType.VIDEO:
|
|
239
|
-
# print(message)
|
|
240
|
-
# vid = await client.get_video_by_id(
|
|
241
|
-
# chat_id=0,
|
|
242
|
-
# video_id=attach.video_id,
|
|
243
|
-
# message_id=message.id,
|
|
244
|
-
# )
|
|
245
|
-
# print(vid.url)
|
|
246
|
-
# elif attach.type == AttachType.FILE:
|
|
247
|
-
# file = await client.get_file_by_id(
|
|
248
|
-
# chat_id=0,
|
|
249
|
-
# file_id=attach.file_id,
|
|
250
|
-
# message_id=message.id,
|
|
251
|
-
# )
|
|
252
|
-
# print(file.url)
|
|
253
|
-
# print(client.me.names[0].first_name)
|
|
254
|
-
# user = await client.get_user(client.me.id)
|
|
255
|
-
|
|
256
|
-
# photo1 = Photo(path="tests/test.jpeg")
|
|
257
|
-
# photo2 = Photo(path="tests/test.jpg")
|
|
258
|
-
|
|
259
|
-
# await client.send_message(
|
|
260
|
-
# "Hello with photo!", chat_id=0, photos=[photo1, photo2], notify=True
|
|
261
|
-
# )
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if __name__ == "__main__":
|
|
265
|
-
try:
|
|
266
|
-
asyncio.run(client.start())
|
|
267
|
-
except KeyboardInterrupt:
|
|
268
|
-
print("Client stopped by user")
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
import pymax
|
|
5
|
-
import pymax.static
|
|
6
|
-
from pymax import MaxClient
|
|
7
|
-
from pymax.filters import Filters
|
|
8
|
-
from pymax.payloads import UserAgentPayload
|
|
9
|
-
from pymax.static.enum import Opcode
|
|
10
|
-
|
|
11
|
-
phone = "+7903223423"
|
|
12
|
-
headers = UserAgentPayload(device_type="WEB")
|
|
13
|
-
|
|
14
|
-
client = MaxClient(
|
|
15
|
-
phone=phone,
|
|
16
|
-
work_dir="cache",
|
|
17
|
-
reconnect=False,
|
|
18
|
-
logger=None,
|
|
19
|
-
headers=headers,
|
|
20
|
-
)
|
|
21
|
-
client.logger.setLevel(logging.DEBUG)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@client.task(seconds=10)
|
|
25
|
-
async def periodic_task() -> None:
|
|
26
|
-
client.logger.info("Periodic task executed")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@client.on_message(Filters.text("test") & ~Filters.chat(0))
|
|
30
|
-
async def handle_message(message: pymax.Message) -> None:
|
|
31
|
-
print(f"New message from {message.sender}: {message.text}")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@client.on_start
|
|
35
|
-
async def on_start():
|
|
36
|
-
print("Client started")
|
|
37
|
-
data = await client._send_and_wait(
|
|
38
|
-
opcode=Opcode.FILE_UPLOAD,
|
|
39
|
-
payload={"count": 1},
|
|
40
|
-
)
|
|
41
|
-
print("File upload response:", data)
|
|
42
|
-
# opcode=pymax.static.enum.Opcode.CHATS_LIST,
|
|
43
|
-
# payload={
|
|
44
|
-
# "marker": 1765721869777,
|
|
45
|
-
# },
|
|
46
|
-
# )
|
|
47
|
-
|
|
48
|
-
# print("Chats list:", data)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
asyncio.run(client.start())
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from pymax import MaxClient
|
|
6
|
-
from pymax.files import File, Video
|
|
7
|
-
|
|
8
|
-
client = MaxClient(phone="+1234567890", work_dir="cache", reconnect=False)
|
|
9
|
-
client.logger.setLevel(logging.INFO)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def create_big_file(file_path: Path, size_in_mb: int) -> None:
|
|
13
|
-
with open(file_path, "wb") as f:
|
|
14
|
-
f.seek(size_in_mb * 1024 * 1024 - 1)
|
|
15
|
-
f.write(b"\0")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@client.on_start
|
|
19
|
-
async def upload_large_file_example():
|
|
20
|
-
await asyncio.sleep(2)
|
|
21
|
-
|
|
22
|
-
file_path = Path("tests2/large_file.dat")
|
|
23
|
-
|
|
24
|
-
if not file_path.exists():
|
|
25
|
-
create_big_file(file_path, size_in_mb=300)
|
|
26
|
-
file_size = file_path.stat().st_size
|
|
27
|
-
client.logger.info(f"File size: {file_size / (1024 * 1024):.2f} MB")
|
|
28
|
-
|
|
29
|
-
file = File(path=str(file_path))
|
|
30
|
-
chat_id = 0
|
|
31
|
-
|
|
32
|
-
client.logger.info("Starting file upload...")
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
await client.send_message(
|
|
36
|
-
chat_id=chat_id,
|
|
37
|
-
text="📎 Вот большой файл",
|
|
38
|
-
attachment=file,
|
|
39
|
-
)
|
|
40
|
-
client.logger.info("File uploaded successfully!")
|
|
41
|
-
|
|
42
|
-
except OSError as e:
|
|
43
|
-
if "malloc failure" in str(e):
|
|
44
|
-
client.logger.error("Memory error - file too large for current memory")
|
|
45
|
-
client.logger.info("Recommendation: Upload smaller files or free up memory")
|
|
46
|
-
else:
|
|
47
|
-
raise
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if __name__ == "__main__":
|
|
51
|
-
asyncio.run(client.start())
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
from pymax import MaxClient, Message
|
|
4
|
-
from pymax.filters import Filters
|
|
5
|
-
|
|
6
|
-
client = MaxClient(
|
|
7
|
-
phone="+1234567890",
|
|
8
|
-
work_dir="cache",
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@client.on_message(Filters.chat(0))
|
|
13
|
-
async def on_message(msg: Message):
|
|
14
|
-
print(f"[{msg.sender}] {msg.text}")
|
|
15
|
-
await client.send_message(chat_id=msg.chat_id, text="Привет!")
|
|
16
|
-
await client.add_reaction(
|
|
17
|
-
chat_id=msg.chat_id, message_id=str(msg.id), reaction="👍"
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@client.on_start
|
|
22
|
-
async def on_start():
|
|
23
|
-
print(f"Клиент запущен. Ваш ID: {client.me.id}")
|
|
24
|
-
history = await client.fetch_history(chat_id=0)
|
|
25
|
-
for m in history:
|
|
26
|
-
print(f"- {m.text}")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
async def main():
|
|
30
|
-
await client.start()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if __name__ == "__main__":
|
|
34
|
-
asyncio.run(main())
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
from pymax import MaxClient
|
|
4
|
-
from pymax.payloads import UserAgentPayload
|
|
5
|
-
|
|
6
|
-
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
|
|
7
|
-
|
|
8
|
-
client = MaxClient(
|
|
9
|
-
phone="+79116290861",
|
|
10
|
-
work_dir="cache",
|
|
11
|
-
headers=ua,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@client.on_start
|
|
16
|
-
async def on_start() -> None:
|
|
17
|
-
print(f"MaxClient started as {client.me.names[0].first_name}!")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
asyncio.run(client.start())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|