maxapi-python 1.1.8__tar.gz → 1.1.10__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 (46) hide show
  1. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/PKG-INFO +1 -1
  2. maxapi_python-1.1.10/examples/example.py +68 -0
  3. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/pyproject.toml +1 -1
  4. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/__init__.py +2 -0
  5. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/group.py +44 -0
  6. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/user.py +17 -1
  7. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/payloads.py +4 -0
  8. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/types.py +65 -0
  9. maxapi_python-1.1.8/examples/example.py +0 -62
  10. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/FUNDING.yml +0 -0
  11. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
  14. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/pull_request_template.md +0 -0
  15. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.github/workflows/publish.yml +0 -0
  16. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/.gitignore +0 -0
  17. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/LICENSE +0 -0
  18. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/README.md +0 -0
  19. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/assets/icon.svg +0 -0
  20. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/assets/logo.svg +0 -0
  21. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/docs/api.md +0 -0
  22. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/docs/assets/icon.svg +0 -0
  23. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/docs/examples.md +0 -0
  24. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/docs/index.md +0 -0
  25. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/mkdocs.yml +0 -0
  26. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/ruff.toml +0 -0
  27. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/scripts/build.py +0 -0
  28. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/__init__.py +0 -0
  29. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/core.py +0 -0
  30. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/crud.py +0 -0
  31. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/exceptions.py +0 -0
  32. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/files.py +0 -0
  33. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/filters.py +0 -0
  34. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/interfaces.py +0 -0
  35. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/auth.py +0 -0
  36. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/channel.py +0 -0
  37. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/handler.py +0 -0
  38. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/message.py +0 -0
  39. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/self.py +0 -0
  40. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/socket.py +0 -0
  41. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/telemetry.py +0 -0
  42. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/mixins/websocket.py +0 -0
  43. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/models.py +0 -0
  44. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/navigation.py +0 -0
  45. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/src/pymax/static.py +0 -0
  46. {maxapi_python-1.1.8 → maxapi_python-1.1.10}/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.8
3
+ Version: 1.1.10
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,68 @@
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
+ sessions = await client.get_sessions()
25
+ for session in sessions:
26
+ print(session.client)
27
+ # print(client.dialogs)
28
+ chat = await client.join_group("join/sdfdfsfdf")
29
+ print(chat.title)
30
+ # if history:
31
+ # for message in history:
32
+ # if message.link:
33
+ # print(message.link.chat_id)
34
+ # print(message.link.message.text)
35
+ # for attach in message.attaches:
36
+ # if attach.type == AttachType.CONTROL:
37
+ # print(attach.event)
38
+ # print(attach.extra)
39
+ # if attach.type == AttachType.VIDEO:
40
+ # print(message)
41
+ # vid = await client.get_video_by_id(
42
+ # chat_id=0,
43
+ # video_id=attach.video_id,
44
+ # message_id=message.id,
45
+ # )
46
+ # print(vid.url)
47
+ # elif attach.type == AttachType.FILE:
48
+ # file = await client.get_file_by_id(
49
+ # chat_id=0,
50
+ # file_id=attach.file_id,
51
+ # message_id=message.id,
52
+ # )
53
+ # print(file.url)
54
+ # print(client.me.names[0].first_name)
55
+ # user = await client.get_user(client.me.id)
56
+
57
+ # print(user.names[0].first_name)
58
+
59
+ # photo1 = Photo(path="tests/test.jpeg")
60
+ # photo2 = Photo(path="tests/test.jpg")
61
+
62
+ # await client.send_message(
63
+ # "Hello with photo!", chat_id=0, photos=[photo1, photo2], notify=True
64
+ # )
65
+
66
+
67
+ if __name__ == "__main__":
68
+ asyncio.run(client.start())
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "maxapi-python"
3
- version = "1.1.8"
3
+ version = "1.1.10"
4
4
  description = "Python wrapper для API мессенджера Max"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,5 +1,6 @@
1
1
  from .auth import AuthMixin
2
2
  from .channel import ChannelMixin
3
+ from .group import GroupMixin
3
4
  from .handler import HandlerMixin
4
5
  from .message import MessageMixin
5
6
  from .self import SelfMixin
@@ -17,6 +18,7 @@ class ApiMixin(
17
18
  SelfMixin,
18
19
  MessageMixin,
19
20
  TelemetryMixin,
21
+ GroupMixin,
20
22
  ):
21
23
  pass
22
24
 
@@ -9,6 +9,7 @@ from pymax.payloads import (
9
9
  CreateGroupMessage,
10
10
  CreateGroupPayload,
11
11
  InviteUsersPayload,
12
+ JoinGroupPayload,
12
13
  RemoveUsersPayload,
13
14
  )
14
15
  from pymax.static import Opcode
@@ -218,3 +219,46 @@ class GroupMixin(ClientProtocol):
218
219
 
219
220
  except Exception:
220
221
  self.logger.exception("Change group profile failed")
222
+
223
+ def _process_chat_join_link(self, link: str) -> str | None:
224
+ idx = link.find("join/")
225
+ return link[idx:] if idx != -1 else None
226
+
227
+ async def join_group(self, link: str) -> Chat | None:
228
+ """
229
+ Вступает в группу по ссылке
230
+
231
+ Args:
232
+ link (str): Ссылка на группу.
233
+
234
+ Returns:
235
+ bool: True, если успешно вступил в группу
236
+ """
237
+ try:
238
+ proceed_link = self._process_chat_join_link(link)
239
+ if proceed_link is None:
240
+ self.logger.error("Invalid group link: %s", link)
241
+ return None
242
+
243
+ payload = JoinGroupPayload(link=proceed_link).model_dump(by_alias=True)
244
+
245
+ data = await self._send_and_wait(opcode=Opcode.CHAT_JOIN, payload=payload)
246
+
247
+ if error := data.get("payload", {}).get("error"):
248
+ self.logger.error("Join group error: %s", error)
249
+ return None
250
+
251
+ chat = Chat.from_dict(data["payload"]["chat"])
252
+ if chat:
253
+ cached_chat = await self._get_chat(chat.id)
254
+ if cached_chat is None:
255
+ self.chats.append(chat)
256
+ else:
257
+ idx = self.chats.index(cached_chat)
258
+ self.chats[idx] = chat
259
+
260
+ return chat
261
+
262
+ except Exception:
263
+ self.logger.exception("Join group failed")
264
+ return None
@@ -1,7 +1,7 @@
1
1
  from pymax.interfaces import ClientProtocol
2
2
  from pymax.payloads import FetchContactsPayload, SearchByPhonePayload
3
3
  from pymax.static import Opcode
4
- from pymax.types import User
4
+ from pymax.types import Session, User
5
5
 
6
6
 
7
7
  class UserMixin(ClientProtocol):
@@ -115,3 +115,19 @@ class UserMixin(ClientProtocol):
115
115
  except Exception:
116
116
  self.logger.exception("Search by phone failed")
117
117
  return None
118
+
119
+ async def get_sessions(self) -> list[Session] | None:
120
+ try:
121
+ self.logger.info("Fetching sessions")
122
+
123
+ data = await self._send_and_wait(opcode=Opcode.SESSIONS_INFO, payload={})
124
+
125
+ if error := data.get("payload", {}).get("error"):
126
+ self.logger.error("Fetching sessions error: %s", error)
127
+ return None
128
+
129
+ return [Session.from_dict(s) for s in data["payload"].get("sessions", [])]
130
+
131
+ except Exception:
132
+ self.logger.exception("Fetching sessions failed")
133
+ return None
@@ -209,3 +209,7 @@ class GetFilePayload(CamelModel):
209
209
 
210
210
  class SearchByPhonePayload(CamelModel):
211
211
  phone: str
212
+
213
+
214
+ class JoinGroupPayload(CamelModel):
215
+ link: str
@@ -294,6 +294,29 @@ class Element:
294
294
  return f"{self.type}({self.length})"
295
295
 
296
296
 
297
+ class MessageLink:
298
+ def __init__(self, chat_id: int, message: "Message", type: str) -> None:
299
+ self.chat_id = chat_id
300
+ self.message = message
301
+ self.type = type
302
+
303
+ @classmethod
304
+ def from_dict(cls, data: dict[str, Any]) -> "MessageLink":
305
+ return cls(
306
+ chat_id=data["chatId"],
307
+ message=Message.from_dict(data["message"]),
308
+ type=data["type"],
309
+ )
310
+
311
+ @override
312
+ def __repr__(self) -> str:
313
+ return f"MessageLink(chat_id={self.chat_id!r}, message={self.message!r}, type={self.type!r})"
314
+
315
+ @override
316
+ def __str__(self) -> str:
317
+ return f"MessageLink: {self.chat_id}/{self.message.id}"
318
+
319
+
297
320
  class Message:
298
321
  def __init__(
299
322
  self,
@@ -304,6 +327,7 @@ class Message:
304
327
  options: int | None,
305
328
  id: int,
306
329
  time: int,
330
+ link: MessageLink | None,
307
331
  text: str,
308
332
  status: MessageStatus | str | None,
309
333
  type: MessageType | str,
@@ -319,6 +343,7 @@ class Message:
319
343
  self.type = type
320
344
  self.attaches = attaches
321
345
  self.status = status
346
+ self.link = link
322
347
  self.reactionInfo = reaction_info
323
348
 
324
349
  @classmethod
@@ -345,6 +370,9 @@ class Message:
345
370
  type=message["type"],
346
371
  attaches=attaches,
347
372
  status=message.get("status"),
373
+ link=MessageLink.from_dict(message.get("link"))
374
+ if message.get("link")
375
+ else None,
348
376
  reaction_info=message.get("reactionInfo"),
349
377
  )
350
378
 
@@ -642,3 +670,40 @@ class Attach: # УБРАТЬ ГАДА!!! или нет...
642
670
  @override
643
671
  def __str__(self) -> str:
644
672
  return f"Attach: {self.type}"
673
+
674
+
675
+ class Session:
676
+ def __init__(
677
+ self,
678
+ client: str,
679
+ info: str,
680
+ location: str,
681
+ time: int,
682
+ current: bool | None = None,
683
+ ) -> None:
684
+ self.client = client
685
+ self.info = info
686
+ self.location = location
687
+ self.time = time
688
+ self.current = current if current is not None else False
689
+
690
+ @classmethod
691
+ def from_dict(cls, data: dict[str, Any]) -> "Session":
692
+ return cls(
693
+ client=data["client"],
694
+ info=data["info"],
695
+ location=data["location"],
696
+ time=data["time"],
697
+ current=data.get("current"),
698
+ )
699
+
700
+ @override
701
+ def __repr__(self) -> str:
702
+ return (
703
+ f"Session(client={self.client!r}, info={self.info!r}, "
704
+ f"location={self.location!r}, time={self.time!r}, current={self.current!r})"
705
+ )
706
+
707
+ @override
708
+ def __str__(self) -> str:
709
+ return f"Session: {self.client} from {self.location} at {self.time} (current={self.current})"
@@ -1,62 +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
- @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.CONTROL:
31
- print(attach.event)
32
- print(attach.extra)
33
- # if attach.type == AttachType.VIDEO:
34
- # print(message)
35
- # vid = await client.get_video_by_id(
36
- # chat_id=0,
37
- # video_id=attach.video_id,
38
- # message_id=message.id,
39
- # )
40
- # print(vid.url)
41
- # elif attach.type == AttachType.FILE:
42
- # file = await client.get_file_by_id(
43
- # chat_id=0,
44
- # file_id=attach.file_id,
45
- # message_id=message.id,
46
- # )
47
- # print(file.url)
48
- # print(client.me.names[0].first_name)
49
- # user = await client.get_user(client.me.id)
50
-
51
- # print(user.names[0].first_name)
52
-
53
- # photo1 = Photo(path="tests/test.jpeg")
54
- # photo2 = Photo(path="tests/test.jpg")
55
-
56
- # await client.send_message(
57
- # "Hello with photo!", chat_id=0, photos=[photo1, photo2], notify=True
58
- # )
59
-
60
-
61
- if __name__ == "__main__":
62
- asyncio.run(client.start())
File without changes
File without changes
File without changes
File without changes
File without changes