maxapi-python 1.1.5__tar.gz → 1.1.7__tar.gz

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 (42) hide show
  1. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/PKG-INFO +1 -1
  2. maxapi_python-1.1.7/examples/example.py +59 -0
  3. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/pyproject.toml +1 -1
  4. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/message.py +39 -29
  5. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/user.py +36 -1
  6. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/payloads.py +8 -2
  7. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/types.py +8 -7
  8. maxapi_python-1.1.5/examples/example.py +0 -82
  9. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/.github/FUNDING.yml +0 -0
  10. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/.github/workflows/publish.yml +0 -0
  11. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/.gitignore +0 -0
  12. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/LICENSE +0 -0
  13. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/README.md +0 -0
  14. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/assets/icon.svg +0 -0
  15. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/assets/logo.svg +0 -0
  16. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/docs/api.md +0 -0
  17. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/docs/assets/icon.svg +0 -0
  18. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/docs/examples.md +0 -0
  19. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/docs/index.md +0 -0
  20. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/mkdocs.yml +0 -0
  21. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/ruff.toml +0 -0
  22. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/scripts/build.py +0 -0
  23. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/__init__.py +0 -0
  24. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/core.py +0 -0
  25. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/crud.py +0 -0
  26. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/exceptions.py +0 -0
  27. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/files.py +0 -0
  28. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/filters.py +0 -0
  29. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/interfaces.py +0 -0
  30. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/__init__.py +0 -0
  31. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/auth.py +0 -0
  32. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/channel.py +0 -0
  33. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/group.py +0 -0
  34. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/handler.py +0 -0
  35. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/self.py +0 -0
  36. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/socket.py +0 -0
  37. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/telemetry.py +0 -0
  38. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/mixins/websocket.py +0 -0
  39. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/models.py +0 -0
  40. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/navigation.py +0 -0
  41. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/static.py +0 -0
  42. {maxapi_python-1.1.5 → maxapi_python-1.1.7}/src/pymax/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxapi-python
3
- Version: 1.1.5
3
+ Version: 1.1.7
4
4
  Summary: Python wrapper для API мессенджера Max
5
5
  Project-URL: Homepage, https://github.com/noxzion/PyMax
6
6
  Project-URL: Repository, https://github.com/noxzion/PyMax
@@ -0,0 +1,59 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from pymax import MaxClient, Message, SocketMaxClient
5
+ from pymax.files import Photo
6
+ from pymax.filters import Filter
7
+ from pymax.static import AttachType
8
+
9
+ phone = "+1234567890"
10
+
11
+
12
+ client = MaxClient(phone=phone, work_dir="cache")
13
+ # client = SocketMaxClient(phone=phone, work_dir="cache")
14
+
15
+
16
+ @client.on_message(filter=Filter(chat_id=0))
17
+ async def handle_message(message: Message) -> None:
18
+ print(str(message.sender) + ": " + message.text)
19
+
20
+
21
+ @client.on_start
22
+ async def handle_start() -> None:
23
+ print("Client started successfully!")
24
+ # print(client.dialogs)
25
+ # history = await client.fetch_history(chat_id=0)
26
+ # if history:
27
+ # for message in history:
28
+ # if message.attaches:
29
+ # for attach in message.attaches:
30
+ # if attach.type == AttachType.VIDEO:
31
+ # print(message)
32
+ # vid = await client.get_video_by_id(
33
+ # chat_id=0,
34
+ # video_id=attach.video_id,
35
+ # message_id=message.id,
36
+ # )
37
+ # print(vid.url)
38
+ # elif attach.type == AttachType.FILE:
39
+ # file = await client.get_file_by_id(
40
+ # chat_id=0,
41
+ # file_id=attach.file_id,
42
+ # message_id=message.id,
43
+ # )
44
+ # print(file.url)
45
+ # print(client.me.names[0].first_name)
46
+ # user = await client.get_user(client.me.id)
47
+
48
+ # print(user.names[0].first_name)
49
+
50
+ # photo1 = Photo(path="tests/test.jpeg")
51
+ # photo2 = Photo(path="tests/test.jpg")
52
+
53
+ # await client.send_message(
54
+ # "Hello with photo!", chat_id=0, photos=[photo1, photo2], notify=True
55
+ # )
56
+
57
+
58
+ if __name__ == "__main__":
59
+ asyncio.run(client.start())
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "maxapi-python"
3
- version = "1.1.5"
3
+ version = "1.1.7"
4
4
  description = "Python wrapper для API мессенджера Max"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -10,16 +10,16 @@ from pymax.payloads import (
10
10
  DeleteMessagePayload,
11
11
  EditMessagePayload,
12
12
  FetchHistoryPayload,
13
+ GetFilePayload,
14
+ GetVideoPayload,
13
15
  PinMessagePayload,
14
16
  ReplyLink,
15
17
  SendMessagePayload,
16
18
  SendMessagePayloadMessage,
17
19
  UploadPhotoPayload,
18
- GetVideoPayload,
19
- GetFilePayload,
20
20
  )
21
21
  from pymax.static import AttachType, Opcode
22
- from pymax.types import Attach, Message, FileRequest, VideoRequest
22
+ from pymax.types import Attach, FileRequest, Message, VideoRequest
23
23
 
24
24
 
25
25
  class MessageMixin(ClientProtocol):
@@ -143,7 +143,6 @@ class MessageMixin(ClientProtocol):
143
143
  data = await self._send_and_wait(opcode=Opcode.MSG_SEND, payload=payload)
144
144
  if error := data.get("payload", {}).get("error"):
145
145
  self.logger.error("Send message error: %s", error)
146
- print(data)
147
146
  return None
148
147
  msg = Message.from_dict(data["payload"]) if data.get("payload") else None
149
148
  self.logger.debug("send_message result: %r", msg)
@@ -286,12 +285,11 @@ class MessageMixin(ClientProtocol):
286
285
  self.logger.exception("Fetch history failed")
287
286
  return None
288
287
 
289
-
290
288
  async def get_video_by_id(
291
289
  self,
292
290
  chat_id: int,
293
291
  message_id: int,
294
- video_id: str,
292
+ video_id: int,
295
293
  ) -> VideoRequest | None:
296
294
  """
297
295
  Получает видео
@@ -307,33 +305,37 @@ class MessageMixin(ClientProtocol):
307
305
  url (str): Ссылка на видео
308
306
  """
309
307
  try:
310
- self.logger.info(
311
- "Getting video_id=%s message_id=%s", video_id, message_id
312
- )
308
+ self.logger.info("Getting video_id=%s message_id=%s", video_id, message_id)
313
309
 
314
- payload = GetVideoPayload(
315
- chat_id=chat_id,
316
- message_id=message_id,
317
- video_id=video_id
318
- ).model_dump(by_alias=True)
310
+ if self.is_connected and self._socket is not None:
311
+ payload = GetVideoPayload(
312
+ chat_id=chat_id, message_id=message_id, video_id=video_id
313
+ ).model_dump(by_alias=True)
314
+ else:
315
+ payload = GetVideoPayload(
316
+ chat_id=chat_id, message_id=str(message_id), video_id=video_id
317
+ ).model_dump(by_alias=True)
319
318
 
320
319
  data = await self._send_and_wait(opcode=Opcode.VIDEO_PLAY, payload=payload)
321
320
 
322
321
  if error := data.get("payload", {}).get("error"):
323
322
  self.logger.error("Get video error: %s", error)
324
-
325
- video = VideoRequest.from_dict(data["payload"]) if data.get("payload") else None
326
- self.logger.debug(" result: %r", video)
323
+ return
324
+
325
+ video = (
326
+ VideoRequest.from_dict(data["payload"]) if data.get("payload") else None
327
+ )
328
+ self.logger.debug("result: %r", video)
327
329
  return video
328
330
  except Exception:
329
331
  self.logger.exception("Get video error")
330
332
  return None
331
-
333
+
332
334
  async def get_file_by_id(
333
335
  self,
334
336
  chat_id: int,
335
337
  message_id: int,
336
- file_id: str,
338
+ file_id: int,
337
339
  ) -> FileRequest | None:
338
340
  """
339
341
  Получает файл
@@ -348,20 +350,28 @@ class MessageMixin(ClientProtocol):
348
350
  url (str): Ссылка на скачивание файла
349
351
  """
350
352
  try:
351
- self.logger.info(
352
- "Getting file_id=%s message_id=%s", file_id, message_id
353
+ self.logger.info("Getting file_id=%s message_id=%s", file_id, message_id)
354
+ if self.is_connected and self._socket is not None:
355
+ payload = GetFilePayload(
356
+ chat_id=chat_id, message_id=message_id, file_id=file_id
357
+ ).model_dump(by_alias=True)
358
+ else:
359
+ payload = GetFilePayload(
360
+ chat_id=chat_id, message_id=str(message_id), file_id=file_id
361
+ ).model_dump(by_alias=True)
362
+ data = await self._send_and_wait(
363
+ opcode=Opcode.FILE_DOWNLOAD, payload=payload
353
364
  )
354
- payload = GetFilePayload(
355
- chat_id=chat_id,
356
- message_id=message_id,
357
- file_id=file_id
358
- ).model_dump(by_alias=True)
359
- data = await self._send_and_wait(opcode=Opcode.FILE_DOWNLOAD, payload=payload)
365
+
360
366
  if error := data.get("payload", {}).get("error"):
361
367
  self.logger.error("Get file error: %s", error)
362
- file = FileRequest.from_dict(data["payload"]) if data.get("payload") else None
368
+ return
369
+
370
+ file = (
371
+ FileRequest.from_dict(data["payload"]) if data.get("payload") else None
372
+ )
363
373
  self.logger.debug(" result: %r", file)
364
374
  return file
365
375
  except Exception:
366
376
  self.logger.exception("Get video error")
367
- return None
377
+ return None
@@ -1,5 +1,5 @@
1
1
  from pymax.interfaces import ClientProtocol
2
- from pymax.payloads import FetchContactsPayload
2
+ from pymax.payloads import FetchContactsPayload, SearchByPhonePayload
3
3
  from pymax.static import Opcode
4
4
  from pymax.types import User
5
5
 
@@ -80,3 +80,38 @@ class UserMixin(ClientProtocol):
80
80
  except Exception:
81
81
  self.logger.exception("Fetch users failed")
82
82
  return []
83
+
84
+ async def search_by_phone(self, phone: str) -> User | None:
85
+ """
86
+ Ищет пользователя по номеру телефона.
87
+
88
+ Args:
89
+ phone (str): Номер телефона.
90
+
91
+ Returns:
92
+ User | None: Объект User или None при ошибке.
93
+ """
94
+ try:
95
+ self.logger.info("Searching user by phone: %s", phone)
96
+
97
+ payload = SearchByPhonePayload(phone=phone).model_dump(by_alias=True)
98
+
99
+ data = await self._send_and_wait(
100
+ opcode=Opcode.CONTACT_INFO_BY_PHONE, payload=payload
101
+ )
102
+ if error := data.get("payload", {}).get("error"):
103
+ self.logger.error("Search by phone error: %s", error)
104
+ return None
105
+
106
+ user = (
107
+ User.from_dict(data["payload"]["contact"])
108
+ if data.get("payload")
109
+ else None
110
+ )
111
+ if user:
112
+ self._users[user.id] = user
113
+ self.logger.debug("Found user by phone: %s", user)
114
+ return user
115
+ except Exception:
116
+ self.logger.exception("Search by phone failed")
117
+ return None
@@ -194,12 +194,18 @@ class NavigationEventPayload(CamelModel):
194
194
  class NavigationPayload(CamelModel):
195
195
  events: list[NavigationEventPayload]
196
196
 
197
+
197
198
  class GetVideoPayload(CamelModel):
198
199
  chat_id: int
199
- message_id: str
200
+ message_id: int | str
200
201
  video_id: int
201
202
 
203
+
202
204
  class GetFilePayload(CamelModel):
203
205
  chat_id: int
204
- message_id: str
206
+ message_id: str | int
205
207
  file_id: int
208
+
209
+
210
+ class SearchByPhonePayload(CamelModel):
211
+ phone: str
@@ -181,7 +181,7 @@ class FileRequest:
181
181
  return cls(
182
182
  unsafe=data["unsafe"],
183
183
  url=data["url"],
184
- )
184
+ )
185
185
 
186
186
 
187
187
  class VideoRequest:
@@ -197,15 +197,16 @@ class VideoRequest:
197
197
 
198
198
  @classmethod
199
199
  def from_dict(cls, data: dict[str, Any]) -> "VideoRequest":
200
-
201
- listdata = list(data.values()) # Костыль
202
-
200
+ # listdata = list(data.values()) # Костыль ✅
201
+ url = [v for k, v in data.items() if k not in ("EXTERNAL", "cache")][
202
+ 0
203
+ ] # Еще больший костыль ✅
203
204
  return cls(
204
205
  external=data["EXTERNAL"],
205
206
  cache=data["cache"],
206
- url=listdata[2],
207
- )
208
-
207
+ url=url,
208
+ )
209
+
209
210
 
210
211
  class Me:
211
212
  def __init__(
@@ -1,82 +0,0 @@
1
- import asyncio
2
- import logging
3
-
4
- from pymax import MaxClient, Message, SocketMaxClient
5
- from pymax.files import Photo
6
- from pymax.filters import Filter
7
- from pymax.static import AttachType
8
-
9
- phone = "+1234567890"
10
-
11
-
12
- client = MaxClient(phone=phone, work_dir="cache")
13
- # client = SocketMaxClient(phone=phone, work_dir="cache")
14
-
15
-
16
- async def main() -> None:
17
- for chat in client.chats:
18
- print(chat.title)
19
-
20
- message = await client.send_message(
21
- "Hello from MaxClient!", chat.id, notify=True
22
- )
23
-
24
- await asyncio.sleep(5)
25
- message = await client.edit_message(
26
- chat.id, message.id, "Hello from MaxClient! (edited)"
27
- )
28
- await asyncio.sleep(5)
29
-
30
- await client.delete_message(chat.id, [message.id], for_me=False)
31
-
32
- for dialog in client.dialogs:
33
- print(dialog.last_message.text)
34
-
35
- for channel in client.channels:
36
- print(channel.title)
37
-
38
- await client.close()
39
-
40
-
41
- @client.on_message(filter=Filter(chat_id=0))
42
- async def handle_message(message: Message) -> None:
43
- print(str(message.sender) + ": " + message.text)
44
-
45
-
46
- @client.on_start
47
- async def handle_start() -> None:
48
- print("Client started successfully!")
49
- history = await client.fetch_history(chat_id=0)
50
- if history:
51
- for message in history:
52
- if message.attaches:
53
- for attach in message.attaches:
54
- if attach.type == AttachType.VIDEO:
55
- vid = await client.get_video_by_id(
56
- chat_id=0,
57
- video_id=attach.video_id,
58
- message_id=message.id,
59
- )
60
- print(vid.url)
61
- elif attach.type == AttachType.FILE:
62
- file = await client.get_file_by_id(
63
- chat_id=0,
64
- file_id=attach.file_id,
65
- message_id=message.id,
66
- )
67
- print(file.url)
68
- # print(client.me.names[0].first_name)
69
- # user = await client.get_user(client.me.id)
70
-
71
- # print(user.names[0].first_name)
72
-
73
- # photo1 = Photo(path="tests/test.jpeg")
74
- # photo2 = Photo(path="tests/test.jpg")
75
-
76
- # await client.send_message(
77
- # "Hello with photo!", chat_id=0, photos=[photo1, photo2], notify=True
78
- # )
79
-
80
-
81
- if __name__ == "__main__":
82
- asyncio.run(client.start())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes