Files
MusicServer/app/sdk/auth/lr.py
T
snowy-yang dfc06bf4de feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复
- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
2026-10-23 21:55:45 +08:00

60 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""懒人听书登录(对应 sdk/lr/auth.ts,web 通道 m.lrts.me/ajax/logon)。
参考社区下载器(yoval/lrts 等):POST form {account, pwd: base64(password)},
成功响应 json.phone 非空,Set-Cookie 种下会话;取流接口必须携带该会话
(免费章节匿名也返回"收费章节未购买",2026-10-02 实测)。
"""
from __future__ import annotations
import base64
from typing import Any
from urllib.parse import quote
from app.http.api_error import ApiError
from app.sdk.auth_store import get_credentials, set_credentials
from app.sdk.common.request import http_fetch
UA_MI = (
"Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 "
"(KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0"
)
LOGON_URL = "https://m.lrts.me/ajax/logon"
async def login(account: str, password: str) -> dict[str, Any]:
"""账号密码登录(成功保存会话 Cookie)。"""
form = f"account={quote(str(account), safe='')}&pwd={base64.b64encode(str(password).encode()).decode()}"
resp = await http_fetch(
LOGON_URL,
method="POST",
headers={
"User-Agent": UA_MI,
"Referer": "https://m.lrts.me/",
"Content-Type": "application/x-www-form-urlencoded",
},
body=form,
timeout=15_000,
)
body = resp.body if isinstance(resp.body, dict) else {}
if resp.status_code != 200 or not body.get("phone"):
raise ApiError(401, str(body.get("msg") or "登录失败(账号或密码错误)"))
cookie = "; ".join(part for part in (item.split(";")[0].strip() for item in resp.set_cookies) if part)
if not cookie:
raise RuntimeError("登录成功但未返回会话 Cookie")
set_credentials("lr", {"cookie": cookie, "phone": str(body["phone"])})
return {"logged": True, "phone": str(body["phone"])}
def get_lr_cookie() -> str:
"""登录会话 Cookie(未登录返回空串)。"""
return str((get_credentials("lr") or {}).get("cookie") or "")
def status() -> dict[str, Any]:
"""登录状态。"""
cred = get_credentials("lr") or {}
if cred.get("cookie"):
return {"logged": True, "phone": cred.get("phone"), "updatedAt": cred.get("updatedAt")}
return {"logged": False}