|
15 | 15 | SERVERS = ["cn", "de", "i2", "ru", "sg", "us"] |
16 | 16 | SID = "xiaomiio" |
17 | 17 |
|
| 18 | +FLAG_PHONE = 4 |
| 19 | +FLAG_EMAIL = 8 |
| 20 | + |
18 | 21 |
|
19 | 22 | class AuthResult(TypedDict, total=False): |
20 | 23 | ok: bool |
21 | 24 | captcha: bytes | None |
22 | | - email: str | None |
| 25 | + verify: str | None # phone or email address |
23 | 26 | token: str | None |
24 | 27 | exception: Exception | None |
25 | 28 |
|
@@ -54,9 +57,7 @@ async def login(self, username: str, password: str, **kwargs) -> AuthResult: |
54 | 57 | return {"ok": False, "captcha": data["image"]} |
55 | 58 |
|
56 | 59 | if notification_url := res1.get("notificationUrl"): |
57 | | - data = await self._send_email_ticket(notification_url) |
58 | | - self.auth = {"identity_session": data["identity_session"]} |
59 | | - return {"ok": False, "email": data["email"]} |
| 60 | + return await self._get_notification_url(notification_url) |
60 | 61 |
|
61 | 62 | res2 = await self._get_location(res1["location"]) |
62 | 63 |
|
@@ -90,15 +91,23 @@ async def login_token(self, token: str) -> AuthResult: |
90 | 91 | return {"ok": False, "exception": e} |
91 | 92 |
|
92 | 93 | async def login_captcha(self, code: str) -> AuthResult: |
| 94 | + if "flag" in self.auth and "identity_session" in self.auth: |
| 95 | + return await self._send_ticket( |
| 96 | + self.auth["flag"], self.auth["identity_session"], code |
| 97 | + ) |
| 98 | + |
93 | 99 | return await self.login( |
94 | 100 | self.auth["username"], self.auth["password"], captcha_code=code |
95 | 101 | ) |
96 | 102 |
|
97 | | - async def verify_email(self, ticket: str) -> AuthResult: |
| 103 | + async def login_verify(self, ticket: str) -> AuthResult: |
| 104 | + flag = self.auth["flag"] |
| 105 | + key = "Phone" if flag == FLAG_PHONE else "Email" |
| 106 | + |
98 | 107 | r = await self.session.post( |
99 | | - "https://account.xiaomi.com/identity/auth/verifyEmail", |
| 108 | + f"https://account.xiaomi.com/identity/auth/verify{key}", |
100 | 109 | cookies={"identity_session": self.auth["identity_session"]}, |
101 | | - params={"_flag": 8, "ticket": ticket, "trust": "false", "_json": "true"}, |
| 110 | + params={"_flag": flag, "ticket": ticket, "trust": "false", "_json": "true"}, |
102 | 111 | ) |
103 | 112 | res1 = parse_auth_response(await r.read()) |
104 | 113 | assert res1["code"] == 0, res1 |
@@ -162,33 +171,56 @@ async def _get_captcha_url(self, captcha_url: str) -> dict: |
162 | 171 | body = await r.read() |
163 | 172 | return {"image": body, "ick": r.cookies["ick"]} |
164 | 173 |
|
165 | | - async def _send_email_ticket(self, notification_url: str) -> dict: |
| 174 | + async def _get_notification_url(self, notification_url: str) -> AuthResult: |
166 | 175 | assert "/identity/authStart" in notification_url, notification_url |
167 | 176 | notification_url = notification_url.replace("authStart", "list") |
168 | 177 |
|
169 | 178 | r = await self.session.get(notification_url) |
170 | 179 | res1 = parse_auth_response(await r.read()) |
171 | 180 | assert res1["code"] == 2, res1 |
172 | 181 |
|
173 | | - identity_session = r.cookies["identity_session"] |
| 182 | + flag = res1["flag"] |
| 183 | + assert flag in (FLAG_EMAIL, FLAG_PHONE), res1 |
| 184 | + |
| 185 | + return await self._send_ticket(flag, r.cookies["identity_session"]) |
| 186 | + |
| 187 | + async def _send_ticket( |
| 188 | + self, flag: int, identity_session, captcha_code: str = None |
| 189 | + ) -> AuthResult: |
| 190 | + key = "Phone" if flag == FLAG_PHONE else "Email" |
174 | 191 |
|
175 | 192 | r = await self.session.get( |
176 | | - "https://account.xiaomi.com/identity/auth/verifyEmail", |
| 193 | + f"https://account.xiaomi.com/identity/auth/verify{key}", |
177 | 194 | cookies={"identity_session": identity_session}, |
178 | | - params={"_flag": 8, "_json": "true"}, |
| 195 | + params={"_flag": flag, "_json": "true"}, |
179 | 196 | ) |
180 | | - res2 = parse_auth_response(await r.read()) |
181 | | - assert res2["code"] == 0, res2 |
| 197 | + res1 = parse_auth_response(await r.read()) |
| 198 | + assert res1["code"] == 0, res1 |
| 199 | + |
| 200 | + if captcha_code: |
| 201 | + cookies = {"identity_session": identity_session, "ick": self.auth["ick"]} |
| 202 | + data = {"retry": 0, "icode": captcha_code, "_json": "true"} |
| 203 | + else: |
| 204 | + cookies = {"identity_session": identity_session} |
| 205 | + data = {"retry": 0, "icode": "", "_json": "true"} |
182 | 206 |
|
183 | 207 | r = await self.session.post( |
184 | | - "https://account.xiaomi.com/identity/auth/sendEmailTicket", |
185 | | - cookies={"identity_session": identity_session}, |
186 | | - data={"retry": 0, "icode": "", "_json": "true"}, |
| 208 | + f"https://account.xiaomi.com/identity/auth/send{key}Ticket", |
| 209 | + cookies=cookies, |
| 210 | + data=data, |
187 | 211 | ) |
188 | | - res3 = parse_auth_response(await r.read()) |
189 | | - assert res3["code"] == 0, res3 |
| 212 | + res2 = parse_auth_response(await r.read()) |
| 213 | + |
| 214 | + self.auth = {"flag": flag, "identity_session": identity_session} |
| 215 | + |
| 216 | + if captcha_url := res2.get("captchaUrl"): |
| 217 | + data = await self._get_captcha_url(captcha_url) |
| 218 | + self.auth["ick"] = data["ick"] |
| 219 | + return {"ok": False, "captcha": data["image"]} |
| 220 | + |
| 221 | + assert res2["code"] == 0, res2 |
190 | 222 |
|
191 | | - return {"email": res2["maskedEmail"], "identity_session": identity_session} |
| 223 | + return {"ok": False, "verify": res1[f"masked{key}"]} |
192 | 224 |
|
193 | 225 | async def request(self, server: str, path: str, params: dict) -> dict: |
194 | 226 | form: dict[str, str] = {"data": json.dumps(params, separators=(",", ":"))} |
|
0 commit comments