maxapi-python 0.1.2__py3-none-any.whl → 1.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxapi-python
3
- Version: 0.1.2
3
+ Version: 1.0.1
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
@@ -14,9 +14,11 @@ Classifier: Programming Language :: Python :: 3
14
14
  Requires-Python: >=3.10
15
15
  Requires-Dist: aiofiles>=24.1.0
16
16
  Requires-Dist: aiohttp>=3.12.15
17
+ Requires-Dist: build>=1.3.0
17
18
  Requires-Dist: lz4>=4.4.4
18
19
  Requires-Dist: msgpack>=1.1.1
19
20
  Requires-Dist: sqlmodel>=0.0.24
21
+ Requires-Dist: twine>=6.2.0
20
22
  Requires-Dist: websockets>=11.0
21
23
  Description-Content-Type: text/markdown
22
24
 
@@ -0,0 +1,27 @@
1
+ pymax/__init__.py,sha256=LiFVkKUiO1OTVppKcpnVON1xiH3j4XXgCuUCMMzSkmM,895
2
+ pymax/core.py,sha256=1X0_-r8E5_mtRhdKDapueYSy_YG9sUUtiGenQ2-FqyY,6472
3
+ pymax/crud.py,sha256=Mk-c87GItS91BlJu6INDbw1-ovXyoB2D9rXHK8voxpU,3207
4
+ pymax/exceptions.py,sha256=msS11MD7qZPm0qZ6O8fobTm-GTldm2IA3uQLTX6eDxc,919
5
+ pymax/files.py,sha256=Tpv-43gS7I4Pwlaimb8mZ2B-ZkF3aMsrLYT20NPaqhE,2656
6
+ pymax/filters.py,sha256=EejNuJMmSBhw3bUqDoqXEnCnLjGy_sw5aH3Vynpxc0A,1306
7
+ pymax/interfaces.py,sha256=jAJRDu7_SVCaM5YJShWvoBPpCSEu7jsyNS8eDp8RMJk,2231
8
+ pymax/models.py,sha256=PsPGbOkERxesZZltjNrmqhOfRcO44Is2ThbEToREcB8,201
9
+ pymax/navigation.py,sha256=16c1_FZrw24uFlP6W5-F8OrEQE73bkQA3HSFqTdBtgo,5725
10
+ pymax/payloads.py,sha256=yeBRxiMq6ixUQjMBBFcBDtBpYzqqfaEII3Z1kJq8pe8,3907
11
+ pymax/static.py,sha256=rLWpGtDF12i9OoBANKUAgzfRqfoP88Zb9JCY2zNl834,4697
12
+ pymax/types.py,sha256=_lYohGaGEjrJBhloR-SC3qdn7RXdUigYWJ9NWti0dCg,13738
13
+ pymax/utils.py,sha256=F2TdoWfSwDLeh2uIcMIE_GTdXd7hU7gWti2i5P727bA,1364
14
+ pymax/mixins/__init__.py,sha256=KQJg77oZewBga1wsltHOYfVFhobkB7ZMC3Pv0eLzEO8,411
15
+ pymax/mixins/auth.py,sha256=vTNSZ6AunvDIMPQAvgYozpIZaCWMYiMDiabCBI7Sm6c,3079
16
+ pymax/mixins/channel.py,sha256=Stnf63GPtlQnsMPVEC9P0oardEOz50I4DCXN5H5s1SM,823
17
+ pymax/mixins/group.py,sha256=QJCd5MLYCVRrClcuAuRkLV3oylJRAOaGw0xUqFm2uXk,7820
18
+ pymax/mixins/handler.py,sha256=I1iNPaEgpvFnphaxV6liLwVaBCJ8sN6-h7908-_tPFk,2104
19
+ pymax/mixins/message.py,sha256=0y7fO61zg9XG46SKxOPCOLMJW1LJBWfTi-PR3HjJfJo,10249
20
+ pymax/mixins/self.py,sha256=V0gbkY3jfX9fnd7v06n4_s7P3HTPcS1KNPqUzA0vNi8,1169
21
+ pymax/mixins/telemetry.py,sha256=0sQl6kvFVxobLthNAPNS9LzMrwwzZFA1xmOnvfiHWos,3522
22
+ pymax/mixins/user.py,sha256=U-epgvLruTDHBCrLDE0N0iWeOypGE1_SU8cKD3TE90U,3045
23
+ pymax/mixins/websocket.py,sha256=ru7QoYOFd8J2fDVG9GZYHLeYrZme-xuisNiCvm9P82Y,10076
24
+ maxapi_python-1.0.1.dist-info/METADATA,sha256=vAKzkTZo3RfAg9R3ZrQWZ9MKMccyFSKzGOIaouzSo2A,5986
25
+ maxapi_python-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ maxapi_python-1.0.1.dist-info/licenses/LICENSE,sha256=oe-AGp86WMKawV4KmqF28Q0m-kGAhPfAOPrEUm4MnVw,1064
27
+ maxapi_python-1.0.1.dist-info/RECORD,,
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 noxzion
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 noxzion
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pymax/__init__.py CHANGED
@@ -1,55 +1,55 @@
1
- """
2
- Python wrapper для API мессенджера Max
3
- """
4
-
5
- from .core import (
6
- InvalidPhoneError,
7
- MaxClient,
8
- WebSocketNotConnectedError,
9
- )
10
- from .static import (
11
- AccessType,
12
- AuthType,
13
- ChatType,
14
- Constants,
15
- DeviceType,
16
- ElementType,
17
- MessageStatus,
18
- MessageType,
19
- Opcode,
20
- )
21
- from .types import (
22
- Channel,
23
- Chat,
24
- Dialog,
25
- Element,
26
- Message,
27
- User,
28
- )
29
-
30
- __author__ = "noxzion"
31
-
32
- __all__ = [
33
- # Перечисления и константы
34
- "AccessType",
35
- "AuthType",
36
- # Типы данных
37
- "Channel",
38
- "Chat",
39
- "ChatType",
40
- "Constants",
41
- "DeviceType",
42
- "Dialog",
43
- "Element",
44
- "ElementType",
45
- # Исключения
46
- "InvalidPhoneError",
47
- # Клиент
48
- "MaxClient",
49
- "Message",
50
- "MessageStatus",
51
- "MessageType",
52
- "Opcode",
53
- "User",
54
- "WebSocketNotConnectedError",
55
- ]
1
+ """
2
+ Python wrapper для API мессенджера Max
3
+ """
4
+
5
+ from .core import (
6
+ InvalidPhoneError,
7
+ MaxClient,
8
+ WebSocketNotConnectedError,
9
+ )
10
+ from .static import (
11
+ AccessType,
12
+ AuthType,
13
+ ChatType,
14
+ Constants,
15
+ DeviceType,
16
+ ElementType,
17
+ MessageStatus,
18
+ MessageType,
19
+ Opcode,
20
+ )
21
+ from .types import (
22
+ Channel,
23
+ Chat,
24
+ Dialog,
25
+ Element,
26
+ Message,
27
+ User,
28
+ )
29
+
30
+ __author__ = "noxzion"
31
+
32
+ __all__ = [
33
+ # Перечисления и константы
34
+ "AccessType",
35
+ "AuthType",
36
+ # Типы данных
37
+ "Channel",
38
+ "Chat",
39
+ "ChatType",
40
+ "Constants",
41
+ "DeviceType",
42
+ "Dialog",
43
+ "Element",
44
+ "ElementType",
45
+ # Исключения
46
+ "InvalidPhoneError",
47
+ # Клиент
48
+ "MaxClient",
49
+ "Message",
50
+ "MessageStatus",
51
+ "MessageType",
52
+ "Opcode",
53
+ "User",
54
+ "WebSocketNotConnectedError",
55
+ ]
pymax/core.py CHANGED
@@ -1,156 +1,170 @@
1
- import asyncio
2
- import json
3
- import logging
4
- import re
5
- import time
6
- from collections.abc import Awaitable, Callable
7
- from pathlib import Path
8
- from typing import TYPE_CHECKING, Any
9
-
10
- import websockets
11
-
12
- from .crud import Database
13
- from .exceptions import InvalidPhoneError, WebSocketNotConnectedError
14
- from .mixins import ApiMixin, WebSocketMixin
15
- from .payloads import (
16
- BaseWebSocketMessage,
17
- SyncPayload,
18
- )
19
- from .static import ChatType, Constants, Opcode
20
- from .types import Channel, Chat, Dialog, Me, Message, User, override
21
-
22
- if TYPE_CHECKING:
23
- from .filters import Filter
24
-
25
- logger = logging.getLogger(__name__)
26
-
27
-
28
- class MaxClient(ApiMixin, WebSocketMixin):
29
- """
30
- Основной клиент для работы с WebSocket API сервиса Max.
31
-
32
-
33
- Args:
34
- phone (str): Номер телефона для авторизации.
35
- uri (str, optional): URI WebSocket сервера. По умолчанию Constants.WEBSOCKET_URI.value.
36
- work_dir (str, optional): Рабочая директория для хранения базы данных. По умолчанию ".".
37
- logger (logging.Logger | None): Пользовательский логгер. Если не передан — используется
38
- логгер модуля с именем f"{__name__}.MaxClient".
39
-
40
- Raises:
41
- InvalidPhoneError: Если формат номера телефона неверный.
42
- """
43
-
44
- def __init__(
45
- self,
46
- phone: str,
47
- uri: str = Constants.WEBSOCKET_URI.value,
48
- headers: dict[str, Any] | None = Constants.DEFAULT_USER_AGENT.value,
49
- token: str | None = None,
50
- work_dir: str = ".",
51
- logger: logging.Logger | None = None,
52
- ) -> None:
53
- self.uri: str = uri
54
- self.is_connected: bool = False
55
- self.phone: str = phone
56
- self.chats: list[Chat] = []
57
- self.dialogs: list[Dialog] = []
58
- self.channels: list[Channel] = []
59
- self.me: Me | None = None
60
- self._users: dict[int, User] = {}
61
- if not self._check_phone():
62
- raise InvalidPhoneError(self.phone)
63
- self._work_dir: str = work_dir
64
- self._database_path: Path = Path(work_dir) / "session.db"
65
- self._database_path.parent.mkdir(parents=True, exist_ok=True)
66
- self._database_path.touch(exist_ok=True)
67
- self._database = Database(self._work_dir)
68
- self._ws: websockets.ClientConnection | None = None
69
- self._seq: int = 0
70
- self._pending: dict[int, asyncio.Future[dict[str, Any]]] = {}
71
- self._recv_task: asyncio.Task[Any] | None = None
72
- self._incoming: asyncio.Queue[dict[str, Any]] | None = None
73
- self._device_id = self._database.get_device_id()
74
- self._token = self._database.get_auth_token() or token
75
- self.user_agent = headers
76
- self._on_message_handlers: list[
77
- tuple[Callable[[Message], Any], Filter | None]
78
- ] = []
79
- self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
80
- self._background_tasks: set[asyncio.Task[Any]] = set()
81
- self.logger = logger or logging.getLogger(f"{__name__}.MaxClient")
82
- self._setup_logger()
83
-
84
- self.logger.debug(
85
- "Initialized MaxClient uri=%s work_dir=%s", self.uri, self._work_dir
86
- )
87
-
88
- def _setup_logger(self) -> None:
89
- self.logger.setLevel(logging.INFO)
90
-
91
- if not logger.handlers:
92
- handler = logging.StreamHandler()
93
- formatter = logging.Formatter(
94
- "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
95
- )
96
- handler.setFormatter(formatter)
97
- logger.addHandler(handler)
98
-
99
- async def close(self) -> None:
100
- try:
101
- self.logger.info("Closing client")
102
- if self._recv_task:
103
- self._recv_task.cancel()
104
- try:
105
- await self._recv_task
106
- except asyncio.CancelledError:
107
- self.logger.debug("recv_task cancelled")
108
- if self._ws:
109
- await self._ws.close()
110
- self.is_connected = False
111
- self.logger.info("Client closed")
112
- except Exception:
113
- self.logger.exception("Error closing client")
114
-
115
- async def start(self) -> None:
116
- """
117
- Запускает клиент, подключается к WebSocket, авторизует
118
- пользователя (если нужно) и запускает фоновый цикл.
119
- """
120
- try:
121
- self.logger.info("Client starting")
122
- await self._connect(self.user_agent)
123
-
124
- if self._token and self._database.get_auth_token() is None:
125
- self._database.update_auth_token(self._device_id, self._token)
126
-
127
- if self._token is None:
128
- await self._login()
129
- else:
130
- await self._sync()
131
-
132
- if self._on_start_handler:
133
- self.logger.debug("Calling on_start handler")
134
- result = self._on_start_handler()
135
- if asyncio.iscoroutine(result):
136
- await result
137
-
138
- if self._ws:
139
- ping_task = asyncio.create_task(self._send_interactive_ping())
140
- self._background_tasks.add(ping_task)
141
- ping_task.add_done_callback(
142
- lambda t: self._background_tasks.discard(t)
143
- or self._log_task_exception(t)
144
- )
145
-
146
- try:
147
- await self._ws.wait_closed()
148
- except asyncio.CancelledError:
149
- self.logger.debug("wait_closed cancelled")
150
- except Exception:
151
- self.logger.exception("Client start failed")
152
-
153
-
154
- class SocketMaxClient:
155
- pass # нокс займись
156
- # нет не займусь
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import re
5
+ import time
6
+ from collections.abc import Awaitable, Callable
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ import websockets
11
+
12
+ from .crud import Database
13
+ from .exceptions import InvalidPhoneError, WebSocketNotConnectedError
14
+ from .mixins import ApiMixin, WebSocketMixin
15
+ from .payloads import (
16
+ BaseWebSocketMessage,
17
+ SyncPayload,
18
+ )
19
+ from .static import ChatType, Constants, Opcode
20
+ from .types import Channel, Chat, Dialog, Me, Message, User, override
21
+
22
+ if TYPE_CHECKING:
23
+ from .filters import Filter
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class MaxClient(ApiMixin, WebSocketMixin):
29
+ """
30
+ Основной клиент для работы с WebSocket API сервиса Max.
31
+
32
+
33
+ Args:
34
+ phone (str): Номер телефона для авторизации.
35
+ uri (str, optional): URI WebSocket сервера. По умолчанию Constants.WEBSOCKET_URI.value.
36
+ work_dir (str, optional): Рабочая директория для хранения базы данных. По умолчанию ".".
37
+ logger (logging.Logger | None): Пользовательский логгер. Если не передан — используется
38
+ логгер модуля с именем f"{__name__}.MaxClient".
39
+
40
+ Raises:
41
+ InvalidPhoneError: Если формат номера телефона неверный.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ phone: str,
47
+ uri: str = Constants.WEBSOCKET_URI.value,
48
+ headers: dict[str, Any] | None = Constants.DEFAULT_USER_AGENT.value,
49
+ token: str | None = None,
50
+ send_fake_telemetry: bool = True,
51
+ work_dir: str = ".",
52
+ logger: logging.Logger | None = None,
53
+ ) -> None:
54
+ self.uri: str = uri
55
+ self.is_connected: bool = False
56
+ self.phone: str = phone
57
+ self.chats: list[Chat] = []
58
+ self.dialogs: list[Dialog] = []
59
+ self.channels: list[Channel] = []
60
+ self.me: Me | None = None
61
+ self._users: dict[int, User] = {}
62
+ if not self._check_phone():
63
+ raise InvalidPhoneError(self.phone)
64
+ self._work_dir: str = work_dir
65
+ self._database_path: Path = Path(work_dir) / "session.db"
66
+ self._database_path.parent.mkdir(parents=True, exist_ok=True)
67
+ self._database_path.touch(exist_ok=True)
68
+ self._database = Database(self._work_dir)
69
+ self._ws: websockets.ClientConnection | None = None
70
+ self._seq: int = 0
71
+ self._pending: dict[int, asyncio.Future[dict[str, Any]]] = {}
72
+ self._recv_task: asyncio.Task[Any] | None = None
73
+ self._incoming: asyncio.Queue[dict[str, Any]] | None = None
74
+ self._device_id = self._database.get_device_id()
75
+ self._token = self._database.get_auth_token() or token
76
+ self.user_agent = headers
77
+
78
+ self._send_fake_telemetry: bool = send_fake_telemetry
79
+ self._session_id: int = int(time.time() * 1000)
80
+ self._action_id: int = 1
81
+ self._current_screen: str = "chats_list_tab"
82
+
83
+ self._on_message_handlers: list[
84
+ tuple[Callable[[Message], Any], Filter | None]
85
+ ] = []
86
+ self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
87
+ self._background_tasks: set[asyncio.Task[Any]] = set()
88
+ self.logger = logger or logging.getLogger(f"{__name__}.MaxClient")
89
+ self._setup_logger()
90
+
91
+ self.logger.debug(
92
+ "Initialized MaxClient uri=%s work_dir=%s", self.uri, self._work_dir
93
+ )
94
+
95
+ def _setup_logger(self) -> None:
96
+ self.logger.setLevel(logging.INFO)
97
+
98
+ if not logger.handlers:
99
+ handler = logging.StreamHandler()
100
+ formatter = logging.Formatter(
101
+ "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
102
+ )
103
+ handler.setFormatter(formatter)
104
+ logger.addHandler(handler)
105
+
106
+ async def close(self) -> None:
107
+ try:
108
+ self.logger.info("Closing client")
109
+ if self._recv_task:
110
+ self._recv_task.cancel()
111
+ try:
112
+ await self._recv_task
113
+ except asyncio.CancelledError:
114
+ self.logger.debug("recv_task cancelled")
115
+ if self._ws:
116
+ await self._ws.close()
117
+ self.is_connected = False
118
+ self.logger.info("Client closed")
119
+ except Exception:
120
+ self.logger.exception("Error closing client")
121
+
122
+ async def start(self) -> None:
123
+ """
124
+ Запускает клиент, подключается к WebSocket, авторизует
125
+ пользователя (если нужно) и запускает фоновый цикл.
126
+ """
127
+ try:
128
+ self.logger.info("Client starting")
129
+ await self._connect(self.user_agent)
130
+
131
+ if self._token and self._database.get_auth_token() is None:
132
+ self._database.update_auth_token(self._device_id, self._token)
133
+
134
+ if self._token is None:
135
+ await self._login()
136
+ else:
137
+ await self._sync()
138
+
139
+ if self._on_start_handler:
140
+ self.logger.debug("Calling on_start handler")
141
+ result = self._on_start_handler()
142
+ if asyncio.iscoroutine(result):
143
+ await result
144
+
145
+ if self._ws:
146
+ ping_task = asyncio.create_task(self._send_interactive_ping())
147
+ if self._send_fake_telemetry:
148
+ telemetry_task = asyncio.create_task(self._start())
149
+ self._background_tasks.add(telemetry_task)
150
+ telemetry_task.add_done_callback(
151
+ lambda t: self._background_tasks.discard(t)
152
+ or self._log_task_exception(t)
153
+ )
154
+ self._background_tasks.add(ping_task)
155
+ ping_task.add_done_callback(
156
+ lambda t: self._background_tasks.discard(t)
157
+ or self._log_task_exception(t)
158
+ )
159
+
160
+ try:
161
+ await self._ws.wait_closed()
162
+ except asyncio.CancelledError:
163
+ self.logger.debug("wait_closed cancelled")
164
+ except Exception:
165
+ self.logger.exception("Client start failed")
166
+
167
+
168
+ class SocketMaxClient:
169
+ pass # нокс займись
170
+ # нет не займусь