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.
- maxapi_python-2.0.0.dist-info/METADATA +217 -0
- maxapi_python-2.0.0.dist-info/RECORD +140 -0
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +50 -105
- pymax/api/__init__.py +17 -0
- pymax/api/auth/__init__.py +1 -0
- pymax/api/auth/enums.py +17 -0
- pymax/api/auth/payloads.py +129 -0
- pymax/api/auth/service.py +313 -0
- pymax/api/auth/types.py +13 -0
- pymax/api/chats/__init__.py +8 -0
- pymax/api/chats/enums.py +27 -0
- pymax/api/chats/payloads.py +103 -0
- pymax/api/chats/service.py +277 -0
- pymax/api/facade.py +32 -0
- pymax/api/messages/__init__.py +1 -0
- pymax/api/messages/enums.py +17 -0
- pymax/api/messages/payloads.py +92 -0
- pymax/api/messages/service.py +337 -0
- pymax/api/models.py +13 -0
- pymax/api/response.py +123 -0
- pymax/api/self/__init__.py +2 -0
- pymax/api/self/enums.py +11 -0
- pymax/api/self/payloads.py +41 -0
- pymax/api/self/service.py +142 -0
- pymax/api/session/__init__.py +1 -0
- pymax/api/session/enums.py +10 -0
- pymax/api/session/payloads.py +76 -0
- pymax/api/session/service.py +72 -0
- pymax/api/uploads/__init__.py +1 -0
- pymax/api/uploads/models.py +49 -0
- pymax/api/uploads/payloads.py +25 -0
- pymax/api/uploads/service.py +458 -0
- pymax/api/users/__init__.py +2 -0
- pymax/api/users/enums.py +12 -0
- pymax/api/users/payloads.py +16 -0
- pymax/api/users/service.py +124 -0
- pymax/app.py +273 -0
- pymax/auth/__init__.py +25 -0
- pymax/auth/base.py +37 -0
- pymax/auth/email.py +0 -0
- pymax/auth/models.py +5 -0
- pymax/auth/providers.py +127 -0
- pymax/auth/qr.py +135 -0
- pymax/auth/service.py +25 -0
- pymax/auth/sms.py +122 -0
- pymax/base.py +204 -0
- pymax/client.py +106 -0
- pymax/client_web.py +83 -0
- pymax/config.py +215 -0
- pymax/connection/__init__.py +1 -0
- pymax/connection/connection.py +205 -0
- pymax/connection/pending.py +46 -0
- pymax/connection/readers/__init__.py +2 -0
- pymax/connection/readers/base.py +6 -0
- pymax/connection/readers/tcp.py +29 -0
- pymax/connection/readers/ws.py +14 -0
- pymax/dispatch/__init__.py +10 -0
- pymax/dispatch/dispatcher.py +222 -0
- pymax/dispatch/enums.py +12 -0
- pymax/dispatch/mapping.py +73 -0
- pymax/dispatch/resolvers.py +52 -0
- pymax/dispatch/router.py +216 -0
- pymax/exceptions.py +22 -89
- pymax/files/__init__.py +9 -0
- pymax/files/base.py +82 -0
- pymax/files/file.py +76 -0
- pymax/files/photo.py +108 -0
- pymax/files/static.py +10 -0
- pymax/files/video.py +74 -0
- pymax/formatting/__init__.py +0 -0
- pymax/formatting/markdown.py +217 -0
- pymax/infra/__init__.py +1 -0
- pymax/infra/auth.py +55 -0
- pymax/infra/base.py +15 -0
- pymax/infra/chat.py +240 -0
- pymax/infra/message.py +252 -0
- pymax/infra/protocol.py +9 -0
- pymax/infra/self.py +139 -0
- pymax/infra/user.py +107 -0
- pymax/logging.py +129 -0
- pymax/protocol/__init__.py +11 -0
- pymax/protocol/base.py +13 -0
- pymax/{static/enum.py → protocol/enums.py} +36 -79
- pymax/protocol/models.py +33 -0
- pymax/protocol/tcp/__init__.py +1 -0
- pymax/protocol/tcp/compression.py +97 -0
- pymax/protocol/tcp/framing.py +68 -0
- pymax/protocol/tcp/payload.py +127 -0
- pymax/protocol/tcp/protocol.py +68 -0
- pymax/protocol/ws/__init__.py +1 -0
- pymax/protocol/ws/protocol.py +27 -0
- pymax/py.typed +0 -0
- pymax/routers.py +8 -0
- pymax/session/__init__.py +3 -0
- pymax/session/models.py +11 -0
- pymax/session/protocol.py +14 -0
- pymax/session/store.py +232 -0
- pymax/telemetry/__init__.py +3 -0
- pymax/telemetry/navigation.py +181 -0
- pymax/telemetry/payloads.py +142 -0
- pymax/telemetry/service.py +225 -0
- pymax/transport/__init__.py +0 -0
- pymax/transport/base.py +14 -0
- pymax/transport/tcp.py +93 -0
- pymax/transport/websocket.py +50 -0
- pymax/types/__init__.py +2 -0
- pymax/types/domain/__init__.py +11 -0
- pymax/types/domain/attachments/__init__.py +11 -0
- pymax/types/domain/attachments/audio.py +35 -0
- pymax/types/domain/attachments/call.py +26 -0
- pymax/types/domain/attachments/contact.py +32 -0
- pymax/types/domain/attachments/control.py +20 -0
- pymax/types/domain/attachments/enums.py +27 -0
- pymax/types/domain/attachments/file.py +56 -0
- pymax/types/domain/attachments/keyboards/__init__.py +1 -0
- pymax/types/domain/attachments/keyboards/inline.py +19 -0
- pymax/types/domain/attachments/photo.py +45 -0
- pymax/types/domain/attachments/share.py +29 -0
- pymax/types/domain/attachments/sticker.py +50 -0
- pymax/types/domain/attachments/video.py +90 -0
- pymax/types/domain/auth.py +161 -0
- pymax/types/domain/base.py +17 -0
- pymax/types/domain/chat.py +426 -0
- pymax/types/domain/element.py +24 -0
- pymax/types/domain/enums.py +24 -0
- pymax/types/domain/error.py +20 -0
- pymax/types/domain/folder.py +74 -0
- pymax/types/domain/login.py +35 -0
- pymax/types/domain/message.py +378 -0
- pymax/types/domain/name.py +20 -0
- pymax/types/domain/profile.py +15 -0
- pymax/types/domain/session.py +52 -0
- pymax/types/domain/sync.py +80 -0
- pymax/types/domain/user.py +117 -0
- pymax/types/events/__init__.py +3 -0
- pymax/types/events/file.py +5 -0
- pymax/types/events/message.py +37 -0
- pymax/types/events/video.py +5 -0
- maxapi_python-1.2.4.dist-info/METADATA +0 -205
- maxapi_python-1.2.4.dist-info/RECORD +0 -33
- pymax/core.py +0 -390
- pymax/crud.py +0 -96
- pymax/files.py +0 -138
- pymax/filters.py +0 -164
- pymax/formatter.py +0 -31
- pymax/formatting.py +0 -74
- pymax/interfaces.py +0 -552
- pymax/mixins/__init__.py +0 -40
- pymax/mixins/auth.py +0 -368
- pymax/mixins/channel.py +0 -130
- pymax/mixins/group.py +0 -458
- pymax/mixins/handler.py +0 -285
- pymax/mixins/message.py +0 -879
- pymax/mixins/scheduler.py +0 -28
- pymax/mixins/self.py +0 -259
- pymax/mixins/socket.py +0 -297
- pymax/mixins/telemetry.py +0 -112
- pymax/mixins/user.py +0 -219
- pymax/mixins/websocket.py +0 -142
- pymax/models.py +0 -8
- pymax/navigation.py +0 -187
- pymax/payloads.py +0 -367
- pymax/protocols.py +0 -123
- pymax/static/constant.py +0 -89
- pymax/types.py +0 -1220
- pymax/utils.py +0 -90
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from pydantic import Field, field_serializer
|
|
2
|
+
|
|
3
|
+
from pymax.api.models import CamelModel
|
|
4
|
+
from pymax.api.session.payloads import MobileUserAgentPayload
|
|
5
|
+
from pymax.types.domain.sync import DEFAULT_CONFIG_HASH, ConfigHash, SyncState
|
|
6
|
+
|
|
7
|
+
from .enums import AuthType, Capability
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RequestCodePayload(CamelModel):
|
|
11
|
+
phone: str
|
|
12
|
+
type: AuthType = AuthType.START_AUTH
|
|
13
|
+
language: str = "ru"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SendCodePayload(CamelModel):
|
|
17
|
+
token: str
|
|
18
|
+
verify_code: str
|
|
19
|
+
auth_token_type: AuthType = AuthType.CHECK_CODE
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CheckPasswordChallengePayload(CamelModel):
|
|
23
|
+
track_id: str
|
|
24
|
+
password: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Exp(CamelModel):
|
|
28
|
+
chats_count_groups: bytearray = bytearray.fromhex("0a32")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WebSyncPayload(CamelModel):
|
|
32
|
+
token: str
|
|
33
|
+
chats_count: int = 40
|
|
34
|
+
interactive: bool = True
|
|
35
|
+
chats_sync: int = -1
|
|
36
|
+
contacts_sync: int = -1
|
|
37
|
+
presence_sync: int = -1
|
|
38
|
+
drafts_sync: int = -1
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_sync_state(
|
|
42
|
+
cls,
|
|
43
|
+
token: str,
|
|
44
|
+
sync: SyncState,
|
|
45
|
+
) -> "WebSyncPayload":
|
|
46
|
+
return cls(
|
|
47
|
+
token=token,
|
|
48
|
+
chats_sync=sync.chats_sync,
|
|
49
|
+
contacts_sync=sync.contacts_sync,
|
|
50
|
+
drafts_sync=sync.drafts_sync,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SyncPayload(CamelModel):
|
|
55
|
+
user_agent: MobileUserAgentPayload
|
|
56
|
+
token: str
|
|
57
|
+
chat_hash_fingerprint: str | None = None
|
|
58
|
+
chats_count: int | None = None
|
|
59
|
+
chats_sync: int = -1
|
|
60
|
+
contacts_sync: int = -1
|
|
61
|
+
drafts_sync: int = -1
|
|
62
|
+
interactive: bool = True
|
|
63
|
+
presence_sync: int = -1
|
|
64
|
+
exp: Exp = Field(default_factory=Exp)
|
|
65
|
+
config_hash: ConfigHash = DEFAULT_CONFIG_HASH
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_sync_state(
|
|
69
|
+
cls,
|
|
70
|
+
user_agent: MobileUserAgentPayload,
|
|
71
|
+
token: str,
|
|
72
|
+
sync: SyncState,
|
|
73
|
+
) -> "SyncPayload":
|
|
74
|
+
return cls(
|
|
75
|
+
user_agent=user_agent,
|
|
76
|
+
token=token,
|
|
77
|
+
chats_sync=sync.chats_sync,
|
|
78
|
+
contacts_sync=sync.contacts_sync,
|
|
79
|
+
drafts_sync=sync.drafts_sync,
|
|
80
|
+
presence_sync=sync.presence_sync,
|
|
81
|
+
config_hash=sync.config_hash,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class CheckQrPayload(CamelModel):
|
|
86
|
+
track_id: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ConfirmQrPayload(CamelModel):
|
|
90
|
+
track_id: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CreateAuthTrackPayload(CamelModel):
|
|
94
|
+
type: int = 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SetPasswordPayload(CamelModel):
|
|
98
|
+
track_id: str
|
|
99
|
+
password: str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RequestEmailCodePayload(CamelModel):
|
|
103
|
+
track_id: str
|
|
104
|
+
email: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SendEmailCodePayload(CamelModel):
|
|
108
|
+
track_id: str
|
|
109
|
+
verify_code: str
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class SetHintPayload(CamelModel):
|
|
113
|
+
track_id: str
|
|
114
|
+
hint: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SetTwoFactorPayload(CamelModel):
|
|
118
|
+
expected_capabilities: list[Capability]
|
|
119
|
+
track_id: str
|
|
120
|
+
password: str
|
|
121
|
+
hint: str | None = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RemoveTwoFactorPayload(CamelModel):
|
|
125
|
+
track_id: str
|
|
126
|
+
remove2fa: bool = True
|
|
127
|
+
expected_capabilities: list[Capability] = Field(
|
|
128
|
+
default_factory=lambda: [Capability.REMOVE_2FA]
|
|
129
|
+
)
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from pymax.api.response import payload_item, payload_keys, require_payload_model
|
|
6
|
+
from pymax.api.session.enums import DeviceType
|
|
7
|
+
from pymax.auth import EmailCodeProvider
|
|
8
|
+
from pymax.auth.providers import ConsoleEmailCodeProvider
|
|
9
|
+
from pymax.logging import get_logger
|
|
10
|
+
from pymax.protocol import Opcode
|
|
11
|
+
from pymax.types.domain.auth import (
|
|
12
|
+
CheckCodeResponse,
|
|
13
|
+
CheckPasswordResponse,
|
|
14
|
+
CheckQrResponse,
|
|
15
|
+
RequestQrResponse,
|
|
16
|
+
StartAuthResponse,
|
|
17
|
+
)
|
|
18
|
+
from pymax.types.domain.login import LoginResponse
|
|
19
|
+
|
|
20
|
+
from .enums import Capability
|
|
21
|
+
from .payloads import (
|
|
22
|
+
CheckPasswordChallengePayload,
|
|
23
|
+
CheckQrPayload,
|
|
24
|
+
ConfirmQrPayload,
|
|
25
|
+
CreateAuthTrackPayload,
|
|
26
|
+
MobileUserAgentPayload,
|
|
27
|
+
RemoveTwoFactorPayload,
|
|
28
|
+
RequestCodePayload,
|
|
29
|
+
RequestEmailCodePayload,
|
|
30
|
+
SendCodePayload,
|
|
31
|
+
SendEmailCodePayload,
|
|
32
|
+
SetHintPayload,
|
|
33
|
+
SetPasswordPayload,
|
|
34
|
+
SetTwoFactorPayload,
|
|
35
|
+
SyncPayload,
|
|
36
|
+
WebSyncPayload,
|
|
37
|
+
)
|
|
38
|
+
from .types import MISSING, Missing
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from pymax.app import App
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
logger = get_logger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AuthService:
|
|
48
|
+
def __init__(self, app: App) -> None:
|
|
49
|
+
self.app = app
|
|
50
|
+
|
|
51
|
+
async def request_code(self, phone: str) -> StartAuthResponse:
|
|
52
|
+
logger.info("requesting sms code phone_set=%s", bool(phone))
|
|
53
|
+
frame = RequestCodePayload(phone=phone)
|
|
54
|
+
response = await self.app.invoke(Opcode.AUTH_REQUEST, frame.to_payload())
|
|
55
|
+
logger.debug(
|
|
56
|
+
"sms code request accepted payload_keys=%s",
|
|
57
|
+
payload_keys(response),
|
|
58
|
+
)
|
|
59
|
+
return require_payload_model(response, StartAuthResponse)
|
|
60
|
+
|
|
61
|
+
async def send_code(self, token: str, verify_code: str) -> CheckCodeResponse:
|
|
62
|
+
logger.info(
|
|
63
|
+
"sending sms code token_set=%s code_set=%s",
|
|
64
|
+
bool(token),
|
|
65
|
+
bool(verify_code),
|
|
66
|
+
)
|
|
67
|
+
frame = SendCodePayload(token=token, verify_code=verify_code)
|
|
68
|
+
response = await self.app.invoke(Opcode.AUTH, frame.to_payload())
|
|
69
|
+
logger.debug(
|
|
70
|
+
"sms code response payload_keys=%s",
|
|
71
|
+
payload_keys(response),
|
|
72
|
+
)
|
|
73
|
+
return require_payload_model(response, CheckCodeResponse)
|
|
74
|
+
|
|
75
|
+
async def check_password(
|
|
76
|
+
self,
|
|
77
|
+
track_id: str,
|
|
78
|
+
password: str,
|
|
79
|
+
) -> CheckPasswordResponse:
|
|
80
|
+
logger.info(
|
|
81
|
+
"checking 2fa password track_id_set=%s password_set=%s",
|
|
82
|
+
bool(track_id),
|
|
83
|
+
bool(password),
|
|
84
|
+
)
|
|
85
|
+
frame = CheckPasswordChallengePayload(
|
|
86
|
+
track_id=track_id,
|
|
87
|
+
password=password,
|
|
88
|
+
)
|
|
89
|
+
response = await self.app.invoke(
|
|
90
|
+
Opcode.AUTH_LOGIN_CHECK_PASSWORD,
|
|
91
|
+
frame.to_payload(),
|
|
92
|
+
)
|
|
93
|
+
logger.debug(
|
|
94
|
+
"2fa password response payload_keys=%s",
|
|
95
|
+
payload_keys(response),
|
|
96
|
+
)
|
|
97
|
+
return require_payload_model(response, CheckPasswordResponse)
|
|
98
|
+
|
|
99
|
+
async def login(self, user_agent: MobileUserAgentPayload) -> LoginResponse:
|
|
100
|
+
if user_agent.device_type == DeviceType.WEB:
|
|
101
|
+
return await self.web_login()
|
|
102
|
+
|
|
103
|
+
return await self.mobile_login()
|
|
104
|
+
|
|
105
|
+
async def mobile_login(self) -> LoginResponse:
|
|
106
|
+
session = self.app.session
|
|
107
|
+
if session is None:
|
|
108
|
+
logger.error("login requested without session")
|
|
109
|
+
raise RuntimeError("No session available for login")
|
|
110
|
+
|
|
111
|
+
logger.info("logging in")
|
|
112
|
+
sync = self.app.config.sync.resolve(session.sync)
|
|
113
|
+
frame = SyncPayload.from_sync_state(
|
|
114
|
+
user_agent=self.app.config.device.user_agent,
|
|
115
|
+
token=session.token,
|
|
116
|
+
sync=sync,
|
|
117
|
+
)
|
|
118
|
+
response = await self.app.invoke(Opcode.LOGIN, frame.to_payload())
|
|
119
|
+
|
|
120
|
+
logger.debug("login response payload_keys=%s", payload_keys(response))
|
|
121
|
+
|
|
122
|
+
login_response = require_payload_model(response, LoginResponse)
|
|
123
|
+
await self._update_session(login_response)
|
|
124
|
+
return login_response
|
|
125
|
+
|
|
126
|
+
async def web_login(self) -> LoginResponse:
|
|
127
|
+
session = self.app.session
|
|
128
|
+
if session is None:
|
|
129
|
+
logger.error("login requested without session")
|
|
130
|
+
raise RuntimeError("No session available for login")
|
|
131
|
+
|
|
132
|
+
logger.info("logging in")
|
|
133
|
+
sync = self.app.config.sync.resolve(session.sync)
|
|
134
|
+
|
|
135
|
+
frame = WebSyncPayload.from_sync_state(
|
|
136
|
+
token=session.token,
|
|
137
|
+
sync=sync,
|
|
138
|
+
)
|
|
139
|
+
response = await self.app.invoke(Opcode.LOGIN, frame.to_payload())
|
|
140
|
+
|
|
141
|
+
logger.debug("login response payload_keys=%s", payload_keys(response))
|
|
142
|
+
|
|
143
|
+
login_response = require_payload_model(response, LoginResponse)
|
|
144
|
+
await self._update_session(login_response)
|
|
145
|
+
return login_response
|
|
146
|
+
|
|
147
|
+
async def request_qr(self) -> RequestQrResponse:
|
|
148
|
+
response = await self.app.invoke(Opcode.GET_QR, {})
|
|
149
|
+
|
|
150
|
+
return require_payload_model(response, RequestQrResponse)
|
|
151
|
+
|
|
152
|
+
async def check_qr(self, track_id: str) -> CheckQrResponse:
|
|
153
|
+
frame = CheckQrPayload(track_id=track_id)
|
|
154
|
+
|
|
155
|
+
response = await self.app.invoke(Opcode.GET_QR_STATUS, frame.to_payload())
|
|
156
|
+
|
|
157
|
+
return require_payload_model(response, CheckQrResponse)
|
|
158
|
+
|
|
159
|
+
async def confirm_qr(self, track_id: str) -> CheckCodeResponse:
|
|
160
|
+
frame = ConfirmQrPayload(track_id=track_id)
|
|
161
|
+
|
|
162
|
+
response = await self.app.invoke(Opcode.LOGIN_BY_QR, frame.to_payload())
|
|
163
|
+
|
|
164
|
+
return require_payload_model(response, CheckCodeResponse)
|
|
165
|
+
|
|
166
|
+
async def _update_session(self, response: LoginResponse) -> None:
|
|
167
|
+
session = self.app.session
|
|
168
|
+
if session is None:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
sync = response.update_sync_state(session.sync)
|
|
172
|
+
updated = session.model_copy(
|
|
173
|
+
update={
|
|
174
|
+
"mt_instance_id": self.app.config.device.mt_instance_id,
|
|
175
|
+
"sync": sync,
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
self.app.session = updated
|
|
179
|
+
await self.app.store.save_session(updated)
|
|
180
|
+
|
|
181
|
+
async def _get_track_id(self) -> str | None:
|
|
182
|
+
logger.debug("creating auth track")
|
|
183
|
+
frame = CreateAuthTrackPayload()
|
|
184
|
+
|
|
185
|
+
response = await self.app.invoke(Opcode.AUTH_CREATE_TRACK, frame.to_payload())
|
|
186
|
+
|
|
187
|
+
return payload_item(response, "trackId", str)
|
|
188
|
+
|
|
189
|
+
async def _set_email(self, track_id: str, email: str, provider: EmailCodeProvider) -> bool:
|
|
190
|
+
logger.info("setting 2fa email email_set=%s", bool(email))
|
|
191
|
+
|
|
192
|
+
frame = RequestEmailCodePayload(
|
|
193
|
+
track_id=track_id,
|
|
194
|
+
email=email,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
await self.app.invoke(Opcode.AUTH_VERIFY_EMAIL, frame.to_payload())
|
|
198
|
+
|
|
199
|
+
code = await provider.get_code(email)
|
|
200
|
+
|
|
201
|
+
frame = SendEmailCodePayload(
|
|
202
|
+
track_id=track_id,
|
|
203
|
+
verify_code=code,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
await self.app.invoke(Opcode.AUTH_CHECK_EMAIL, frame.to_payload())
|
|
207
|
+
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
async def _set_hint(self, track_id: str, hint: str) -> bool:
|
|
211
|
+
logger.info("setting 2fa hint hint_set=%s", bool(hint))
|
|
212
|
+
|
|
213
|
+
frame = SetHintPayload(
|
|
214
|
+
track_id=track_id,
|
|
215
|
+
hint=hint,
|
|
216
|
+
)
|
|
217
|
+
await self.app.invoke(Opcode.AUTH_VALIDATE_HINT, frame.to_payload())
|
|
218
|
+
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
async def _set_password(self, track_id: str, password: str) -> bool:
|
|
222
|
+
logger.info("setting 2fa password password_set=%s", bool(password))
|
|
223
|
+
|
|
224
|
+
frame = SetPasswordPayload(
|
|
225
|
+
track_id=track_id,
|
|
226
|
+
password=password,
|
|
227
|
+
)
|
|
228
|
+
await self.app.invoke(Opcode.AUTH_VALIDATE_PASSWORD, frame.to_payload())
|
|
229
|
+
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
async def set_2fa(
|
|
233
|
+
self,
|
|
234
|
+
password: str,
|
|
235
|
+
email: str | Missing = MISSING,
|
|
236
|
+
hint: str | Missing = MISSING,
|
|
237
|
+
email_code_provider: EmailCodeProvider | None = None,
|
|
238
|
+
) -> bool:
|
|
239
|
+
logger.info(
|
|
240
|
+
"setting 2fa password password_set=%s email_set=%s hint_set=%s",
|
|
241
|
+
bool(password),
|
|
242
|
+
bool(email),
|
|
243
|
+
bool(hint),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
track_id = await self._get_track_id()
|
|
247
|
+
|
|
248
|
+
if track_id is None:
|
|
249
|
+
logger.error("missing track_id in auth create track response")
|
|
250
|
+
raise RuntimeError("Failed to create auth track")
|
|
251
|
+
|
|
252
|
+
has_hint = False
|
|
253
|
+
has_email = False
|
|
254
|
+
|
|
255
|
+
await self._set_password(track_id, password)
|
|
256
|
+
|
|
257
|
+
if email is not MISSING:
|
|
258
|
+
provider = email_code_provider or ConsoleEmailCodeProvider()
|
|
259
|
+
await self._set_email(track_id, str(email), provider)
|
|
260
|
+
has_email = True
|
|
261
|
+
|
|
262
|
+
if hint is not MISSING:
|
|
263
|
+
await self._set_hint(track_id, str(hint))
|
|
264
|
+
has_hint = True
|
|
265
|
+
|
|
266
|
+
expected_capabilities = [Capability.DEFAULT]
|
|
267
|
+
|
|
268
|
+
if has_hint:
|
|
269
|
+
expected_capabilities.append(Capability.SECOND_FACTOR_HAS_HINT)
|
|
270
|
+
|
|
271
|
+
if has_email:
|
|
272
|
+
expected_capabilities.append(Capability.SECOND_FACTOR_HAS_EMAIL)
|
|
273
|
+
|
|
274
|
+
frame = SetTwoFactorPayload(
|
|
275
|
+
track_id=track_id,
|
|
276
|
+
password=password,
|
|
277
|
+
hint=str(hint) if has_hint else None,
|
|
278
|
+
expected_capabilities=expected_capabilities,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
await self.app.invoke(Opcode.AUTH_SET_2FA, frame.to_payload())
|
|
282
|
+
logger.info("2fa password set successfully")
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
async def _check_2fa_password(self, track_id: str, password: str) -> bool:
|
|
286
|
+
logger.info("entering 2fa password password_set=%s", bool(password))
|
|
287
|
+
|
|
288
|
+
frame = SetPasswordPayload(
|
|
289
|
+
track_id=track_id,
|
|
290
|
+
password=password,
|
|
291
|
+
)
|
|
292
|
+
await self.app.invoke(Opcode.AUTH_CHECK_PASSWORD, frame.to_payload())
|
|
293
|
+
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
async def remove_2fa(self, password: str) -> bool:
|
|
297
|
+
logger.info("removing 2fa password_set=%s", bool(password))
|
|
298
|
+
|
|
299
|
+
track_id = await self._get_track_id()
|
|
300
|
+
|
|
301
|
+
if track_id is None:
|
|
302
|
+
logger.error("missing track_id in auth create track response")
|
|
303
|
+
raise RuntimeError("Failed to create auth track")
|
|
304
|
+
|
|
305
|
+
await self._check_2fa_password(track_id, password)
|
|
306
|
+
|
|
307
|
+
frame = RemoveTwoFactorPayload(
|
|
308
|
+
track_id=track_id,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
await self.app.invoke(Opcode.AUTH_SET_2FA, frame.to_payload())
|
|
312
|
+
|
|
313
|
+
return True
|
pymax/api/auth/types.py
ADDED
pymax/api/chats/enums.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ControlEvent(str, Enum):
|
|
5
|
+
NEW = "new"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ChatMemberOperation(str, Enum):
|
|
9
|
+
ADD = "add"
|
|
10
|
+
REMOVE = "remove"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChatOption(str, Enum):
|
|
14
|
+
ONLY_OWNER_CAN_CHANGE_ICON_TITLE = "ONLY_OWNER_CAN_CHANGE_ICON_TITLE"
|
|
15
|
+
ALL_CAN_PIN_MESSAGE = "ALL_CAN_PIN_MESSAGE"
|
|
16
|
+
ONLY_ADMIN_CAN_ADD_MEMBER = "ONLY_ADMIN_CAN_ADD_MEMBER"
|
|
17
|
+
ONLY_ADMIN_CAN_CALL = "ONLY_ADMIN_CAN_CALL"
|
|
18
|
+
MEMBERS_CAN_SEE_PRIVATE_LINK = "MEMBERS_CAN_SEE_PRIVATE_LINK"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ChatPayloadKey(str, Enum):
|
|
22
|
+
CHAT = "chat"
|
|
23
|
+
CHATS = "chats"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ChatLinkPrefix(str, Enum):
|
|
27
|
+
JOIN = "join/"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from pymax.api.models import CamelModel
|
|
6
|
+
from pymax.types.domain.attachments.enums import AttachmentType
|
|
7
|
+
from pymax.types.domain.enums import ChatType
|
|
8
|
+
|
|
9
|
+
from .enums import ChatMemberOperation, ChatOption, ControlEvent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CreateGroupAttach(CamelModel):
|
|
13
|
+
type: Literal[AttachmentType.CONTROL] = Field(
|
|
14
|
+
default=AttachmentType.CONTROL,
|
|
15
|
+
alias="_type",
|
|
16
|
+
)
|
|
17
|
+
event: ControlEvent = ControlEvent.NEW
|
|
18
|
+
chat_type: Literal[ChatType.CHAT] = ChatType.CHAT
|
|
19
|
+
title: str
|
|
20
|
+
user_ids: list[int]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CreateGroupMessage(CamelModel):
|
|
24
|
+
cid: int
|
|
25
|
+
attaches: list[CreateGroupAttach]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CreateGroupPayload(CamelModel):
|
|
29
|
+
message: CreateGroupMessage
|
|
30
|
+
notify: bool = True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InviteUsersPayload(CamelModel):
|
|
34
|
+
chat_id: int
|
|
35
|
+
user_ids: list[int]
|
|
36
|
+
show_history: bool
|
|
37
|
+
operation: ChatMemberOperation = ChatMemberOperation.ADD
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RemoveUsersPayload(CamelModel):
|
|
41
|
+
chat_id: int
|
|
42
|
+
user_ids: list[int]
|
|
43
|
+
operation: ChatMemberOperation = ChatMemberOperation.REMOVE
|
|
44
|
+
clean_msg_period: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ChangeGroupSettingsOptions(CamelModel):
|
|
48
|
+
only_owner_can_change_icon_title: bool | None = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
serialization_alias=ChatOption.ONLY_OWNER_CAN_CHANGE_ICON_TITLE.value,
|
|
51
|
+
)
|
|
52
|
+
all_can_pin_message: bool | None = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
serialization_alias=ChatOption.ALL_CAN_PIN_MESSAGE.value,
|
|
55
|
+
)
|
|
56
|
+
only_admin_can_add_member: bool | None = Field(
|
|
57
|
+
default=None,
|
|
58
|
+
serialization_alias=ChatOption.ONLY_ADMIN_CAN_ADD_MEMBER.value,
|
|
59
|
+
)
|
|
60
|
+
only_admin_can_call: bool | None = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
serialization_alias=ChatOption.ONLY_ADMIN_CAN_CALL.value,
|
|
63
|
+
)
|
|
64
|
+
members_can_see_private_link: bool | None = Field(
|
|
65
|
+
default=None,
|
|
66
|
+
serialization_alias=ChatOption.MEMBERS_CAN_SEE_PRIVATE_LINK.value,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ChangeGroupSettingsPayload(CamelModel):
|
|
71
|
+
chat_id: int
|
|
72
|
+
options: ChangeGroupSettingsOptions
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ChangeGroupProfilePayload(CamelModel):
|
|
76
|
+
chat_id: int
|
|
77
|
+
theme: str | None
|
|
78
|
+
description: str | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class JoinChatPayload(CamelModel):
|
|
82
|
+
link: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class LinkInfoPayload(CamelModel):
|
|
86
|
+
link: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ReworkInviteLinkPayload(CamelModel):
|
|
90
|
+
revoke_private_link: bool = True
|
|
91
|
+
chat_id: int
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class GetChatInfoPayload(CamelModel):
|
|
95
|
+
chat_ids: list[int]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class LeaveChatPayload(CamelModel):
|
|
99
|
+
chat_id: int
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class FetchChatsPayload(CamelModel):
|
|
103
|
+
marker: int
|