执行 Ruff 安全自动修复
This commit is contained in:
+10
-10
@@ -1,11 +1,11 @@
|
||||
"""斗鱼登录模块"""
|
||||
|
||||
from .login import DouyuLogin, CredentialError
|
||||
from .login_api import LoginAPIStrategy
|
||||
from .login_api_wgapi import WgapiLoginAPI
|
||||
from .login_api_iframe import IframeLoginAPI
|
||||
from .email_verifier import EmailVerifier
|
||||
from .activity_client import DouyuActivityClient, DouyuActivityError
|
||||
from .email_verifier import EmailVerifier
|
||||
from .login import CredentialError, DouyuLogin
|
||||
from .login_api import LoginAPIStrategy
|
||||
from .login_api_iframe import IframeLoginAPI
|
||||
from .login_api_wgapi import WgapiLoginAPI
|
||||
from .recharge_api import (
|
||||
FishFinRechargeClient,
|
||||
FishFinRechargeConfig,
|
||||
@@ -14,16 +14,16 @@ from .recharge_api import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DouyuLogin",
|
||||
"CredentialError",
|
||||
"LoginAPIStrategy",
|
||||
"WgapiLoginAPI",
|
||||
"IframeLoginAPI",
|
||||
"EmailVerifier",
|
||||
"DouyuActivityClient",
|
||||
"DouyuActivityError",
|
||||
"DouyuLogin",
|
||||
"EmailVerifier",
|
||||
"FishFinRechargeClient",
|
||||
"FishFinRechargeConfig",
|
||||
"FishFinRechargeConfigError",
|
||||
"FishFinRechargeError",
|
||||
"IframeLoginAPI",
|
||||
"LoginAPIStrategy",
|
||||
"WgapiLoginAPI",
|
||||
]
|
||||
|
||||
@@ -18,7 +18,6 @@ from .cookie_utils import (
|
||||
normalize_douyu_cookie,
|
||||
)
|
||||
|
||||
|
||||
PC_UA = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
@@ -1446,8 +1445,7 @@ class DouyuActivityClient:
|
||||
if not match:
|
||||
raise DouyuActivityError(f"响应中找不到 var {varname}=: {text[:200]}")
|
||||
chunk = text[match.end() :].strip()
|
||||
if chunk.endswith(";"):
|
||||
chunk = chunk[:-1]
|
||||
chunk = chunk.removesuffix(";")
|
||||
try:
|
||||
return json.loads(chunk)
|
||||
except json.JSONDecodeError as exc:
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Cipher import ARC4
|
||||
|
||||
from Crypto.Cipher import AES, ARC4, PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
# 斗鱼RSA公钥(从JS中提取)
|
||||
DOUYU_RSA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
||||
|
||||
@@ -6,11 +6,9 @@ import re
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
import requests
|
||||
|
||||
from loguru import logger
|
||||
|
||||
# Roundcube Webmail 地址。可通过环境变量覆盖。空值时回退到默认。
|
||||
ROUNDCUBE_URL = os.getenv("MAIL_ROUNDCUBE_URL") or "http://111.229.206.54:8000/"
|
||||
@@ -40,7 +38,7 @@ class EmailVerifier:
|
||||
max_messages: int = 20,
|
||||
use_ssl: bool = False,
|
||||
roundcube_url: str = "",
|
||||
backup_passwords: Optional[list[str]] = None,
|
||||
backup_passwords: list[str] | None = None,
|
||||
):
|
||||
# 兼容旧参数名
|
||||
self.imap_server = imap_server
|
||||
@@ -54,14 +52,13 @@ class EmailVerifier:
|
||||
)
|
||||
|
||||
# Roundcube 会话(懒初始化)
|
||||
self._rc_session: Optional[requests.Session] = None
|
||||
self._rc_token: Optional[str] = None
|
||||
self._rc_session: requests.Session | None = None
|
||||
self._rc_token: str | None = None
|
||||
self._rc_logged_in = False
|
||||
self._rc_login_error = ""
|
||||
|
||||
def connect(self) -> None:
|
||||
"""兼容接口"""
|
||||
pass
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""关闭 Roundcube 会话"""
|
||||
@@ -239,7 +236,7 @@ class EmailVerifier:
|
||||
|
||||
# ── Roundcube 读取邮件内容 ─────────────────────────────
|
||||
|
||||
def _rc_fetch_email_body(self, uid: int) -> Optional[str]:
|
||||
def _rc_fetch_email_body(self, uid: int) -> str | None:
|
||||
"""通过 Roundcube 读取指定 UID 邮件的正文"""
|
||||
if not self._ensure_roundcube_session():
|
||||
if self._rc_login_error:
|
||||
@@ -262,7 +259,7 @@ class EmailVerifier:
|
||||
# ── Roundcube 日期解析 ─────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _parse_rc_date(date_str: str) -> Optional[datetime]:
|
||||
def _parse_rc_date(date_str: str) -> datetime | None:
|
||||
"""
|
||||
解析 Roundcube 返回的日期字符串为 datetime。
|
||||
|
||||
@@ -363,9 +360,9 @@ class EmailVerifier:
|
||||
self,
|
||||
max_wait: int = 60,
|
||||
interval: int = 3,
|
||||
after_timestamp: Optional[float] = None,
|
||||
after_timestamp: float | None = None,
|
||||
allow_old_seconds: int = 15,
|
||||
stop_event: Optional[threading.Event] = None,
|
||||
stop_event: threading.Event | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
轮询获取斗鱼验证码。
|
||||
@@ -417,9 +414,9 @@ class EmailVerifier:
|
||||
|
||||
def _fetch_code_via_roundcube(
|
||||
self,
|
||||
after_timestamp: Optional[float] = None,
|
||||
after_timestamp: float | None = None,
|
||||
allow_old_seconds: int = 15,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""通过 Roundcube API 获取最新斗鱼验证码"""
|
||||
messages = self._rc_fetch_mail_list()
|
||||
|
||||
@@ -465,7 +462,7 @@ class EmailVerifier:
|
||||
|
||||
# ── 通用工具方法 ───────────────────────────────────────
|
||||
|
||||
def _extract_verification_code(self, text: str) -> Optional[str]:
|
||||
def _extract_verification_code(self, text: str) -> str | None:
|
||||
"""从文本中提取6位验证码"""
|
||||
# 清理HTML标签和实体
|
||||
text = html.unescape(text)
|
||||
|
||||
+26
-24
@@ -4,28 +4,30 @@ import json
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
import requests
|
||||
from typing import Mapping, Optional, Protocol, Tuple
|
||||
from collections.abc import Mapping
|
||||
from typing import Protocol
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
from core.geetest.common.network import (
|
||||
get_c_s,
|
||||
get_js_address,
|
||||
req_fullpage_validate,
|
||||
)
|
||||
from core.geetest.v3_slide.solver import (
|
||||
_generate_seed,
|
||||
get_w1,
|
||||
get_w2,
|
||||
)
|
||||
|
||||
from .cookie_enricher import CookieEnricher
|
||||
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 (
|
||||
_generate_seed,
|
||||
get_w1,
|
||||
get_w2,
|
||||
)
|
||||
from core.geetest.common.network import (
|
||||
get_js_address,
|
||||
get_c_s,
|
||||
req_fullpage_validate,
|
||||
)
|
||||
|
||||
# ── 全局极验并发限制:同一时刻最多2个线程做极验验证 ──
|
||||
_geetest_semaphore = threading.Semaphore(2)
|
||||
|
||||
@@ -95,14 +97,14 @@ class DouyuLogin:
|
||||
def __init__(
|
||||
self,
|
||||
account: AccountLike,
|
||||
proxy: Optional[str | Mapping[str, str]] = None,
|
||||
proxy_api_url: Optional[str] = None,
|
||||
proxy: str | Mapping[str, str] | None = None,
|
||||
proxy_api_url: str | None = None,
|
||||
timeout: tuple[float, float] = REQUEST_TIMEOUT,
|
||||
max_login_retries: int = 0,
|
||||
max_total_time: float = 0,
|
||||
proxy_fetcher: Optional[ProxyFetcher] = None,
|
||||
stop_event: Optional[threading.Event] = None,
|
||||
api_strategy: Optional[LoginAPIStrategy] = None,
|
||||
proxy_fetcher: ProxyFetcher | None = None,
|
||||
stop_event: threading.Event | None = None,
|
||||
api_strategy: LoginAPIStrategy | None = None,
|
||||
):
|
||||
self.account = account
|
||||
self.proxy = proxy
|
||||
@@ -117,7 +119,7 @@ class DouyuLogin:
|
||||
ProxyFetcher(api_url=proxy_api_url) if proxy_api_url else None
|
||||
)
|
||||
|
||||
self._current_proxy_url: Optional[str] = None
|
||||
self._current_proxy_url: str | None = None
|
||||
self._cookie_enrich_error = ""
|
||||
self._static_retry_count = 0
|
||||
self._setup_session()
|
||||
@@ -565,7 +567,7 @@ class DouyuLogin:
|
||||
)
|
||||
return True
|
||||
|
||||
def _first_login(self) -> Tuple[str, str, str, dict]:
|
||||
def _first_login(self) -> tuple[str, str, str, dict]:
|
||||
"""
|
||||
第一次登录,获取极验参数
|
||||
|
||||
@@ -608,7 +610,7 @@ class DouyuLogin:
|
||||
|
||||
def _solve_geetest(
|
||||
self, gt: str, challenge: str, deadline: float = 0
|
||||
) -> Tuple[str, str]:
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
解决极验 fullpage 验证(最多3次尝试,失败直接抛异常回到login换新代理)
|
||||
|
||||
@@ -635,7 +637,7 @@ class DouyuLogin:
|
||||
|
||||
def _solve_geetest_inner(
|
||||
self, gt: str, challenge: str, deadline: float = 0
|
||||
) -> Tuple[str, str]:
|
||||
) -> tuple[str, str]:
|
||||
"""极验验证内部实现:最多3次尝试,失败直接抛异常。"""
|
||||
_MAX_ATTEMPTS = 3
|
||||
|
||||
@@ -652,7 +654,7 @@ class DouyuLogin:
|
||||
|
||||
# 超时兜底
|
||||
if deadline and time.monotonic() > deadline:
|
||||
raise ValueError(f"极验验证超时(登录整体时间耗尽)")
|
||||
raise ValueError("极验验证超时(登录整体时间耗尽)")
|
||||
|
||||
try:
|
||||
logger.info(f"极验验证尝试 {attempt}/{_MAX_ATTEMPTS}")
|
||||
@@ -825,7 +827,7 @@ class DouyuLogin:
|
||||
|
||||
logger.info("验证邮件已发送")
|
||||
|
||||
def _get_email_code(self, after_timestamp: Optional[float] = None) -> str:
|
||||
def _get_email_code(self, after_timestamp: float | None = None) -> str:
|
||||
"""获取邮箱验证码"""
|
||||
verifier = EmailVerifier(
|
||||
imap_server=self.account.email_imap_server,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""登录接口策略基类"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class LoginAPIStrategy(ABC):
|
||||
@@ -78,7 +77,7 @@ class LoginAPIStrategy(ABC):
|
||||
"""构建提交验证码参数"""
|
||||
...
|
||||
|
||||
def extract_geetest_params(self, payload: dict) -> Tuple[str, str, str]:
|
||||
def extract_geetest_params(self, payload: dict) -> tuple[str, str, str]:
|
||||
"""从第一次登录响应提取极验参数
|
||||
|
||||
Args:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .proxy_resolver import ProxyResolver
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def parse_proxy_response(text: str) -> tuple[list[str], Optional[str]]:
|
||||
def parse_proxy_response(text: str) -> tuple[list[str], str | None]:
|
||||
"""
|
||||
解析代理 API 响应,支持 JSON 格式与旧版纯文本格式。
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from .xiequ import XiequAdapter
|
||||
from .xkdaili import XkdailiAdapter
|
||||
|
||||
# ── 平台注册表 ──
|
||||
_PLATFORM_REGISTRY: dict[str, Type[BaseWhitelistAdapter]] = {
|
||||
_PLATFORM_REGISTRY: dict[str, type[BaseWhitelistAdapter]] = {
|
||||
"xiequ": XiequAdapter,
|
||||
"xkdaili": XkdailiAdapter,
|
||||
}
|
||||
@@ -49,7 +49,7 @@ _PLATFORM_LABELS: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
def create_adapter(platform: str, credentials: dict) -> Optional[BaseWhitelistAdapter]:
|
||||
def create_adapter(platform: str, credentials: dict) -> BaseWhitelistAdapter | None:
|
||||
"""根据平台标识符和凭据创建适配器实例。"""
|
||||
cls = _PLATFORM_REGISTRY.get(platform)
|
||||
if not cls:
|
||||
|
||||
@@ -8,7 +8,6 @@ import re
|
||||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
@@ -144,12 +143,12 @@ class BaseWhitelistAdapter(ABC):
|
||||
return False, msg
|
||||
|
||||
@staticmethod
|
||||
def get_local_exit_ip() -> Optional[str]:
|
||||
def get_local_exit_ip() -> str | None:
|
||||
"""获取本机当前公网出口IP(不走代理)。"""
|
||||
return _get_local_exit_ip()
|
||||
|
||||
|
||||
def _get_local_exit_ip() -> Optional[str]:
|
||||
def _get_local_exit_ip() -> str | None:
|
||||
"""获取本机当前公网出口IP(不走代理)。"""
|
||||
targets = [
|
||||
"https://myip.ipip.net",
|
||||
@@ -171,7 +170,7 @@ def _get_local_exit_ip() -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def get_exit_ip_via_proxy(proxy: str) -> Optional[str]:
|
||||
def get_exit_ip_via_proxy(proxy: str) -> str | None:
|
||||
"""通过代理获取出口IP。"""
|
||||
targets = [
|
||||
"https://qifu-api.baidubce.com/ip/local/geo/v1/district",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import Callable, Optional, Protocol
|
||||
from collections.abc import Callable
|
||||
from typing import Protocol
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
@@ -19,7 +20,7 @@ class WhitelistSyncer(Protocol):
|
||||
|
||||
def sync_ip(self, ip: str) -> tuple[bool, str]: ...
|
||||
|
||||
def get_local_exit_ip(self) -> Optional[str]: ...
|
||||
def get_local_exit_ip(self) -> str | None: ...
|
||||
|
||||
|
||||
class ProxyResolver:
|
||||
@@ -28,11 +29,11 @@ class ProxyResolver:
|
||||
def __init__(
|
||||
self,
|
||||
api_url: str,
|
||||
whitelist_syncer: Optional[WhitelistSyncer] = None,
|
||||
log_func: Optional[LogFunc] = None,
|
||||
whitelist_syncer: WhitelistSyncer | None = None,
|
||||
log_func: LogFunc | None = None,
|
||||
sync_local_exit_ip: bool = False,
|
||||
sync_whitelist_once: bool = True,
|
||||
stop_event: Optional[threading.Event] = None,
|
||||
stop_event: threading.Event | None = None,
|
||||
):
|
||||
self.api_url = api_url
|
||||
self.whitelist_syncer = whitelist_syncer
|
||||
@@ -40,7 +41,7 @@ class ProxyResolver:
|
||||
self.sync_local_exit_ip = sync_local_exit_ip
|
||||
self.sync_whitelist_once = sync_whitelist_once
|
||||
self.stop_event = stop_event
|
||||
self._last_synced_ip: Optional[str] = None
|
||||
self._last_synced_ip: str | None = None
|
||||
self._has_synced_whitelist = False
|
||||
|
||||
def _is_stopped(self) -> bool:
|
||||
@@ -95,7 +96,7 @@ class ProxyResolver:
|
||||
self,
|
||||
max_attempts: int = 4,
|
||||
return_all: bool = False,
|
||||
) -> tuple[Optional[str | list[str]], str]:
|
||||
) -> tuple[str | list[str] | None, str]:
|
||||
"""
|
||||
获取并验证代理。
|
||||
|
||||
@@ -178,8 +179,8 @@ def resolve_working_proxy(
|
||||
whitelist_platform: str = "xiequ",
|
||||
whitelist_credentials: dict | None = None,
|
||||
max_attempts: int = 4,
|
||||
log_func: Optional[LogFunc] = None,
|
||||
) -> tuple[Optional[str], str]:
|
||||
log_func: LogFunc | None = None,
|
||||
) -> tuple[str | None, str]:
|
||||
"""从代理 API 获取可用代理,自动处理白名单同步。"""
|
||||
syncer = (
|
||||
DouyuWhitelistSyncer(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""代理可用性验证。"""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
@@ -45,7 +44,7 @@ def verify_proxies_concurrent(
|
||||
timeout: tuple = (5, 8),
|
||||
max_workers: int = 5,
|
||||
return_all: bool = False,
|
||||
) -> tuple[Optional[str | list[str]], str]:
|
||||
) -> tuple[str | list[str] | None, str]:
|
||||
"""
|
||||
并发验证多个代理 URL。
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""代理模块使用的白名单适配器。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .proxy_platforms import create_adapter
|
||||
from .proxy_platforms.base import BaseWhitelistAdapter, _get_local_exit_ip
|
||||
@@ -17,7 +16,7 @@ class DouyuWhitelistSyncer:
|
||||
def __init__(
|
||||
self,
|
||||
platform: str = "xiequ",
|
||||
credentials: Optional[dict] = None,
|
||||
credentials: dict | None = None,
|
||||
uid: str = "",
|
||||
ukey: str = "",
|
||||
):
|
||||
@@ -28,7 +27,7 @@ class DouyuWhitelistSyncer:
|
||||
|
||||
self.platform = platform
|
||||
self.credentials = credentials or {}
|
||||
self._adapter: Optional[BaseWhitelistAdapter] = None
|
||||
self._adapter: BaseWhitelistAdapter | None = None
|
||||
|
||||
if self.credentials:
|
||||
self._adapter = create_adapter(platform, self.credentials)
|
||||
@@ -38,5 +37,5 @@ class DouyuWhitelistSyncer:
|
||||
return False, "未配置白名单凭据"
|
||||
return self._adapter.sync_ip(ip)
|
||||
|
||||
def get_local_exit_ip(self) -> Optional[str]:
|
||||
def get_local_exit_ip(self) -> str | None:
|
||||
return _get_local_exit_ip()
|
||||
|
||||
@@ -6,9 +6,10 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any, Callable, Mapping
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
@@ -34,7 +35,7 @@ class FishFinRechargeConfig:
|
||||
debug: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "FishFinRechargeConfig":
|
||||
def from_env(cls) -> FishFinRechargeConfig:
|
||||
"""从环境变量读取配置,不在代码或数据库中保存商户密钥。"""
|
||||
timeout = float(os.getenv("FISH_FIN_RECHARGE_TIMEOUT", "20"))
|
||||
return cls(
|
||||
@@ -189,7 +190,7 @@ class FishFinRechargeClient:
|
||||
"sign_params": sign_params,
|
||||
# 不把可重放签名原文或 AppSecret 写入日志,只记录待签名串摘要。
|
||||
"sign_source_digest": hashlib.sha256(
|
||||
f"{sign_query}{method.upper()}".encode("utf-8")
|
||||
f"{sign_query}{method.upper()}".encode()
|
||||
).hexdigest()[:12],
|
||||
}
|
||||
)
|
||||
@@ -240,7 +241,7 @@ class FishFinRechargeClient:
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
def _amount(value: Decimal | int | float | str) -> str:
|
||||
def _amount(value: Decimal | float | str) -> str:
|
||||
"""规范化金额,避免浮点数表达式进入签名或订单请求。"""
|
||||
try:
|
||||
price = Decimal(str(value))
|
||||
@@ -251,7 +252,7 @@ class FishFinRechargeClient:
|
||||
return format(price.normalize(), "f")
|
||||
|
||||
@classmethod
|
||||
def _json_amount(cls, value: Decimal | int | float | str) -> int | float:
|
||||
def _json_amount(cls, value: Decimal | float | str) -> int | float:
|
||||
"""按文档以 JSON 数字发送金额,整数不附带无意义的小数位。"""
|
||||
amount_text = cls._amount(value)
|
||||
return int(amount_text) if "." not in amount_text else float(amount_text)
|
||||
@@ -267,7 +268,7 @@ class FishFinRechargeClient:
|
||||
self,
|
||||
*,
|
||||
buy_num: int,
|
||||
pay_amount: Decimal | int | float | str,
|
||||
pay_amount: Decimal | float | str,
|
||||
out_order_id: str,
|
||||
product_id: str,
|
||||
recharge_arg: list[dict[str, Any]],
|
||||
|
||||
Reference in New Issue
Block a user