maxapi-python 2.1.0__py3-none-any.whl → 2.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxapi-python
3
- Version: 2.1.0
3
+ Version: 2.1.2
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
@@ -1,5 +1,5 @@
1
- pymax/__init__.py,sha256=-f9yJXBxQn-YB5JLrX3VrTKV7_vFjPJs13AQqraH2ao,1255
2
- pymax/app.py,sha256=SZMvhC237H1N58b5XcjRNGjwK-FiTgsevHnpzt-sdvg,9643
1
+ pymax/__init__.py,sha256=6Fe_FJ1NgKBmqSWmix3_fmVbhFWFiLekXEjyH7rDIds,1255
2
+ pymax/app.py,sha256=QzvHYsSGOu00f6GrN4a_AiLJkuFLb6RN81FRKk9togw,9814
3
3
  pymax/base.py,sha256=Q6FR5K1nOaeVnYlzRgqC6_v7AWUiJJCkI4TuzCYbj-U,7255
4
4
  pymax/client.py,sha256=KiDEiUL49IGxRtq4-Sr3IABa8Rh1TRPq41Gv5jH2m_Q,4013
5
5
  pymax/client_web.py,sha256=iTwlsY75d_wg-vUGObTq7SVinp0L__2r8cvG49BtmUI,3041
@@ -18,8 +18,8 @@ pymax/api/auth/payloads.py,sha256=XxrVdNcz1uV6hUxDp9Dt_stENECi_eDlxuiqt44DHeo,30
18
18
  pymax/api/auth/service.py,sha256=sjZcMyP6siH0dR19eYwkhhLU09bVJD4FCNeSra2Rjac,11639
19
19
  pymax/api/auth/types.py,sha256=VrYR7rIZOrW45yKnQmC5MSe9kxgXmqPzh9VG_y1tSEs,200
20
20
  pymax/api/bots/__init__.py,sha256=4P7rM-XeeDOElEktov1Vbfo8nzjJgmyA4_pQl4WvzRQ,33
21
- pymax/api/bots/payloads.py,sha256=-vrswY_VlQrLKoqOxrFBl7pE8QG4dLQSaGOtKs4Lq1o,152
22
- pymax/api/bots/service.py,sha256=ssVty2_4WMQbecQGcIJYEeYZ0RT6VgMu5qzqgAcegWo,877
21
+ pymax/api/bots/payloads.py,sha256=aXriolCN_YvBvDZO_MPmD-fkrIVJIpVDXkOkOZVes_c,166
22
+ pymax/api/bots/service.py,sha256=ziqVlQUMFHjQydMDFyWcQMIATboQTvIdGtJhf-AK9tA,847
23
23
  pymax/api/chats/__init__.py,sha256=UMp3uKdB3tZlnWckndgF3jaU-3-49OJE18z2d0OlmIU,155
24
24
  pymax/api/chats/enums.py,sha256=FdByRiHYX9Xs7RyNabKqxGugNs9tJ3VBXwTb_43XGC8,627
25
25
  pymax/api/chats/payloads.py,sha256=Y4S2x60i3txeA75GV_pGJ3ov7pLMp4GjGuhoqlRI2ow,2824
@@ -31,7 +31,7 @@ pymax/api/messages/service.py,sha256=N5_KYArZB-VX7kOLMfkrwzrpNgvqNFLRLOym_ghM6IQ
31
31
  pymax/api/self/__init__.py,sha256=TfbqL4xLb5IMhbW8mlAK-AwVFqqPWMADogKZeWtkw0A,79
32
32
  pymax/api/self/enums.py,sha256=iKEqPy44LyQKvAPfyhpkSXgmWWohxry73F0CVgtekwY,180
33
33
  pymax/api/self/payloads.py,sha256=-SFqkNxC5ZLKnLty_Gyz_b_P3X1esu_7Urb4HjrQxM4,774
34
- pymax/api/self/service.py,sha256=3TBB-wxGTJ9jRCJJNfitk0hJNtWQDn9gWS_sap1Hou8,4685
34
+ pymax/api/self/service.py,sha256=Fr4PlGT4Vn50kVQC9rleZu5UTvxLjTAsfR5mxY2IIUw,4921
35
35
  pymax/api/session/__init__.py,sha256=zo-rCKBVBsFK-810T-Le4VbAYKt109cQ6elf0UkgiNk,36
36
36
  pymax/api/session/enums.py,sha256=InBdwAu_filqW3rMfGrOkHgmFfqgB1ytjnCcxDq29xc,236
37
37
  pymax/api/session/payloads.py,sha256=mDXgI_oVfqLim7J9F9BMUwKx9Vs8EknJ02kstrF3ClE,2340
@@ -39,7 +39,7 @@ pymax/api/session/service.py,sha256=8ELcsnKNHLT8hgExw5laZvHF6SIOSQB6RpXoxf1rbbI,
39
39
  pymax/api/uploads/__init__.py,sha256=E7INNsBeMStze2VWMADX4mrswsQeJb85C71sSuiOpv0,35
40
40
  pymax/api/uploads/models.py,sha256=c2Yunnl9nRkDZuItXGKlY0Oi5DjnB4f5bcruQ49ARZk,934
41
41
  pymax/api/uploads/payloads.py,sha256=hf0MwLdtTHnt5KGONbV4hnByjy5X0PJLqer6uq9nuuw,694
42
- pymax/api/uploads/service.py,sha256=5wa4MI3jL7BFWCWbMLWZBP-NTOqRcblUq4Iz19ekXx4,19481
42
+ pymax/api/uploads/service.py,sha256=qiYYPqryWr9h0EOWrglWz6z70I3UYXgBglTn0e0USFY,19533
43
43
  pymax/api/users/__init__.py,sha256=CDakgSKwVAkX-kNoDmIo2JsFPsS7Nx3j2vIMwQnPRck,82
44
44
  pymax/api/users/enums.py,sha256=m3d225sjWkBIRF5NNi3ChnyZ6BgKUEbPYoYwVjpifZ4,205
45
45
  pymax/api/users/payloads.py,sha256=VoXXctQDl7WXt5zqoRKzdONqmris5Cxf7-6Hy_Gcd-A,288
@@ -80,7 +80,7 @@ pymax/infra/bots.py,sha256=IJs4ErFxjbMeiHfQgpDiDhtEu9ws-lIp8fb8I96szy8,1202
80
80
  pymax/infra/chat.py,sha256=gZdkciUl_BCnPucxb8JubQ6phPCTcmVfX9C3edKXZRM,12690
81
81
  pymax/infra/message.py,sha256=Hee5z9a47aZRnSAvTMdTaa5BzwMnOnSoB-z3JVX3J3o,8518
82
82
  pymax/infra/protocol.py,sha256=I2WrAeAgATuNSdK2gvHU-MhhxdRBfSMGMovF5HPFsQw,208
83
- pymax/infra/self.py,sha256=w2eHIgmKkarhfbtAdGo_iZ4Qbs2DaSzzm-TLzXKNn8E,4963
83
+ pymax/infra/self.py,sha256=h2GnzcCF2-FeY9-qBjtHjrSCx7elc4RjuCXPaewDkgg,4828
84
84
  pymax/infra/user.py,sha256=nv9jkWpWvCWETHZsyUMmm5FWkzLTJT2SHXZ7SglZE8M,4331
85
85
  pymax/protocol/__init__.py,sha256=rDn1Twi1iBDUk1155YI9jyeQyaGVDRuPoWdlaaLZnsw,242
86
86
  pymax/protocol/base.py,sha256=_bisk1BU_GSMSPIqfnizSCgm_qBrdbB5cJw6foAa2tc,294
@@ -103,7 +103,7 @@ pymax/telemetry/payloads.py,sha256=MCKYGpQ4Tvx_8LH86Et0yevea2Th2z3b7a9pfmrQRys,3
103
103
  pymax/telemetry/service.py,sha256=xuAUsLt0QOwypxn8NjSUtSTfcZxgIY9YVWnvZ-KawZY,7120
104
104
  pymax/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
105
  pymax/transport/base.py,sha256=0D3srQ36F0s23pIjqd6YzRGKK9wCmXKr3wfe2XYYlos,448
106
- pymax/transport/tcp.py,sha256=9HCmPK-qBC5AvFINW4jHyzXQPTWvIYFiwxDmVHA8-og,2998
106
+ pymax/transport/tcp.py,sha256=aV7KzjAbjtQ7irTKsdlfDyZWhREW1QqOe_fBZSxMekE,3097
107
107
  pymax/transport/websocket.py,sha256=0ezkSoENWKV00EATIizGx16-ihCnaTC76zzGnwbS_3c,1504
108
108
  pymax/types/__init__.py,sha256=y77mfDRgxmfcamobi0rj2d3PsxhH0zxyvKPhL2iUdS8,44
109
109
  pymax/types/domain/__init__.py,sha256=R4iJv0b5gnNVG3MrerVlGVzPvNR-n36QZFukEX64fn4,470
@@ -114,8 +114,8 @@ pymax/types/domain/chat.py,sha256=xpLutj9uCIQupAO1zu2eXzEEXL8DcQ4N6tkZQeQUsCM,18
114
114
  pymax/types/domain/element.py,sha256=slMVlp5Fn1H1hFUinruuP7s2wawkSrS5c1eCeY31p-g,620
115
115
  pymax/types/domain/enums.py,sha256=eGMWnlv0mYJ8K-0FsC_wp4Co2M6YWyKI1Lx90MJ6s4g,412
116
116
  pymax/types/domain/error.py,sha256=WZoDAXf5vt8O4VYy7iEHLGBfH6hZQZ1fVkTz9rVCTN0,584
117
- pymax/types/domain/folder.py,sha256=Xe1CmWfh3BhjpPNV-aWqT2JA4sX6ya2HaC3BSDMEqL8,2445
118
- pymax/types/domain/login.py,sha256=mpGWNcFi5pwRsZbPA_GCtC1b-dreZA1hxayz5P7eL90,1451
117
+ pymax/types/domain/folder.py,sha256=Z9pXMzOTfxuhUfrJpLjl5LPnVXqqFsBVfKBQm4IbGWM,2225
118
+ pymax/types/domain/login.py,sha256=FBKpRhNPv6Zdjo6Wfl-lK8JpzpMx7zh84f1qrq6Z0og,1301
119
119
  pymax/types/domain/member.py,sha256=8gUNn4fBS_S2ap45A40lRlrDXYFMoq0Kx-dh57hMWQc,490
120
120
  pymax/types/domain/message.py,sha256=vb1Rmxnz145EP5_C1fpjvGLrenATN6HBivrst9M8OOc,14387
121
121
  pymax/types/domain/name.py,sha256=qMIshIqgWkVuROxmiCRhwfJf8XtmhSBoEU6nXs_gEh0,523
@@ -141,7 +141,7 @@ pymax/types/events/__init__.py,sha256=fNLP_8zYRsew2k-1s8wXCJeSPm8QQ4gmiZ86B51SuC
141
141
  pymax/types/events/file.py,sha256=XhpXn0Vt9OODkx1OkGb9jfQwc1uNgz1NTn_j9FZnQ8s,102
142
142
  pymax/types/events/message.py,sha256=d4dlnpu3VCtZ4vFdRXaACvydAE9sbGGkk52E48K0U_U,1248
143
143
  pymax/types/events/video.py,sha256=swBHYadmDS0SjLXGqVqRYsuMoVZ6MjD2aYR2fbM-AJc,104
144
- maxapi_python-2.1.0.dist-info/METADATA,sha256=kmE4GpooJO7S8X4GH8qELBv0cQZqDJvXZlBH_t2vktE,7637
145
- maxapi_python-2.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
146
- maxapi_python-2.1.0.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
147
- maxapi_python-2.1.0.dist-info/RECORD,,
144
+ maxapi_python-2.1.2.dist-info/METADATA,sha256=3jzi0v-s6AoiHhnIWurSxL6LLtBOcr3ZGDZGWcBshpU,7637
145
+ maxapi_python-2.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
146
+ maxapi_python-2.1.2.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
147
+ maxapi_python-2.1.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
pymax/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "2.1.0"
1
+ __version__ = "2.1.2"
2
2
 
3
3
 
4
4
  from .auth import (
@@ -3,5 +3,5 @@ from pymax.api.models import CamelModel
3
3
 
4
4
  class RequestInitDataPayload(CamelModel):
5
5
  bot_id: int
6
- chat_id: int
6
+ chat_id: int | None = None
7
7
  start_param: str | None = None
pymax/api/bots/service.py CHANGED
@@ -23,13 +23,9 @@ class BotsService:
23
23
  async def get_init_data(
24
24
  self,
25
25
  bot_id: int,
26
- chat_id: int,
26
+ chat_id: int | None = None,
27
27
  start_param: str | None = None,
28
28
  ) -> InitData:
29
- frame = RequestInitDataPayload(
30
- bot_id=bot_id, chat_id=chat_id, start_param=start_param
31
- )
32
- response = await self.app.invoke(
33
- Opcode.WEB_APP_INIT_DATA, frame.to_payload()
34
- )
29
+ frame = RequestInitDataPayload(bot_id=bot_id, chat_id=chat_id, start_param=start_param)
30
+ response = await self.app.invoke(Opcode.WEB_APP_INIT_DATA, frame.to_payload())
35
31
  return require_payload_model(response, InitData)
pymax/api/self/service.py CHANGED
@@ -9,6 +9,7 @@ from pymax.api.response import (
9
9
  require_payload_item_model,
10
10
  require_payload_model,
11
11
  )
12
+ from pymax.files import Photo
12
13
  from pymax.logging import get_logger
13
14
  from pymax.protocol import Opcode
14
15
  from pymax.types.domain import FolderList, FolderUpdate, Profile
@@ -47,15 +48,20 @@ class SelfService:
47
48
  first_name: str,
48
49
  last_name: str | None = None,
49
50
  description: str | None = None,
50
- photo: Any | None = None,
51
+ photo: Photo | None = None,
51
52
  *,
52
53
  photo_token: str | None = None,
53
54
  ) -> bool:
54
55
  if photo is not None:
55
- raise NotImplementedError(
56
- "Profile photo upload is not implemented without upload infra. "
57
- "Pass photo_token instead."
56
+ attach = await self.app.api.uploads.upload_photo(
57
+ photo, profile=True
58
58
  )
59
+ if photo_token:
60
+ logger.warning(
61
+ "photo_token argument was provided but will be overridden by the uploaded photo token"
62
+ )
63
+
64
+ photo_token = attach.photo_token
59
65
 
60
66
  frame = ChangeProfilePayload(
61
67
  first_name=first_name,
@@ -145,6 +151,7 @@ class SelfService:
145
151
  return False
146
152
 
147
153
  await self.app.store.update_token(self.app.session.token, token)
154
+ self.app.session.token = token
148
155
 
149
156
  return True
150
157
 
@@ -51,11 +51,13 @@ class UploadService:
51
51
  self.on_file_attach
52
52
  )
53
53
 
54
- async def upload_photo(self, photo: Photo) -> AttachPhotoPayload:
54
+ async def upload_photo(
55
+ self, photo: Photo, profile: bool = False
56
+ ) -> AttachPhotoPayload:
55
57
  logger.info("Uploading photo")
56
58
  logger.debug("Preparing photo upload payload")
57
59
 
58
- payload = UploadPayload().model_dump()
60
+ payload = UploadPayload(profile=profile).model_dump()
59
61
 
60
62
  try:
61
63
  data = await self.app.invoke(
pymax/app.py CHANGED
@@ -33,9 +33,7 @@ class App(Generic[ClientT]):
33
33
  self.dispatcher: Dispatcher[ClientT] = Dispatcher(self, root_router)
34
34
  self.api = ApiFacade(self)
35
35
  self.config = config
36
- self.store = self.config.store or SessionStore(
37
- config.work_dir, config.session_name
38
- )
36
+ self.store = self.config.store or SessionStore(config.work_dir, config.session_name)
39
37
  self.auth_flow = auth_flow
40
38
 
41
39
  self.me: Profile | None = None
@@ -76,18 +74,14 @@ class App(Generic[ClientT]):
76
74
  await self.connection.open()
77
75
 
78
76
  handshake_device_id = (
79
- session_data.device_id
80
- if session_data
81
- else self.config.device.device_id
77
+ session_data.device_id if session_data else self.config.device.device_id
82
78
  )
83
79
  logger.debug("running handshake")
84
80
  await self.handshake(handshake_device_id)
85
81
  except (ConnectionError, EOFError, OSError, TimeoutError) as e:
86
82
  logger.exception("failed to connect or handshake")
87
83
  await self.connection.close()
88
- raise ConnectionError(
89
- f"Failed to connect and handshake: {e}"
90
- ) from e
84
+ raise ConnectionError(f"Failed to connect and handshake: {e}") from e
91
85
 
92
86
  self._ping_task = asyncio.create_task(self._ping_loop())
93
87
 
@@ -108,9 +102,7 @@ class App(Generic[ClientT]):
108
102
 
109
103
  if not auth_result.token:
110
104
  logger.error("authentication finished without token")
111
- raise RuntimeError(
112
- "Authentication failed: no token received"
113
- )
105
+ raise RuntimeError("Authentication failed: no token received")
114
106
 
115
107
  await self.store.save_session(
116
108
  session_data := SessionInfo(
@@ -135,6 +127,10 @@ class App(Generic[ClientT]):
135
127
  self.config.device.user_agent,
136
128
  )
137
129
 
130
+ if response.token is not None and response.token != self.session.token:
131
+ await self.store.update_token(self.session.token, response.token)
132
+ self.session.token = response.token
133
+
138
134
  self.me = response.profile
139
135
  self.chats = response.chats
140
136
  self.users[self.me.contact.id] = self.me.contact
@@ -185,7 +181,7 @@ class App(Generic[ClientT]):
185
181
  opcode: int,
186
182
  payload: dict[str, Any],
187
183
  cmd: int = Command.REQUEST,
188
- timeout: float | None = 30.0,
184
+ timeout: float | None = None,
189
185
  compress: bool = False,
190
186
  ) -> InboundFrame:
191
187
  seq = self.connection.next_seq()
@@ -207,10 +203,11 @@ class App(Generic[ClientT]):
207
203
  payload_keys,
208
204
  )
209
205
  logger.debug("Request data=%s", frame.model_dump())
210
- response = await self.connection.request(frame, timeout=timeout)
211
- response_keys = (
212
- sorted(response.payload.keys()) if response.payload else []
206
+ request_timeout = (
207
+ self.config.request_timeout if timeout is None else timeout
213
208
  )
209
+ response = await self.connection.request(frame, timeout=request_timeout)
210
+ response_keys = sorted(response.payload.keys()) if response.payload else []
214
211
  logger.debug(
215
212
  "response opcode=%s cmd=%s seq=%s payload_keys=%s",
216
213
  response.opcode,
pymax/infra/self.py CHANGED
@@ -29,17 +29,13 @@ class SelfMixin(IClientProtocol):
29
29
  first_name: Имя профиля.
30
30
  last_name: Фамилия профиля.
31
31
  description: Описание профиля.
32
- photo: Файл или объект фото. Прямая загрузка через этот
33
- параметр пока не реализована.
32
+ photo: Файл или объект фото, который нужно загрузить как новую
33
+ фотографию профиля.
34
34
  photo_token: Токен фотографии, уже загруженной через API Max.
35
35
 
36
36
  Returns:
37
37
  ``True`` после успешного обновления. Клиент также обновит
38
38
  ``client.me`` и кеш текущего контакта.
39
-
40
- Raises:
41
- NotImplementedError: Если передан ``photo`` вместо
42
- ``photo_token``.
43
39
  """
44
40
  return await self._app.api.account.change_profile(
45
41
  first_name=first_name,
pymax/transport/tcp.py CHANGED
@@ -33,10 +33,12 @@ class TCPTransport(Transport):
33
33
  sock = await proxy.connect(
34
34
  dest_host=self._host,
35
35
  dest_port=self._port,
36
- ssl=self._use_ssl,
37
36
  )
37
+ server_hostname = self._host if self._use_ssl else None
38
38
  self._reader, self._writer = await asyncio.open_connection(
39
- sock=sock, ssl=self._use_ssl
39
+ sock=sock,
40
+ ssl=self._use_ssl,
41
+ server_hostname=server_hostname,
40
42
  )
41
43
  logger.info(
42
44
  "tcp connected via proxy %s host=%s port=%s ssl=%s",
@@ -1,8 +1,6 @@
1
- from collections.abc import Iterator
2
1
  from typing import Any
3
2
 
4
3
  from pydantic import Field
5
- from typing_extensions import override
6
4
 
7
5
  from .base import CamelModel
8
6
 
@@ -68,7 +66,3 @@ class FolderList(CamelModel):
68
66
  folders: list[Folder] = Field(default_factory=list)
69
67
  all_filter_exclude_folders: list[Any] = Field(default_factory=list)
70
68
  folder_sync: int = 0
71
-
72
- @override
73
- def __iter__(self) -> Iterator[Folder]: # pyright: ignore[reportIncompatibleMethodOverride]
74
- yield from self.folders
@@ -16,11 +16,9 @@ class LoginConfig(CamelModel):
16
16
  class LoginResponse(CamelModel):
17
17
  chats: list[Chat] = Field(default_factory=list)
18
18
  profile: Profile
19
- messages: dict[int, list[Message]] = Field(
20
- default_factory=dict
21
- ) # chat_id -> [message]
19
+ messages: dict[int, list[Message]] = Field(default_factory=dict) # chat_id -> [message]
22
20
  contacts: list[User | None] = Field(default_factory=list)
23
- token: str
21
+ token: str | None = None
24
22
  time: int | None = None
25
23
  config: LoginConfig | None = None
26
24
 
@@ -29,19 +27,9 @@ class LoginResponse(CamelModel):
29
27
  config_hash = self.config.hash if self.config is not None else None
30
28
 
31
29
  return SyncState(
32
- chats_sync=(
33
- sync_time if sync_time is not None else current.chats_sync
34
- ),
35
- contacts_sync=(
36
- sync_time if sync_time is not None else current.contacts_sync
37
- ),
38
- drafts_sync=(
39
- sync_time if sync_time is not None else current.drafts_sync
40
- ),
41
- presence_sync=(
42
- sync_time if sync_time is not None else current.presence_sync
43
- ),
44
- config_hash=(
45
- config_hash if config_hash is not None else current.config_hash
46
- ),
30
+ chats_sync=(sync_time if sync_time is not None else current.chats_sync),
31
+ contacts_sync=(sync_time if sync_time is not None else current.contacts_sync),
32
+ drafts_sync=(sync_time if sync_time is not None else current.drafts_sync),
33
+ presence_sync=(sync_time if sync_time is not None else current.presence_sync),
34
+ config_hash=(config_hash if config_hash is not None else current.config_hash),
47
35
  )