maxapi-python 1.2.4__py3-none-any.whl → 2.0.0__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.
Files changed (168) hide show
  1. maxapi_python-2.0.0.dist-info/METADATA +217 -0
  2. maxapi_python-2.0.0.dist-info/RECORD +140 -0
  3. {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +50 -105
  5. pymax/api/__init__.py +17 -0
  6. pymax/api/auth/__init__.py +1 -0
  7. pymax/api/auth/enums.py +17 -0
  8. pymax/api/auth/payloads.py +129 -0
  9. pymax/api/auth/service.py +313 -0
  10. pymax/api/auth/types.py +13 -0
  11. pymax/api/chats/__init__.py +8 -0
  12. pymax/api/chats/enums.py +27 -0
  13. pymax/api/chats/payloads.py +103 -0
  14. pymax/api/chats/service.py +277 -0
  15. pymax/api/facade.py +32 -0
  16. pymax/api/messages/__init__.py +1 -0
  17. pymax/api/messages/enums.py +17 -0
  18. pymax/api/messages/payloads.py +92 -0
  19. pymax/api/messages/service.py +337 -0
  20. pymax/api/models.py +13 -0
  21. pymax/api/response.py +123 -0
  22. pymax/api/self/__init__.py +2 -0
  23. pymax/api/self/enums.py +11 -0
  24. pymax/api/self/payloads.py +41 -0
  25. pymax/api/self/service.py +142 -0
  26. pymax/api/session/__init__.py +1 -0
  27. pymax/api/session/enums.py +10 -0
  28. pymax/api/session/payloads.py +76 -0
  29. pymax/api/session/service.py +72 -0
  30. pymax/api/uploads/__init__.py +1 -0
  31. pymax/api/uploads/models.py +49 -0
  32. pymax/api/uploads/payloads.py +25 -0
  33. pymax/api/uploads/service.py +458 -0
  34. pymax/api/users/__init__.py +2 -0
  35. pymax/api/users/enums.py +12 -0
  36. pymax/api/users/payloads.py +16 -0
  37. pymax/api/users/service.py +124 -0
  38. pymax/app.py +273 -0
  39. pymax/auth/__init__.py +25 -0
  40. pymax/auth/base.py +37 -0
  41. pymax/auth/email.py +0 -0
  42. pymax/auth/models.py +5 -0
  43. pymax/auth/providers.py +127 -0
  44. pymax/auth/qr.py +135 -0
  45. pymax/auth/service.py +25 -0
  46. pymax/auth/sms.py +122 -0
  47. pymax/base.py +204 -0
  48. pymax/client.py +106 -0
  49. pymax/client_web.py +83 -0
  50. pymax/config.py +215 -0
  51. pymax/connection/__init__.py +1 -0
  52. pymax/connection/connection.py +205 -0
  53. pymax/connection/pending.py +46 -0
  54. pymax/connection/readers/__init__.py +2 -0
  55. pymax/connection/readers/base.py +6 -0
  56. pymax/connection/readers/tcp.py +29 -0
  57. pymax/connection/readers/ws.py +14 -0
  58. pymax/dispatch/__init__.py +10 -0
  59. pymax/dispatch/dispatcher.py +222 -0
  60. pymax/dispatch/enums.py +12 -0
  61. pymax/dispatch/mapping.py +73 -0
  62. pymax/dispatch/resolvers.py +52 -0
  63. pymax/dispatch/router.py +216 -0
  64. pymax/exceptions.py +22 -89
  65. pymax/files/__init__.py +9 -0
  66. pymax/files/base.py +82 -0
  67. pymax/files/file.py +76 -0
  68. pymax/files/photo.py +108 -0
  69. pymax/files/static.py +10 -0
  70. pymax/files/video.py +74 -0
  71. pymax/formatting/__init__.py +0 -0
  72. pymax/formatting/markdown.py +217 -0
  73. pymax/infra/__init__.py +1 -0
  74. pymax/infra/auth.py +55 -0
  75. pymax/infra/base.py +15 -0
  76. pymax/infra/chat.py +240 -0
  77. pymax/infra/message.py +252 -0
  78. pymax/infra/protocol.py +9 -0
  79. pymax/infra/self.py +139 -0
  80. pymax/infra/user.py +107 -0
  81. pymax/logging.py +129 -0
  82. pymax/protocol/__init__.py +11 -0
  83. pymax/protocol/base.py +13 -0
  84. pymax/{static/enum.py → protocol/enums.py} +36 -79
  85. pymax/protocol/models.py +33 -0
  86. pymax/protocol/tcp/__init__.py +1 -0
  87. pymax/protocol/tcp/compression.py +97 -0
  88. pymax/protocol/tcp/framing.py +68 -0
  89. pymax/protocol/tcp/payload.py +127 -0
  90. pymax/protocol/tcp/protocol.py +68 -0
  91. pymax/protocol/ws/__init__.py +1 -0
  92. pymax/protocol/ws/protocol.py +27 -0
  93. pymax/py.typed +0 -0
  94. pymax/routers.py +8 -0
  95. pymax/session/__init__.py +3 -0
  96. pymax/session/models.py +11 -0
  97. pymax/session/protocol.py +14 -0
  98. pymax/session/store.py +232 -0
  99. pymax/telemetry/__init__.py +3 -0
  100. pymax/telemetry/navigation.py +181 -0
  101. pymax/telemetry/payloads.py +142 -0
  102. pymax/telemetry/service.py +225 -0
  103. pymax/transport/__init__.py +0 -0
  104. pymax/transport/base.py +14 -0
  105. pymax/transport/tcp.py +93 -0
  106. pymax/transport/websocket.py +50 -0
  107. pymax/types/__init__.py +2 -0
  108. pymax/types/domain/__init__.py +11 -0
  109. pymax/types/domain/attachments/__init__.py +11 -0
  110. pymax/types/domain/attachments/audio.py +35 -0
  111. pymax/types/domain/attachments/call.py +26 -0
  112. pymax/types/domain/attachments/contact.py +32 -0
  113. pymax/types/domain/attachments/control.py +20 -0
  114. pymax/types/domain/attachments/enums.py +27 -0
  115. pymax/types/domain/attachments/file.py +56 -0
  116. pymax/types/domain/attachments/keyboards/__init__.py +1 -0
  117. pymax/types/domain/attachments/keyboards/inline.py +19 -0
  118. pymax/types/domain/attachments/photo.py +45 -0
  119. pymax/types/domain/attachments/share.py +29 -0
  120. pymax/types/domain/attachments/sticker.py +50 -0
  121. pymax/types/domain/attachments/video.py +90 -0
  122. pymax/types/domain/auth.py +161 -0
  123. pymax/types/domain/base.py +17 -0
  124. pymax/types/domain/chat.py +426 -0
  125. pymax/types/domain/element.py +24 -0
  126. pymax/types/domain/enums.py +24 -0
  127. pymax/types/domain/error.py +20 -0
  128. pymax/types/domain/folder.py +74 -0
  129. pymax/types/domain/login.py +35 -0
  130. pymax/types/domain/message.py +378 -0
  131. pymax/types/domain/name.py +20 -0
  132. pymax/types/domain/profile.py +15 -0
  133. pymax/types/domain/session.py +52 -0
  134. pymax/types/domain/sync.py +80 -0
  135. pymax/types/domain/user.py +117 -0
  136. pymax/types/events/__init__.py +3 -0
  137. pymax/types/events/file.py +5 -0
  138. pymax/types/events/message.py +37 -0
  139. pymax/types/events/video.py +5 -0
  140. maxapi_python-1.2.4.dist-info/METADATA +0 -205
  141. maxapi_python-1.2.4.dist-info/RECORD +0 -33
  142. pymax/core.py +0 -390
  143. pymax/crud.py +0 -96
  144. pymax/files.py +0 -138
  145. pymax/filters.py +0 -164
  146. pymax/formatter.py +0 -31
  147. pymax/formatting.py +0 -74
  148. pymax/interfaces.py +0 -552
  149. pymax/mixins/__init__.py +0 -40
  150. pymax/mixins/auth.py +0 -368
  151. pymax/mixins/channel.py +0 -130
  152. pymax/mixins/group.py +0 -458
  153. pymax/mixins/handler.py +0 -285
  154. pymax/mixins/message.py +0 -879
  155. pymax/mixins/scheduler.py +0 -28
  156. pymax/mixins/self.py +0 -259
  157. pymax/mixins/socket.py +0 -297
  158. pymax/mixins/telemetry.py +0 -112
  159. pymax/mixins/user.py +0 -219
  160. pymax/mixins/websocket.py +0 -142
  161. pymax/models.py +0 -8
  162. pymax/navigation.py +0 -187
  163. pymax/payloads.py +0 -367
  164. pymax/protocols.py +0 -123
  165. pymax/static/constant.py +0 -89
  166. pymax/types.py +0 -1220
  167. pymax/utils.py +0 -90
  168. {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import TYPE_CHECKING
5
+
6
+ from pymax.api.response import (
7
+ parse_payload_item_model,
8
+ parse_payload_list,
9
+ require_payload_item_model,
10
+ require_payload_model,
11
+ )
12
+ from pymax.exceptions import PyMaxError
13
+ from pymax.logging import get_logger
14
+ from pymax.protocol import Opcode
15
+ from pymax.types.domain import Chat, Message
16
+
17
+ from .enums import ChatLinkPrefix, ChatPayloadKey
18
+ from .payloads import (
19
+ ChangeGroupProfilePayload,
20
+ ChangeGroupSettingsOptions,
21
+ ChangeGroupSettingsPayload,
22
+ CreateGroupAttach,
23
+ CreateGroupMessage,
24
+ CreateGroupPayload,
25
+ FetchChatsPayload,
26
+ GetChatInfoPayload,
27
+ InviteUsersPayload,
28
+ JoinChatPayload,
29
+ LeaveChatPayload,
30
+ LinkInfoPayload,
31
+ RemoveUsersPayload,
32
+ ReworkInviteLinkPayload,
33
+ )
34
+
35
+ if TYPE_CHECKING:
36
+ from pymax.app import App
37
+
38
+
39
+ logger = get_logger(__name__)
40
+
41
+
42
+ class ChatService:
43
+ def __init__(self, app: App) -> None:
44
+ self.app = app
45
+
46
+ def _bind_chat(self, chat: Chat) -> Chat:
47
+ return chat.bind(self.app.api.messages, self)
48
+
49
+ def _cache_chat(self, chat: Chat) -> Chat:
50
+ chat = self._bind_chat(chat)
51
+ if self.app.chats is None:
52
+ self.app.chats = [chat]
53
+ return chat
54
+
55
+ for index, cached in enumerate(self.app.chats):
56
+ if cached.id == chat.id:
57
+ self.app.chats[index] = chat
58
+ return chat
59
+
60
+ self.app.chats.append(chat)
61
+ return chat
62
+
63
+ def _get_cached_chat(self, chat_id: int) -> Chat | None:
64
+ for chat in self.app.chats or []:
65
+ if chat.id == chat_id:
66
+ return chat
67
+ return None
68
+
69
+ def _remove_cached_chat(self, chat_id: int) -> None:
70
+ if self.app.chats is None:
71
+ return
72
+
73
+ self.app.chats = [chat for chat in self.app.chats if chat.id != chat_id]
74
+
75
+ @staticmethod
76
+ def _process_chat_join_link(link: str) -> str | None:
77
+ idx = link.find(ChatLinkPrefix.JOIN)
78
+ return link[idx:] if idx != -1 else None
79
+
80
+ async def create_group(
81
+ self,
82
+ name: str,
83
+ participant_ids: list[int] | None = None,
84
+ notify: bool = True,
85
+ ) -> tuple[Chat, Message] | None:
86
+ logger.info(
87
+ "creating group name_len=%s participants=%s notify=%s",
88
+ len(name),
89
+ len(participant_ids or []),
90
+ notify,
91
+ )
92
+ frame = CreateGroupPayload(
93
+ message=CreateGroupMessage(
94
+ cid=int(time.time() * 1000),
95
+ attaches=[
96
+ CreateGroupAttach(
97
+ title=name,
98
+ user_ids=participant_ids or [],
99
+ )
100
+ ],
101
+ ),
102
+ notify=notify,
103
+ )
104
+
105
+ response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
106
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
107
+ if chat is None:
108
+ return None
109
+
110
+ chat = self._cache_chat(chat)
111
+ message = require_payload_model(response, Message).bind(self.app.api.messages)
112
+ return chat, message
113
+
114
+ async def invite_users_to_group(
115
+ self,
116
+ chat_id: int,
117
+ user_ids: list[int],
118
+ show_history: bool = True,
119
+ ) -> Chat | None:
120
+ frame = InviteUsersPayload(
121
+ chat_id=chat_id,
122
+ user_ids=user_ids,
123
+ show_history=show_history,
124
+ )
125
+
126
+ response = await self.app.invoke(
127
+ Opcode.CHAT_MEMBERS_UPDATE,
128
+ frame.to_payload(),
129
+ )
130
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
131
+ if chat:
132
+ return self._cache_chat(chat)
133
+
134
+ return None
135
+
136
+ async def invite_users_to_channel(
137
+ self,
138
+ chat_id: int,
139
+ user_ids: list[int],
140
+ show_history: bool = True,
141
+ ) -> Chat | None:
142
+ return await self.invite_users_to_group(chat_id, user_ids, show_history)
143
+
144
+ async def remove_users_from_group(
145
+ self,
146
+ chat_id: int,
147
+ user_ids: list[int],
148
+ clean_msg_period: int,
149
+ ) -> bool:
150
+ frame = RemoveUsersPayload(
151
+ chat_id=chat_id,
152
+ user_ids=user_ids,
153
+ clean_msg_period=clean_msg_period,
154
+ )
155
+
156
+ response = await self.app.invoke(
157
+ Opcode.CHAT_MEMBERS_UPDATE,
158
+ frame.to_payload(),
159
+ )
160
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
161
+ if chat:
162
+ self._cache_chat(chat)
163
+
164
+ return True
165
+
166
+ async def change_group_settings(
167
+ self,
168
+ chat_id: int,
169
+ all_can_pin_message: bool | None = None,
170
+ only_owner_can_change_icon_title: bool | None = None,
171
+ only_admin_can_add_member: bool | None = None,
172
+ only_admin_can_call: bool | None = None,
173
+ members_can_see_private_link: bool | None = None,
174
+ ) -> None:
175
+ frame = ChangeGroupSettingsPayload(
176
+ chat_id=chat_id,
177
+ options=ChangeGroupSettingsOptions(
178
+ all_can_pin_message=all_can_pin_message,
179
+ only_owner_can_change_icon_title=only_owner_can_change_icon_title,
180
+ only_admin_can_add_member=only_admin_can_add_member,
181
+ only_admin_can_call=only_admin_can_call,
182
+ members_can_see_private_link=members_can_see_private_link,
183
+ ),
184
+ )
185
+
186
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
187
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
188
+ if chat:
189
+ self._cache_chat(chat)
190
+
191
+ async def change_group_profile(
192
+ self,
193
+ chat_id: int,
194
+ name: str | None,
195
+ description: str | None = None,
196
+ ) -> None:
197
+ frame = ChangeGroupProfilePayload(
198
+ chat_id=chat_id,
199
+ theme=name,
200
+ description=description,
201
+ )
202
+
203
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
204
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
205
+ if chat:
206
+ self._cache_chat(chat)
207
+
208
+ async def join_group(self, link: str) -> Chat:
209
+ proceed_link = self._process_chat_join_link(link)
210
+ if proceed_link is None:
211
+ raise ValueError("Invalid group link")
212
+
213
+ frame = JoinChatPayload(link=proceed_link)
214
+ response = await self.app.invoke(Opcode.CHAT_JOIN, frame.to_payload())
215
+ chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
216
+ return self._cache_chat(chat)
217
+
218
+ async def resolve_group_by_link(self, link: str) -> Chat | None:
219
+ proceed_link = self._process_chat_join_link(link)
220
+ if proceed_link is None:
221
+ raise ValueError("Invalid group link")
222
+
223
+ frame = LinkInfoPayload(link=proceed_link)
224
+ response = await self.app.invoke(Opcode.LINK_INFO, frame.to_payload())
225
+ chat = parse_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
226
+ if chat:
227
+ return self._bind_chat(chat)
228
+
229
+ return None
230
+
231
+ async def rework_invite_link(self, chat_id: int) -> Chat:
232
+ frame = ReworkInviteLinkPayload(chat_id=chat_id)
233
+ response = await self.app.invoke(Opcode.CHAT_UPDATE, frame.to_payload())
234
+ chat = require_payload_item_model(response, ChatPayloadKey.CHAT, Chat)
235
+ return self._cache_chat(chat)
236
+
237
+ async def get_chats(self, chat_ids: list[int]) -> list[Chat]:
238
+ cached = {
239
+ chat_id: chat
240
+ for chat_id in chat_ids
241
+ if (chat := self._get_cached_chat(chat_id)) is not None
242
+ }
243
+ missed_chat_ids = [chat_id for chat_id in chat_ids if chat_id not in cached]
244
+
245
+ if missed_chat_ids:
246
+ frame = GetChatInfoPayload(chat_ids=missed_chat_ids)
247
+ response = await self.app.invoke(Opcode.CHAT_INFO, frame.to_payload())
248
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat):
249
+ chat = self._cache_chat(chat)
250
+ cached[chat.id] = chat
251
+
252
+ return [cached[chat_id] for chat_id in chat_ids if chat_id in cached]
253
+
254
+ async def get_chat(self, chat_id: int) -> Chat:
255
+ chats = await self.get_chats([chat_id])
256
+ if not chats:
257
+ raise PyMaxError("Chat not found in response")
258
+
259
+ return chats[0]
260
+
261
+ async def leave_group(self, chat_id: int) -> None:
262
+ frame = LeaveChatPayload(chat_id=chat_id)
263
+ await self.app.invoke(Opcode.CHAT_LEAVE, frame.to_payload())
264
+ self._remove_cached_chat(chat_id)
265
+
266
+ async def leave_channel(self, chat_id: int) -> None:
267
+ await self.leave_group(chat_id)
268
+
269
+ async def fetch_chats(self, marker: int | None = None) -> list[Chat]:
270
+ frame = FetchChatsPayload(marker=marker or int(time.time() * 1000))
271
+ response = await self.app.invoke(Opcode.CHATS_LIST, frame.to_payload())
272
+
273
+ chats = [
274
+ self._cache_chat(chat)
275
+ for chat in parse_payload_list(response, ChatPayloadKey.CHATS, Chat)
276
+ ]
277
+ return chats
pymax/api/facade.py ADDED
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from pymax.logging import get_logger
6
+
7
+ from .auth import AuthService
8
+ from .chats import ChatService
9
+ from .messages import MessageService
10
+ from .self import SelfService
11
+ from .session import SessionService
12
+ from .uploads import UploadService
13
+ from .users import UserService
14
+
15
+ if TYPE_CHECKING:
16
+ from pymax.app import App
17
+
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class ApiFacade:
23
+ def __init__(self, app: App) -> None:
24
+ self.app = app
25
+ self.messages = MessageService(app)
26
+ self.chats = ChatService(app)
27
+ self.users = UserService(app)
28
+ self.account = SelfService(app)
29
+ self.session = SessionService(app)
30
+ self.auth = AuthService(app)
31
+ self.uploads = UploadService(app)
32
+ logger.debug("api facade initialized")
@@ -0,0 +1 @@
1
+ from .service import MessageService
@@ -0,0 +1,17 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ItemType(str, Enum):
5
+ REGULAR = "REGULAR"
6
+ DELAYED = "DELAYED"
7
+
8
+
9
+ class ReadAction(str, Enum):
10
+ READ_MESSAGE = "READ_MESSAGE"
11
+ READ_REACTION = "READ_REACTION"
12
+
13
+
14
+ class MessagePayloadKey(str, Enum):
15
+ MESSAGES = "messages"
16
+ REACTION_INFO = "reactionInfo"
17
+ MESSAGES_REACTIONS = "messagesReactions"
@@ -0,0 +1,92 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field
4
+
5
+ from pymax.api.models import CamelModel
6
+ from pymax.api.uploads.payloads import AttachFilePayload, AttachPhotoPayload, VideoAttachPayload
7
+
8
+ from .enums import ItemType, ReadAction
9
+
10
+
11
+ class ReplyLink(CamelModel):
12
+ type: str = "REPLY" # TODO: enum?
13
+ message_id: int
14
+
15
+
16
+ class SendMessagePayloadMessage(CamelModel):
17
+ text: str
18
+ cid: int
19
+ elements: list[Any]
20
+ attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload]
21
+ link: ReplyLink | None = None
22
+
23
+
24
+ class SendMessagePayload(CamelModel):
25
+ chat_id: int
26
+ message: SendMessagePayloadMessage
27
+ notify: bool = False
28
+
29
+
30
+ class ChatHistoryPayload(CamelModel):
31
+ chat_id: int
32
+ forward: int
33
+ backward: int = 40
34
+ backward_time: int = 0
35
+ forward_time: int = 0
36
+ get_chat: bool = False
37
+ from_: int = Field(serialization_alias="from")
38
+ item_type: ItemType = ItemType.REGULAR
39
+ get_messages: bool = True
40
+ interactive: bool = False
41
+
42
+
43
+ class DeleteMessagePayload(CamelModel):
44
+ chat_id: int
45
+ message_ids: list[int]
46
+ for_me: bool = False
47
+
48
+
49
+ class PinMessagePayload(CamelModel):
50
+ chat_id: int
51
+ notify_pin: bool
52
+ pin_message_id: int
53
+
54
+
55
+ class GetVideoPayload(CamelModel):
56
+ chat_id: int
57
+ message_id: int | str
58
+ video_id: int
59
+
60
+
61
+ class GetFilePayload(CamelModel):
62
+ chat_id: int
63
+ message_id: int | str
64
+ file_id: int
65
+
66
+
67
+ class ReactionInfoPayload(CamelModel):
68
+ reaction_type: str = "EMOJI"
69
+ id: str
70
+
71
+
72
+ class AddReactionPayload(CamelModel):
73
+ chat_id: int
74
+ message_id: str
75
+ reaction: ReactionInfoPayload
76
+
77
+
78
+ class GetReactionsPayload(CamelModel):
79
+ chat_id: int
80
+ message_ids: list[str]
81
+
82
+
83
+ class RemoveReactionPayload(CamelModel):
84
+ chat_id: int
85
+ message_id: str
86
+
87
+
88
+ class ReadMessagesPayload(CamelModel):
89
+ type: ReadAction
90
+ chat_id: int
91
+ message_id: str
92
+ mark: int