maxapi-python 1.2.4__py3-none-any.whl → 2.0.0__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.
Files changed (168) hide show
  1. maxapi_python-2.0.0.dist-info/METADATA +217 -0
  2. maxapi_python-2.0.0.dist-info/RECORD +140 -0
  3. {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +50 -105
  5. pymax/api/__init__.py +17 -0
  6. pymax/api/auth/__init__.py +1 -0
  7. pymax/api/auth/enums.py +17 -0
  8. pymax/api/auth/payloads.py +129 -0
  9. pymax/api/auth/service.py +313 -0
  10. pymax/api/auth/types.py +13 -0
  11. pymax/api/chats/__init__.py +8 -0
  12. pymax/api/chats/enums.py +27 -0
  13. pymax/api/chats/payloads.py +103 -0
  14. pymax/api/chats/service.py +277 -0
  15. pymax/api/facade.py +32 -0
  16. pymax/api/messages/__init__.py +1 -0
  17. pymax/api/messages/enums.py +17 -0
  18. pymax/api/messages/payloads.py +92 -0
  19. pymax/api/messages/service.py +337 -0
  20. pymax/api/models.py +13 -0
  21. pymax/api/response.py +123 -0
  22. pymax/api/self/__init__.py +2 -0
  23. pymax/api/self/enums.py +11 -0
  24. pymax/api/self/payloads.py +41 -0
  25. pymax/api/self/service.py +142 -0
  26. pymax/api/session/__init__.py +1 -0
  27. pymax/api/session/enums.py +10 -0
  28. pymax/api/session/payloads.py +76 -0
  29. pymax/api/session/service.py +72 -0
  30. pymax/api/uploads/__init__.py +1 -0
  31. pymax/api/uploads/models.py +49 -0
  32. pymax/api/uploads/payloads.py +25 -0
  33. pymax/api/uploads/service.py +458 -0
  34. pymax/api/users/__init__.py +2 -0
  35. pymax/api/users/enums.py +12 -0
  36. pymax/api/users/payloads.py +16 -0
  37. pymax/api/users/service.py +124 -0
  38. pymax/app.py +273 -0
  39. pymax/auth/__init__.py +25 -0
  40. pymax/auth/base.py +37 -0
  41. pymax/auth/email.py +0 -0
  42. pymax/auth/models.py +5 -0
  43. pymax/auth/providers.py +127 -0
  44. pymax/auth/qr.py +135 -0
  45. pymax/auth/service.py +25 -0
  46. pymax/auth/sms.py +122 -0
  47. pymax/base.py +204 -0
  48. pymax/client.py +106 -0
  49. pymax/client_web.py +83 -0
  50. pymax/config.py +215 -0
  51. pymax/connection/__init__.py +1 -0
  52. pymax/connection/connection.py +205 -0
  53. pymax/connection/pending.py +46 -0
  54. pymax/connection/readers/__init__.py +2 -0
  55. pymax/connection/readers/base.py +6 -0
  56. pymax/connection/readers/tcp.py +29 -0
  57. pymax/connection/readers/ws.py +14 -0
  58. pymax/dispatch/__init__.py +10 -0
  59. pymax/dispatch/dispatcher.py +222 -0
  60. pymax/dispatch/enums.py +12 -0
  61. pymax/dispatch/mapping.py +73 -0
  62. pymax/dispatch/resolvers.py +52 -0
  63. pymax/dispatch/router.py +216 -0
  64. pymax/exceptions.py +22 -89
  65. pymax/files/__init__.py +9 -0
  66. pymax/files/base.py +82 -0
  67. pymax/files/file.py +76 -0
  68. pymax/files/photo.py +108 -0
  69. pymax/files/static.py +10 -0
  70. pymax/files/video.py +74 -0
  71. pymax/formatting/__init__.py +0 -0
  72. pymax/formatting/markdown.py +217 -0
  73. pymax/infra/__init__.py +1 -0
  74. pymax/infra/auth.py +55 -0
  75. pymax/infra/base.py +15 -0
  76. pymax/infra/chat.py +240 -0
  77. pymax/infra/message.py +252 -0
  78. pymax/infra/protocol.py +9 -0
  79. pymax/infra/self.py +139 -0
  80. pymax/infra/user.py +107 -0
  81. pymax/logging.py +129 -0
  82. pymax/protocol/__init__.py +11 -0
  83. pymax/protocol/base.py +13 -0
  84. pymax/{static/enum.py → protocol/enums.py} +36 -79
  85. pymax/protocol/models.py +33 -0
  86. pymax/protocol/tcp/__init__.py +1 -0
  87. pymax/protocol/tcp/compression.py +97 -0
  88. pymax/protocol/tcp/framing.py +68 -0
  89. pymax/protocol/tcp/payload.py +127 -0
  90. pymax/protocol/tcp/protocol.py +68 -0
  91. pymax/protocol/ws/__init__.py +1 -0
  92. pymax/protocol/ws/protocol.py +27 -0
  93. pymax/py.typed +0 -0
  94. pymax/routers.py +8 -0
  95. pymax/session/__init__.py +3 -0
  96. pymax/session/models.py +11 -0
  97. pymax/session/protocol.py +14 -0
  98. pymax/session/store.py +232 -0
  99. pymax/telemetry/__init__.py +3 -0
  100. pymax/telemetry/navigation.py +181 -0
  101. pymax/telemetry/payloads.py +142 -0
  102. pymax/telemetry/service.py +225 -0
  103. pymax/transport/__init__.py +0 -0
  104. pymax/transport/base.py +14 -0
  105. pymax/transport/tcp.py +93 -0
  106. pymax/transport/websocket.py +50 -0
  107. pymax/types/__init__.py +2 -0
  108. pymax/types/domain/__init__.py +11 -0
  109. pymax/types/domain/attachments/__init__.py +11 -0
  110. pymax/types/domain/attachments/audio.py +35 -0
  111. pymax/types/domain/attachments/call.py +26 -0
  112. pymax/types/domain/attachments/contact.py +32 -0
  113. pymax/types/domain/attachments/control.py +20 -0
  114. pymax/types/domain/attachments/enums.py +27 -0
  115. pymax/types/domain/attachments/file.py +56 -0
  116. pymax/types/domain/attachments/keyboards/__init__.py +1 -0
  117. pymax/types/domain/attachments/keyboards/inline.py +19 -0
  118. pymax/types/domain/attachments/photo.py +45 -0
  119. pymax/types/domain/attachments/share.py +29 -0
  120. pymax/types/domain/attachments/sticker.py +50 -0
  121. pymax/types/domain/attachments/video.py +90 -0
  122. pymax/types/domain/auth.py +161 -0
  123. pymax/types/domain/base.py +17 -0
  124. pymax/types/domain/chat.py +426 -0
  125. pymax/types/domain/element.py +24 -0
  126. pymax/types/domain/enums.py +24 -0
  127. pymax/types/domain/error.py +20 -0
  128. pymax/types/domain/folder.py +74 -0
  129. pymax/types/domain/login.py +35 -0
  130. pymax/types/domain/message.py +378 -0
  131. pymax/types/domain/name.py +20 -0
  132. pymax/types/domain/profile.py +15 -0
  133. pymax/types/domain/session.py +52 -0
  134. pymax/types/domain/sync.py +80 -0
  135. pymax/types/domain/user.py +117 -0
  136. pymax/types/events/__init__.py +3 -0
  137. pymax/types/events/file.py +5 -0
  138. pymax/types/events/message.py +37 -0
  139. pymax/types/events/video.py +5 -0
  140. maxapi_python-1.2.4.dist-info/METADATA +0 -205
  141. maxapi_python-1.2.4.dist-info/RECORD +0 -33
  142. pymax/core.py +0 -390
  143. pymax/crud.py +0 -96
  144. pymax/files.py +0 -138
  145. pymax/filters.py +0 -164
  146. pymax/formatter.py +0 -31
  147. pymax/formatting.py +0 -74
  148. pymax/interfaces.py +0 -552
  149. pymax/mixins/__init__.py +0 -40
  150. pymax/mixins/auth.py +0 -368
  151. pymax/mixins/channel.py +0 -130
  152. pymax/mixins/group.py +0 -458
  153. pymax/mixins/handler.py +0 -285
  154. pymax/mixins/message.py +0 -879
  155. pymax/mixins/scheduler.py +0 -28
  156. pymax/mixins/self.py +0 -259
  157. pymax/mixins/socket.py +0 -297
  158. pymax/mixins/telemetry.py +0 -112
  159. pymax/mixins/user.py +0 -219
  160. pymax/mixins/websocket.py +0 -142
  161. pymax/models.py +0 -8
  162. pymax/navigation.py +0 -187
  163. pymax/payloads.py +0 -367
  164. pymax/protocols.py +0 -123
  165. pymax/static/constant.py +0 -89
  166. pymax/types.py +0 -1220
  167. pymax/utils.py +0 -90
  168. {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/licenses/LICENSE +0 -0
pymax/mixins/auth.py DELETED
@@ -1,368 +0,0 @@
1
- import asyncio
2
- import datetime
3
- import re
4
- import sys
5
- from typing import Any
6
-
7
- import qrcode
8
-
9
- from pymax.exceptions import Error
10
- from pymax.payloads import RegisterPayload, RequestCodePayload, SendCodePayload
11
- from pymax.protocols import ClientProtocol
12
- from pymax.static.constant import PHONE_REGEX
13
- from pymax.static.enum import AuthType, DeviceType, Opcode
14
- from pymax.utils import MixinsUtils
15
-
16
-
17
- class AuthMixin(ClientProtocol):
18
- def _check_phone(self) -> bool:
19
- return bool(re.match(PHONE_REGEX, self.phone))
20
-
21
- async def request_code(self, phone: str, language: str = "ru") -> str:
22
- """
23
- Запрашивает код аутентификации для указанного номера телефона и возвращает временный токен.
24
-
25
- Метод отправляет запрос на получение кода верификации на переданный номер телефона.
26
- Используется в процессе аутентификации или регистрации.
27
-
28
- :param phone: Номер телефона в международном формате.
29
- :type phone: str
30
- :param language: Язык для сообщения с кодом. По умолчанию "ru".
31
- :type language: str
32
- :return: Временный токен для дальнейшей аутентификации.
33
- :rtype: str
34
- :raises ValueError: Если полученные данные имеют неверный формат.
35
- :raises Error: Если сервер вернул ошибку.
36
-
37
- .. note::
38
- Используется только в пользовательском flow аутентификации.
39
- """
40
- self.logger.info("Requesting auth code")
41
-
42
- payload = RequestCodePayload(
43
- phone=phone, type=AuthType.START_AUTH, language=language
44
- ).model_dump(by_alias=True)
45
-
46
- data = await self._send_and_wait(opcode=Opcode.AUTH_REQUEST, payload=payload)
47
-
48
- if data.get("payload", {}).get("error"):
49
- MixinsUtils.handle_error(data)
50
-
51
- self.logger.debug(
52
- "Code request response opcode=%s seq=%s",
53
- data.get("opcode"),
54
- data.get("seq"),
55
- )
56
- payload_data = data.get("payload")
57
- if isinstance(payload_data, dict):
58
- return payload_data["token"]
59
- else:
60
- self.logger.error("Invalid payload data received")
61
- raise ValueError("Invalid payload data received")
62
-
63
- async def resend_code(self, phone: str, language: str = "ru") -> str:
64
- """
65
- Повторно запрашивает код аутентификации для указанного номера телефона и возвращает временный токен.
66
-
67
- :param phone: Номер телефона в международном формате.
68
- :type phone: str
69
- :param language: Язык для сообщения с кодом. По умолчанию "ru".
70
- :type language: str
71
- :return: Временный токен для дальнейшей аутентификации.
72
- :rtype: str
73
- :raises ValueError: Если полученные данные имеют неверный формат.
74
- :raises Error: Если сервер вернул ошибку.
75
- """
76
- self.logger.info("Resending auth code")
77
-
78
- payload = RequestCodePayload(
79
- phone=phone, type=AuthType.RESEND, language=language
80
- ).model_dump(by_alias=True)
81
-
82
- data = await self._send_and_wait(opcode=Opcode.AUTH_REQUEST, payload=payload)
83
-
84
- if data.get("payload", {}).get("error"):
85
- MixinsUtils.handle_error(data)
86
-
87
- self.logger.debug(
88
- "Code resend response opcode=%s seq=%s",
89
- data.get("opcode"),
90
- data.get("seq"),
91
- )
92
- payload_data = data.get("payload")
93
- if isinstance(payload_data, dict):
94
- return payload_data["token"]
95
- else:
96
- self.logger.error("Invalid payload data received")
97
- raise ValueError("Invalid payload data received")
98
-
99
- async def _send_code(self, code: str, token: str) -> dict[str, Any]:
100
- """
101
- Отправляет код верификации на сервер для подтверждения.
102
-
103
- :param code: Код верификации (6 цифр).
104
- :type code: str
105
- :param token: Временный токен, полученный из request_code.
106
- :type token: str
107
- :return: Словарь с данными ответа сервера, содержащий токены аутентификации.
108
- :rtype: dict[str, Any]
109
- :raises Error: Если сервер вернул ошибку.
110
- """
111
- self.logger.info("Sending verification code")
112
-
113
- payload = SendCodePayload(
114
- token=token,
115
- verify_code=code,
116
- auth_token_type=AuthType.CHECK_CODE,
117
- ).model_dump(by_alias=True)
118
-
119
- data = await self._send_and_wait(opcode=Opcode.AUTH, payload=payload)
120
-
121
- if data.get("payload", {}).get("error"):
122
- MixinsUtils.handle_error(data)
123
-
124
- self.logger.debug(
125
- "Send code response opcode=%s seq=%s",
126
- data.get("opcode"),
127
- data.get("seq"),
128
- )
129
- payload_data = data.get("payload")
130
- if isinstance(payload_data, dict):
131
- return payload_data
132
- else:
133
- self.logger.error("Invalid payload data received")
134
- raise ValueError("Invalid payload data received")
135
-
136
- def _print_qr(self, qr_link: str) -> None:
137
- qr = qrcode.QRCode(
138
- version=1,
139
- error_correction=qrcode.ERROR_CORRECT_L,
140
- box_size=1,
141
- border=1,
142
- )
143
- qr.add_data(qr_link)
144
- qr.make(fit=True)
145
-
146
- qr.print_ascii()
147
-
148
- async def _request_qr_login(self) -> dict[str, Any]:
149
- self.logger.info("Requesting QR login data")
150
-
151
- data = await self._send_and_wait(opcode=Opcode.GET_QR, payload={})
152
-
153
- if data.get("payload", {}).get("error"):
154
- MixinsUtils.handle_error(data)
155
-
156
- self.logger.debug(
157
- "QR login data response opcode=%s seq=%s",
158
- data.get("opcode"),
159
- data.get("seq"),
160
- )
161
- payload_data = data.get("payload")
162
- if isinstance(payload_data, dict):
163
- return payload_data
164
- else:
165
- self.logger.error("Invalid payload data received")
166
- raise ValueError("Invalid payload data received")
167
-
168
- def _validate_version(self, version: str, min_version: str) -> bool:
169
- def version_tuple(v: str) -> tuple[int, ...]:
170
- return tuple(map(int, (v.split("."))))
171
-
172
- return version_tuple(version) >= version_tuple(min_version)
173
-
174
- async def _login(self) -> None:
175
- self.logger.info("Starting login flow")
176
-
177
- if self.user_agent.device_type == DeviceType.WEB.value and self._ws:
178
- if not self._validate_version(self.user_agent.app_version, "25.12.13"):
179
- self.logger.error("Your app version is too old")
180
- raise ValueError("Your app version is too old")
181
-
182
- login_resp = await self._login_by_qr()
183
- else:
184
- temp_token = await self.request_code(self.phone)
185
- if not temp_token or not isinstance(temp_token, str):
186
- self.logger.critical("Failed to request code: token missing")
187
- raise ValueError("Failed to request code")
188
-
189
- print("Введите код: ", end="", flush=True)
190
- code = await asyncio.to_thread(lambda: sys.stdin.readline().strip())
191
- if len(code) != 6 or not code.isdigit():
192
- self.logger.error("Invalid code format entered")
193
- raise ValueError("Invalid code format")
194
-
195
- login_resp = await self._send_code(code, temp_token)
196
-
197
- token = login_resp.get("tokenAttrs", {}).get("LOGIN", {}).get("token", "")
198
-
199
- if not token:
200
- self.logger.critical("Failed to login, token not received")
201
- raise ValueError("Failed to login, token not received")
202
-
203
- self._token = token
204
- self._database.update_auth_token((self._device_id), self._token)
205
- self.logger.info("Login successful, token saved to database")
206
-
207
- async def _poll_qr_login(self, track_id: str, poll_interval: int) -> bool:
208
- self.logger.info("Polling for QR login confirmation")
209
-
210
- while True:
211
- data = await self._send_and_wait(
212
- opcode=Opcode.GET_QR_STATUS,
213
- payload={"trackId": track_id},
214
- )
215
-
216
- payload = data.get("payload", {})
217
-
218
- if payload.get("error"):
219
- MixinsUtils.handle_error(data)
220
- status = payload.get("status")
221
-
222
- if not status:
223
- self.logger.warning("No status in QR login response")
224
- continue
225
-
226
- if status.get("loginAvailable"):
227
- self.logger.info("QR login confirmed")
228
- return True
229
- else:
230
- exp_at = status.get("expiresAt")
231
- if (
232
- exp_at
233
- and isinstance(exp_at, (int, float))
234
- and exp_at < datetime.datetime.now().timestamp() * 1000
235
- ):
236
- self.logger.warning("QR code expired")
237
- return False
238
-
239
- await asyncio.sleep(poll_interval / 1000)
240
-
241
- async def _get_qr_login_data(self, track_id: str) -> dict[str, Any]:
242
- self.logger.info("Getting QR login data")
243
-
244
- data = await self._send_and_wait(
245
- opcode=Opcode.LOGIN_BY_QR,
246
- payload={"trackId": track_id},
247
- )
248
-
249
- self.logger.debug(
250
- "QR login data response opcode=%s seq=%s",
251
- data.get("opcode"),
252
- data.get("seq"),
253
- )
254
- payload_data = data.get("payload")
255
- if isinstance(payload_data, dict):
256
- return payload_data
257
- else:
258
- self.logger.error("Invalid payload data received")
259
- raise ValueError("Invalid payload data received")
260
-
261
- async def _login_by_qr(self) -> dict[str, Any]:
262
- data = await self._request_qr_login()
263
-
264
- poll_interval = data.get("pollingInterval")
265
- link = data.get("qrLink")
266
- track_id = data.get("trackId")
267
- expires_at = data.get("expiresAt")
268
-
269
- if not poll_interval or not link or not track_id or not expires_at:
270
- self.logger.critical("Invalid QR login data received")
271
- raise ValueError("Invalid QR login data received")
272
-
273
- self.logger.info("Starting QR login flow")
274
- self._print_qr(link)
275
-
276
- poll_qr_task = asyncio.create_task(self._poll_qr_login(track_id, poll_interval))
277
-
278
- while True:
279
- now_ms = datetime.datetime.now().timestamp() * 1000
280
-
281
- done, pending = await asyncio.wait(
282
- [poll_qr_task],
283
- timeout=1,
284
- return_when=asyncio.FIRST_COMPLETED,
285
- )
286
-
287
- if now_ms >= expires_at:
288
- poll_qr_task.cancel()
289
- self.logger.error("QR code expired before confirmation")
290
- raise RuntimeError("QR code expired before confirmation")
291
-
292
- if poll_qr_task in done:
293
- exc = poll_qr_task.exception()
294
- if exc is not None:
295
- raise exc
296
- elif poll_qr_task.result():
297
- self.logger.info("QR login successful")
298
-
299
- data = await self._get_qr_login_data(track_id)
300
- return data
301
-
302
- else:
303
- self.logger.error("QR login failed or expired")
304
- raise RuntimeError("QR login failed or expired")
305
-
306
- async def _submit_reg_info(
307
- self, first_name: str, last_name: str | None, token: str
308
- ) -> dict[str, Any]:
309
- try:
310
- self.logger.info("Submitting registration info")
311
-
312
- payload = RegisterPayload(
313
- first_name=first_name,
314
- last_name=last_name,
315
- token=token,
316
- ).model_dump(by_alias=True)
317
-
318
- data = await self._send_and_wait(opcode=Opcode.AUTH_CONFIRM, payload=payload)
319
- if data.get("payload", {}).get("error"):
320
- MixinsUtils.handle_error(data)
321
-
322
- self.logger.debug(
323
- "Registration info response opcode=%s seq=%s",
324
- data.get("opcode"),
325
- data.get("seq"),
326
- )
327
- payload_data = data.get("payload")
328
- if isinstance(payload_data, dict):
329
- return payload_data
330
- raise ValueError("Invalid payload data received")
331
- except Exception:
332
- self.logger.error("Submit registration info failed", exc_info=True)
333
- raise RuntimeError("Submit registration info failed")
334
-
335
- async def _register(self, first_name: str, last_name: str | None = None) -> None:
336
- self.logger.info("Starting registration flow")
337
-
338
- request_code_payload = await self.request_code(self.phone)
339
- temp_token = request_code_payload
340
-
341
- if not temp_token or not isinstance(temp_token, str):
342
- self.logger.critical("Failed to request code: token missing")
343
- raise ValueError("Failed to request code")
344
-
345
- print("Введите код: ", end="", flush=True)
346
- code = await asyncio.to_thread(lambda: sys.stdin.readline().strip())
347
- if len(code) != 6 or not code.isdigit():
348
- self.logger.error("Invalid code format entered")
349
- raise ValueError("Invalid code format")
350
-
351
- registration_response = await self._send_code(code, temp_token)
352
- token = registration_response.get("tokenAttrs", {}).get("REGISTER", {}).get("token", "")
353
- if not token:
354
- self.logger.critical("Failed to register, token not received")
355
- raise ValueError("Failed to register, token not received")
356
-
357
- data = await self._submit_reg_info(first_name, last_name, token)
358
- self._token = data.get("token")
359
- if not self._token:
360
- self.logger.critical("Failed to register, token not received")
361
- raise ValueError("Failed to register, token not received")
362
-
363
- self.logger.info("Registration successful")
364
- self.logger.info("Token: %s", self._token)
365
- self.logger.warning(
366
- "IMPORTANT: Use this token ONLY with device_type='DESKTOP' and the special init user agent"
367
- )
368
- self.logger.warning("This token MUST NOT be used in web clients")
pymax/mixins/channel.py DELETED
@@ -1,130 +0,0 @@
1
- from pymax.exceptions import Error, ResponseError, ResponseStructureError
2
- from pymax.payloads import (
3
- GetGroupMembersPayload,
4
- JoinChatPayload,
5
- ResolveLinkPayload,
6
- SearchGroupMembersPayload,
7
- )
8
- from pymax.protocols import ClientProtocol
9
- from pymax.static.constant import (
10
- DEFAULT_CHAT_MEMBERS_LIMIT,
11
- DEFAULT_MARKER_VALUE,
12
- )
13
- from pymax.static.enum import Opcode
14
- from pymax.types import Channel, Member
15
- from pymax.utils import MixinsUtils
16
-
17
-
18
- class ChannelMixin(ClientProtocol):
19
- async def resolve_channel_by_name(self, name: str) -> Channel | None:
20
- """
21
- Получает информацию о канале по его имени
22
-
23
- :param name: Имя канала
24
- :type name: str
25
- :return: Объект Channel или None, если канал не найден
26
- :rtype: Channel | None
27
- """
28
- payload = ResolveLinkPayload(
29
- link=f"https://max.ru/{name}",
30
- ).model_dump(by_alias=True)
31
-
32
- data = await self._send_and_wait(opcode=Opcode.LINK_INFO, payload=payload)
33
- if data.get("payload", {}).get("error"):
34
- MixinsUtils.handle_error(data)
35
-
36
- channel = Channel.from_dict(data.get("payload", {}).get("chat", {}))
37
- if channel not in self.channels:
38
- self.channels.append(channel)
39
- return channel
40
-
41
- async def join_channel(self, link: str) -> Channel | None:
42
- """
43
- Присоединяется к каналу по ссылке
44
-
45
- :param link: Ссылка на канал
46
- :type link: str
47
- :return: Объект канала, если присоединение прошло успешно, иначе None
48
- :rtype: Channel | None
49
- """
50
- payload = JoinChatPayload(
51
- link=link,
52
- ).model_dump(by_alias=True)
53
-
54
- data = await self._send_and_wait(opcode=Opcode.CHAT_JOIN, payload=payload)
55
- if data.get("payload", {}).get("error"):
56
- MixinsUtils.handle_error(data)
57
-
58
- channel = Channel.from_dict(data.get("payload", {}).get("chat", {}))
59
- if channel not in self.channels:
60
- self.channels.append(channel)
61
- return channel
62
-
63
- async def _query_members(
64
- self, payload: GetGroupMembersPayload | SearchGroupMembersPayload
65
- ) -> tuple[list[Member], int | None]:
66
- data = await self._send_and_wait(
67
- opcode=Opcode.CHAT_MEMBERS,
68
- payload=payload.model_dump(by_alias=True, exclude_none=True),
69
- )
70
- response_payload = data.get("payload", {})
71
- if data.get("payload", {}).get("error"):
72
- MixinsUtils.handle_error(data)
73
- marker = response_payload.get("marker")
74
- if isinstance(marker, str):
75
- marker = int(marker)
76
- elif isinstance(marker, int):
77
- pass
78
- elif marker is None:
79
- # маркер может отсутствовать
80
- pass
81
- else:
82
- raise ResponseStructureError("Invalid marker type in response")
83
- members = response_payload.get("members")
84
- member_list = []
85
- if isinstance(members, list):
86
- for item in members:
87
- if not isinstance(item, dict):
88
- raise ResponseStructureError("Invalid member structure in response")
89
- member_list.append(Member.from_dict(item))
90
- else:
91
- raise ResponseStructureError("Invalid members type in response")
92
- return member_list, marker
93
-
94
- async def load_members(
95
- self,
96
- chat_id: int,
97
- marker: int | None = DEFAULT_MARKER_VALUE,
98
- count: int = DEFAULT_CHAT_MEMBERS_LIMIT,
99
- ) -> tuple[list[Member], int | None]:
100
- """
101
- Загружает членов канала
102
-
103
- :param chat_id: Идентификатор канала
104
- :type chat_id: int
105
- :param marker: Маркер для пагинации. По умолчанию DEFAULT_MARKER_VALUE
106
- :type marker: int | None
107
- :param count: Количество членов для загрузки. По умолчанию DEFAULT_CHAT_MEMBERS_LIMIT.
108
- :type count: int
109
- :return: Список участников канала и маркер для следующей страницы
110
- :rtype: tuple[list[Member], int | None]
111
- """
112
-
113
- payload = GetGroupMembersPayload(chat_id=chat_id, marker=marker, count=count)
114
- return await self._query_members(payload)
115
-
116
- async def find_members(self, chat_id: int, query: str) -> tuple[list[Member], int | None]:
117
- """
118
- Поиск участников канала по строке
119
- Внимание! веб-клиент всегда возвращает только определённое количество пользователей,
120
- тоесть пагинация здесь не реализована!
121
-
122
- :param chat_id: Идентификатор канала
123
- :type chat_id: int
124
- :param query: Строка для поиска участников
125
- :type query: str
126
- :return: Список участников канала
127
- :rtype: tuple[list[Member], int | None]
128
- """
129
- payload = SearchGroupMembersPayload(chat_id=chat_id, query=query)
130
- return await self._query_members(payload)