增加另一个登录接口
This commit is contained in:
+38
-69
@@ -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失败")
|
||||
|
||||
Reference in New Issue
Block a user