maxapi-python 2.1.3__py3-none-any.whl → 2.2.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 (58) hide show
  1. {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/METADATA +3 -11
  2. {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/RECORD +58 -53
  3. pymax/__init__.py +18 -3
  4. pymax/api/auth/payloads.py +7 -0
  5. pymax/api/auth/service.py +33 -30
  6. pymax/api/binding.py +57 -0
  7. pymax/api/chats/service.py +34 -47
  8. pymax/api/messages/enums.py +1 -0
  9. pymax/api/messages/payloads.py +16 -1
  10. pymax/api/messages/service.py +85 -33
  11. pymax/api/models.py +4 -6
  12. pymax/api/response.py +2 -2
  13. pymax/api/self/service.py +17 -26
  14. pymax/api/session/payloads.py +2 -9
  15. pymax/api/session/service.py +1 -3
  16. pymax/api/uploads/payloads.py +3 -9
  17. pymax/api/uploads/service.py +33 -99
  18. pymax/api/users/service.py +8 -16
  19. pymax/app.py +2 -0
  20. pymax/auth/qr.py +3 -9
  21. pymax/auth/sms.py +23 -11
  22. pymax/base.py +38 -1
  23. pymax/client.py +2 -1
  24. pymax/client_web.py +1 -2
  25. pymax/config.py +42 -3
  26. pymax/connection/connection.py +2 -0
  27. pymax/connection/readers/tcp.py +1 -3
  28. pymax/dispatch/dispatcher.py +36 -18
  29. pymax/dispatch/enums.py +4 -0
  30. pymax/dispatch/mapping.py +34 -11
  31. pymax/dispatch/resolvers.py +18 -0
  32. pymax/dispatch/router.py +34 -0
  33. pymax/formatting/markdown.py +22 -13
  34. pymax/infra/chat.py +12 -0
  35. pymax/infra/message.py +74 -3
  36. pymax/logging.py +2 -0
  37. pymax/protocol/tcp/compression.py +1 -3
  38. pymax/protocol/tcp/framing.py +1 -3
  39. pymax/protocol/ws/protocol.py +3 -9
  40. pymax/session/protocol.py +2 -6
  41. pymax/session/store.py +8 -24
  42. pymax/telemetry/navigation.py +1 -3
  43. pymax/telemetry/service.py +5 -17
  44. pymax/transport/tcp.py +1 -3
  45. pymax/types/domain/attachments/unknown.py +1 -3
  46. pymax/types/domain/auth.py +24 -2
  47. pymax/types/domain/chat.py +38 -1
  48. pymax/types/domain/message.py +31 -1
  49. pymax/types/domain/presence.py +3 -3
  50. pymax/types/domain/sync.py +5 -21
  51. pymax/types/events/__init__.py +4 -0
  52. pymax/types/events/mark.py +23 -0
  53. pymax/types/events/message.py +57 -5
  54. pymax/types/events/presence.py +15 -0
  55. pymax/types/events/reaction.py +21 -0
  56. pymax/types/events/typing.py +14 -0
  57. {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/WHEEL +0 -0
  58. {maxapi_python-2.1.3.dist-info → maxapi_python-2.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import time
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from pymax.api.binding import bind_api_model
6
7
  from pymax.api.response import (
7
8
  parse_payload_item_model,
8
9
  parse_payload_list,
@@ -46,7 +47,7 @@ class ChatService:
46
47
  self.app = app
47
48
 
48
49
  def _bind_chat(self, chat: Chat) -> Chat:
49
- return chat.bind(self.app.api.messages, self)
50
+ return bind_api_model(self.app, chat)
50
51
 
51
52
  def _cache_chat(self, chat: Chat) -> Chat:
52
53
  chat = self._bind_chat(chat)
@@ -72,15 +73,19 @@ class ChatService:
72
73
  if self.app.chats is None:
73
74
  return
74
75
 
75
- self.app.chats = [
76
- chat for chat in self.app.chats if chat.id != chat_id
77
- ]
76
+ self.app.chats = [chat for chat in self.app.chats if chat.id != chat_id]
78
77
 
79
78
  @staticmethod
80
79
  def _process_chat_join_link(link: str) -> str | None:
81
80
  idx = link.find(ChatLinkPrefix.JOIN)
82
81
  return link[idx:] if idx != -1 else None
83
82
 
83
+ async def _join_chat(self, link: str) -> Chat:
84
+ frame = JoinChatPayload(link=link)
85
+ response = await self.app.invoke(Opcode.CHAT_JOIN, frame.to_payload())
86
+ chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
87
+ return self._cache_chat(chat)
88
+
84
89
  async def create_group(
85
90
  self,
86
91
  name: str,
@@ -112,8 +117,9 @@ class ChatService:
112
117
  return None
113
118
 
114
119
  chat = self._cache_chat(chat)
115
- message = require_payload_model(response, Message).bind(
116
- self.app.api.messages
120
+ message = bind_api_model(
121
+ self.app,
122
+ require_payload_model(response, Message),
117
123
  )
118
124
  return chat, message
119
125
 
@@ -145,9 +151,7 @@ class ChatService:
145
151
  user_ids: list[int],
146
152
  show_history: bool = True,
147
153
  ) -> Chat | None:
148
- return await self.invite_users_to_group(
149
- chat_id, user_ids, show_history
150
- )
154
+ return await self.invite_users_to_group(chat_id, user_ids, show_history)
151
155
 
152
156
  async def remove_users_from_group(
153
157
  self,
@@ -191,9 +195,7 @@ class ChatService:
191
195
  ),
192
196
  )
193
197
 
194
- response = await self.app.invoke(
195
- Opcode.CHAT_UPDATE, frame.to_payload()
196
- )
198
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
197
199
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
198
200
  if chat:
199
201
  self._cache_chat(chat)
@@ -210,9 +212,7 @@ class ChatService:
210
212
  description=description,
211
213
  )
212
214
 
213
- response = await self.app.invoke(
214
- Opcode.CHAT_UPDATE, frame.to_payload()
215
- )
215
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
216
216
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
217
217
  if chat:
218
218
  self._cache_chat(chat)
@@ -222,10 +222,12 @@ class ChatService:
222
222
  if proceed_link is None:
223
223
  raise ValueError("Invalid group link")
224
224
 
225
- frame = JoinChatPayload(link=proceed_link)
226
- response = await self.app.invoke(Opcode.CHAT_JOIN, frame.to_payload())
227
- chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
228
- return self._cache_chat(chat)
225
+ return await self._join_chat(proceed_link)
226
+
227
+ async def join_channel(self, link: str) -> Chat:
228
+ proceed_link = self._process_chat_join_link(link)
229
+
230
+ return await self._join_chat(proceed_link or link)
229
231
 
230
232
  async def resolve_group_by_link(self, link: str) -> Chat | None:
231
233
  proceed_link = self._process_chat_join_link(link)
@@ -242,9 +244,7 @@ class ChatService:
242
244
 
243
245
  async def rework_invite_link(self, chat_id: int) -> Chat:
244
246
  frame = ReworkInviteLinkPayload(chat_id=chat_id)
245
- response = await self.app.invoke(
246
- Opcode.CHAT_UPDATE, frame.to_payload()
247
- )
247
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
248
248
  chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
249
249
  return self._cache_chat(chat)
250
250
 
@@ -254,18 +254,12 @@ class ChatService:
254
254
  for chat_id in chat_ids
255
255
  if (chat := self._get_cached_chat(chat_id)) is not None
256
256
  }
257
- missed_chat_ids = [
258
- chat_id for chat_id in chat_ids if chat_id not in cached
259
- ]
257
+ missed_chat_ids = [chat_id for chat_id in chat_ids if chat_id not in cached]
260
258
 
261
259
  if missed_chat_ids:
262
260
  frame = GetChatInfoPayload(chat_ids=missed_chat_ids)
263
- response = await self.app.invoke(
264
- Opcode.CHAT_INFO, frame.to_payload()
265
- )
266
- for chat in parse_payload_list(
267
- response, ChatPayloadKey.CHATS, Chat
268
- ):
261
+ response = await self.app.invoke(Opcode.CHAT_INFO, frame.to_payload())
262
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat):
269
263
  chat = self._cache_chat(chat)
270
264
  cached[chat.id] = chat
271
265
 
@@ -292,22 +286,19 @@ class ChatService:
292
286
 
293
287
  chats = [
294
288
  self._cache_chat(chat)
295
- for chat in parse_payload_list(
296
- response, ChatPayloadKey.CHATS, Chat
297
- )
289
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat)
298
290
  ]
299
291
  return chats
300
292
 
301
- async def get_join_requests(
302
- self, chat_id: int, count: int = 100
303
- ) -> list[Member]:
293
+ async def get_join_requests(self, chat_id: int, count: int = 100) -> list[Member]:
304
294
  frame = FetchJoinRequests(chat_id=chat_id, count=count)
305
295
 
306
- response = await self.app.invoke(
307
- Opcode.CHAT_MEMBERS, frame.to_payload()
308
- )
296
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS, frame.to_payload())
309
297
 
310
- return parse_payload_list(response, ChatPayloadKey.MEMBERS, Member)
298
+ return bind_api_model(
299
+ self.app,
300
+ parse_payload_list(response, ChatPayloadKey.MEMBERS, Member),
301
+ )
311
302
 
312
303
  async def confirm_join_requests(
313
304
  self,
@@ -322,9 +313,7 @@ class ChatService:
322
313
  operation=ChatMemberOperation.ADD,
323
314
  )
324
315
 
325
- response = await self.app.invoke(
326
- Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload()
327
- )
316
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
328
317
 
329
318
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
330
319
  if chat:
@@ -356,9 +345,7 @@ class ChatService:
356
345
  operation=ChatMemberOperation.REMOVE,
357
346
  )
358
347
 
359
- response = await self.app.invoke(
360
- Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload()
361
- )
348
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
362
349
 
363
350
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
364
351
  if chat:
@@ -12,6 +12,7 @@ class ReadAction(str, Enum):
12
12
 
13
13
 
14
14
  class MessagePayloadKey(str, Enum):
15
+ MESSAGE = "message"
15
16
  MESSAGES = "messages"
16
17
  REACTION_INFO = "reactionInfo"
17
18
  MESSAGES_REACTIONS = "messagesReactions"
@@ -12,6 +12,21 @@ from pymax.api.uploads.payloads import (
12
12
  from .enums import ItemType, ReadAction
13
13
 
14
14
 
15
+ class GetMessagesPayload(CamelModel):
16
+ chat_id: int
17
+ message_ids: list[int]
18
+
19
+
20
+ class EditMessagePayload(CamelModel):
21
+ chat_id: int
22
+ message_id: int
23
+ text: str
24
+ elements: list[Any]
25
+ attachments: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = Field(
26
+ default_factory=list
27
+ )
28
+
29
+
15
30
  class ReplyLink(CamelModel):
16
31
  type: str = "REPLY" # TODO: enum?
17
32
  message_id: int
@@ -92,5 +107,5 @@ class RemoveReactionPayload(CamelModel):
92
107
  class ReadMessagesPayload(CamelModel):
93
108
  type: ReadAction
94
109
  chat_id: int
95
- message_id: str
110
+ message_id: str | int # Сокет просит int а вс str
96
111
  mark: int
@@ -3,10 +3,12 @@ from __future__ import annotations
3
3
  import time
4
4
  from typing import TYPE_CHECKING, TypeAlias
5
5
 
6
+ from pymax.api.binding import bind_api_model, bind_api_models
6
7
  from pymax.api.response import (
7
8
  parse_payload_list,
8
9
  parse_payload_model,
9
10
  payload_item,
11
+ require_payload_item_model,
10
12
  require_payload_model,
11
13
  )
12
14
  from pymax.api.uploads.payloads import (
@@ -32,7 +34,9 @@ from .payloads import (
32
34
  AddReactionPayload,
33
35
  ChatHistoryPayload,
34
36
  DeleteMessagePayload,
37
+ EditMessagePayload,
35
38
  GetFilePayload,
39
+ GetMessagesPayload,
36
40
  GetReactionsPayload,
37
41
  GetVideoPayload,
38
42
  PinMessagePayload,
@@ -69,17 +73,13 @@ class MessageService:
69
73
  async def _upload_attachments(
70
74
  self, attachments: SendAttachments
71
75
  ) -> list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload]:
72
- result: list[
73
- AttachPhotoPayload | VideoAttachPayload | AttachFilePayload
74
- ] = []
76
+ result: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = []
75
77
  if not attachments:
76
78
  return result
77
79
 
78
80
  for attachment in attachments:
79
81
  if isinstance(attachment, Photo):
80
- upload_result = await self.app.api.uploads.upload_photo(
81
- attachment
82
- )
82
+ upload_result = await self.app.api.uploads.upload_photo(attachment)
83
83
  if not upload_result:
84
84
  logger.error("Photo uploading failed")
85
85
  raise UploadError("Photo uploading failed")
@@ -87,9 +87,7 @@ class MessageService:
87
87
  result.append(upload_result)
88
88
 
89
89
  elif isinstance(attachment, Video):
90
- upload_result = await self.app.api.uploads.upload_video(
91
- attachment
92
- )
90
+ upload_result = await self.app.api.uploads.upload_video(attachment)
93
91
  if not upload_result:
94
92
  logger.error("Video uploading failed")
95
93
  raise UploadError("Video uploading failed")
@@ -97,9 +95,7 @@ class MessageService:
97
95
  result.append(upload_result)
98
96
 
99
97
  elif isinstance(attachment, File):
100
- upload_result = await self.app.api.uploads.upload_file(
101
- attachment
102
- )
98
+ upload_result = await self.app.api.uploads.upload_file(attachment)
103
99
  if not upload_result:
104
100
  logger.error("File uploading failed")
105
101
  raise UploadError("File uploading failed")
@@ -117,9 +113,7 @@ class MessageService:
117
113
  *,
118
114
  notify: bool = True,
119
115
  ) -> Message | None:
120
- logger.info(
121
- "sending message chat_id=%s text_len=%s", chat_id, len(text)
122
- )
116
+ logger.info("sending message chat_id=%s text_len=%s", chat_id, len(text))
123
117
 
124
118
  clean_text, elements = Formatter.format_markdown(text)
125
119
 
@@ -137,10 +131,75 @@ class MessageService:
137
131
 
138
132
  response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
139
133
 
140
- message = require_payload_model(response, Message).bind(self)
134
+ message = bind_api_model(
135
+ self.app,
136
+ require_payload_model(response, Message),
137
+ )
141
138
  logger.info("message sent chat_id=%s", chat_id)
142
139
  return message
143
140
 
141
+ async def get_messages(
142
+ self,
143
+ chat_id: int,
144
+ message_ids: list[int],
145
+ ) -> list[Message]:
146
+ frame = GetMessagesPayload(
147
+ chat_id=chat_id,
148
+ message_ids=message_ids,
149
+ )
150
+
151
+ response = await self.app.invoke(Opcode.MSG_GET, frame.to_payload())
152
+ messages = parse_payload_list(response, MessagePayloadKey.MESSAGES, Message)
153
+ for message in messages:
154
+ if message.chat_id is None:
155
+ message.chat_id = chat_id
156
+
157
+ return bind_api_models(self.app, messages)
158
+
159
+ async def get_message(
160
+ self,
161
+ chat_id: int,
162
+ message_id: int,
163
+ ) -> Message | None:
164
+ messages = await self.get_messages(chat_id, [message_id])
165
+ return messages[0] if messages else None
166
+
167
+ async def edit_message(
168
+ self,
169
+ chat_id: int,
170
+ message_id: int,
171
+ text: str,
172
+ attachment: SendAttachment | None = None,
173
+ attachments: SendAttachments = None,
174
+ ) -> Message:
175
+ if attachment is not None and attachments:
176
+ logger.warning("both attachment and attachments provided; using attachments")
177
+ attachment = None
178
+
179
+ edit_attachments = attachments
180
+ if attachment is not None:
181
+ edit_attachments = [attachment]
182
+
183
+ clean_text, elements = Formatter.format_markdown(text)
184
+ frame = EditMessagePayload(
185
+ chat_id=chat_id,
186
+ message_id=message_id,
187
+ text=clean_text,
188
+ elements=elements,
189
+ attachments=await self._upload_attachments(edit_attachments),
190
+ )
191
+
192
+ response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
193
+ message = require_payload_item_model(
194
+ response,
195
+ MessagePayloadKey.MESSAGE,
196
+ Message,
197
+ )
198
+ if message.chat_id is None:
199
+ message.chat_id = chat_id
200
+
201
+ return bind_api_model(self.app, message)
202
+
144
203
  async def fetch_history(
145
204
  self,
146
205
  chat_id: int,
@@ -171,10 +230,11 @@ class MessageService:
171
230
  Opcode.CHAT_HISTORY,
172
231
  payload=frame.to_payload(),
173
232
  )
174
- return (
175
- parse_payload_list(response, MessagePayloadKey.MESSAGES, Message)
176
- or None
233
+ messages = bind_api_models(
234
+ self.app,
235
+ parse_payload_list(response, MessagePayloadKey.MESSAGES, Message),
177
236
  )
237
+ return messages or None
178
238
 
179
239
  async def delete_message(
180
240
  self,
@@ -195,9 +255,7 @@ class MessageService:
195
255
  )
196
256
 
197
257
  await self.app.invoke(Opcode.MSG_DELETE, frame.to_payload())
198
- logger.info(
199
- "messages deleted chat_id=%s count=%s", chat_id, len(message_ids)
200
- )
258
+ logger.info("messages deleted chat_id=%s count=%s", chat_id, len(message_ids))
201
259
  return True
202
260
 
203
261
  async def pin_message(
@@ -219,9 +277,7 @@ class MessageService:
219
277
  )
220
278
 
221
279
  await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
222
- logger.info(
223
- "message pinned chat_id=%s message_id=%s", chat_id, message_id
224
- )
280
+ logger.info("message pinned chat_id=%s message_id=%s", chat_id, message_id)
225
281
  return True
226
282
 
227
283
  async def get_video_by_id(
@@ -263,9 +319,7 @@ class MessageService:
263
319
  file_id=file_id,
264
320
  )
265
321
 
266
- response = await self.app.invoke(
267
- Opcode.FILE_DOWNLOAD, frame.to_payload()
268
- )
322
+ response = await self.app.invoke(Opcode.FILE_DOWNLOAD, frame.to_payload())
269
323
  return parse_payload_model(response, FileRequest)
270
324
 
271
325
  async def add_reaction(
@@ -286,9 +340,7 @@ class MessageService:
286
340
  reaction=ReactionInfoPayload(id=reaction),
287
341
  )
288
342
 
289
- response = await self.app.invoke(
290
- Opcode.MSG_REACTION, frame.to_payload()
291
- )
343
+ response = await self.app.invoke(Opcode.MSG_REACTION, frame.to_payload())
292
344
  reaction_info = payload_item(response, MessagePayloadKey.REACTION_INFO)
293
345
  if reaction_info:
294
346
  return ReactionInfo.model_validate(reaction_info)
@@ -345,7 +397,7 @@ class MessageService:
345
397
 
346
398
  return None
347
399
 
348
- async def read_message(self, message_id: int, chat_id: int) -> ReadState:
400
+ async def read_message(self, message_id: int | str, chat_id: int) -> ReadState:
349
401
  logger.info(
350
402
  "marking message as read chat_id=%s message_id=%s",
351
403
  chat_id,
@@ -354,7 +406,7 @@ class MessageService:
354
406
  frame = ReadMessagesPayload(
355
407
  type=ReadAction.READ_MESSAGE,
356
408
  chat_id=chat_id,
357
- message_id=str(message_id),
409
+ message_id=message_id,
358
410
  mark=int(time.time() * 1000),
359
411
  )
360
412
 
pymax/api/models.py CHANGED
@@ -1,13 +1,11 @@
1
- from pydantic import BaseModel
1
+ from pydantic import BaseModel, ConfigDict
2
2
  from pydantic.alias_generators import to_camel
3
3
 
4
4
 
5
5
  class CamelModel(BaseModel):
6
- model_config = {
7
- "alias_generator": to_camel,
8
- "populate_by_name": True,
9
- "arbitrary_types_allowed": True,
10
- }
6
+ model_config = ConfigDict(
7
+ alias_generator=to_camel, populate_by_name=True, arbitrary_types_allowed=True
8
+ )
11
9
 
12
10
  def to_payload(self) -> dict:
13
11
  return self.model_dump(by_alias=True, exclude_none=True)
pymax/api/response.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Any, TypeVar, overload
2
+ from typing import Any, TypeVar, cast, overload
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
@@ -60,7 +60,7 @@ def payload_item(
60
60
  if validation_type is None:
61
61
  return data
62
62
 
63
- return validation_type(data)
63
+ return cast(Any, validation_type)(data)
64
64
 
65
65
 
66
66
  def require_payload_item(
pymax/api/self/service.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
  from uuid import uuid4
5
5
 
6
+ from pymax.api.binding import bind_api_model
6
7
  from pymax.api.response import (
7
8
  payload_item,
8
9
  require_payload_item,
@@ -38,9 +39,7 @@ class SelfService:
38
39
  async def request_profile_photo_upload_url(self) -> str:
39
40
  logger.info("requesting profile photo upload url")
40
41
  frame = UploadPayload(profile=True)
41
- response = await self.app.invoke(
42
- Opcode.PHOTO_UPLOAD, frame.to_payload()
43
- )
42
+ response = await self.app.invoke(Opcode.PHOTO_UPLOAD, frame.to_payload())
44
43
  return str(require_payload_item(response, SelfPayloadKey.URL))
45
44
 
46
45
  async def change_profile(
@@ -53,12 +52,11 @@ class SelfService:
53
52
  photo_token: str | None = None,
54
53
  ) -> bool:
55
54
  if photo is not None:
56
- attach = await self.app.api.uploads.upload_photo(
57
- photo, profile=True
58
- )
55
+ attach = await self.app.api.uploads.upload_photo(photo, profile=True)
59
56
  if photo_token:
60
57
  logger.warning(
61
- "photo_token argument was provided but will be overridden by the uploaded photo token"
58
+ "photo_token argument was provided but will be overridden by "
59
+ "the uploaded photo token"
62
60
  )
63
61
 
64
62
  photo_token = attach.photo_token
@@ -70,10 +68,13 @@ class SelfService:
70
68
  photo_token=photo_token,
71
69
  )
72
70
  response = await self.app.invoke(Opcode.PROFILE, frame.to_payload())
73
- profile = require_payload_item_model(
74
- response,
75
- SelfPayloadKey.PROFILE,
76
- Profile,
71
+ profile = bind_api_model(
72
+ self.app,
73
+ require_payload_item_model(
74
+ response,
75
+ SelfPayloadKey.PROFILE,
76
+ Profile,
77
+ ),
77
78
  )
78
79
  self.app.me = profile
79
80
  self.app.users[profile.contact.id] = profile.contact
@@ -92,17 +93,13 @@ class SelfService:
92
93
  include=chat_include,
93
94
  filters=filters or [],
94
95
  )
95
- response = await self.app.invoke(
96
- Opcode.FOLDERS_UPDATE, frame.to_payload()
97
- )
96
+ response = await self.app.invoke(Opcode.FOLDERS_UPDATE, frame.to_payload())
98
97
  return require_payload_model(response, FolderUpdate)
99
98
 
100
99
  async def get_folders(self, folder_sync: int = 0) -> FolderList:
101
100
  logger.info("fetching folders")
102
101
  frame = GetFolderPayload(folder_sync=folder_sync)
103
- response = await self.app.invoke(
104
- Opcode.FOLDERS_GET, frame.to_payload()
105
- )
102
+ response = await self.app.invoke(Opcode.FOLDERS_GET, frame.to_payload())
106
103
  return require_payload_model(response, FolderList)
107
104
 
108
105
  async def update_folder(
@@ -121,17 +118,13 @@ class SelfService:
121
118
  filters=filters or [],
122
119
  options=options or [],
123
120
  )
124
- response = await self.app.invoke(
125
- Opcode.FOLDERS_UPDATE, frame.to_payload()
126
- )
121
+ response = await self.app.invoke(Opcode.FOLDERS_UPDATE, frame.to_payload())
127
122
  return require_payload_model(response, FolderUpdate)
128
123
 
129
124
  async def delete_folder(self, folder_id: str) -> FolderUpdate:
130
125
  logger.info("deleting folder")
131
126
  frame = DeleteFolderPayload(folder_ids=[folder_id])
132
- response = await self.app.invoke(
133
- Opcode.FOLDERS_DELETE, frame.to_payload()
134
- )
127
+ response = await self.app.invoke(Opcode.FOLDERS_DELETE, frame.to_payload())
135
128
  return require_payload_model(response, FolderUpdate)
136
129
 
137
130
  async def close_all_sessions(self) -> bool:
@@ -145,9 +138,7 @@ class SelfService:
145
138
  token = payload_item(response, SelfPayloadKey.TOKEN, str)
146
139
 
147
140
  if not token:
148
- logger.warning(
149
- "no token received after closing sessions, skipping token update"
150
- )
141
+ logger.warning("no token received after closing sessions, skipping token update")
151
142
  return False
152
143
 
153
144
  await self.app.store.update_token(self.app.session.token, token)
@@ -52,17 +52,10 @@ class MobileUserAgentPayload(CamelModel):
52
52
  by_alias=True,
53
53
  exclude_none=True,
54
54
  )
55
- if (
56
- self.device_type == DeviceType.WEB
57
- and "headerUserAgent" not in payload
58
- ):
55
+ if self.device_type == DeviceType.WEB and "headerUserAgent" not in payload:
59
56
  payload["headerUserAgent"] = DEFAULT_WEB_HEADER_USER_AGENT
60
57
 
61
- return {
62
- alias: payload[alias]
63
- for alias in WEB_USER_AGENT_ALIASES
64
- if alias in payload
65
- }
58
+ return {alias: payload[alias] for alias in WEB_USER_AGENT_ALIASES if alias in payload}
66
59
 
67
60
 
68
61
  class MobileHandshakePayload(CamelModel):
@@ -55,9 +55,7 @@ class SessionService:
55
55
  await self.app.invoke(Opcode.SESSION_INIT, frame.to_payload())
56
56
  logger.info("mobile handshake completed")
57
57
 
58
- async def web_handshake(
59
- self, user_agent: MobileUserAgentPayload, device_id: str
60
- ) -> None:
58
+ async def web_handshake(self, user_agent: MobileUserAgentPayload, device_id: str) -> None:
61
59
  logger.debug(
62
60
  "web handshake device_id=%s app_version=%s browser=%s",
63
61
  device_id,
@@ -5,24 +5,18 @@ from pymax.types import AttachmentType
5
5
 
6
6
 
7
7
  class AttachPhotoPayload(CamelModel):
8
- type: AttachmentType = Field(
9
- default=AttachmentType.PHOTO, serialization_alias="_type"
10
- )
8
+ type: AttachmentType = Field(default=AttachmentType.PHOTO, serialization_alias="_type")
11
9
  photo_token: str
12
10
 
13
11
 
14
12
  class VideoAttachPayload(CamelModel):
15
- type: AttachmentType = Field(
16
- default=AttachmentType.VIDEO, serialization_alias="_type"
17
- )
13
+ type: AttachmentType = Field(default=AttachmentType.VIDEO, serialization_alias="_type")
18
14
  video_id: int
19
15
  token: str
20
16
 
21
17
 
22
18
  class AttachFilePayload(CamelModel):
23
- type: AttachmentType = Field(
24
- default=AttachmentType.FILE, serialization_alias="_type"
25
- )
19
+ type: AttachmentType = Field(default=AttachmentType.FILE, serialization_alias="_type")
26
20
  file_id: int
27
21
 
28
22