maxapi-python 1.2.2__py3-none-any.whl → 1.2.4__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/types.py CHANGED
@@ -47,7 +47,7 @@ class Name:
47
47
  def __init__(
48
48
  self,
49
49
  name: str | None,
50
- first_name: None,
50
+ first_name: None | str,
51
51
  last_name: str | None,
52
52
  type: str | None,
53
53
  ) -> None:
@@ -90,16 +90,14 @@ class Names(Name):
90
90
  def __init__(
91
91
  self,
92
92
  name: str | None,
93
- first_name: None,
93
+ first_name: None | str,
94
94
  last_name: str | None,
95
95
  type: str | None,
96
96
  ) -> None:
97
97
  """
98
98
  Синоним для класса Name.
99
99
  """
100
- super().__init__(
101
- name=name, first_name=first_name, last_name=last_name, type=type
102
- )
100
+ super().__init__(name=name, first_name=first_name, last_name=last_name, type=type)
103
101
 
104
102
 
105
103
  class Contact:
@@ -219,7 +217,7 @@ class StickerAttach:
219
217
  def __init__(
220
218
  self,
221
219
  author_type: str,
222
- lottie_url: str,
220
+ lottie_url: str | None,
223
221
  url: str,
224
222
  sticker_id: int,
225
223
  tags: list[str] | None,
@@ -248,7 +246,7 @@ class StickerAttach:
248
246
  def from_dict(cls, data: dict[str, Any]) -> Self:
249
247
  return cls(
250
248
  author_type=data["authorType"],
251
- lottie_url=data["lottieUrl"],
249
+ lottie_url=data.get("lottieUrl"),
252
250
  url=data["url"],
253
251
  sticker_id=data["stickerId"],
254
252
  tags=data.get("tags"),
@@ -443,9 +441,7 @@ class VideoAttach:
443
441
 
444
442
 
445
443
  class FileAttach:
446
- def __init__(
447
- self, file_id: int, name: str, size: int, token: str, type: AttachType
448
- ) -> None:
444
+ def __init__(self, file_id: int, name: str, size: int, token: str, type: AttachType) -> None:
449
445
  self.file_id = file_id
450
446
  self.name = name
451
447
  self.size = size
@@ -553,9 +549,7 @@ class Me:
553
549
 
554
550
 
555
551
  class Element:
556
- def __init__(
557
- self, type: FormattingType | str, length: int, from_: int | None = None
558
- ) -> None:
552
+ def __init__(self, type: FormattingType | str, length: int, from_: int | None = None) -> None:
559
553
  self.type = type
560
554
  self.length = length
561
555
  self.from_ = from_
@@ -566,9 +560,7 @@ class Element:
566
560
 
567
561
  @override
568
562
  def __repr__(self) -> str:
569
- return (
570
- f"Element(type={self.type!r}, length={self.length!r}, from_={self.from_!r})"
571
- )
563
+ return f"Element(type={self.type!r}, length={self.length!r}, from_={self.from_!r})"
572
564
 
573
565
  @override
574
566
  def __str__(self) -> str:
@@ -591,7 +583,9 @@ class MessageLink:
591
583
 
592
584
  @override
593
585
  def __repr__(self) -> str:
594
- return f"MessageLink(chat_id={self.chat_id!r}, message={self.message!r}, type={self.type!r})"
586
+ return (
587
+ f"MessageLink(chat_id={self.chat_id!r}, message={self.message!r}, type={self.type!r})"
588
+ )
595
589
 
596
590
  @override
597
591
  def __str__(self) -> str:
@@ -636,6 +630,36 @@ class ReactionInfo:
636
630
  )
637
631
 
638
632
 
633
+ class ContactAttach:
634
+ def __init__(
635
+ self, contact_id: int, first_name: str, last_name: str, name: str, photo_url: str
636
+ ) -> None:
637
+ self.contact_id = contact_id
638
+ self.first_name = first_name
639
+ self.last_name = last_name
640
+ self.name = name
641
+ self.photo_url = photo_url
642
+ self.type = AttachType.CONTACT
643
+
644
+ @classmethod
645
+ def from_dict(cls, data: dict[str, Any]) -> Self:
646
+ return cls(
647
+ contact_id=data["contactId"],
648
+ first_name=data["firstName"],
649
+ last_name=data["lastName"],
650
+ name=data["name"],
651
+ photo_url=data["photoUrl"],
652
+ )
653
+
654
+ @override
655
+ def __repr__(self) -> str:
656
+ return f"ContactAttach(contact_id={self.contact_id!r}, first_name={self.first_name!r}, last_name={self.last_name!r}, name={self.name!r}, photo_url={self.photo_url!r})"
657
+
658
+ @override
659
+ def __str__(self) -> str:
660
+ return f"ContactAttach: {self.name}"
661
+
662
+
639
663
  class Message:
640
664
  def __init__(
641
665
  self,
@@ -658,6 +682,7 @@ class Message:
658
682
  | ControlAttach
659
683
  | StickerAttach
660
684
  | AudioAttach
685
+ | ContactAttach
661
686
  ]
662
687
  | None
663
688
  ),
@@ -679,7 +704,13 @@ class Message:
679
704
  def from_dict(cls, data: dict[Any, Any]) -> Self:
680
705
  message = data["message"] if data.get("message") else data
681
706
  attaches: list[
682
- PhotoAttach | VideoAttach | FileAttach | ControlAttach | StickerAttach
707
+ PhotoAttach
708
+ | VideoAttach
709
+ | FileAttach
710
+ | ControlAttach
711
+ | StickerAttach
712
+ | AudioAttach
713
+ | ContactAttach
683
714
  ] = []
684
715
  for a in message.get("attaches", []):
685
716
  if a["_type"] == AttachType.PHOTO:
@@ -694,6 +725,8 @@ class Message:
694
725
  attaches.append(StickerAttach.from_dict(a))
695
726
  elif a["_type"] == AttachType.AUDIO:
696
727
  attaches.append(AudioAttach.from_dict(a))
728
+ elif a["_type"] == AttachType.CONTACT:
729
+ attaches.append(ContactAttach.from_dict(a))
697
730
  link_value = message.get("link")
698
731
  if isinstance(link_value, dict):
699
732
  link = MessageLink.from_dict(link_value)
@@ -778,9 +811,7 @@ class Dialog:
778
811
  join_time=data["joinTime"],
779
812
  created=data["created"],
780
813
  last_message=(
781
- Message.from_dict(data["lastMessage"])
782
- if data.get("lastMessage")
783
- else None
814
+ Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
784
815
  ),
785
816
  type=ChatType(data["type"]),
786
817
  last_fire_delayed_error_time=data["lastFireDelayedErrorTime"],
@@ -865,14 +896,10 @@ class Chat:
865
896
  @classmethod
866
897
  def from_dict(cls, data: dict[Any, Any]) -> Self:
867
898
  raw_admins = data.get("adminParticipants", {}) or {}
868
- admin_participants: dict[int, dict[Any, Any]] = {
869
- int(k): v for k, v in raw_admins.items()
870
- }
899
+ admin_participants: dict[int, dict[Any, Any]] = {int(k): v for k, v in raw_admins.items()}
871
900
  raw_participants = data.get("participants", {}) or {}
872
901
  participants: dict[int, int] = {int(k): v for k, v in raw_participants.items()}
873
- last_msg = (
874
- Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
875
- )
902
+ last_msg = Message.from_dict(data["lastMessage"]) if data.get("lastMessage") else None
876
903
  return cls(
877
904
  participants_count=data.get("participantsCount", 0),
878
905
  access=AccessType(data.get("access", AccessType.PUBLIC.value)),
@@ -1051,7 +1078,9 @@ class Session:
1051
1078
 
1052
1079
  @override
1053
1080
  def __str__(self) -> str:
1054
- return f"Session: {self.client} from {self.location} at {self.time} (current={self.current})"
1081
+ return (
1082
+ f"Session: {self.client} from {self.location} at {self.time} (current={self.current})"
1083
+ )
1055
1084
 
1056
1085
 
1057
1086
  class Folder:
@@ -1164,3 +1193,28 @@ class FolderList:
1164
1193
  @override
1165
1194
  def __str__(self) -> str:
1166
1195
  return f"FolderList: {len(self.folders)} folders"
1196
+
1197
+
1198
+ class ReadState:
1199
+ def __init__(
1200
+ self,
1201
+ unread: int,
1202
+ mark: int,
1203
+ ) -> None:
1204
+ self.unread = unread
1205
+ self.mark = mark
1206
+
1207
+ @classmethod
1208
+ def from_dict(cls, data: dict[str, Any]) -> Self:
1209
+ return cls(
1210
+ unread=data["unread"],
1211
+ mark=data["mark"],
1212
+ )
1213
+
1214
+ @override
1215
+ def __repr__(self) -> str:
1216
+ return f"ReadState(unread={self.unread!r}, mark={self.mark!r})"
1217
+
1218
+ @override
1219
+ def __str__(self) -> str:
1220
+ return f"ReadState: unread={self.unread}, mark={self.mark}"
pymax/utils.py ADDED
@@ -0,0 +1,90 @@
1
+ import re
2
+ import time
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from typing import Any, NoReturn
5
+
6
+ import requests
7
+
8
+ from pymax.exceptions import Error, RateLimitError
9
+
10
+
11
+ class MixinsUtils:
12
+ @staticmethod
13
+ def handle_error(data: dict[str, Any]) -> NoReturn:
14
+ error = data.get("payload", {}).get("error")
15
+ localized_message = data.get("payload", {}).get("localizedMessage")
16
+ title = data.get("payload", {}).get("title")
17
+ message = data.get("payload", {}).get("message")
18
+
19
+ if error == "too.many.requests": # TODO: вынести в статик
20
+ raise RateLimitError(
21
+ error=error,
22
+ message=message,
23
+ title=title,
24
+ localized_message=localized_message,
25
+ )
26
+
27
+ raise Error(
28
+ error=error,
29
+ message=message,
30
+ title=title,
31
+ localized_message=localized_message,
32
+ )
33
+
34
+ @staticmethod
35
+ def _fetch_and_extract(url: str, session: requests.Session) -> str | None:
36
+ try:
37
+ js_code = session.get(url, timeout=10).text
38
+ except requests.RequestException:
39
+ return None
40
+ return MixinsUtils._extract_version(js_code)
41
+
42
+ @staticmethod
43
+ def _extract_version(js_code: str) -> str | None:
44
+ ws_anchor = "wss://ws-api.oneme.ru/websocket"
45
+ pos = js_code.find(ws_anchor)
46
+ if pos == -1:
47
+ return None
48
+
49
+ snippet = js_code[pos : pos + 2000]
50
+
51
+ match = re.search(r'[:=]\s*"(\d{1,2}\.\d{1,2}\.\d{1,2})"', snippet)
52
+ if match:
53
+ version = match.group(1)
54
+ return version
55
+
56
+ return None
57
+
58
+ @staticmethod
59
+ def get_current_web_version() -> str | None:
60
+ try:
61
+ html = requests.get("https://web.max.ru/", timeout=10).text
62
+ except requests.RequestException:
63
+ return None
64
+
65
+ main_chunk_import = html.split("import(")[2].split(")")[0].strip("\"'")
66
+ main_chunk_url = f"https://web.max.ru{main_chunk_import}"
67
+ try:
68
+ main_chunk_code = requests.get(main_chunk_url, timeout=10).text
69
+ except requests.exceptions.RequestException as e:
70
+ return None
71
+
72
+ arr = main_chunk_code.split("\n")[0].split("[")[1].split("]")[0].split(",")
73
+ urls = []
74
+ for i in arr:
75
+ if "/chunks/" in i:
76
+ url = "https://web.max.ru/_app/immutable" + i[3 : len(i) - 1]
77
+ urls.append(url)
78
+
79
+ session = requests.Session()
80
+ session.headers["User-Agent"] = "Mozilla/5.0"
81
+ if urls:
82
+ with ThreadPoolExecutor(max_workers=8) as pool:
83
+ futures = [
84
+ pool.submit(MixinsUtils._fetch_and_extract, url, session) for url in urls
85
+ ]
86
+ for f in as_completed(futures):
87
+ ver = f.result()
88
+ if ver:
89
+ return ver
90
+ return None
@@ -1,32 +0,0 @@
1
- pymax/__init__.py,sha256=6wUKKwsyxFpWG3b7kwptOvHd-w78C-ygw42iCDBYQvc,1915
2
- pymax/core.py,sha256=OXGNaQ0pDaf6Ofr1Fb9m7vh5ffpbiMyvUMM0EfwlnIQ,14907
3
- pymax/crud.py,sha256=YC92TyhA2mr1tJCcfd-tvh8umtXKgqJfgiLo7nXUl3Q,3076
4
- pymax/exceptions.py,sha256=nDUNx7bM-Yjugj-qfIllcrnwLg9JpZroYqfXapjYbMQ,3178
5
- pymax/files.py,sha256=AvFIr34Desq2p4CNWXIngRqeyTBKMT98VmcnI-zvUU0,3462
6
- pymax/filters.py,sha256=gSHPJ1Vi37HKPxf0jRRv9Q3iGwhiQjw1MGrCaouqHzs,4325
7
- pymax/formatter.py,sha256=RJ_5VbY7Li8UM3xL1AvcXo8v1iYnY8GvDDkreaFqtnY,860
8
- pymax/formatting.py,sha256=XRtuXJGweuNZevJFdPxksDftIrfuMGEA-AOUc_v6IhQ,2484
9
- pymax/interfaces.py,sha256=wKF1z1QRw8LcjvM9rzSHWXTK6gPb6sDt2UGiQLvyMf8,8790
10
- pymax/models.py,sha256=PsPGbOkERxesZZltjNrmqhOfRcO44Is2ThbEToREcB8,201
11
- pymax/navigation.py,sha256=4ia6RGY2pXMArboNhHkbWlWX7LtcYK1VGVXorPX0Pb4,5747
12
- pymax/payloads.py,sha256=GuTLK6HYe_bLW3ardgpKeZ98f79j349tD_6B6EwkGww,7879
13
- pymax/types.py,sha256=_ARcVXLGHyiGAJKYPd6EU9QDKzz4VwS6kjTu3YEH_u4,35523
14
- pymax/mixins/__init__.py,sha256=5sXJME34S1EssuDETaN4DLRH7vhMw_Q3Jmay9myAIZM,775
15
- pymax/mixins/auth.py,sha256=e90vIpEOwAjUxgYMYaG7R6jR_5t9rKsei_mTBQUirL4,14716
16
- pymax/mixins/channel.py,sha256=W52YnBay1sUYXxF9oAWsz44ZUh_s45jSvKmAyjTbULM,5357
17
- pymax/mixins/group.py,sha256=LqI1QHmZlmtuQ0-4H1MrNeBV-O9SMDMfHT9f4B_2poE,15189
18
- pymax/mixins/handler.py,sha256=ETnI8fA386LYJGjWtUhhWzQHREUA78di1aO1oWwtscA,12523
19
- pymax/mixins/message.py,sha256=AznKKmTMxdzsYl8IecT43RjWpGvlQM85GzSNGFbI8BA,33279
20
- pymax/mixins/scheduler.py,sha256=rcMfgfZnzu5V6MkcCg6uRgbi-jkc7UyqOjemulydWbc,964
21
- pymax/mixins/self.py,sha256=Be5L64eNYylGM-NmoxFpQZv1ohsC1Dx_Cs3Om__V96s,6976
22
- pymax/mixins/socket.py,sha256=tdHgd1NwWoEZhHCDd74XLOHFKUq-rladxhXV8Z_-APU,22860
23
- pymax/mixins/telemetry.py,sha256=LWr68DNQkPhAjGRDYQ5lORXxC3Yw6M9E8sF0TCNISTE,3609
24
- pymax/mixins/user.py,sha256=RSZd4t-aq8P2k3cVzNVWBkUf-_xTWILrBzwxLRgk1pw,9450
25
- pymax/mixins/utils.py,sha256=s3FUf3i_wjn2Gbg5YY1rWZB-90ZEGrrcUuND_MqqSTE,853
26
- pymax/mixins/websocket.py,sha256=GpdboEVWzyN1qLTcsgKZym6TlPnklcQuNeXJ5YKwg8c,17724
27
- pymax/static/constant.py,sha256=nM0svv3VpsVxK-RqoADn9qsTdQvB-IYv0Sgv-bQcWs4,1116
28
- pymax/static/enum.py,sha256=Hk0e6zSbGOJC_9Aw7gNXX3hcavnjzQfDyr8vjW22cFo,4648
29
- maxapi_python-1.2.2.dist-info/METADATA,sha256=rgiQKdSqYAO743n6jWOy0F76jZyjaGMY7A6qUlHlk64,6753
30
- maxapi_python-1.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- maxapi_python-1.2.2.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
32
- maxapi_python-1.2.2.dist-info/RECORD,,
pymax/mixins/utils.py DELETED
@@ -1,27 +0,0 @@
1
- from typing import Any, NoReturn
2
-
3
- from pymax.exceptions import Error, RateLimitError
4
-
5
-
6
- class MixinsUtils:
7
- @staticmethod
8
- def handle_error(data: dict[str, Any]) -> NoReturn:
9
- error = data.get("payload", {}).get("error")
10
- localized_message = data.get("payload", {}).get("localizedMessage")
11
- title = data.get("payload", {}).get("title")
12
- message = data.get("payload", {}).get("message")
13
-
14
- if error == "too.many.requests": # TODO: вынести в статик
15
- raise RateLimitError(
16
- error=error,
17
- message=message,
18
- title=title,
19
- localized_message=localized_message,
20
- )
21
-
22
- raise Error(
23
- error=error,
24
- message=message,
25
- title=title,
26
- localized_message=localized_message,
27
- )