执行 Ruff 安全自动修复

This commit is contained in:
yml2213
2026-08-31 10:28:14 +08:00
parent 2a1d27f953
commit b58c6b4357
145 changed files with 940 additions and 993 deletions
+10 -10
View File
@@ -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",
]
+1 -3
View File
@@ -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 -4
View File
@@ -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-----
+11 -14
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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:
+1
View File
@@ -10,6 +10,7 @@
import threading
import time
from loguru import logger
from .proxy_resolver import ProxyResolver
+1 -2
View File
@@ -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 格式与旧版纯文本格式。
+2 -2
View File
@@ -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:
+3 -4
View File
@@ -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",
+10 -9
View File
@@ -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 -2
View File
@@ -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。
+3 -4
View File
@@ -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()
+7 -6
View File
@@ -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]],