maxapi-python 2.1.3__py3-none-any.whl → 2.3.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 (64) hide show
  1. {maxapi_python-2.1.3.dist-info → maxapi_python-2.3.0.dist-info}/METADATA +3 -11
  2. {maxapi_python-2.1.3.dist-info → maxapi_python-2.3.0.dist-info}/RECORD +64 -59
  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/payloads.py +6 -0
  8. pymax/api/chats/service.py +52 -47
  9. pymax/api/messages/enums.py +1 -0
  10. pymax/api/messages/payloads.py +16 -1
  11. pymax/api/messages/service.py +78 -34
  12. pymax/api/models.py +4 -6
  13. pymax/api/response.py +2 -2
  14. pymax/api/self/service.py +17 -26
  15. pymax/api/session/payloads.py +2 -9
  16. pymax/api/session/service.py +1 -3
  17. pymax/api/uploads/payloads.py +3 -9
  18. pymax/api/uploads/service.py +33 -99
  19. pymax/api/users/payloads.py +22 -0
  20. pymax/api/users/service.py +22 -17
  21. pymax/app.py +28 -6
  22. pymax/auth/qr.py +3 -9
  23. pymax/auth/sms.py +23 -11
  24. pymax/base.py +86 -4
  25. pymax/client.py +2 -1
  26. pymax/client_web.py +1 -2
  27. pymax/config.py +42 -3
  28. pymax/connection/connection.py +2 -0
  29. pymax/connection/readers/tcp.py +1 -3
  30. pymax/dispatch/__init__.py +12 -1
  31. pymax/dispatch/dispatcher.py +170 -34
  32. pymax/dispatch/enums.py +5 -0
  33. pymax/dispatch/mapping.py +34 -11
  34. pymax/dispatch/resolvers.py +18 -0
  35. pymax/dispatch/router.py +120 -4
  36. pymax/formatting/markdown.py +22 -13
  37. pymax/infra/chat.py +33 -0
  38. pymax/infra/message.py +69 -2
  39. pymax/infra/user.py +12 -1
  40. pymax/logging.py +2 -0
  41. pymax/protocol/tcp/compression.py +1 -3
  42. pymax/protocol/tcp/framing.py +1 -3
  43. pymax/protocol/ws/protocol.py +3 -9
  44. pymax/session/protocol.py +2 -6
  45. pymax/session/store.py +19 -24
  46. pymax/telemetry/navigation.py +1 -3
  47. pymax/telemetry/service.py +5 -17
  48. pymax/transport/tcp.py +1 -3
  49. pymax/types/domain/__init__.py +1 -1
  50. pymax/types/domain/attachments/unknown.py +1 -3
  51. pymax/types/domain/auth.py +24 -2
  52. pymax/types/domain/chat.py +58 -1
  53. pymax/types/domain/message.py +28 -2
  54. pymax/types/domain/presence.py +3 -3
  55. pymax/types/domain/sync.py +5 -21
  56. pymax/types/domain/user.py +8 -0
  57. pymax/types/events/__init__.py +4 -0
  58. pymax/types/events/mark.py +23 -0
  59. pymax/types/events/message.py +57 -5
  60. pymax/types/events/presence.py +15 -0
  61. pymax/types/events/reaction.py +21 -0
  62. pymax/types/events/typing.py +14 -0
  63. {maxapi_python-2.1.3.dist-info → maxapi_python-2.3.0.dist-info}/WHEEL +0 -0
  64. {maxapi_python-2.1.3.dist-info → maxapi_python-2.3.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,
@@ -22,6 +23,7 @@ from .payloads import (
22
23
  CreateGroupAttach,
23
24
  CreateGroupMessage,
24
25
  CreateGroupPayload,
26
+ DeleteChatPayload,
25
27
  FetchChatsPayload,
26
28
  FetchJoinRequests,
27
29
  GetChatInfoPayload,
@@ -46,7 +48,7 @@ class ChatService:
46
48
  self.app = app
47
49
 
48
50
  def _bind_chat(self, chat: Chat) -> Chat:
49
- return chat.bind(self.app.api.messages, self)
51
+ return bind_api_model(self.app, chat)
50
52
 
51
53
  def _cache_chat(self, chat: Chat) -> Chat:
52
54
  chat = self._bind_chat(chat)
@@ -72,15 +74,19 @@ class ChatService:
72
74
  if self.app.chats is None:
73
75
  return
74
76
 
75
- self.app.chats = [
76
- chat for chat in self.app.chats if chat.id != chat_id
77
- ]
77
+ self.app.chats = [chat for chat in self.app.chats if chat.id != chat_id]
78
78
 
79
79
  @staticmethod
80
80
  def _process_chat_join_link(link: str) -> str | None:
81
81
  idx = link.find(ChatLinkPrefix.JOIN)
82
82
  return link[idx:] if idx != -1 else None
83
83
 
84
+ async def _join_chat(self, link: str) -> Chat:
85
+ frame = JoinChatPayload(link=link)
86
+ response = await self.app.invoke(Opcode.CHAT_JOIN, frame.to_payload())
87
+ chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
88
+ return self._cache_chat(chat)
89
+
84
90
  async def create_group(
85
91
  self,
86
92
  name: str,
@@ -112,8 +118,9 @@ class ChatService:
112
118
  return None
113
119
 
114
120
  chat = self._cache_chat(chat)
115
- message = require_payload_model(response, Message).bind(
116
- self.app.api.messages
121
+ message = bind_api_model(
122
+ self.app,
123
+ require_payload_model(response, Message),
117
124
  )
118
125
  return chat, message
119
126
 
@@ -145,9 +152,7 @@ class ChatService:
145
152
  user_ids: list[int],
146
153
  show_history: bool = True,
147
154
  ) -> Chat | None:
148
- return await self.invite_users_to_group(
149
- chat_id, user_ids, show_history
150
- )
155
+ return await self.invite_users_to_group(chat_id, user_ids, show_history)
151
156
 
152
157
  async def remove_users_from_group(
153
158
  self,
@@ -191,9 +196,7 @@ class ChatService:
191
196
  ),
192
197
  )
193
198
 
194
- response = await self.app.invoke(
195
- Opcode.CHAT_UPDATE, frame.to_payload()
196
- )
199
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
197
200
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
198
201
  if chat:
199
202
  self._cache_chat(chat)
@@ -210,9 +213,7 @@ class ChatService:
210
213
  description=description,
211
214
  )
212
215
 
213
- response = await self.app.invoke(
214
- Opcode.CHAT_UPDATE, frame.to_payload()
215
- )
216
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
216
217
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
217
218
  if chat:
218
219
  self._cache_chat(chat)
@@ -222,10 +223,12 @@ class ChatService:
222
223
  if proceed_link is None:
223
224
  raise ValueError("Invalid group link")
224
225
 
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)
226
+ return await self._join_chat(proceed_link)
227
+
228
+ async def join_channel(self, link: str) -> Chat:
229
+ proceed_link = self._process_chat_join_link(link)
230
+
231
+ return await self._join_chat(proceed_link or link)
229
232
 
230
233
  async def resolve_group_by_link(self, link: str) -> Chat | None:
231
234
  proceed_link = self._process_chat_join_link(link)
@@ -242,9 +245,7 @@ class ChatService:
242
245
 
243
246
  async def rework_invite_link(self, chat_id: int) -> Chat:
244
247
  frame = ReworkInviteLinkPayload(chat_id=chat_id)
245
- response = await self.app.invoke(
246
- Opcode.CHAT_UPDATE, frame.to_payload()
247
- )
248
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
248
249
  chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
249
250
  return self._cache_chat(chat)
250
251
 
@@ -254,18 +255,12 @@ class ChatService:
254
255
  for chat_id in chat_ids
255
256
  if (chat := self._get_cached_chat(chat_id)) is not None
256
257
  }
257
- missed_chat_ids = [
258
- chat_id for chat_id in chat_ids if chat_id not in cached
259
- ]
258
+ missed_chat_ids = [chat_id for chat_id in chat_ids if chat_id not in cached]
260
259
 
261
260
  if missed_chat_ids:
262
261
  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
- ):
262
+ response = await self.app.invoke(Opcode.CHAT_INFO, frame.to_payload())
263
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat):
269
264
  chat = self._cache_chat(chat)
270
265
  cached[chat.id] = chat
271
266
 
@@ -292,22 +287,19 @@ class ChatService:
292
287
 
293
288
  chats = [
294
289
  self._cache_chat(chat)
295
- for chat in parse_payload_list(
296
- response, ChatPayloadKey.CHATS, Chat
297
- )
290
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat)
298
291
  ]
299
292
  return chats
300
293
 
301
- async def get_join_requests(
302
- self, chat_id: int, count: int = 100
303
- ) -> list[Member]:
294
+ async def get_join_requests(self, chat_id: int, count: int = 100) -> list[Member]:
304
295
  frame = FetchJoinRequests(chat_id=chat_id, count=count)
305
296
 
306
- response = await self.app.invoke(
307
- Opcode.CHAT_MEMBERS, frame.to_payload()
308
- )
297
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS, frame.to_payload())
309
298
 
310
- return parse_payload_list(response, ChatPayloadKey.MEMBERS, Member)
299
+ return bind_api_model(
300
+ self.app,
301
+ parse_payload_list(response, ChatPayloadKey.MEMBERS, Member),
302
+ )
311
303
 
312
304
  async def confirm_join_requests(
313
305
  self,
@@ -322,9 +314,7 @@ class ChatService:
322
314
  operation=ChatMemberOperation.ADD,
323
315
  )
324
316
 
325
- response = await self.app.invoke(
326
- Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload()
327
- )
317
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
328
318
 
329
319
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
330
320
  if chat:
@@ -356,9 +346,7 @@ class ChatService:
356
346
  operation=ChatMemberOperation.REMOVE,
357
347
  )
358
348
 
359
- response = await self.app.invoke(
360
- Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload()
361
- )
349
+ response = await self.app.invoke(Opcode.CHAT_MEMBERS_UPDATE, frame.to_payload())
362
350
 
363
351
  chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
364
352
  if chat:
@@ -375,3 +363,20 @@ class ChatService:
375
363
  chat_id=chat_id,
376
364
  user_ids=[user_id],
377
365
  )
366
+
367
+ async def delete_chat(
368
+ self,
369
+ chat_id: int,
370
+ last_event_time: int | None = None,
371
+ for_all: bool = True,
372
+ ) -> None:
373
+ frame = DeleteChatPayload(
374
+ chat_id=chat_id,
375
+ last_event_time=(
376
+ last_event_time if last_event_time is not None else int(time.time() * 1000)
377
+ ),
378
+ for_all=for_all,
379
+ )
380
+
381
+ await self.app.invoke(Opcode.CHAT_DELETE, frame.to_payload())
382
+ self._remove_cached_chat(chat_id)
@@ -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
@@ -1,12 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
+ from collections.abc import Sequence
4
5
  from typing import TYPE_CHECKING, TypeAlias
5
6
 
7
+ from pymax.api.binding import bind_api_model, bind_api_models
6
8
  from pymax.api.response import (
7
9
  parse_payload_list,
8
10
  parse_payload_model,
9
11
  payload_item,
12
+ require_payload_item_model,
10
13
  require_payload_model,
11
14
  )
12
15
  from pymax.api.uploads.payloads import (
@@ -32,7 +35,9 @@ from .payloads import (
32
35
  AddReactionPayload,
33
36
  ChatHistoryPayload,
34
37
  DeleteMessagePayload,
38
+ EditMessagePayload,
35
39
  GetFilePayload,
40
+ GetMessagesPayload,
36
41
  GetReactionsPayload,
37
42
  GetVideoPayload,
38
43
  PinMessagePayload,
@@ -48,7 +53,7 @@ if TYPE_CHECKING:
48
53
  from pymax.app import App
49
54
 
50
55
  SendAttachment: TypeAlias = Photo | File | Video
51
- SendAttachments: TypeAlias = list[SendAttachment] | None
56
+ SendAttachments: TypeAlias = Sequence[SendAttachment] | None
52
57
 
53
58
  logger = get_logger(__name__)
54
59
 
@@ -69,17 +74,13 @@ class MessageService:
69
74
  async def _upload_attachments(
70
75
  self, attachments: SendAttachments
71
76
  ) -> list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload]:
72
- result: list[
73
- AttachPhotoPayload | VideoAttachPayload | AttachFilePayload
74
- ] = []
77
+ result: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = []
75
78
  if not attachments:
76
79
  return result
77
80
 
78
81
  for attachment in attachments:
79
82
  if isinstance(attachment, Photo):
80
- upload_result = await self.app.api.uploads.upload_photo(
81
- attachment
82
- )
83
+ upload_result = await self.app.api.uploads.upload_photo(attachment)
83
84
  if not upload_result:
84
85
  logger.error("Photo uploading failed")
85
86
  raise UploadError("Photo uploading failed")
@@ -87,9 +88,7 @@ class MessageService:
87
88
  result.append(upload_result)
88
89
 
89
90
  elif isinstance(attachment, Video):
90
- upload_result = await self.app.api.uploads.upload_video(
91
- attachment
92
- )
91
+ upload_result = await self.app.api.uploads.upload_video(attachment)
93
92
  if not upload_result:
94
93
  logger.error("Video uploading failed")
95
94
  raise UploadError("Video uploading failed")
@@ -97,9 +96,7 @@ class MessageService:
97
96
  result.append(upload_result)
98
97
 
99
98
  elif isinstance(attachment, File):
100
- upload_result = await self.app.api.uploads.upload_file(
101
- attachment
102
- )
99
+ upload_result = await self.app.api.uploads.upload_file(attachment)
103
100
  if not upload_result:
104
101
  logger.error("File uploading failed")
105
102
  raise UploadError("File uploading failed")
@@ -117,9 +114,7 @@ class MessageService:
117
114
  *,
118
115
  notify: bool = True,
119
116
  ) -> Message | None:
120
- logger.info(
121
- "sending message chat_id=%s text_len=%s", chat_id, len(text)
122
- )
117
+ logger.info("sending message chat_id=%s text_len=%s", chat_id, len(text))
123
118
 
124
119
  clean_text, elements = Formatter.format_markdown(text)
125
120
 
@@ -137,10 +132,66 @@ class MessageService:
137
132
 
138
133
  response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
139
134
 
140
- message = require_payload_model(response, Message).bind(self)
135
+ message = bind_api_model(
136
+ self.app,
137
+ require_payload_model(response, Message),
138
+ )
141
139
  logger.info("message sent chat_id=%s", chat_id)
142
140
  return message
143
141
 
142
+ async def get_messages(
143
+ self,
144
+ chat_id: int,
145
+ message_ids: list[int],
146
+ ) -> list[Message]:
147
+ frame = GetMessagesPayload(
148
+ chat_id=chat_id,
149
+ message_ids=message_ids,
150
+ )
151
+
152
+ response = await self.app.invoke(Opcode.MSG_GET, frame.to_payload())
153
+ messages = parse_payload_list(response, MessagePayloadKey.MESSAGES, Message)
154
+ for message in messages:
155
+ if message.chat_id is None:
156
+ message.chat_id = chat_id
157
+
158
+ return bind_api_models(self.app, messages)
159
+
160
+ async def get_message(
161
+ self,
162
+ chat_id: int,
163
+ message_id: int,
164
+ ) -> Message | None:
165
+ messages = await self.get_messages(chat_id, [message_id])
166
+ return messages[0] if messages else None
167
+
168
+ async def edit_message(
169
+ self,
170
+ chat_id: int,
171
+ message_id: int,
172
+ text: str,
173
+ attachments: SendAttachments = None,
174
+ ) -> Message:
175
+ clean_text, elements = Formatter.format_markdown(text)
176
+ frame = EditMessagePayload(
177
+ chat_id=chat_id,
178
+ message_id=message_id,
179
+ text=clean_text,
180
+ elements=elements,
181
+ attachments=await self._upload_attachments(attachments),
182
+ )
183
+
184
+ response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
185
+ message = require_payload_item_model(
186
+ response,
187
+ MessagePayloadKey.MESSAGE,
188
+ Message,
189
+ )
190
+ if message.chat_id is None:
191
+ message.chat_id = chat_id
192
+
193
+ return bind_api_model(self.app, message)
194
+
144
195
  async def fetch_history(
145
196
  self,
146
197
  chat_id: int,
@@ -171,10 +222,11 @@ class MessageService:
171
222
  Opcode.CHAT_HISTORY,
172
223
  payload=frame.to_payload(),
173
224
  )
174
- return (
175
- parse_payload_list(response, MessagePayloadKey.MESSAGES, Message)
176
- or None
225
+ messages = bind_api_models(
226
+ self.app,
227
+ parse_payload_list(response, MessagePayloadKey.MESSAGES, Message),
177
228
  )
229
+ return messages or None
178
230
 
179
231
  async def delete_message(
180
232
  self,
@@ -195,9 +247,7 @@ class MessageService:
195
247
  )
196
248
 
197
249
  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
- )
250
+ logger.info("messages deleted chat_id=%s count=%s", chat_id, len(message_ids))
201
251
  return True
202
252
 
203
253
  async def pin_message(
@@ -219,9 +269,7 @@ class MessageService:
219
269
  )
220
270
 
221
271
  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
- )
272
+ logger.info("message pinned chat_id=%s message_id=%s", chat_id, message_id)
225
273
  return True
226
274
 
227
275
  async def get_video_by_id(
@@ -263,9 +311,7 @@ class MessageService:
263
311
  file_id=file_id,
264
312
  )
265
313
 
266
- response = await self.app.invoke(
267
- Opcode.FILE_DOWNLOAD, frame.to_payload()
268
- )
314
+ response = await self.app.invoke(Opcode.FILE_DOWNLOAD, frame.to_payload())
269
315
  return parse_payload_model(response, FileRequest)
270
316
 
271
317
  async def add_reaction(
@@ -286,9 +332,7 @@ class MessageService:
286
332
  reaction=ReactionInfoPayload(id=reaction),
287
333
  )
288
334
 
289
- response = await self.app.invoke(
290
- Opcode.MSG_REACTION, frame.to_payload()
291
- )
335
+ response = await self.app.invoke(Opcode.MSG_REACTION, frame.to_payload())
292
336
  reaction_info = payload_item(response, MessagePayloadKey.REACTION_INFO)
293
337
  if reaction_info:
294
338
  return ReactionInfo.model_validate(reaction_info)
@@ -345,7 +389,7 @@ class MessageService:
345
389
 
346
390
  return None
347
391
 
348
- async def read_message(self, message_id: int, chat_id: int) -> ReadState:
392
+ async def read_message(self, message_id: int | str, chat_id: int) -> ReadState:
349
393
  logger.info(
350
394
  "marking message as read chat_id=%s message_id=%s",
351
395
  chat_id,
@@ -354,7 +398,7 @@ class MessageService:
354
398
  frame = ReadMessagesPayload(
355
399
  type=ReadAction.READ_MESSAGE,
356
400
  chat_id=chat_id,
357
- message_id=str(message_id),
401
+ message_id=message_id,
358
402
  mark=int(time.time() * 1000),
359
403
  )
360
404
 
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