maxapi-python 1.2.2__py3-none-any.whl → 1.2.3__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-1.2.2.dist-info → maxapi_python-1.2.3.dist-info}/METADATA +1 -1
- {maxapi_python-1.2.2.dist-info → maxapi_python-1.2.3.dist-info}/RECORD +14 -14
- pymax/core.py +1 -0
- pymax/files.py +5 -0
- pymax/interfaces.py +1 -0
- pymax/mixins/group.py +31 -12
- pymax/mixins/self.py +74 -9
- pymax/mixins/socket.py +10 -0
- pymax/mixins/websocket.py +9 -0
- pymax/payloads.py +3 -0
- pymax/static/enum.py +1 -0
- pymax/types.py +57 -28
- {maxapi_python-1.2.2.dist-info → maxapi_python-1.2.3.dist-info}/WHEEL +0 -0
- {maxapi_python-1.2.2.dist-info → maxapi_python-1.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
pymax/__init__.py,sha256=6wUKKwsyxFpWG3b7kwptOvHd-w78C-ygw42iCDBYQvc,1915
|
|
2
|
-
pymax/core.py,sha256=
|
|
2
|
+
pymax/core.py,sha256=S5el07cMcqIyXOI25G0a2XtVJSz2_AltmKN7ZW6LEeg,14946
|
|
3
3
|
pymax/crud.py,sha256=YC92TyhA2mr1tJCcfd-tvh8umtXKgqJfgiLo7nXUl3Q,3076
|
|
4
4
|
pymax/exceptions.py,sha256=nDUNx7bM-Yjugj-qfIllcrnwLg9JpZroYqfXapjYbMQ,3178
|
|
5
|
-
pymax/files.py,sha256=
|
|
5
|
+
pymax/files.py,sha256=bSjP1m-dLPJCN8HPZtjrLLp0Oc7OxdGxI6U1K2rQzUA,3587
|
|
6
6
|
pymax/filters.py,sha256=gSHPJ1Vi37HKPxf0jRRv9Q3iGwhiQjw1MGrCaouqHzs,4325
|
|
7
7
|
pymax/formatter.py,sha256=RJ_5VbY7Li8UM3xL1AvcXo8v1iYnY8GvDDkreaFqtnY,860
|
|
8
8
|
pymax/formatting.py,sha256=XRtuXJGweuNZevJFdPxksDftIrfuMGEA-AOUc_v6IhQ,2484
|
|
9
|
-
pymax/interfaces.py,sha256=
|
|
9
|
+
pymax/interfaces.py,sha256=5-0RM0tH3eBJrVZkjL494lKTTwvIVmOTJiQxDWZo-7A,8829
|
|
10
10
|
pymax/models.py,sha256=PsPGbOkERxesZZltjNrmqhOfRcO44Is2ThbEToREcB8,201
|
|
11
11
|
pymax/navigation.py,sha256=4ia6RGY2pXMArboNhHkbWlWX7LtcYK1VGVXorPX0Pb4,5747
|
|
12
|
-
pymax/payloads.py,sha256=
|
|
13
|
-
pymax/types.py,sha256=
|
|
12
|
+
pymax/payloads.py,sha256=tlqPT-JFexfklp9idSrbpU3bALcQuiDne9H9vTnwL4w,8022
|
|
13
|
+
pymax/types.py,sha256=_Ee1aycVwe4nfBO_hGMnpm9lQDGzkPOkT9vME8OL-sA,36650
|
|
14
14
|
pymax/mixins/__init__.py,sha256=5sXJME34S1EssuDETaN4DLRH7vhMw_Q3Jmay9myAIZM,775
|
|
15
15
|
pymax/mixins/auth.py,sha256=e90vIpEOwAjUxgYMYaG7R6jR_5t9rKsei_mTBQUirL4,14716
|
|
16
16
|
pymax/mixins/channel.py,sha256=W52YnBay1sUYXxF9oAWsz44ZUh_s45jSvKmAyjTbULM,5357
|
|
17
|
-
pymax/mixins/group.py,sha256=
|
|
17
|
+
pymax/mixins/group.py,sha256=MaiZCQ9R-wWbuEPXyqf4JZAGNmxu_tXCs8qU3JEoYek,15911
|
|
18
18
|
pymax/mixins/handler.py,sha256=ETnI8fA386LYJGjWtUhhWzQHREUA78di1aO1oWwtscA,12523
|
|
19
19
|
pymax/mixins/message.py,sha256=AznKKmTMxdzsYl8IecT43RjWpGvlQM85GzSNGFbI8BA,33279
|
|
20
20
|
pymax/mixins/scheduler.py,sha256=rcMfgfZnzu5V6MkcCg6uRgbi-jkc7UyqOjemulydWbc,964
|
|
21
|
-
pymax/mixins/self.py,sha256=
|
|
22
|
-
pymax/mixins/socket.py,sha256=
|
|
21
|
+
pymax/mixins/self.py,sha256=27ranVw5rdv1qSplhV5mdUvHc4qSxHi63f1gWOjR9B4,9182
|
|
22
|
+
pymax/mixins/socket.py,sha256=tk2puQPsT2Dltg1j6kUSHIbXs64p6q-jiIIcs73Y1Sk,23174
|
|
23
23
|
pymax/mixins/telemetry.py,sha256=LWr68DNQkPhAjGRDYQ5lORXxC3Yw6M9E8sF0TCNISTE,3609
|
|
24
24
|
pymax/mixins/user.py,sha256=RSZd4t-aq8P2k3cVzNVWBkUf-_xTWILrBzwxLRgk1pw,9450
|
|
25
25
|
pymax/mixins/utils.py,sha256=s3FUf3i_wjn2Gbg5YY1rWZB-90ZEGrrcUuND_MqqSTE,853
|
|
26
|
-
pymax/mixins/websocket.py,sha256=
|
|
26
|
+
pymax/mixins/websocket.py,sha256=fL8IcsLkvksizSAg98UaFlDPZGyGbYtbtdHnlDzfR9g,18056
|
|
27
27
|
pymax/static/constant.py,sha256=nM0svv3VpsVxK-RqoADn9qsTdQvB-IYv0Sgv-bQcWs4,1116
|
|
28
|
-
pymax/static/enum.py,sha256=
|
|
29
|
-
maxapi_python-1.2.
|
|
30
|
-
maxapi_python-1.2.
|
|
31
|
-
maxapi_python-1.2.
|
|
32
|
-
maxapi_python-1.2.
|
|
28
|
+
pymax/static/enum.py,sha256=mraN6FK_gcVXdTHBdK8Zf6-92AzVFGAWRnQLA4rzYBA,4672
|
|
29
|
+
maxapi_python-1.2.3.dist-info/METADATA,sha256=0NSb43LEMf9-M1KLeETofzdcjsGJ68lpZ15xP9GouI0,6753
|
|
30
|
+
maxapi_python-1.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
31
|
+
maxapi_python-1.2.3.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
|
|
32
|
+
maxapi_python-1.2.3.dist-info/RECORD,,
|
pymax/core.py
CHANGED
|
@@ -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
|
pymax/files.py
CHANGED
|
@@ -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:
|
pymax/interfaces.py
CHANGED
pymax/mixins/group.py
CHANGED
|
@@ -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
|
|
pymax/mixins/self.py
CHANGED
|
@@ -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(
|
pymax/mixins/socket.py
CHANGED
|
@@ -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(
|
pymax/mixins/websocket.py
CHANGED
|
@@ -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
|
|
pymax/payloads.py
CHANGED
|
@@ -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):
|
pymax/static/enum.py
CHANGED
pymax/types.py
CHANGED
|
@@ -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:
|
|
File without changes
|
|
File without changes
|