maxapi-python 0.1.1__py3-none-any.whl → 0.1.2__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.
pymax/static.py ADDED
@@ -0,0 +1,210 @@
1
+ from enum import Enum, IntEnum
2
+
3
+
4
+ class Opcode(IntEnum):
5
+ PING = 1
6
+ DEBUG = 2
7
+ RECONNECT = 3
8
+ LOG = 5
9
+ SESSION_INIT = 6
10
+ PROFILE = 16
11
+ AUTH_REQUEST = 17
12
+ AUTH = 18
13
+ LOGIN = 19
14
+ LOGOUT = 20
15
+ SYNC = 21
16
+ CONFIG = 22
17
+ AUTH_CONFIRM = 23
18
+ PRESET_AVATARS = 25
19
+ ASSETS_GET = 26
20
+ UNKNOWN_26 = 26
21
+ ASSETS_UPDATE = 27
22
+ ASSETS_GET_BY_IDS = 28
23
+ ASSETS_ADD = 29
24
+ SEARCH_FEEDBACK = 31
25
+ CONTACT_INFO = 32
26
+ CONTACT_ADD = 33
27
+ CONTACT_UPDATE = 34
28
+ CONTACT_PRESENCE = 35
29
+ CONTACT_LIST = 36
30
+ CONTACT_SEARCH = 37
31
+ CONTACT_MUTUAL = 38
32
+ CONTACT_PHOTOS = 39
33
+ CONTACT_SORT = 40
34
+ CONTACT_VERIFY = 42
35
+ REMOVE_CONTACT_PHOTO = 43
36
+ CONTACT_INFO_BY_PHONE = 46
37
+ CHAT_INFO = 48
38
+ CHAT_HISTORY = 49
39
+ CHAT_MARK = 50
40
+ CHAT_MEDIA = 51
41
+ CHAT_DELETE = 52
42
+ CHATS_LIST = 53
43
+ CHAT_CLEAR = 54
44
+ CHAT_UPDATE = 55
45
+ CHAT_CHECK_LINK = 56
46
+ CHAT_JOIN = 57
47
+ CHAT_LEAVE = 58
48
+ CHAT_MEMBERS = 59
49
+ PUBLIC_SEARCH = 60
50
+ CHAT_CLOSE = 61
51
+ CHAT_CREATE = 63
52
+ MSG_SEND = 64
53
+ MSG_TYPING = 65
54
+ MSG_DELETE = 66
55
+ MSG_EDIT = 67
56
+ CHAT_SEARCH = 68
57
+ MSG_SHARE_PREVIEW = 70
58
+ MSG_GET = 71
59
+ MSG_SEARCH_TOUCH = 72
60
+ MSG_SEARCH = 73
61
+ MSG_GET_STAT = 74
62
+ CHAT_SUBSCRIBE = 75
63
+ VIDEO_CHAT_START = 76
64
+ CHAT_MEMBERS_UPDATE = 77
65
+ VIDEO_CHAT_HISTORY = 79
66
+ PHOTO_UPLOAD = 80
67
+ STICKER_UPLOAD = 81
68
+ VIDEO_UPLOAD = 82
69
+ VIDEO_PLAY = 83
70
+ CHAT_PIN_SET_VISIBILITY = 86
71
+ FILE_UPLOAD = 87
72
+ FILE_DOWNLOAD = 88
73
+ LINK_INFO = 89
74
+ MSG_DELETE_RANGE = 92
75
+ SESSIONS_INFO = 96
76
+ SESSIONS_CLOSE = 97
77
+ PHONE_BIND_REQUEST = 98
78
+ PHONE_BIND_CONFIRM = 99
79
+ CONFIRM_PRESENT = 101
80
+ GET_INBOUND_CALLS = 103
81
+ EXTERNAL_CALLBACK = 105
82
+ AUTH_VALIDATE_PASSWORD = 107
83
+ AUTH_VALIDATE_HINT = 108
84
+ AUTH_VERIFY_EMAIL = 109
85
+ AUTH_CHECK_EMAIL = 110
86
+ AUTH_SET_2FA = 111
87
+ AUTH_CREATE_TRACK = 112
88
+ AUTH_LOGIN_CHECK_PASSWORD = 115
89
+ CHAT_COMPLAIN = 117
90
+ MSG_SEND_CALLBACK = 118
91
+ SUSPEND_BOT = 119
92
+ LOCATION_STOP = 124
93
+ LOCATION_SEND = 125
94
+ LOCATION_REQUEST = 126
95
+ GET_LAST_MENTIONS = 127
96
+ NOTIF_MESSAGE = 128
97
+ NOTIF_TYPING = 129
98
+ NOTIF_MARK = 130
99
+ NOTIF_CONTACT = 131
100
+ NOTIF_PRESENCE = 132
101
+ NOTIF_CONFIG = 134
102
+ NOTIF_CHAT = 135
103
+ NOTIF_ATTACH = 136
104
+ NOTIF_CALL_START = 137
105
+ NOTIF_CONTACT_SORT = 139
106
+ NOTIF_MSG_DELETE_RANGE = 140
107
+ NOTIF_MSG_DELETE = 142
108
+ NOTIF_CALLBACK_ANSWER = 143
109
+ CHAT_BOT_COMMANDS = 144
110
+ BOT_INFO = 145
111
+ NOTIF_LOCATION = 147
112
+ NOTIF_LOCATION_REQUEST = 148
113
+ NOTIF_ASSETS_UPDATE = 150
114
+ NOTIF_DRAFT = 152
115
+ NOTIF_DRAFT_DISCARD = 153
116
+ NOTIF_MSG_DELAYED = 154
117
+ NOTIF_MSG_REACTIONS_CHANGED = 155
118
+ NOTIF_MSG_YOU_REACTED = 156
119
+ CALLS_TOKEN = 158
120
+ NOTIF_PROFILE = 159
121
+ WEB_APP_INIT_DATA = 160
122
+ DRAFT_SAVE = 176
123
+ DRAFT_DISCARD = 177
124
+ MSG_REACTION = 178
125
+ MSG_CANCEL_REACTION = 179
126
+ MSG_GET_REACTIONS = 180
127
+ MSG_GET_DETAILED_REACTIONS = 181
128
+ STICKER_CREATE = 193
129
+ STICKER_SUGGEST = 194
130
+ VIDEO_CHAT_MEMBERS = 195
131
+ CHAT_HIDE = 196
132
+ CHAT_SEARCH_COMMON_PARTICIPANTS = 198
133
+ PROFILE_DELETE = 199
134
+ PROFILE_DELETE_TIME = 200
135
+ ASSETS_REMOVE = 259
136
+ ASSETS_MOVE = 260
137
+ ASSETS_LIST_MODIFY = 261
138
+ FOLDERS_GET = 272
139
+ FOLDERS_GET_BY_ID = 273
140
+ FOLDERS_UPDATE = 274
141
+ FOLDERS_REORDER = 275
142
+ FOLDERS_DELETE = 276
143
+ NOTIF_FOLDERS = 277
144
+
145
+
146
+ class ChatType(str, Enum):
147
+ DIALOG = "DIALOG"
148
+ CHAT = "CHAT"
149
+ CHANNEL = "CHANNEL"
150
+
151
+
152
+ class MessageType(str, Enum):
153
+ TEXT = "TEXT"
154
+ SYSTEM = "SYSTEM"
155
+ SERVICE = "SERVICE"
156
+
157
+
158
+ class MessageStatus(str, Enum):
159
+ SENT = "SENT"
160
+ DELIVERED = "DELIVERED"
161
+ READ = "READ"
162
+ ERROR = "ERROR"
163
+
164
+
165
+ class ElementType(str, Enum):
166
+ TEXT = "text"
167
+ MENTION = "mention"
168
+ LINK = "link"
169
+ EMOJI = "emoji"
170
+
171
+
172
+ class AuthType(str, Enum):
173
+ START_AUTH = "START_AUTH"
174
+ CHECK_CODE = "CHECK_CODE"
175
+
176
+
177
+ class AccessType(str, Enum):
178
+ PUBLIC = "PUBLIC"
179
+ PRIVATE = "PRIVATE"
180
+ SECRET = "SECRET"
181
+
182
+
183
+ class DeviceType(str, Enum):
184
+ WEB = "WEB"
185
+ ANDROID = "ANDROID"
186
+ IOS = "IOS"
187
+
188
+
189
+ class AttachType(str, Enum):
190
+ PHOTO = "PHOTO"
191
+ VIDEO = "VIDEO"
192
+ FILE = "FILE"
193
+ STICKER = "STICKER"
194
+
195
+
196
+ class Constants(Enum):
197
+ PHONE_REGEX = r"^\+?\d{10,15}$"
198
+ WEBSOCKET_URI = "wss://ws-api.oneme.ru/websocket"
199
+ DEFAULT_TIMEOUT = 10.0
200
+ DEFAULT_USER_AGENT = {
201
+ "deviceType": "WEB",
202
+ "locale": "ru",
203
+ "deviceLocale": "ru",
204
+ "osVersion": "Linux",
205
+ "deviceName": "Chrome",
206
+ "headerUserAgent": "Mozilla/5.0 ...",
207
+ "appVersion": "25.8.5",
208
+ "screen": "1080x1920 1.0x",
209
+ "timezone": "Europe/Moscow",
210
+ }
pymax/types.py ADDED
@@ -0,0 +1,432 @@
1
+ from typing import Any, override
2
+
3
+ from .static import (
4
+ AccessType,
5
+ AttachType,
6
+ ChatType,
7
+ ElementType,
8
+ MessageStatus,
9
+ MessageType,
10
+ )
11
+
12
+
13
+ class Names:
14
+ def __init__(
15
+ self, name: str, first_name: str, last_name: str | None, type: str
16
+ ) -> None:
17
+ self.name = name
18
+ self.first_name = first_name
19
+ self.last_name = last_name
20
+ self.type = type
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: dict[str, Any]) -> "Names":
24
+ return cls(
25
+ name=data["name"],
26
+ first_name=data["firstName"],
27
+ last_name=data.get("lastName"),
28
+ type=data["type"],
29
+ )
30
+
31
+ @override
32
+ def __repr__(self) -> str:
33
+ return f"Names(name={self.name!r}, first_name={self.first_name!r}, last_name={self.last_name!r}, type={self.type!r})"
34
+
35
+ @override
36
+ def __str__(self) -> str:
37
+ return self.name
38
+
39
+
40
+ class Me:
41
+ def __init__(
42
+ self,
43
+ id: int,
44
+ account_status: int,
45
+ phone: str,
46
+ names: list[Names],
47
+ update_time: int,
48
+ options: list[str] | None = None,
49
+ ) -> None:
50
+ self.id = id
51
+ self.account_status = account_status
52
+ self.phone = phone
53
+ self.update_time = update_time
54
+ self.options = options
55
+ self.names = names
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: dict[str, Any]) -> "Me":
59
+ return cls(
60
+ id=data["id"],
61
+ account_status=data["accountStatus"],
62
+ phone=data["phone"],
63
+ names=[Names.from_dict(n) for n in data["names"]],
64
+ update_time=data["updateTime"],
65
+ options=data.get("options"),
66
+ )
67
+
68
+ @override
69
+ def __repr__(self) -> str:
70
+ return f"Me(id={self.id!r}, account_status={self.account_status!r}, phone={self.phone!r}, names={self.names!r}, update_time={self.update_time!r}, options={self.options!r})"
71
+
72
+ @override
73
+ def __str__(self) -> str:
74
+ return f"Me {self.id}: {', '.join(str(n) for n in self.names)}"
75
+
76
+
77
+ class Element:
78
+ def __init__(
79
+ self, type: ElementType | str, length: int, from_: int | None = None
80
+ ) -> None:
81
+ self.type = type
82
+ self.length = length
83
+ self.from_ = from_
84
+
85
+ @classmethod
86
+ def from_dict(cls, data: dict[Any, Any]) -> "Element":
87
+ return cls(type=data["type"], length=data["length"], from_=data.get("from"))
88
+
89
+ @override
90
+ def __repr__(self) -> str:
91
+ return (
92
+ f"Element(type={self.type!r}, length={self.length!r}, from_={self.from_!r})"
93
+ )
94
+
95
+ @override
96
+ def __str__(self) -> str:
97
+ return f"{self.type}({self.length})"
98
+
99
+
100
+ class Message:
101
+ def __init__(
102
+ self,
103
+ sender: int | None,
104
+ elements: list[Element] | None,
105
+ reaction_info: dict[str, Any] | None,
106
+ options: int | None,
107
+ id: int,
108
+ time: int,
109
+ text: str,
110
+ status: MessageStatus | str | None,
111
+ type: MessageType | str,
112
+ attaches: list[Any],
113
+ ) -> None:
114
+ self.sender = sender
115
+ self.elements = elements
116
+ self.options = options
117
+ self.id = id
118
+ self.time = time
119
+ self.text = text
120
+ self.type = type
121
+ self.attaches = attaches
122
+ self.status = status
123
+ self.reactionInfo = reaction_info
124
+
125
+ @classmethod
126
+ def from_dict(cls, data: dict[Any, Any]) -> "Message":
127
+ return cls(
128
+ sender=data.get("sender"),
129
+ elements=[Element.from_dict(e) for e in data.get("elements", [])],
130
+ options=data.get("options"),
131
+ id=data["id"],
132
+ time=data["time"],
133
+ text=data["text"],
134
+ type=data["type"],
135
+ attaches=data.get("attaches", []),
136
+ status=data.get("status"),
137
+ reaction_info=data.get("reactionInfo"),
138
+ )
139
+
140
+ @override
141
+ def __repr__(self) -> str:
142
+ return (
143
+ f"Message(id={self.id!r}, sender={self.sender!r}, text={self.text!r}, "
144
+ f"type={self.type!r}, status={self.status!r}, elements={self.elements!r})"
145
+ )
146
+
147
+ @override
148
+ def __str__(self) -> str:
149
+ return f"Message {self.id} from {self.sender}: {self.text}"
150
+
151
+
152
+ class Dialog:
153
+ def __init__(
154
+ self,
155
+ cid: int | None,
156
+ owner: int,
157
+ has_bots: bool | None,
158
+ join_time: int,
159
+ created: int,
160
+ last_message: Message | None,
161
+ type: ChatType | str,
162
+ last_fire_delayed_error_time: int,
163
+ last_delayed_update_time: int,
164
+ prev_message_id: str | None,
165
+ options: dict[str, bool],
166
+ modified: int,
167
+ last_event_time: int,
168
+ id: int,
169
+ status: str,
170
+ participants: dict[str, int],
171
+ ) -> None:
172
+ self.cid = cid
173
+ self.owner = owner
174
+ self.has_bots = has_bots
175
+ self.join_time = join_time
176
+ self.created = created
177
+ self.last_message = last_message
178
+ self.type = type
179
+ self.last_fire_delayed_error_time = last_fire_delayed_error_time
180
+ self.last_delayed_update_time = last_delayed_update_time
181
+ self.prev_message_id = prev_message_id
182
+ self.options = options
183
+ self.modified = modified
184
+ self.last_event_time = last_event_time
185
+ self.id = id
186
+ self.status = status
187
+ self.participants = participants
188
+
189
+ @classmethod
190
+ def from_dict(cls, data: dict[Any, Any]) -> "Dialog":
191
+ return cls(
192
+ cid=data.get("cid"),
193
+ owner=data["owner"],
194
+ has_bots=data.get("hasBots"),
195
+ join_time=data["joinTime"],
196
+ created=data["created"],
197
+ last_message=Message.from_dict(data["lastMessage"])
198
+ if data.get("lastMessage")
199
+ else None,
200
+ type=ChatType(data["type"]),
201
+ last_fire_delayed_error_time=data["lastFireDelayedErrorTime"],
202
+ last_delayed_update_time=data["lastDelayedUpdateTime"],
203
+ prev_message_id=data.get("prevMessageId"),
204
+ options=data.get("options", {}),
205
+ modified=data["modified"],
206
+ last_event_time=data["lastEventTime"],
207
+ id=data["id"],
208
+ status=data["status"],
209
+ participants=data["participants"],
210
+ )
211
+
212
+ @override
213
+ def __repr__(self) -> str:
214
+ return f"Dialog(id={self.id!r}, owner={self.owner!r}, type={self.type!r}, last_message={self.last_message!r})"
215
+
216
+ @override
217
+ def __str__(self) -> str:
218
+ return f"Dialog {self.id} ({self.type})"
219
+
220
+
221
+ class Chat:
222
+ def __init__(
223
+ self,
224
+ participants_count: int,
225
+ access: AccessType | str,
226
+ invited_by: int | None,
227
+ link: str | None,
228
+ chat_type: ChatType | str,
229
+ title: str | None,
230
+ last_fire_delayed_error_time: int,
231
+ last_delayed_update_time: int,
232
+ options: dict[str, bool],
233
+ base_raw_icon_url: str | None,
234
+ base_icon_url: str | None,
235
+ description: str | None,
236
+ modified: int,
237
+ id_: int,
238
+ admin_participants: dict[int, dict[Any, Any]],
239
+ participants: dict[int, int],
240
+ owner: int,
241
+ join_time: int,
242
+ created: int,
243
+ last_message: Message | None,
244
+ prev_message_id: str | None,
245
+ last_event_time: int,
246
+ messages_count: int,
247
+ admins: list[int],
248
+ restrictions: int | None,
249
+ status: str,
250
+ cid: int,
251
+ ) -> None:
252
+ self.participants_count = participants_count
253
+ self.access = access
254
+ self.invited_by = invited_by
255
+ self.link = link
256
+ self.type = chat_type
257
+ self.title = title
258
+ self.last_fire_delayed_error_time = last_fire_delayed_error_time
259
+ self.last_delayed_update_time = last_delayed_update_time
260
+ self.options = options
261
+ self.base_raw_icon_url = base_raw_icon_url
262
+ self.base_icon_url = base_icon_url
263
+ self.description = description
264
+ self.modified = modified
265
+ self.id = id_
266
+ self.admin_participants = admin_participants
267
+ self.participants = participants
268
+ self.owner = owner
269
+ self.join_time = join_time
270
+ self.created = created
271
+ self.last_message = last_message
272
+ self.prev_message_id = prev_message_id
273
+ self.last_event_time = last_event_time
274
+ self.messages_count = messages_count
275
+ self.admins = admins
276
+ self.restrictions = restrictions
277
+ self.status = status
278
+ self.cid = cid
279
+
280
+ @classmethod
281
+ def from_dict(cls, data: dict[Any, Any]) -> "Chat":
282
+ raw_admins = data.get("adminParticipants", {}) or {}
283
+ admin_participants: dict[int, dict[Any, Any]] = {
284
+ int(k): v for k, v in raw_admins.items()
285
+ }
286
+ raw_participants = data.get("participants", {}) or {}
287
+ participants: dict[int, int] = {int(k): v for k, v in raw_participants.items()}
288
+ last_msg = (
289
+ Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
290
+ )
291
+ return cls(
292
+ participants_count=data.get("participantsCount", 0),
293
+ access=AccessType(data.get("access", AccessType.PUBLIC.value)),
294
+ invited_by=data.get("invitedBy"),
295
+ link=data.get("link"),
296
+ base_raw_icon_url=data.get("baseRawIconUrl"),
297
+ base_icon_url=data.get("baseIconUrl"),
298
+ description=data.get("description"),
299
+ chat_type=ChatType(data.get("type", ChatType.CHAT.value)),
300
+ title=data.get("title"),
301
+ last_fire_delayed_error_time=data.get("lastFireDelayedErrorTime", 0),
302
+ last_delayed_update_time=data.get("lastDelayedUpdateTime", 0),
303
+ options=data.get("options", {}),
304
+ modified=data.get("modified", 0),
305
+ id_=data.get("id", 0),
306
+ admin_participants=admin_participants,
307
+ participants=participants,
308
+ owner=data.get("owner", 0),
309
+ join_time=data.get("joinTime", 0),
310
+ created=data.get("created", 0),
311
+ last_message=last_msg,
312
+ prev_message_id=data.get("prevMessageId"),
313
+ last_event_time=data.get("lastEventTime", 0),
314
+ messages_count=data.get("messagesCount", 0),
315
+ admins=data.get("admins", []),
316
+ restrictions=data.get("restrictions"),
317
+ status=data.get("status", ""),
318
+ cid=data.get("cid", 0),
319
+ )
320
+
321
+ @override
322
+ def __repr__(self) -> str:
323
+ return f"Chat(id={self.id!r}, title={self.title!r}, type={self.type!r})"
324
+
325
+ @override
326
+ def __str__(self) -> str:
327
+ return f"{self.title} ({self.type})"
328
+
329
+
330
+ class Channel(Chat):
331
+ @override
332
+ def __repr__(self) -> str:
333
+ return f"Channel(id={self.id!r}, title={self.title!r})"
334
+
335
+ @override
336
+ def __str__(self) -> str:
337
+ return f"Channel: {self.title}"
338
+
339
+
340
+ class User:
341
+ def __init__(
342
+ self,
343
+ account_status: int,
344
+ update_time: int,
345
+ id: int,
346
+ names: list[Names],
347
+ options: list[str] | None = None,
348
+ base_url: str | None = None,
349
+ base_raw_url: str | None = None,
350
+ photo_id: int | None = None,
351
+ description: str | None = None,
352
+ gender: int | None = None,
353
+ link: str | None = None,
354
+ web_app: str | None = None,
355
+ menu_button: dict[str, Any] | None = None,
356
+ ) -> None:
357
+ self.account_status = account_status
358
+ self.update_time = update_time
359
+ self.id = id
360
+ self.names = names
361
+ self.options = options or []
362
+ self.base_url = base_url
363
+ self.base_raw_url = base_raw_url
364
+ self.photo_id = photo_id
365
+ self.description = description
366
+ self.gender = gender
367
+ self.link = link
368
+ self.web_app = web_app
369
+ self.menu_button = menu_button
370
+
371
+ @classmethod
372
+ def from_dict(cls, data: dict[str, Any]) -> "User":
373
+ return cls(
374
+ account_status=data["accountStatus"],
375
+ update_time=data["updateTime"],
376
+ id=data["id"],
377
+ names=[Names.from_dict(n) for n in data.get("names", [])],
378
+ options=data.get("options"),
379
+ base_url=data.get("baseUrl"),
380
+ base_raw_url=data.get("baseRawUrl"),
381
+ photo_id=data.get("photoId"),
382
+ description=data.get("description"),
383
+ gender=data.get("gender"),
384
+ link=data.get("link"),
385
+ web_app=data.get("webApp"),
386
+ menu_button=data.get("menuButton"),
387
+ )
388
+
389
+ @override
390
+ def __repr__(self) -> str:
391
+ return f"User(id={self.id!r}, names={self.names!r}, status={self.account_status!r})"
392
+
393
+ @override
394
+ def __str__(self) -> str:
395
+ return f"User {self.id}: {', '.join(str(n) for n in self.names)}"
396
+
397
+
398
+ class Attach:
399
+ def __init__(
400
+ self,
401
+ _type: AttachType,
402
+ video_id: int | None = None,
403
+ photo_token: str | None = None,
404
+ file_id: int | None = None,
405
+ token: str | None = None,
406
+ ) -> None:
407
+ self.type = _type
408
+ self.video_id = video_id
409
+ self.photo_token = photo_token
410
+ self.file_id = file_id
411
+ self.token = token
412
+
413
+ @classmethod
414
+ def from_dict(cls, data: dict[str, Any]) -> "Attach":
415
+ return cls(
416
+ _type=AttachType(data["type"]),
417
+ video_id=data.get("videoId"),
418
+ photo_token=data.get("photoToken"),
419
+ file_id=data.get("fileId"),
420
+ token=data.get("token"),
421
+ )
422
+
423
+ @override
424
+ def __repr__(self) -> str:
425
+ return (
426
+ f"Attach(type={self.type!r}, video_id={self.video_id!r}, "
427
+ f"photo_token={self.photo_token!r}, file_id={self.file_id!r}, token={self.token!r})"
428
+ )
429
+
430
+ @override
431
+ def __str__(self) -> str:
432
+ return f"Attach: {self.type}"
pymax/utils.py ADDED
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ import lz4.block
4
+ import msgpack
5
+
6
+
7
+ def unpack_packet(data: bytes) -> None | dict[str, Any]:
8
+ ver = int.from_bytes(data[0:1], "big")
9
+ cmd = int.from_bytes(data[1:3], "big")
10
+ seq = int.from_bytes(data[3:4], "big")
11
+ opcode = int.from_bytes(data[4:6], "big")
12
+ packed_len = int.from_bytes(data[6:10], "big", signed=False)
13
+ comp_flag = packed_len >> 24
14
+ payload_length = packed_len & 0xFFFFFF
15
+ payload_bytes = data[10 : 10 + payload_length]
16
+ if comp_flag != 0:
17
+ compressed_data = payload_bytes
18
+ try:
19
+ payload_bytes = lz4.block.decompress(compressed_data, uncompressed_size=255)
20
+ except lz4.block.LZ4BlockError:
21
+ return None
22
+ payload = msgpack.unpackb(payload_bytes, raw=False)
23
+ return {"ver": ver, "cmd": cmd, "seq": seq, "opcode": opcode, "payload": payload}
24
+
25
+
26
+ # ToDo: Add lz4 compression
27
+ def pack_packet(
28
+ ver: int, cmd: int, seq: int, opcode: int, payload: dict[str, Any]
29
+ ) -> bytes:
30
+ ver_b = ver.to_bytes(1, "big")
31
+ cmd_b = cmd.to_bytes(2, "big")
32
+ seq_b = seq.to_bytes(1, "big")
33
+ opcode_b = opcode.to_bytes(2, "big")
34
+ payload_bytes = msgpack.packb(payload)
35
+ if payload_bytes is None:
36
+ payload_bytes = b""
37
+ payload_len_b = len(payload_bytes).to_bytes(4, "big")
38
+ return ver_b + cmd_b + seq_b + opcode_b + payload_len_b + payload_bytes
@@ -1,4 +0,0 @@
1
- maxapi_python-0.1.1.dist-info/METADATA,sha256=uHYKlxE6NA9cmATGtlE_AhF05iJarJmX4fKLCnuMbkE,4812
2
- maxapi_python-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
3
- maxapi_python-0.1.1.dist-info/licenses/LICENSE,sha256=Ud-0SKeXO_yA02Bb1nMDnEaSGwz2OqNlfGQbk0IzqPI,1085
4
- maxapi_python-0.1.1.dist-info/RECORD,,