maxapi-python 2.3.1__py3-none-any.whl → 2.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {maxapi_python-2.3.1.dist-info → maxapi_python-2.4.1.dist-info}/METADATA +8 -4
  2. {maxapi_python-2.3.1.dist-info → maxapi_python-2.4.1.dist-info}/RECORD +79 -66
  3. {maxapi_python-2.3.1.dist-info → maxapi_python-2.4.1.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +9 -2
  5. pymax/_data/apk_fingerprints.json +767 -0
  6. pymax/_data/rootca_ssl_rsa2022.crt +33 -0
  7. pymax/api/auth/enums.py +1 -0
  8. pymax/api/auth/payloads.py +28 -2
  9. pymax/api/auth/service.py +100 -3
  10. pymax/api/chats/__init__.py +1 -0
  11. pymax/api/chats/enums.py +15 -0
  12. pymax/api/chats/payloads.py +18 -2
  13. pymax/api/chats/service.py +43 -1
  14. pymax/api/messages/__init__.py +1 -1
  15. pymax/api/messages/payloads.py +21 -7
  16. pymax/api/messages/service.py +161 -24
  17. pymax/api/self/__init__.py +2 -1
  18. pymax/api/self/enums.py +9 -0
  19. pymax/api/self/payloads.py +40 -1
  20. pymax/api/self/service.py +37 -0
  21. pymax/api/session/service.py +21 -8
  22. pymax/api/uploads/payloads.py +39 -1
  23. pymax/api/uploads/service.py +247 -55
  24. pymax/app.py +91 -18
  25. pymax/auth/__init__.py +2 -0
  26. pymax/auth/exceptions.py +9 -0
  27. pymax/auth/providers.py +6 -2
  28. pymax/auth/sms.py +11 -1
  29. pymax/base.py +90 -8
  30. pymax/client.py +39 -10
  31. pymax/client_web.py +17 -14
  32. pymax/config.py +98 -19
  33. pymax/connection/connection.py +29 -18
  34. pymax/dispatch/enums.py +1 -0
  35. pymax/dispatch/mapping.py +3 -1
  36. pymax/dispatch/resolvers.py +7 -1
  37. pymax/files/__init__.py +4 -1
  38. pymax/files/base.py +58 -1
  39. pymax/files/video.py +51 -1
  40. pymax/files/voice.py +53 -0
  41. pymax/fingerprint/__init__.py +1 -0
  42. pymax/fingerprint/fingerprint.py +30 -0
  43. pymax/fingerprint/models.py +12 -0
  44. pymax/infra/auth.py +4 -0
  45. pymax/infra/bots.py +3 -3
  46. pymax/infra/chat.py +47 -5
  47. pymax/infra/message.py +49 -14
  48. pymax/infra/protocol.py +5 -1
  49. pymax/infra/self.py +25 -1
  50. pymax/protocol/enums.py +13 -0
  51. pymax/protocol/tcp/payload.py +21 -1
  52. pymax/session/__init__.py +1 -1
  53. pymax/session/models.py +30 -0
  54. pymax/session/protocol.py +33 -7
  55. pymax/session/store.py +73 -5
  56. pymax/transport/tcp.py +17 -4
  57. pymax/transport/websocket.py +6 -3
  58. pymax/types/domain/__init__.py +2 -1
  59. pymax/types/domain/attachments/__init__.py +2 -1
  60. pymax/types/domain/attachments/call.py +7 -1
  61. pymax/types/domain/attachments/contact.py +3 -3
  62. pymax/types/domain/attachments/control.py +3 -0
  63. pymax/types/domain/attachments/enums.py +33 -1
  64. pymax/types/domain/attachments/poll.py +111 -0
  65. pymax/types/domain/attachments/sticker.py +1 -1
  66. pymax/types/domain/attachments/video.py +32 -11
  67. pymax/types/domain/chat.py +9 -9
  68. pymax/types/domain/enums.py +7 -0
  69. pymax/types/domain/handshake.py +16 -0
  70. pymax/types/domain/login.py +30 -1
  71. pymax/types/domain/message.py +108 -27
  72. pymax/types/domain/user.py +1 -3
  73. pymax/types/events/__init__.py +1 -0
  74. pymax/types/events/reaction.py +2 -2
  75. pymax/types/events/voice.py +5 -0
  76. pymax/versions/__init__.py +2 -0
  77. pymax/versions/catalog.py +104 -0
  78. pymax/versions/exceptions.py +10 -0
  79. {maxapi_python-2.3.1.dist-info → maxapi_python-2.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,33 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
3
+ PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
4
+ ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
5
+ Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
6
+ VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
7
+ YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
8
+ dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
9
+ qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
10
+ XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
11
+ zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
12
+ YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
13
+ Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
14
+ U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
15
+ 4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
16
+ G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
17
+ BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
18
+ ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
19
+ OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
20
+ BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
21
+ BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
22
+ AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
23
+ tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
24
+ W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
25
+ /3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
26
+ AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
27
+ C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
28
+ 4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
29
+ WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
30
+ D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
31
+ EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
32
+ 391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
33
+ -----END CERTIFICATE-----
pymax/api/auth/enums.py CHANGED
@@ -6,6 +6,7 @@ class AuthType(str, Enum):
6
6
  CHECK_CODE = "CHECK_CODE"
7
7
  REGISTER = "REGISTER"
8
8
  RESEND = "RESEND"
9
+ LOGIN = "LOGIN"
9
10
 
10
11
 
11
12
  class ProfileOptions(int, Enum):
@@ -10,12 +10,13 @@ from .enums import AuthType, TwoFactorAction
10
10
  class RequestCodePayload(CamelModel):
11
11
  phone: str
12
12
  type: AuthType = AuthType.START_AUTH
13
- language: str = "ru"
13
+ mode: bytes | None
14
14
 
15
15
 
16
16
  class SendCodePayload(CamelModel):
17
17
  token: str
18
18
  verify_code: str
19
+
19
20
  auth_token_type: AuthType = AuthType.CHECK_CODE
20
21
 
21
22
 
@@ -42,19 +43,21 @@ class WebSyncPayload(CamelModel):
42
43
  cls,
43
44
  token: str,
44
45
  sync: SyncState,
46
+ interactive: bool = True,
45
47
  ) -> "WebSyncPayload":
46
48
  return cls(
47
49
  token=token,
48
50
  chats_sync=sync.chats_sync,
49
51
  contacts_sync=sync.contacts_sync,
50
52
  drafts_sync=sync.drafts_sync,
53
+ interactive=interactive,
51
54
  )
52
55
 
53
56
 
54
57
  class SyncPayload(CamelModel):
55
58
  user_agent: MobileUserAgentPayload
56
59
  token: str
57
- chat_hash_fingerprint: str | None = None
60
+ chat_cache_fingerprint: bytes | None = None
58
61
  chats_count: int | None = None
59
62
  chats_sync: int = -1
60
63
  contacts_sync: int = -1
@@ -70,15 +73,19 @@ class SyncPayload(CamelModel):
70
73
  user_agent: MobileUserAgentPayload,
71
74
  token: str,
72
75
  sync: SyncState,
76
+ chat_cache_fingerprint: bytes | None = None,
77
+ interactive: bool = True,
73
78
  ) -> "SyncPayload":
74
79
  return cls(
75
80
  user_agent=user_agent,
76
81
  token=token,
82
+ chat_cache_fingerprint=chat_cache_fingerprint,
77
83
  chats_sync=sync.chats_sync,
78
84
  contacts_sync=sync.contacts_sync,
79
85
  drafts_sync=sync.drafts_sync,
80
86
  presence_sync=sync.presence_sync,
81
87
  config_hash=sync.config_hash,
88
+ interactive=interactive,
82
89
  )
83
90
 
84
91
 
@@ -138,3 +145,22 @@ class ConfirmRegistrationPayload(CamelModel):
138
145
  last_name: str | None = None
139
146
  token: str
140
147
  token_type: AuthType = AuthType.REGISTER
148
+
149
+
150
+ class Login2Payload(CamelModel):
151
+ need_profile: bool
152
+ contacts_sync: int
153
+ config_hash: ConfigHash
154
+
155
+ @classmethod
156
+ def from_sync_state(
157
+ cls,
158
+ sync: SyncState,
159
+ profile_enabled: bool,
160
+ contact_enabled: bool,
161
+ ) -> "Login2Payload":
162
+ return cls(
163
+ need_profile=profile_enabled,
164
+ contacts_sync=sync.contacts_sync if contact_enabled else -1,
165
+ config_hash=sync.config_hash,
166
+ )
pymax/api/auth/service.py CHANGED
@@ -13,6 +13,7 @@ from pymax.auth import EmailCodeProvider
13
13
  from pymax.auth.providers import ConsoleEmailCodeProvider
14
14
  from pymax.logging import get_logger
15
15
  from pymax.protocol import Opcode
16
+ from pymax.types.domain import Login2Flags, Login2Response
16
17
  from pymax.types.domain.auth import (
17
18
  CheckCodeResponse,
18
19
  CheckPasswordResponse,
@@ -31,6 +32,7 @@ from .payloads import (
31
32
  ConfirmQrPayload,
32
33
  ConfirmRegistrationPayload,
33
34
  CreateAuthTrackPayload,
35
+ Login2Payload,
34
36
  MobileUserAgentPayload,
35
37
  RemoveTwoFactorPayload,
36
38
  RequestCodePayload,
@@ -58,7 +60,38 @@ class AuthService:
58
60
 
59
61
  async def request_code(self, phone: str) -> StartAuthResponse:
60
62
  logger.info("requesting sms code phone_set=%s", bool(phone))
61
- frame = RequestCodePayload(phone=phone)
63
+
64
+ if not self.app.handshake_response:
65
+ logger.error("request_code requested without handshake response")
66
+ raise RuntimeError("No handshake response available for request_code")
67
+
68
+ device_id = (
69
+ self.app.session.device_id if self.app.session else self.app.config.device.device_id
70
+ )
71
+
72
+ if self.app.config.device.user_agent.device_type != DeviceType.WEB:
73
+ if self.app.handshake_response.calls_seed is None:
74
+ raise ValueError(
75
+ "Unexpected internal state: handshake_response.calls_seed is missing "
76
+ + "in AuthService.request_code. Please report this issue to the developer."
77
+ )
78
+
79
+ if self.app.fingerprint_generator is None:
80
+ raise ValueError(
81
+ "Unexpected internal state: self.app.fingerprint_generator is missing and "
82
+ + "DeviceType != WEB"
83
+ + "in AuthService.request_code. Please report this issue to the developer."
84
+ )
85
+
86
+ mode = self.app.fingerprint_generator.generate_fingerprint(
87
+ device_id=device_id,
88
+ calls_seed=self.app.handshake_response.calls_seed,
89
+ arch=self.app.config.device.user_agent.arch or "arm64-v8a",
90
+ )
91
+ else:
92
+ mode = None
93
+
94
+ frame = RequestCodePayload(phone=phone, mode=mode)
62
95
  response = await self.app.invoke(Opcode.AUTH_REQUEST, frame.to_payload())
63
96
  logger.debug(
64
97
  "sms code request accepted payload_keys=%s",
@@ -72,7 +105,11 @@ class AuthService:
72
105
  bool(token),
73
106
  bool(verify_code),
74
107
  )
75
- frame = SendCodePayload(token=token, verify_code=verify_code)
108
+
109
+ frame = SendCodePayload(
110
+ token=token,
111
+ verify_code=verify_code,
112
+ )
76
113
  response = await self.app.invoke(Opcode.AUTH, frame.to_payload())
77
114
  logger.debug(
78
115
  "sms code response payload_keys=%s",
@@ -117,11 +154,41 @@ class AuthService:
117
154
  raise RuntimeError("No session available for login")
118
155
 
119
156
  logger.info("logging in")
157
+
158
+ if not self.app.handshake_response:
159
+ logger.error("login requested without handshake response")
160
+ raise RuntimeError("No handshake response available for login")
161
+
162
+ device_id = (
163
+ self.app.session.device_id if self.app.session else self.app.config.device.device_id
164
+ )
165
+
166
+ if self.app.handshake_response.calls_seed is None:
167
+ raise ValueError(
168
+ "Unexpected internal state: handshake_response.calls_seed is missing "
169
+ + "in AuthService.mobile_login. Please report this issue to the developer."
170
+ )
171
+
172
+ if self.app.fingerprint_generator is None:
173
+ raise ValueError(
174
+ "Unexpected internal state: self.app.fingerprint_generator is missing and "
175
+ + "DeviceType != WEB"
176
+ + "in AuthService.request_code. Please report this issue to the developer."
177
+ )
178
+
179
+ ccf = self.app.fingerprint_generator.generate_fingerprint(
180
+ device_id=device_id,
181
+ calls_seed=self.app.handshake_response.calls_seed,
182
+ arch=self.app.config.device.user_agent.arch or "arm64-v8a",
183
+ )
184
+
120
185
  sync = self.app.config.sync.resolve(session.sync)
121
186
  frame = SyncPayload.from_sync_state(
122
187
  user_agent=self.app.config.device.user_agent,
123
188
  token=session.token,
124
189
  sync=sync,
190
+ chat_cache_fingerprint=ccf,
191
+ interactive=self.app.config.interactive,
125
192
  )
126
193
  response = await self.app.invoke(Opcode.LOGIN, frame.to_payload())
127
194
 
@@ -134,6 +201,30 @@ class AuthService:
134
201
  await self._update_session(login_response)
135
202
  return login_response
136
203
 
204
+ async def mobile_login2(self, flags: Login2Flags) -> Login2Response:
205
+ session = self.app.session
206
+ if session is None:
207
+ logger.error("login2 requested without session")
208
+ raise RuntimeError("No session available for login2")
209
+
210
+ sync = self.app.config.sync.resolve(session.sync)
211
+
212
+ frame = Login2Payload.from_sync_state(
213
+ sync,
214
+ profile_enabled=flags.profile_enabled,
215
+ contact_enabled=flags.contact_enabled,
216
+ )
217
+
218
+ response = await self.app.invoke(Opcode.LOGIN2, frame.to_payload())
219
+ logger.debug("login2 response payload_keys=%s", payload_keys(response))
220
+
221
+ login2_response = bind_api_model(
222
+ self.app,
223
+ require_payload_model(response, Login2Response),
224
+ )
225
+ await self._update_session(login2_response)
226
+ return login2_response
227
+
137
228
  async def web_login(self) -> LoginResponse:
138
229
  session = self.app.session
139
230
  if session is None:
@@ -146,6 +237,7 @@ class AuthService:
146
237
  frame = WebSyncPayload.from_sync_state(
147
238
  token=session.token,
148
239
  sync=sync,
240
+ interactive=self.app.config.interactive,
149
241
  )
150
242
  response = await self.app.invoke(Opcode.LOGIN, frame.to_payload())
151
243
 
@@ -177,7 +269,7 @@ class AuthService:
177
269
 
178
270
  return require_payload_model(response, CheckCodeResponse)
179
271
 
180
- async def _update_session(self, response: LoginResponse) -> None:
272
+ async def _update_session(self, response: LoginResponse | Login2Response) -> None:
181
273
  session = self.app.session
182
274
  if session is None:
183
275
  return
@@ -377,3 +469,8 @@ class AuthService:
377
469
  response = await self.app.invoke(Opcode.AUTH_CONFIRM, frame.to_payload())
378
470
 
379
471
  return require_payload_model(response, ConfirmRegistrationResponse)
472
+
473
+ def is_update_available(self) -> bool:
474
+ return bool(
475
+ self.app.handshake_response.app_update_type if self.app.handshake_response else False
476
+ )
@@ -1,4 +1,5 @@
1
1
  from .enums import (
2
+ ChannelPermissions,
2
3
  ChatLinkPrefix,
3
4
  ChatMemberOperation,
4
5
  ChatOption,
pymax/api/chats/enums.py CHANGED
@@ -22,7 +22,22 @@ class ChatPayloadKey(str, Enum):
22
22
  CHAT = "chat"
23
23
  CHATS = "chats"
24
24
  MEMBERS = "members"
25
+ MARKER = "marker"
25
26
 
26
27
 
27
28
  class ChatLinkPrefix(str, Enum):
28
29
  JOIN = "join/"
30
+
31
+
32
+ class ChannelPermissions(int, Enum):
33
+ ADD_REMOVE_MEMBER = 2
34
+ ADD_ADMIN = 4
35
+ CHANGE_CHAT_INFO = 8
36
+ PIN_MESSAGE = 16
37
+ POST_MESSAGE = 256
38
+ EDIT_MESSAGE = 512
39
+ DELETE_MESSAGE = 1024
40
+
41
+
42
+ class PermType(str, Enum):
43
+ ADMIN = "ADMIN"
@@ -6,7 +6,7 @@ from pymax.api.models import CamelModel
6
6
  from pymax.types.domain.attachments.enums import AttachmentType
7
7
  from pymax.types.domain.enums import ChatType
8
8
 
9
- from .enums import ChatMemberOperation, ChatOption, ControlEvent
9
+ from .enums import ChatMemberOperation, ChatOption, ControlEvent, PermType
10
10
 
11
11
 
12
12
  class CreateGroupAttach(CamelModel):
@@ -74,8 +74,9 @@ class ChangeGroupSettingsPayload(CamelModel):
74
74
 
75
75
  class ChangeGroupProfilePayload(CamelModel):
76
76
  chat_id: int
77
- theme: str | None
77
+ theme: str | None = None
78
78
  description: str | None = None
79
+ photo_token: str | None = None
79
80
 
80
81
 
81
82
  class JoinChatPayload(CamelModel):
@@ -95,6 +96,13 @@ class GetChatInfoPayload(CamelModel):
95
96
  chat_ids: list[int]
96
97
 
97
98
 
99
+ class GetChatMembersPayload(CamelModel):
100
+ type: str = "MEMBER" # TODO: ENUMM!!!
101
+ chat_id: int
102
+ marker: int
103
+ count: int = 50
104
+
105
+
98
106
  class LeaveChatPayload(CamelModel):
99
107
  chat_id: int
100
108
 
@@ -121,3 +129,11 @@ class DeleteChatPayload(CamelModel):
121
129
  chat_id: int
122
130
  last_event_time: int
123
131
  for_all: bool = True
132
+
133
+
134
+ class AddAdminPayload(CamelModel):
135
+ chat_id: int
136
+ user_ids: list[int]
137
+ type: PermType = PermType.ADMIN
138
+ operation: str = "add"
139
+ permissions: int
@@ -1,22 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
+ from functools import reduce
5
+ from operator import or_
4
6
  from typing import TYPE_CHECKING
5
7
 
6
8
  from pymax.api.binding import bind_api_model
7
9
  from pymax.api.response import (
8
10
  parse_payload_item_model,
9
11
  parse_payload_list,
12
+ payload_item,
10
13
  require_payload_item_model,
11
14
  require_payload_model,
12
15
  )
13
16
  from pymax.exceptions import PyMaxError
17
+ from pymax.files import Photo
14
18
  from pymax.logging import get_logger
15
19
  from pymax.protocol import Opcode
16
20
  from pymax.types.domain import Chat, Member, Message
17
21
 
18
- from .enums import ChatLinkPrefix, ChatMemberOperation, ChatPayloadKey
22
+ from .enums import ChannelPermissions, ChatLinkPrefix, ChatMemberOperation, ChatPayloadKey
19
23
  from .payloads import (
24
+ AddAdminPayload,
20
25
  ChangeGroupProfilePayload,
21
26
  ChangeGroupSettingsOptions,
22
27
  ChangeGroupSettingsPayload,
@@ -27,6 +32,7 @@ from .payloads import (
27
32
  FetchChatsPayload,
28
33
  FetchJoinRequests,
29
34
  GetChatInfoPayload,
35
+ GetChatMembersPayload,
30
36
  InviteUsersPayload,
31
37
  JoinChatPayload,
32
38
  JoinRequestActionPayload,
@@ -206,13 +212,19 @@ class ChatService:
206
212
  chat_id: int,
207
213
  name: str | None,
208
214
  description: str | None = None,
215
+ photo: Photo | None = None,
209
216
  ) -> None:
217
+
210
218
  frame = ChangeGroupProfilePayload(
211
219
  chat_id=chat_id,
212
220
  theme=name,
213
221
  description=description,
214
222
  )
215
223
 
224
+ if photo:
225
+ photo_payload = await self.app.api.uploads.upload_photo(photo)
226
+ frame.photo_token = photo_payload.photo_token
227
+
216
228
  response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
217
229
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
218
230
  if chat:
@@ -266,6 +278,22 @@ class ChatService:
266
278
 
267
279
  return [cached[chat_id] for chat_id in chat_ids if chat_id in cached]
268
280
 
281
+ async def get_chat_members(
282
+ self,
283
+ chat_id: int,
284
+ marker: int | None = None,
285
+ count: int = 50,
286
+ ) -> tuple[list[Member], int]:
287
+ frame = GetChatMembersPayload(chat_id=chat_id, marker=marker or 0, count=count)
288
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS, frame.to_payload())
289
+
290
+ members = bind_api_model(
291
+ self.app,
292
+ parse_payload_list(response, ChatPayloadKey.MEMBERS, Member),
293
+ )
294
+ next_marker = payload_item(response, ChatPayloadKey.MARKER, int) or 0
295
+ return members, next_marker
296
+
269
297
  async def get_chat(self, chat_id: int) -> Chat:
270
298
  chats = await self.get_chats([chat_id])
271
299
  if not chats:
@@ -380,3 +408,17 @@ class ChatService:
380
408
 
381
409
  await self.app.invoke(Opcode.CHAT_DELETE, frame.to_payload())
382
410
  self._remove_cached_chat(chat_id)
411
+
412
+ async def add_admin(
413
+ self,
414
+ chat_id: int,
415
+ user_id: int,
416
+ permissions: list[ChannelPermissions],
417
+ ) -> None:
418
+ frame = AddAdminPayload(
419
+ chat_id=chat_id,
420
+ user_ids=[user_id],
421
+ permissions=reduce(or_, permissions),
422
+ )
423
+
424
+ await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
@@ -1 +1 @@
1
- from .service import MessageService
1
+ from .service import DateTimeUnion, MessageService
@@ -8,6 +8,7 @@ from pymax.api.uploads.payloads import (
8
8
  AttachPhotoPayload,
9
9
  VideoAttachPayload,
10
10
  )
11
+ from pymax.types.domain import Poll
11
12
 
12
13
  from .enums import ItemType, ReadAction
13
14
 
@@ -20,9 +21,9 @@ class GetMessagesPayload(CamelModel):
20
21
  class EditMessagePayload(CamelModel):
21
22
  chat_id: int
22
23
  message_id: int
23
- text: str
24
+ text: str | None = None
24
25
  elements: list[Any]
25
- attachments: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = Field(
26
+ attachments: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload | Poll] = Field(
26
27
  default_factory=list
27
28
  )
28
29
 
@@ -32,12 +33,18 @@ class ReplyLink(CamelModel):
32
33
  message_id: int
33
34
 
34
35
 
36
+ class DelayedAttributes(CamelModel):
37
+ time_to_fire: int
38
+ notify_sender: bool
39
+
40
+
35
41
  class SendMessagePayloadMessage(CamelModel):
36
- text: str
42
+ text: str | None = None
37
43
  cid: int
38
44
  elements: list[Any]
39
- attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload]
45
+ attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload | Poll]
40
46
  link: ReplyLink | None = None
47
+ delayed_attributes: DelayedAttributes | None = None
41
48
 
42
49
 
43
50
  class SendMessagePayload(CamelModel):
@@ -110,18 +117,18 @@ class ReactionInfoPayload(CamelModel):
110
117
 
111
118
  class AddReactionPayload(CamelModel):
112
119
  chat_id: int
113
- message_id: str
120
+ message_id: int
114
121
  reaction: ReactionInfoPayload
115
122
 
116
123
 
117
124
  class GetReactionsPayload(CamelModel):
118
125
  chat_id: int
119
- message_ids: list[str]
126
+ message_ids: list[int]
120
127
 
121
128
 
122
129
  class RemoveReactionPayload(CamelModel):
123
130
  chat_id: int
124
- message_id: str
131
+ message_id: int
125
132
 
126
133
 
127
134
  class ReadMessagesPayload(CamelModel):
@@ -129,3 +136,10 @@ class ReadMessagesPayload(CamelModel):
129
136
  chat_id: int
130
137
  message_id: str | int # Сокет просит int а вс str
131
138
  mark: int
139
+
140
+
141
+ class VotePollPayload(CamelModel):
142
+ chat_id: int
143
+ message_id: int
144
+ poll_id: int
145
+ answers_ids: list[int]