maxapi-python 1.1.14__tar.gz → 1.1.15__tar.gz
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-1.1.14 → maxapi_python-1.1.15}/PKG-INFO +2 -2
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/README.md +1 -1
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/examples/example.py +20 -8
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/pyproject.toml +1 -1
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/core.py +32 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/files.py +15 -9
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/interfaces.py +26 -4
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/auth.py +69 -6
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/message.py +187 -126
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/socket.py +93 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/websocket.py +127 -29
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/payloads.py +21 -9
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/static/enum.py +1 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/FUNDING.yml +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/pull_request_template.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.github/workflows/publish.yml +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.gitignore +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/.pre-commit-config.yaml +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/LICENSE +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/assets/icon.svg +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/assets/logo.svg +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/api.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/assets/icon.svg +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/client.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/examples.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/index.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/methods.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/docs/types.md +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/examples/telegram_bridge.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/mkdocs.yml +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/ruff.toml +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/__init__.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/crud.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/exceptions.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/filters.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/formatting.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/__init__.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/channel.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/group.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/handler.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/self.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/telemetry.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/mixins/user.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/models.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/navigation.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/static/constant.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/types.py +0 -0
- {maxapi_python-1.1.14 → maxapi_python-1.1.15}/src/pymax/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.15
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/ink-developer/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/ink-developer/PyMax
|
|
@@ -145,7 +145,7 @@ if __name__ == "__main__":
|
|
|
145
145
|
|
|
146
146
|
## Документация
|
|
147
147
|
|
|
148
|
-
[WIP](https://ink-developer.github.io/)
|
|
148
|
+
[WIP](https://ink-developer.github.io/PyMax)
|
|
149
149
|
|
|
150
150
|
## Лицензия
|
|
151
151
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import datetime
|
|
2
3
|
|
|
3
4
|
from pymax import MaxClient, Message
|
|
5
|
+
from pymax.files import File
|
|
4
6
|
from pymax.filters import Filter
|
|
5
7
|
from pymax.static.enum import AttachType
|
|
6
8
|
|
|
@@ -27,14 +29,24 @@ async def handle_deleted_message(message: Message) -> None:
|
|
|
27
29
|
|
|
28
30
|
@client.on_start
|
|
29
31
|
async def handle_start() -> None:
|
|
30
|
-
print("Client started successfully!")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
print(f"Client started successfully at {datetime.datetime.now()}!")
|
|
33
|
+
file_path = "ruff.toml"
|
|
34
|
+
file = File(path=file_path)
|
|
35
|
+
msg = await client.send_message(
|
|
36
|
+
text="Here is the file you requested.",
|
|
37
|
+
chat_id=0,
|
|
38
|
+
attachment=file,
|
|
39
|
+
notify=True,
|
|
40
|
+
)
|
|
41
|
+
if msg:
|
|
42
|
+
print(f"File sent successfully in message ID: {msg.id}")
|
|
43
|
+
# history = await client.fetch_history(chat_id=0)
|
|
44
|
+
# if history:
|
|
45
|
+
# for message in history:
|
|
46
|
+
# if message.attaches:
|
|
47
|
+
# for attach in message.attaches:
|
|
48
|
+
# if attach.type == AttachType.STICKER:
|
|
49
|
+
# print(attach.lottie_url)
|
|
38
50
|
# chat = await client.rework_invite_link(chat_id=0)
|
|
39
51
|
# print(chat.link)
|
|
40
52
|
# text = """
|
|
@@ -4,6 +4,7 @@ import socket
|
|
|
4
4
|
import ssl
|
|
5
5
|
import time
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Literal
|
|
7
8
|
|
|
8
9
|
from typing_extensions import override
|
|
9
10
|
|
|
@@ -36,6 +37,12 @@ class MaxClient(ApiMixin, WebSocketMixin):
|
|
|
36
37
|
процесс логина по номеру телефона.
|
|
37
38
|
host (str, optional): Хост API сервера. По умолчанию Constants.HOST.value.
|
|
38
39
|
port (int, optional): Порт API сервера. По умолчанию Constants.PORT.value.
|
|
40
|
+
registration (bool, optional): Флаг регистрации нового пользователя. По умолчанию False.
|
|
41
|
+
first_name (str, optional): Имя пользователя для регистрации. Требуется, если registration=True.
|
|
42
|
+
last_name (str | None, optional): Фамилия пользователя для регистрации.
|
|
43
|
+
send_fake_telemetry (bool, optional): Флаг отправки фейковой телеметрии. По умолчанию True.
|
|
44
|
+
proxy (str | Literal[True] | None, optional): Прокси для подключения к WebSocket.
|
|
45
|
+
(См. https://websockets.readthedocs.io/en/stable/topics/proxies.html).
|
|
39
46
|
|
|
40
47
|
Raises:
|
|
41
48
|
InvalidPhoneError: Если формат номера телефона неверный.
|
|
@@ -50,7 +57,11 @@ class MaxClient(ApiMixin, WebSocketMixin):
|
|
|
50
57
|
send_fake_telemetry: bool = True,
|
|
51
58
|
host: str = HOST,
|
|
52
59
|
port: int = PORT,
|
|
60
|
+
proxy: str | Literal[True] | None = None,
|
|
53
61
|
work_dir: str = ".",
|
|
62
|
+
registration: bool = False,
|
|
63
|
+
first_name: str = "",
|
|
64
|
+
last_name: str | None = None,
|
|
54
65
|
logger: logging.Logger | None = None,
|
|
55
66
|
) -> None:
|
|
56
67
|
logger = logger or logging.getLogger(f"{__name__}.MaxClient")
|
|
@@ -62,12 +73,22 @@ class MaxClient(ApiMixin, WebSocketMixin):
|
|
|
62
73
|
raise InvalidPhoneError(self.phone)
|
|
63
74
|
self.host: str = host
|
|
64
75
|
self.port: int = port
|
|
76
|
+
self.registration: bool = registration
|
|
77
|
+
self.first_name: str = first_name
|
|
78
|
+
self.last_name: str | None = last_name
|
|
79
|
+
self.proxy: str | Literal[True] | None = proxy
|
|
65
80
|
self._work_dir: str = work_dir
|
|
66
81
|
self._database_path: Path = Path(work_dir) / "session.db"
|
|
67
82
|
self._database_path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
83
|
self._database_path.touch(exist_ok=True)
|
|
69
84
|
self._database = Database(self._work_dir)
|
|
85
|
+
self._outgoing: asyncio.Queue[dict[str, Any]] | None = None
|
|
86
|
+
self._outgoing_task: asyncio.Task[Any] | None = None
|
|
87
|
+
self._error_count: int = 0
|
|
88
|
+
self._circuit_breaker: bool = False
|
|
89
|
+
self._last_error_time: float = 0.0
|
|
70
90
|
self._device_id = self._database.get_device_id()
|
|
91
|
+
self._file_upload_waiters: dict[int, asyncio.Future[dict[str, Any]]] = {}
|
|
71
92
|
self._token = self._database.get_auth_token() or token
|
|
72
93
|
self.user_agent = headers
|
|
73
94
|
self._send_fake_telemetry: bool = send_fake_telemetry
|
|
@@ -111,6 +132,12 @@ class MaxClient(ApiMixin, WebSocketMixin):
|
|
|
111
132
|
await self._recv_task
|
|
112
133
|
except asyncio.CancelledError:
|
|
113
134
|
self.logger.debug("recv_task cancelled")
|
|
135
|
+
if self._outgoing_task:
|
|
136
|
+
self._outgoing_task.cancel()
|
|
137
|
+
try:
|
|
138
|
+
await self._outgoing_task
|
|
139
|
+
except asyncio.CancelledError:
|
|
140
|
+
self.logger.debug("outgoing_task cancelled")
|
|
114
141
|
if self._ws:
|
|
115
142
|
await self._ws.close()
|
|
116
143
|
self.is_connected = False
|
|
@@ -127,6 +154,11 @@ class MaxClient(ApiMixin, WebSocketMixin):
|
|
|
127
154
|
self.logger.info("Client starting")
|
|
128
155
|
await self._connect(self.user_agent)
|
|
129
156
|
|
|
157
|
+
if self.registration:
|
|
158
|
+
if not self.first_name:
|
|
159
|
+
raise ValueError("First name is required for registration")
|
|
160
|
+
await self._register(self.first_name, self.last_name)
|
|
161
|
+
|
|
130
162
|
if self._token and self._database.get_auth_token() is None:
|
|
131
163
|
self._database.update_auth_token(self._device_id, self._token)
|
|
132
164
|
|
|
@@ -9,9 +9,7 @@ from typing_extensions import override
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class BaseFile(ABC):
|
|
12
|
-
def __init__(
|
|
13
|
-
self, url: str | None = None, path: str | None = None
|
|
14
|
-
) -> None:
|
|
12
|
+
def __init__(self, url: str | None = None, path: str | None = None) -> None:
|
|
15
13
|
self.url = url
|
|
16
14
|
self.path = path
|
|
17
15
|
|
|
@@ -47,9 +45,7 @@ class Photo(BaseFile):
|
|
|
47
45
|
".bmp",
|
|
48
46
|
} # FIXME: костыль ✅
|
|
49
47
|
|
|
50
|
-
def __init__(
|
|
51
|
-
self, url: str | None = None, path: str | None = None
|
|
52
|
-
) -> None:
|
|
48
|
+
def __init__(self, url: str | None = None, path: str | None = None) -> None:
|
|
53
49
|
super().__init__(url, path)
|
|
54
50
|
|
|
55
51
|
def validate_photo(self) -> tuple[str, str] | None:
|
|
@@ -71,9 +67,7 @@ class Photo(BaseFile):
|
|
|
71
67
|
mime_type = mimetypes.guess_type(self.url)[0]
|
|
72
68
|
|
|
73
69
|
if not mime_type or not mime_type.startswith("image/"):
|
|
74
|
-
raise ValueError(
|
|
75
|
-
f"URL does not appear to be an image: {self.url}"
|
|
76
|
-
)
|
|
70
|
+
raise ValueError(f"URL does not appear to be an image: {self.url}")
|
|
77
71
|
|
|
78
72
|
return (extension[1:], mime_type)
|
|
79
73
|
return None
|
|
@@ -90,6 +84,18 @@ class Video(BaseFile):
|
|
|
90
84
|
|
|
91
85
|
|
|
92
86
|
class File(BaseFile):
|
|
87
|
+
def __init__(self, url: str | None = None, path: str | None = None) -> None:
|
|
88
|
+
self.file_name: str = ""
|
|
89
|
+
if path:
|
|
90
|
+
self.file_name = Path(path).name
|
|
91
|
+
elif url:
|
|
92
|
+
self.file_name = Path(url).name
|
|
93
|
+
|
|
94
|
+
if not self.file_name:
|
|
95
|
+
raise ValueError("Either url or path must be provided.")
|
|
96
|
+
|
|
97
|
+
super().__init__(url, path)
|
|
98
|
+
|
|
93
99
|
@override
|
|
94
100
|
async def read(self) -> bytes:
|
|
95
101
|
return await super().read()
|
|
@@ -5,10 +5,12 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from collections.abc import Awaitable, Callable
|
|
6
6
|
from logging import Logger
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
9
9
|
|
|
10
10
|
import websockets
|
|
11
11
|
|
|
12
|
+
from pymax.static.constant import DEFAULT_USER_AGENT
|
|
13
|
+
|
|
12
14
|
from .filters import Filter
|
|
13
15
|
from .payloads import UserAgentPayload
|
|
14
16
|
from .static.constant import DEFAULT_TIMEOUT
|
|
@@ -37,6 +39,10 @@ class ClientProtocol(ABC):
|
|
|
37
39
|
self.me: Me | None = None
|
|
38
40
|
self.host: str
|
|
39
41
|
self.port: int
|
|
42
|
+
self.proxy: str | Literal[True] | None
|
|
43
|
+
self.registration: bool
|
|
44
|
+
self.first_name: str
|
|
45
|
+
self.last_name: str | None
|
|
40
46
|
self._work_dir: str
|
|
41
47
|
self._database_path: Path
|
|
42
48
|
self._ws: websockets.ClientConnection | None = None
|
|
@@ -44,7 +50,14 @@ class ClientProtocol(ABC):
|
|
|
44
50
|
self._pending: dict[int, asyncio.Future[dict[str, Any]]] = {}
|
|
45
51
|
self._recv_task: asyncio.Task[Any] | None = None
|
|
46
52
|
self._incoming: asyncio.Queue[dict[str, Any]] | None = None
|
|
53
|
+
self._file_upload_waiters: dict[int, asyncio.Future[dict[str, Any]]] = {}
|
|
47
54
|
self.user_agent = UserAgentPayload()
|
|
55
|
+
self._outgoing: asyncio.Queue[dict[str, Any]] | None = None
|
|
56
|
+
self._outgoing_task: asyncio.Task[Any] | None = None
|
|
57
|
+
self._error_count: int = 0
|
|
58
|
+
self._circuit_breaker: bool = False
|
|
59
|
+
self._last_error_time: float = 0.0
|
|
60
|
+
self.user_agent = DEFAULT_USER_AGENT
|
|
48
61
|
self._session_id: int
|
|
49
62
|
self._action_id: int = 0
|
|
50
63
|
self._current_screen: str = "chats_list_tab"
|
|
@@ -57,9 +70,7 @@ class ClientProtocol(ABC):
|
|
|
57
70
|
self._on_message_delete_handlers: list[
|
|
58
71
|
tuple[Callable[[Message], Any], Filter | None]
|
|
59
72
|
] = []
|
|
60
|
-
self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None =
|
|
61
|
-
None
|
|
62
|
-
)
|
|
73
|
+
self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
|
|
63
74
|
self._background_tasks: set[asyncio.Task[Any]] = set()
|
|
64
75
|
self._ssl_context: ssl.SSLContext
|
|
65
76
|
self._socket: socket.socket | None = None
|
|
@@ -77,3 +88,14 @@ class ClientProtocol(ABC):
|
|
|
77
88
|
@abstractmethod
|
|
78
89
|
async def _get_chat(self, chat_id: int) -> Chat | None:
|
|
79
90
|
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
async def _queue_message(
|
|
94
|
+
self,
|
|
95
|
+
opcode: int,
|
|
96
|
+
payload: dict[str, Any],
|
|
97
|
+
cmd: int = 0,
|
|
98
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
99
|
+
max_retries: int = 3,
|
|
100
|
+
) -> Message | None:
|
|
101
|
+
pass
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import re
|
|
3
|
+
import sys
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from pymax.interfaces import ClientProtocol
|
|
6
|
-
from pymax.payloads import RequestCodePayload, SendCodePayload
|
|
7
|
+
from pymax.payloads import RegisterPayload, RequestCodePayload, SendCodePayload
|
|
7
8
|
from pymax.static.constant import PHONE_REGEX
|
|
8
9
|
from pymax.static.enum import AuthType, Opcode
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class AuthMixin(ClientProtocol):
|
|
12
|
-
|
|
13
13
|
def __init__(self, token: str | None = None, *args, **kwargs) -> None:
|
|
14
14
|
super().__init__(*args, **kwargs)
|
|
15
15
|
self._token = token
|
|
@@ -55,9 +55,7 @@ class AuthMixin(ClientProtocol):
|
|
|
55
55
|
auth_token_type=AuthType.CHECK_CODE,
|
|
56
56
|
).model_dump(by_alias=True)
|
|
57
57
|
|
|
58
|
-
data = await self._send_and_wait(
|
|
59
|
-
opcode=Opcode.AUTH, payload=payload
|
|
60
|
-
)
|
|
58
|
+
data = await self._send_and_wait(opcode=Opcode.AUTH, payload=payload)
|
|
61
59
|
self.logger.debug(
|
|
62
60
|
"Send code response opcode=%s seq=%s",
|
|
63
61
|
data.get("opcode"),
|
|
@@ -75,13 +73,15 @@ class AuthMixin(ClientProtocol):
|
|
|
75
73
|
|
|
76
74
|
async def _login(self) -> None:
|
|
77
75
|
self.logger.info("Starting login flow")
|
|
76
|
+
|
|
78
77
|
request_code_payload = await self._request_code(self.phone)
|
|
79
78
|
temp_token = request_code_payload.get("token")
|
|
80
79
|
if not temp_token or not isinstance(temp_token, str):
|
|
81
80
|
self.logger.critical("Failed to request code: token missing")
|
|
82
81
|
raise ValueError("Failed to request code")
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
print("Введите код: ", end="", flush=True)
|
|
84
|
+
code = await asyncio.to_thread(lambda: sys.stdin.readline().strip())
|
|
85
85
|
if len(code) != 6 or not code.isdigit():
|
|
86
86
|
self.logger.error("Invalid code format entered")
|
|
87
87
|
raise ValueError("Invalid code format")
|
|
@@ -97,3 +97,66 @@ class AuthMixin(ClientProtocol):
|
|
|
97
97
|
self._token = token
|
|
98
98
|
self._database.update_auth_token(self._device_id, self._token)
|
|
99
99
|
self.logger.info("Login successful, token saved to database")
|
|
100
|
+
|
|
101
|
+
async def _submit_reg_info(
|
|
102
|
+
self, first_name: str, last_name: str | None, token: str
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
try:
|
|
105
|
+
self.logger.info("Submitting registration info")
|
|
106
|
+
|
|
107
|
+
payload = RegisterPayload(
|
|
108
|
+
first_name=first_name,
|
|
109
|
+
last_name=last_name,
|
|
110
|
+
token=token,
|
|
111
|
+
).model_dump(by_alias=True)
|
|
112
|
+
|
|
113
|
+
data = await self._send_and_wait(
|
|
114
|
+
opcode=Opcode.AUTH_CONFIRM, payload=payload
|
|
115
|
+
)
|
|
116
|
+
self.logger.debug(
|
|
117
|
+
"Registration info response opcode=%s seq=%s",
|
|
118
|
+
data.get("opcode"),
|
|
119
|
+
data.get("seq"),
|
|
120
|
+
)
|
|
121
|
+
payload_data = data.get("payload")
|
|
122
|
+
if isinstance(payload_data, dict):
|
|
123
|
+
return payload_data
|
|
124
|
+
else:
|
|
125
|
+
self.logger.error("Invalid payload data received")
|
|
126
|
+
raise ValueError("Invalid payload data received")
|
|
127
|
+
except Exception:
|
|
128
|
+
self.logger.error("Submit registration info failed", exc_info=True)
|
|
129
|
+
raise RuntimeError("Submit registration info failed")
|
|
130
|
+
|
|
131
|
+
async def _register(self, first_name: str, last_name: str | None = None) -> None:
|
|
132
|
+
self.logger.info("Starting registration flow")
|
|
133
|
+
|
|
134
|
+
request_code_payload = await self._request_code(self.phone)
|
|
135
|
+
temp_token = request_code_payload.get("token")
|
|
136
|
+
|
|
137
|
+
if not temp_token or not isinstance(temp_token, str):
|
|
138
|
+
self.logger.critical("Failed to request code: token missing")
|
|
139
|
+
raise ValueError("Failed to request code")
|
|
140
|
+
|
|
141
|
+
print("Введите код: ", end="", flush=True)
|
|
142
|
+
code = await asyncio.to_thread(lambda: sys.stdin.readline().strip())
|
|
143
|
+
if len(code) != 6 or not code.isdigit():
|
|
144
|
+
self.logger.error("Invalid code format entered")
|
|
145
|
+
raise ValueError("Invalid code format")
|
|
146
|
+
|
|
147
|
+
registration_response = await self._send_code(code, temp_token)
|
|
148
|
+
token: str | None = (
|
|
149
|
+
registration_response.get("tokenAttrs", {}).get("REGISTER", {}).get("token")
|
|
150
|
+
)
|
|
151
|
+
if not token:
|
|
152
|
+
self.logger.critical("Failed to register, token not received")
|
|
153
|
+
raise ValueError("Failed to register, token not received")
|
|
154
|
+
|
|
155
|
+
data = await self._submit_reg_info(first_name, last_name, token)
|
|
156
|
+
self._token = data.get("token")
|
|
157
|
+
if not self._token:
|
|
158
|
+
self.logger.critical("Failed to register, token not received")
|
|
159
|
+
raise ValueError("Failed to register, token not received")
|
|
160
|
+
|
|
161
|
+
self._database.update_auth_token(self._device_id, self._token)
|
|
162
|
+
self.logger.info("Registration successful, token saved to database")
|