- pyrightconfig.json 钉死 Python 3.12 虚拟环境,消除由于查找系统 Python 3.9 带来的假性报错 - 修复 huya/login.py 引用不存在函数的问题并补充单元测试 - 修复 browser_harvest.py 对 set 下标访问的错误并提升报错信息质量 - 虎牙 6 个子路由显式导入依赖,消除 import * 带来的未定义变量报警 - 规范化 Pydantic Field(default=...) 默认值声明 - 完善 solver.py / app_login.py 等核心路径类型守卫与 @overload 重载 - 在 pyproject.toml 中显式锁定 ruff lint 规则集 (E4, E7, E9, F, B, I, BLE) - 门禁脚本同步增加 ruff check 检查并在 AGENTS.md 补齐实践经验总结
715 lines
24 KiB
Python
715 lines
24 KiB
Python
"""虎牙登录:Web / App / 短信及批量选中登录。"""
|
|
|
|
import time
|
|
|
|
from fastapi import APIRouter
|
|
|
|
from ._common import (
|
|
UTC,
|
|
AccountBulkSelection,
|
|
Depends,
|
|
HTTPException,
|
|
HuyaAccount,
|
|
HuyaAppLoginError,
|
|
HuyaAppPasswordLoginRequest,
|
|
HuyaAppQrAuthRequiredError,
|
|
HuyaCredentialError,
|
|
HuyaLoginError,
|
|
HuyaPasswordLoginRequest,
|
|
HuyaPasswordLoginSelectedRequest,
|
|
HuyaSmsCodeRequest,
|
|
HuyaSmsLoginRequest,
|
|
HuyaTestSmsUrlRequest,
|
|
Session,
|
|
User,
|
|
_account_out,
|
|
_can_view_huya_cookie,
|
|
_require_huya_perm,
|
|
_selected_huya_account_ids,
|
|
_visible_huya_accounts_query,
|
|
cast,
|
|
datetime,
|
|
get_current_user,
|
|
get_db,
|
|
logger,
|
|
login_huya_app_password,
|
|
login_huya_password,
|
|
login_huya_sms,
|
|
save_huya_login_cookie_to_account,
|
|
send_huya_sms_code,
|
|
upsert_huya_cookie,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/accounts/password-login")
|
|
def password_login_account(
|
|
req: HuyaPasswordLoginRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""使用 Web 方式账号密码登录虎牙(旧版),成功后保存 Cookie。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
try:
|
|
result = login_huya_password(
|
|
username=req.username.strip(),
|
|
password=req.password,
|
|
cookie=req.cookie.strip() or None,
|
|
)
|
|
except HuyaCredentialError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
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"虎牙 Web 密码登录失败: {exc}"
|
|
) from exc
|
|
|
|
if not result.success or not result.cookie:
|
|
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"
|
|
account.login_channel = "web"
|
|
db.commit()
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
return {
|
|
"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")
|
|
username_clean = req.username.strip()
|
|
account_db = (
|
|
db.query(HuyaAccount).filter(HuyaAccount.username == username_clean).first()
|
|
)
|
|
sms_url = account_db.sms_url if account_db and account_db.sms_url else ""
|
|
try:
|
|
result = login_huya_app_password(
|
|
username=username_clean,
|
|
password=req.password,
|
|
force_new_device=req.force_new_device,
|
|
sms_url=sms_url,
|
|
)
|
|
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:
|
|
logger.warning(
|
|
f"[huya-app] 账号 {req.username} 密码登录未完成: {result.message} (code={result.code})"
|
|
)
|
|
raise HTTPException(
|
|
status_code=400
|
|
if result.code in ("SMS_AUTH_REQUIRED", "QR_AUTH_REQUIRED")
|
|
else 502,
|
|
detail=result.message or "虎牙 App 密码登录失败",
|
|
)
|
|
|
|
try:
|
|
account = upsert_huya_cookie(
|
|
db,
|
|
result.cookie,
|
|
tag=req.tag,
|
|
username_hint=req.username,
|
|
raw_cookie=getattr(result, "raw_cookie", ""),
|
|
)
|
|
if hasattr(account, "account_password") and req.password:
|
|
account.account_password = req.password
|
|
account.status = "active"
|
|
account.login_channel = "app"
|
|
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,
|
|
}
|
|
|
|
|
|
@router.post("/accounts/sms-code")
|
|
def send_sms_code(
|
|
req: HuyaSmsCodeRequest,
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""发送虎牙短信验证码,返回提交登录所需 state。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
try:
|
|
result = send_huya_sms_code(
|
|
phone=req.phone.strip(),
|
|
cookie=req.cookie.strip() or None,
|
|
)
|
|
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
|
|
|
|
if not result.success or not result.state:
|
|
raise HTTPException(
|
|
status_code=502, detail=result.message or "虎牙短信发码失败"
|
|
)
|
|
|
|
return {
|
|
"message": result.message or "短信已发送",
|
|
"success": True,
|
|
"state": result.state,
|
|
"sdid": result.sdid,
|
|
"context": result.context,
|
|
"request_id": result.request_id,
|
|
}
|
|
|
|
|
|
@router.post("/accounts/sms-login")
|
|
def sms_login_account(
|
|
req: HuyaSmsLoginRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""提交虎牙短信验证码登录,成功后保存 Cookie。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
try:
|
|
result = login_huya_sms(
|
|
authcode=req.authcode.strip(),
|
|
state=req.state.strip(),
|
|
phone=req.phone.strip(),
|
|
)
|
|
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
|
|
|
|
if not result.success or not result.cookie:
|
|
raise HTTPException(
|
|
status_code=502, detail=result.message or "虎牙短信登录失败"
|
|
)
|
|
|
|
try:
|
|
account = upsert_huya_cookie(db, result.cookie, tag=req.tag, username_hint="")
|
|
account.login_channel = "sms"
|
|
phone = (req.phone or "").strip()
|
|
if phone:
|
|
account.game_phone = phone
|
|
account.updated_at = datetime.now(UTC)
|
|
db.commit()
|
|
db.refresh(account)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
|
|
return {
|
|
"message": "登录成功,Cookie 已保存",
|
|
"success": True,
|
|
"account": _account_out(account),
|
|
"sdid": result.sdid,
|
|
}
|
|
|
|
|
|
@router.post("/accounts/test-sms-url")
|
|
def test_sms_url_endpoint(
|
|
req: HuyaTestSmsUrlRequest,
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""测试从指定接码链接获取验证码。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
url = req.url.strip()
|
|
if not url:
|
|
raise HTTPException(status_code=400, detail="接码链接不能为空")
|
|
from core.sms_provider import SmsLine, SmsProviderClient, detect_provider
|
|
|
|
client = SmsProviderClient(timeout=(8, 15))
|
|
item = SmsLine(
|
|
phone=req.phone.strip(),
|
|
url=url,
|
|
provider=detect_provider(url),
|
|
raw=f"{req.phone}----{url}",
|
|
)
|
|
poll_result = client.poll(item)
|
|
return {
|
|
"success": poll_result.has_code,
|
|
"code": poll_result.code,
|
|
"status": poll_result.status,
|
|
"message": poll_result.message
|
|
or ("成功识别验证码" if poll_result.has_code else "未识别到验证码"),
|
|
"raw": poll_result.raw[:500],
|
|
}
|
|
|
|
|
|
@router.post("/accounts/{account_id}/fetch-sms-code")
|
|
def fetch_account_sms_code(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""通过该账号绑定的接码链接获取最新短信验证码。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
account = (
|
|
_visible_huya_accounts_query(db, current)
|
|
.filter(HuyaAccount.id == account_id)
|
|
.first()
|
|
)
|
|
if account is None:
|
|
raise HTTPException(status_code=404, detail="虎牙账号不存在或无权操作")
|
|
if not account.sms_url:
|
|
raise HTTPException(status_code=400, detail="该账号未配置接码链接 (sms_url)")
|
|
from core.sms_provider import SmsLine, SmsProviderClient, detect_provider
|
|
|
|
client = SmsProviderClient(timeout=(8, 15))
|
|
item = SmsLine(
|
|
phone=account.game_phone or "",
|
|
url=account.sms_url,
|
|
provider=detect_provider(account.sms_url),
|
|
raw=f"{account.game_phone}----{account.sms_url}",
|
|
)
|
|
poll_result = client.poll(item)
|
|
return {
|
|
"account_id": account.id,
|
|
"username": account.username or "",
|
|
"phone": account.game_phone or "",
|
|
"sms_url": account.sms_url,
|
|
"success": poll_result.has_code,
|
|
"code": poll_result.code,
|
|
"status": poll_result.status,
|
|
"message": poll_result.message
|
|
or ("成功识别验证码" if poll_result.has_code else "未识别到验证码"),
|
|
"raw": poll_result.raw[:500],
|
|
}
|
|
|
|
|
|
@router.post("/accounts/{account_id}/auto-sms-login")
|
|
def auto_sms_login_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""使用该账号绑定的手机号与接码链接一键执行虎牙短信登录。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
account = (
|
|
_visible_huya_accounts_query(db, current)
|
|
.filter(HuyaAccount.id == account_id)
|
|
.first()
|
|
)
|
|
if account is None:
|
|
raise HTTPException(status_code=404, detail="虎牙账号不存在或无权操作")
|
|
phone = (account.game_phone or "").strip()
|
|
sms_url = (account.sms_url or "").strip()
|
|
if not phone or not sms_url:
|
|
raise HTTPException(status_code=400, detail="该账号缺少手机号或接码链接")
|
|
# ⚠️ 这里原来是 `auto_register_huya_phone` —— 该名字在 core.huya.auto_register
|
|
# 里**从来没存在过**(2026-09-06 起就是死的),每次调用都在 import 阶段抛
|
|
# ImportError → 500。真实函数名是 register_huya_with_sms_line,返回的
|
|
# HuyaAutoRegisterResult 同样有 .success / .cookie / .message。
|
|
# 回归用例:tests/test_huya_auto_sms_login.py
|
|
from core.huya.auto_register import register_huya_with_sms_line
|
|
from core.sms_provider import SmsLine, detect_provider
|
|
|
|
item = SmsLine(
|
|
phone=phone,
|
|
url=sms_url,
|
|
provider=detect_provider(sms_url),
|
|
raw=f"{phone}----{sms_url}",
|
|
)
|
|
res = register_huya_with_sms_line(item, wait_seconds=90, poll_interval=2)
|
|
if not res.success or not res.cookie:
|
|
account.status = "login_failed"
|
|
account.updated_at = datetime.now(UTC)
|
|
db.commit()
|
|
raise HTTPException(
|
|
status_code=502, detail=res.message or "虎牙短信接码登录失败"
|
|
)
|
|
saved = save_huya_login_cookie_to_account(
|
|
db,
|
|
account,
|
|
res.cookie,
|
|
tag=account.tag or "",
|
|
username_hint=account.username or "",
|
|
)
|
|
saved.status = "active"
|
|
saved.login_channel = "sms"
|
|
db.commit()
|
|
return {
|
|
"message": "短信接码登录成功,Cookie 已保存",
|
|
"success": True,
|
|
"account": _account_out(saved),
|
|
}
|
|
|
|
|
|
_NON_RETRYABLE_LOGIN_CODES = frozenset({"QR_AUTH_REQUIRED", "SMS_AUTH_REQUIRED"})
|
|
|
|
|
|
def _sleep_seconds(seconds: float) -> None:
|
|
if seconds and seconds > 0:
|
|
time.sleep(seconds)
|
|
|
|
|
|
def _is_retryable_login_code(code: object) -> bool:
|
|
return str(code or "") not in _NON_RETRYABLE_LOGIN_CODES
|
|
|
|
|
|
def _login_fail_item(
|
|
*,
|
|
account_id: int,
|
|
username: str,
|
|
message: str,
|
|
account=None,
|
|
include_cookie: bool = False,
|
|
sdid: str = "",
|
|
) -> dict:
|
|
item: dict = {
|
|
"line": account_id,
|
|
"username": username,
|
|
"success": False,
|
|
"message": message,
|
|
}
|
|
if account is not None:
|
|
item["account"] = _account_out(account, include_cookie=include_cookie)
|
|
if sdid:
|
|
item["sdid"] = sdid
|
|
return item
|
|
|
|
|
|
def _mark_account_login_failed(db: Session, account) -> None:
|
|
account.status = "login_failed"
|
|
account.updated_at = datetime.now(UTC)
|
|
db.commit()
|
|
|
|
|
|
def _attempt_web_password_login(
|
|
account, *, include_cookie: bool, db: Session
|
|
) -> tuple[dict, bool]:
|
|
username = (account.username or "").strip()
|
|
try:
|
|
result = login_huya_password(
|
|
username=username,
|
|
password=(account.account_password or "").strip(),
|
|
cookie=account.cookie or None,
|
|
)
|
|
if not result.success or not result.cookie:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=result.message or "虎牙密码登录失败",
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
sdid=result.sdid,
|
|
),
|
|
_is_retryable_login_code(result.code),
|
|
)
|
|
saved = save_huya_login_cookie_to_account(
|
|
db,
|
|
account,
|
|
result.cookie,
|
|
tag=account.tag or "",
|
|
username_hint=username,
|
|
)
|
|
saved.status = "active"
|
|
saved.login_channel = "web"
|
|
db.commit()
|
|
return (
|
|
{
|
|
"line": account.id,
|
|
"username": username,
|
|
"success": True,
|
|
"message": "登录成功,Cookie 已保存",
|
|
"account": _account_out(saved, include_cookie=include_cookie),
|
|
"sdid": result.sdid,
|
|
},
|
|
False,
|
|
)
|
|
except HuyaCredentialError as exc:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=str(exc),
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
False,
|
|
)
|
|
except (HuyaLoginError, ValueError) as exc:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=str(exc),
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
True,
|
|
)
|
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=f"虎牙密码登录失败: {exc}",
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
True,
|
|
)
|
|
|
|
|
|
def _attempt_app_password_login(
|
|
account, *, force_new_device: bool, include_cookie: bool, db: Session
|
|
) -> tuple[dict, bool]:
|
|
username = (account.username or "").strip()
|
|
sms_url = (account.sms_url or "").strip()
|
|
try:
|
|
result = login_huya_app_password(
|
|
username=username,
|
|
password=(account.account_password or "").strip(),
|
|
force_new_device=force_new_device,
|
|
sms_url=sms_url,
|
|
)
|
|
if not result.success or not result.cookie:
|
|
_mark_account_login_failed(db, account)
|
|
logger.warning(
|
|
f"[huya-app] 账号 {username} 登录未完成: {result.message} (code={result.code})"
|
|
)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=result.message or "虎牙 App 密码登录失败",
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
sdid=result.sdid,
|
|
),
|
|
_is_retryable_login_code(result.code),
|
|
)
|
|
saved = save_huya_login_cookie_to_account(
|
|
db,
|
|
account,
|
|
result.cookie,
|
|
tag=account.tag or "",
|
|
username_hint=username,
|
|
)
|
|
saved.status = "active"
|
|
saved.login_channel = "app"
|
|
db.commit()
|
|
return (
|
|
{
|
|
"line": account.id,
|
|
"username": username,
|
|
"success": True,
|
|
"message": "App 登录成功,Cookie 已保存",
|
|
"account": _account_out(saved, include_cookie=include_cookie),
|
|
"sdid": result.sdid,
|
|
},
|
|
False,
|
|
)
|
|
except HuyaAppQrAuthRequiredError as exc:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=str(exc),
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
False,
|
|
)
|
|
except HuyaCredentialError as exc:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=str(exc),
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
False,
|
|
)
|
|
except (HuyaAppLoginError, HuyaLoginError, ValueError) as exc:
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=str(exc),
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
True,
|
|
)
|
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
|
_mark_account_login_failed(db, account)
|
|
return (
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message=f"虎牙 App 密码登录失败: {exc}",
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
),
|
|
True,
|
|
)
|
|
|
|
|
|
def _run_selected_password_logins(
|
|
*,
|
|
channel: str,
|
|
req: HuyaPasswordLoginSelectedRequest,
|
|
db: Session,
|
|
current: User,
|
|
) -> dict:
|
|
"""单线程逐个登录:同一时间只登一个号,失败最多重试,号与号之间留间隔。"""
|
|
_require_huya_perm(current, "huya:import")
|
|
selected_ids = _selected_huya_account_ids(
|
|
db, current, cast(AccountBulkSelection, 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}
|
|
include_cookie = _can_view_huya_cookie(current)
|
|
results: list[dict] = []
|
|
success_count = 0
|
|
failed_count = 0
|
|
max_attempts = req.max_attempts
|
|
interval_seconds = req.interval_seconds
|
|
retry_delay_seconds = req.retry_delay_seconds
|
|
label = "App" if channel == "app" else "Web"
|
|
|
|
for index, account_id in enumerate(selected_ids):
|
|
if index > 0:
|
|
_sleep_seconds(interval_seconds)
|
|
|
|
account = account_map.get(account_id)
|
|
if account is None:
|
|
failed_count += 1
|
|
results.append(
|
|
_login_fail_item(
|
|
account_id=account_id,
|
|
username="",
|
|
message="账号不存在或无权登录",
|
|
)
|
|
)
|
|
continue
|
|
|
|
username = (account.username or "").strip()
|
|
password = (account.account_password or "").strip()
|
|
if not username or not password:
|
|
failed_count += 1
|
|
results.append(
|
|
_login_fail_item(
|
|
account_id=account.id,
|
|
username=username,
|
|
message="该账号未导入密码",
|
|
account=account,
|
|
include_cookie=include_cookie,
|
|
)
|
|
)
|
|
continue
|
|
|
|
last_item: dict | None = None
|
|
used_attempts = 0
|
|
for attempt in range(1, max_attempts + 1):
|
|
if attempt > 1:
|
|
_sleep_seconds(retry_delay_seconds)
|
|
used_attempts = attempt
|
|
if channel == "app":
|
|
last_item, retryable = _attempt_app_password_login(
|
|
account,
|
|
force_new_device=req.force_new_device,
|
|
include_cookie=include_cookie,
|
|
db=db,
|
|
)
|
|
else:
|
|
last_item, retryable = _attempt_web_password_login(
|
|
account,
|
|
include_cookie=include_cookie,
|
|
db=db,
|
|
)
|
|
if last_item["success"] or not retryable:
|
|
break
|
|
if attempt < max_attempts:
|
|
logger.warning(
|
|
f"[huya-{channel}] 账号 {username} 第 {attempt}/{max_attempts} 次失败,"
|
|
f"{retry_delay_seconds}s 后重试: {last_item['message']}"
|
|
)
|
|
|
|
assert last_item is not None
|
|
last_item["attempts"] = used_attempts
|
|
if last_item["success"]:
|
|
success_count += 1
|
|
if used_attempts > 1:
|
|
last_item["message"] = (
|
|
f"第 {used_attempts} 次尝试成功。" + last_item["message"]
|
|
)
|
|
else:
|
|
failed_count += 1
|
|
if used_attempts > 1:
|
|
last_item["message"] = (
|
|
f"已尝试 {used_attempts} 次:{last_item['message']}"
|
|
)
|
|
results.append(last_item)
|
|
|
|
return {
|
|
"message": f"{label} 登录完成:成功 {success_count} 条,失败 {failed_count} 条",
|
|
"success": True,
|
|
"count": success_count,
|
|
"failed": failed_count,
|
|
"results": results,
|
|
}
|
|
|
|
|
|
@router.post("/accounts/password-login/selected")
|
|
def password_login_selected_accounts(
|
|
req: HuyaPasswordLoginSelectedRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""对已导入的虎牙账号执行 Web 密码登录(单线程,失败可重试)。"""
|
|
return _run_selected_password_logins(channel="web", req=req, db=db, current=current)
|
|
|
|
|
|
@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 协议逐个登录(推荐,单线程)。"""
|
|
return _run_selected_password_logins(channel="app", req=req, db=db, current=current)
|