feat(huya): 整合 App 协议登录获取 Cookie 全链路并与 Web 登录解耦

- core/huya: 新增 app_login, wup_encoder, nonce_forge, cert_forge, envelope_forge, device_profile, udb_aes
- core/huya/__init__.py: 导出 login_huya_app_password 与 HuyaAppPasswordLogin
- web/backend: 新增 /accounts/app-password-login 与 /accounts/app-password-login/selected 路由及 Schema,与原 Web 密码登录独立分开
- web/frontend: 增加 App 登录 API 与前端界面“App 登录选中”操作,弹窗结果明确区分
- tests: 新增 test_huya_app_login.py 单元测试覆盖全链路
This commit is contained in:
yml2213
2026-08-26 15:51:30 +08:00
parent 4eec01568f
commit da2cf5a61d
22 changed files with 2572 additions and 11 deletions
+200 -5
View File
@@ -12,8 +12,11 @@ from sqlalchemy import func, or_
from sqlalchemy.orm import Session, defer, joinedload
from core.huya import (
HuyaAppLoginError,
HuyaAppQrAuthRequiredError,
HuyaCredentialError,
HuyaLoginError,
login_huya_app_password,
login_huya_password,
login_huya_sms,
send_huya_sms_code,
@@ -51,6 +54,7 @@ from ..schemas import (
HuyaGoodsOut,
HuyaPasswordAccountImport,
HuyaPasswordLoginRequest,
HuyaAppPasswordLoginRequest,
HuyaPasswordLoginSelectedRequest,
HuyaRechargeGoodsOut,
HuyaRegisterSuccessLogOut,
@@ -528,7 +532,7 @@ def password_login_account(
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""使用账号密码登录虎牙,成功后保存 Cookie。"""
"""使用 Web 方式账号密码登录虎牙(旧版),成功后保存 Cookie。"""
_require_huya_perm(current, "huya:import")
try:
result = login_huya_password(
@@ -541,17 +545,63 @@ def password_login_account(
except HuyaLoginError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=502, detail=f"虎牙密码登录失败: {exc}") from exc
raise HTTPException(status_code=502, detail=f"虎牙 Web 密码登录失败: {exc}") from exc
if not result.success or not result.cookie:
raise HTTPException(status_code=502, detail=result.message or "虎牙密码登录失败")
raise HTTPException(status_code=502, detail=result.message or "虎牙 Web 密码登录失败")
try:
account = upsert_huya_cookie(db, result.cookie, tag=req.tag, username_hint=req.username)
if hasattr(account, "account_password") and req.password:
account.account_password = req.password
account.status = "active"
db.commit()
except ValueError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return {
"message": "登录成功,Cookie 已保存",
"message": "Web 登录成功,Cookie 已保存",
"success": True,
"account": _account_out(account),
"sdid": result.sdid,
}
@router.post("/accounts/app-password-login")
def app_password_login_account(
req: HuyaAppPasswordLoginRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""使用 App 协议密码登录虎牙(推荐),自动过滑块并保存 Cookie。"""
_require_huya_perm(current, "huya:import")
try:
result = login_huya_app_password(
username=req.username.strip(),
password=req.password,
force_new_device=req.force_new_device,
)
except HuyaAppQrAuthRequiredError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except HuyaCredentialError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except (HuyaAppLoginError, HuyaLoginError) as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=502, detail=f"虎牙 App 密码登录失败: {exc}") from exc
if not result.success or not result.cookie:
raise HTTPException(status_code=502, detail=result.message or "虎牙 App 密码登录失败")
try:
account = upsert_huya_cookie(db, result.cookie, tag=req.tag, username_hint=req.username)
if hasattr(account, "account_password") and req.password:
account.account_password = req.password
account.status = "active"
db.commit()
except ValueError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return {
"message": "App 协议登录成功,Cookie 已保存",
"success": True,
"account": _account_out(account),
"sdid": result.sdid,
@@ -918,7 +968,152 @@ def password_login_selected_accounts(
})
return {
"message": f"登录完成:成功 {success_count} 条,失败 {failed_count}",
"message": f"Web 登录完成:成功 {success_count} 条,失败 {failed_count}",
"success": True,
"count": success_count,
"failed": failed_count,
"results": results,
}
@router.post("/accounts/app-password-login/selected")
def app_password_login_selected_accounts(
req: HuyaPasswordLoginSelectedRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""对已导入的虎牙账号使用 App 协议批量执行密码登录(推荐)。"""
_require_huya_perm(current, "huya:import")
selected_ids = _selected_huya_account_ids(db, current, req)
if not selected_ids:
raise HTTPException(status_code=400, detail="请选择虎牙账号")
accounts = (
_visible_huya_accounts_query(db, current)
.filter(HuyaAccount.id.in_(selected_ids))
.all()
)
account_map = {account.id: account for account in accounts}
results = []
success_count = 0
failed_count = 0
include_cookie = _can_view_huya_cookie(current)
for account_id in selected_ids:
account = account_map.get(account_id)
if account is None:
failed_count += 1
results.append({
"line": account_id,
"username": "",
"success": False,
"message": "账号不存在或无权登录",
})
continue
username = (account.username or "").strip()
password = (account.account_password or "").strip()
if not username or not password:
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": "该账号未导入密码",
"account": _account_out(account, include_cookie=include_cookie),
})
continue
try:
result = login_huya_app_password(
username=username,
password=password,
force_new_device=req.force_new_device,
)
if not result.success or not result.cookie:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": result.message or "虎牙 App 密码登录失败",
"account": _account_out(account, include_cookie=include_cookie),
"sdid": result.sdid,
})
continue
saved = save_huya_login_cookie_to_account(
db,
account,
result.cookie,
tag=account.tag or "",
username_hint=username,
)
saved.status = "active"
db.commit()
success_count += 1
results.append({
"line": account.id,
"username": username,
"success": True,
"message": "App 登录成功,Cookie 已保存",
"account": _account_out(saved, include_cookie=include_cookie),
"sdid": result.sdid,
})
except HuyaAppQrAuthRequiredError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": str(exc),
"account": _account_out(account, include_cookie=include_cookie),
})
except HuyaCredentialError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": str(exc),
"account": _account_out(account, include_cookie=include_cookie),
})
except (HuyaAppLoginError, HuyaLoginError, ValueError) as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": str(exc),
"account": _account_out(account, include_cookie=include_cookie),
})
except Exception as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": f"虎牙 App 密码登录失败: {exc}",
"account": _account_out(account, include_cookie=include_cookie),
})
return {
"message": f"App 登录完成:成功 {success_count} 条,失败 {failed_count}",
"success": True,
"count": success_count,
"failed": failed_count,