增加另一个登录接口

This commit is contained in:
yml2213
2026-06-26 17:22:51 +08:00
parent f736ef4f4c
commit d7d7c0cbb0
12 changed files with 1739 additions and 73 deletions
+38 -69
View File
@@ -10,8 +10,9 @@ from urllib.parse import urlsplit, urlunsplit
from loguru import logger
from .cookie_enricher import CookieEnricher
from .crypto import encrypt_password, encrypt_nickname_or_phone
from .email_verifier import EmailLoginError, EmailVerifier
from .login_api import LoginAPIStrategy
from .login_api_wgapi import WgapiLoginAPI
from .proxy_fetcher import ProxyFetcher
from core.geetest.v3_slide.solver import (
@@ -27,6 +28,11 @@ from core.geetest.common.network import (
_geetest_semaphore = threading.Semaphore(2)
class CredentialError(ValueError):
"""账号或密码错误,不应重试。"""
pass
class AccountLike(Protocol):
"""DouyuLogin 所需的最小账号接口,ORM 对象或 SimpleNamespace 均可满足。"""
username: str
@@ -78,6 +84,7 @@ class DouyuLogin:
max_total_time: float = 0,
proxy_fetcher: Optional[ProxyFetcher] = None,
stop_event: Optional[threading.Event] = None,
api_strategy: Optional[LoginAPIStrategy] = None,
):
self.account = account
self.proxy = proxy
@@ -86,6 +93,7 @@ class DouyuLogin:
self.max_total_time = max_total_time # 0=不限制单账号登录总时长
self.stop_event = stop_event
self.session = requests.Session()
self.api = api_strategy or WgapiLoginAPI() # 默认使用 wgapi 接口
self.proxy_fetcher = proxy_fetcher or (
ProxyFetcher(api_url=proxy_api_url) if proxy_api_url else None
@@ -310,6 +318,9 @@ class DouyuLogin:
except InterruptedError as e:
logger.warning(f"登录任务已停止: {e}")
return LoginResult(success=False, message=str(e))
except CredentialError as e:
logger.error(f"登录失败(凭据错误,不再重试): {e}")
return LoginResult(success=False, message=str(e), code="credential_error")
except EmailLoginError as e:
logger.error(f"登录失败(邮箱登录失败,不再重试): {e}")
return LoginResult(success=False, message=str(e), code="email_login_failed")
@@ -361,27 +372,15 @@ class DouyuLogin:
Returns:
(gt, challenge, code_token, cookies)
"""
# 加密用户名和密码
encrypted_username = encrypt_nickname_or_phone(self.account.username)
encrypted_password = encrypt_password(self.account.password)
data = {
'type': '1',
'nicknameOrPhoneEncrypt': encrypted_username,
'password': encrypted_password,
'biz_type': '1',
'room_id': '0',
'redirect_url': self.LOGIN_REFERER,
't': str(int(time.time() * 1000)),
'client_id': '1',
'did': '',
'lang': '',
'isMultiAccount': '0',
}
data = self.api.build_first_login_data(
self.account.username,
self.account.password,
self.LOGIN_REFERER,
)
payload = self._request_json(
'post',
self.LOGIN_API,
self.api.first_login_url,
'第一次登录接口',
data=data,
)
@@ -390,15 +389,13 @@ class DouyuLogin:
if payload.get('error') != 81:
error_msg = payload.get('msg', '未知错误')
# 密码错误等凭据问题不应重试
if any(kw in error_msg for kw in ['密码错误', '账号或密码', '账号或者密码', '账号不存在']):
raise CredentialError(f"第一次登录失败: {error_msg}")
raise ValueError(f"第一次登录失败: {error_msg}")
# 提取极验参数
geetest_data = payload.get('data', {}).get('geetest', {})
code_data = geetest_data.get('code_data', {})
code_token = geetest_data.get('code_token', '')
gt = code_data.get('gt', '')
challenge = code_data.get('challenge', '')
gt, challenge, code_token = self.api.extract_geetest_params(payload)
if not gt or not challenge:
raise ValueError("获取极验参数失败")
@@ -517,39 +514,18 @@ class DouyuLogin:
Returns:
remote_code: 用于邮箱验证的code
"""
encrypted_username = encrypt_nickname_or_phone(self.account.username)
encrypted_password = encrypt_password(self.account.password)
data = {
'type': '1',
'nicknameOrPhoneEncrypt': encrypted_username,
'password': encrypted_password,
'room_id': '0',
'code_type': '1',
'code_token': code_token,
'gt_version': 'v3',
'geetest_challenge': challenge,
'geetest_validate': validate,
'geetest_seccode': seccode,
'code_data[geetest_challenge]': challenge,
'code_data[geetest_validate]': validate,
'code_data[geetest_seccode]': seccode,
'code_data[gt_version]': 'v3',
'code_data[code]': '',
'redirect_url': self.LOGIN_REFERER,
't': str(int(time.time() * 1000)),
'client_id': '1',
'did': '',
'lang': '',
'isMultiAccount': '0',
'biz_type': '1',
}
data = self.api.build_second_login_data(
self.account.username,
self.account.password,
gt, challenge, validate, seccode, code_token,
self.LOGIN_REFERER,
)
logger.debug(f"第二次登录参数: challenge={challenge[:20]}..., validate={validate[:20]}...")
payload = self._request_json(
'post',
self.LOGIN_API,
self.api.login_url,
'第二次登录接口',
data=data,
)
@@ -558,10 +534,13 @@ class DouyuLogin:
if payload.get('error') != 130014:
error_msg = payload.get('msg', '未知错误')
# 密码错误等凭据问题不应重试
if any(kw in error_msg for kw in ['密码错误', '账号或密码', '账号或者密码', '账号不存在']):
raise CredentialError(f"第二次登录失败: {error_msg}")
raise ValueError(f"第二次登录失败: {error_msg}")
# 提取remote_code
remote_code = payload.get('data', {}).get('remoteLogin', {}).get('code', '')
remote_code = self.api.extract_remote_code(payload)
if not remote_code:
raise ValueError("获取remote_code失败")
@@ -572,14 +551,11 @@ class DouyuLogin:
def _send_email_verify(self, remote_code: str) -> None:
"""发送邮箱验证"""
data = {
'code': remote_code,
'client_id': '1',
}
data = self.api.build_send_email_data(remote_code)
payload = self._request_json(
'post',
self.SEND_EMAIL_API,
self.api.send_email_url,
'发送验证邮件接口',
data=data,
)
@@ -612,18 +588,11 @@ class DouyuLogin:
Returns:
login_url: 登录回调URL
"""
data = {
'verify_type': '2',
'captcha': verify_code,
'isMultiAccount': '0',
'code': remote_code,
'client_id': '1',
'redirect_url': '//www.douyu.com/api/passport/login',
}
data = self.api.build_verify_data(remote_code, verify_code)
payload = self._request_json(
'post',
self.VERIFY_API,
self.api.verify_url,
'提交验证码接口',
data=data,
)
@@ -631,7 +600,7 @@ class DouyuLogin:
if payload.get('error') != 0:
raise ValueError(f"提交验证码失败: {payload.get('msg')}")
login_url = payload.get('data', {}).get('url', '')
login_url = self.api.extract_login_url(payload)
if not login_url:
raise ValueError("获取登录URL失败")