This commit is contained in:
dela
2026-01-26 15:04:02 +08:00
commit 4813449f9c
31 changed files with 8439 additions and 0 deletions

541
config.py Normal file
View File

@@ -0,0 +1,541 @@
"""
配置管理模块
使用 Pydantic Settings 从环境变量和 .env 文件加载配置
支持的配置项:
- 代理设置(代理池、轮换策略)
- 并发控制(最大并发数、重试次数)
- 日志级别
- 邮箱配置(可选)
- Sentinel 配置(可选)
"""
from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import List, Optional, Literal, Dict, Any
from dotenv import load_dotenv
from pathlib import Path
import random
# 加载 .env 文件
load_dotenv()
class ProxyConfig(BaseModel):
"""代理配置"""
enabled: bool = Field(default=False, description="是否启用代理")
pool: List[str] = Field(default_factory=list, description="代理池列表")
rotation: Literal["random", "round_robin"] = Field(
default="random",
description="代理轮换策略"
)
_current_index: int = 0 # 用于 round_robin 策略
def get_next_proxy(self) -> Optional[str]:
"""
获取下一个代理地址
根据配置的轮换策略返回代理
- random: 随机选择
- round_robin: 轮流使用
返回:
代理地址,如果代理池为空则返回 None
"""
if not self.enabled or not self.pool:
return None
if self.rotation == "random":
return random.choice(self.pool)
else: # round_robin
proxy = self.pool[self._current_index % len(self.pool)]
self._current_index += 1
return proxy
def validate_proxy_format(self, proxy: str) -> bool:
"""
验证代理格式是否正确
支持格式:
- http://ip:port
- http://user:pass@ip:port
- https://ip:port
- socks5://ip:port
参数:
proxy: 代理地址
返回:
True 如果格式正确,否则 False
"""
import re
pattern = r'^(http|https|socks5)://([^:]+:[^@]+@)?[\w.-]+:\d+$'
return bool(re.match(pattern, proxy))
class MailConfig(BaseModel):
"""邮箱配置"""
enabled: bool = Field(default=False, description="是否启用邮箱功能")
type: Literal["imap", "tempmail", "api", "manual", "cloudmail"] = Field(
default="manual",
description="邮箱类型"
)
# IMAP 配置
imap_host: Optional[str] = Field(default=None, description="IMAP 服务器地址")
imap_port: int = Field(default=993, description="IMAP 端口")
imap_username: Optional[str] = Field(default=None, description="邮箱用户名")
imap_password: Optional[str] = Field(default=None, description="邮箱密码")
# 临时邮箱 API 配置
api_key: Optional[str] = Field(default=None, description="临时邮箱 API Key")
api_endpoint: Optional[str] = Field(default=None, description="临时邮箱 API 端点")
# CloudMail 配置
cloudmail_api_url: Optional[str] = Field(
default=None,
description="Cloud Mail API 基础 URL"
)
cloudmail_token: Optional[str] = Field(
default=None,
description="Cloud Mail 身份令牌(预先生成)"
)
cloudmail_domain: Optional[str] = Field(
default=None,
description="Cloud Mail 邮箱域名(例如 mygoband.com"
)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式(供 MailHandler 使用)"""
config = {"type": self.type}
if self.type == "imap":
config.update({
"host": self.imap_host,
"port": self.imap_port,
"username": self.imap_username,
"password": self.imap_password,
})
elif self.type in ["tempmail", "api"]:
config.update({
"api_key": self.api_key,
"api_endpoint": self.api_endpoint,
})
elif self.type == "cloudmail":
config.update({
"api_base_url": self.cloudmail_api_url,
"token": self.cloudmail_token,
"domain": self.cloudmail_domain,
})
return config
class SentinelConfig(BaseModel):
"""Sentinel 配置"""
enabled: bool = Field(default=False, description="是否启用 Sentinel 解决器")
solver_type: Literal["external_script", "api", "module"] = Field(
default="external_script",
description="解决器类型"
)
# 外部脚本配置
script_path: Optional[str] = Field(
default=None,
description="外部脚本路径Node.js 或其他)"
)
# API 配置
api_endpoint: Optional[str] = Field(
default=None,
description="Sentinel solver API 端点"
)
api_key: Optional[str] = Field(default=None, description="API Key")
# Python 模块配置
module_name: Optional[str] = Field(
default=None,
description="Python 模块名称(例如 'my_sentinel_solver'"
)
class AppConfig(BaseSettings):
"""
应用配置(从环境变量加载)
环境变量优先级:
1. 系统环境变量
2. .env 文件
3. 默认值
"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore" # 忽略未定义的环境变量
)
# ========== 代理配置 ==========
proxy_enabled: bool = Field(
default=False,
description="是否启用代理"
)
proxy_pool: str = Field(
default="",
description="代理池(逗号分隔),例如: http://ip1:port,socks5://ip2:port"
)
proxy_rotation: Literal["random", "round_robin"] = Field(
default="random",
description="代理轮换策略"
)
# ========== 并发配置 ==========
max_workers: int = Field(
default=1,
ge=1,
le=50,
description="最大并发任务数"
)
retry_limit: int = Field(
default=3,
ge=0,
le=10,
description="失败重试次数"
)
retry_delay: int = Field(
default=5,
ge=0,
description="重试延迟(秒)"
)
# ========== 日志配置 ==========
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field(
default="INFO",
description="日志级别"
)
log_to_file: bool = Field(
default=True,
description="是否记录日志到文件"
)
# ========== 邮箱配置 ==========
mail_enabled: bool = Field(default=False, description="是否启用邮箱功能")
mail_type: Literal["imap", "tempmail", "api", "manual", "cloudmail"] = Field(
default="manual",
description="邮箱类型"
)
mail_imap_host: Optional[str] = Field(default=None, description="IMAP 服务器")
mail_imap_port: int = Field(default=993, description="IMAP 端口")
mail_imap_username: Optional[str] = Field(default=None, description="邮箱用户名")
mail_imap_password: Optional[str] = Field(default=None, description="邮箱密码")
mail_api_key: Optional[str] = Field(default=None, description="临时邮箱 API Key")
mail_api_endpoint: Optional[str] = Field(default=None, description="API 端点")
# CloudMail 配置
mail_cloudmail_api_url: Optional[str] = Field(
default=None,
description="Cloud Mail API URL"
)
mail_cloudmail_token: Optional[str] = Field(
default=None,
description="Cloud Mail 身份令牌"
)
mail_cloudmail_domain: Optional[str] = Field(
default=None,
description="Cloud Mail 邮箱域名(例如 mygoband.com"
)
# ========== Sentinel 配置 ==========
sentinel_enabled: bool = Field(default=False, description="是否启用 Sentinel")
sentinel_solver_type: Literal["external_script", "api", "module"] = Field(
default="external_script",
description="Sentinel 解决器类型"
)
sentinel_script_path: Optional[str] = Field(default=None, description="脚本路径")
sentinel_api_endpoint: Optional[str] = Field(default=None, description="API 端点")
sentinel_api_key: Optional[str] = Field(default=None, description="API Key")
sentinel_module_name: Optional[str] = Field(default=None, description="模块名称")
# ========== TLS 指纹配置 ==========
tls_impersonate: Literal["chrome110", "chrome120", "chrome124"] = Field(
default="chrome124",
description="模拟的浏览器版本"
)
# ========== 其他配置 ==========
accounts_output_file: str = Field(
default="accounts.txt",
description="成功账号保存文件路径"
)
request_timeout: int = Field(
default=30,
ge=5,
le=300,
description="HTTP 请求超时时间(秒)"
)
@field_validator("proxy_pool")
@classmethod
def validate_proxy_pool(cls, v: str) -> str:
"""验证代理池格式"""
if not v:
return v
proxies = [p.strip() for p in v.split(",") if p.strip()]
for proxy in proxies:
# 基本格式检查
if not any(proxy.startswith(prefix) for prefix in ["http://", "https://", "socks5://"]):
raise ValueError(
f"Invalid proxy format: {proxy}. "
"Must start with http://, https://, or socks5://"
)
return v
@property
def proxy(self) -> ProxyConfig:
"""获取代理配置对象"""
pool = [p.strip() for p in self.proxy_pool.split(",") if p.strip()]
return ProxyConfig(
enabled=self.proxy_enabled,
pool=pool,
rotation=self.proxy_rotation
)
@property
def mail(self) -> MailConfig:
"""获取邮箱配置对象"""
return MailConfig(
enabled=self.mail_enabled,
type=self.mail_type,
imap_host=self.mail_imap_host,
imap_port=self.mail_imap_port,
imap_username=self.mail_imap_username,
imap_password=self.mail_imap_password,
api_key=self.mail_api_key,
api_endpoint=self.mail_api_endpoint,
cloudmail_api_url=self.mail_cloudmail_api_url,
cloudmail_token=self.mail_cloudmail_token,
cloudmail_domain=self.mail_cloudmail_domain,
)
@property
def sentinel(self) -> SentinelConfig:
"""获取 Sentinel 配置对象"""
return SentinelConfig(
enabled=self.sentinel_enabled,
solver_type=self.sentinel_solver_type,
script_path=self.sentinel_script_path,
api_endpoint=self.sentinel_api_endpoint,
api_key=self.sentinel_api_key,
module_name=self.sentinel_module_name,
)
def validate_config(self) -> List[str]:
"""
验证配置完整性,返回警告列表
返回:
警告信息列表,空列表表示无警告
"""
warnings = []
# 检查代理配置
if self.proxy_enabled and not self.proxy_pool:
warnings.append("Proxy enabled but proxy_pool is empty")
# 检查邮箱配置
if self.mail_enabled:
if self.mail_type == "imap":
if not all([self.mail_imap_host, self.mail_imap_username, self.mail_imap_password]):
warnings.append("IMAP mail enabled but credentials incomplete")
elif self.mail_type in ["tempmail", "api"]:
if not self.mail_api_key:
warnings.append(f"{self.mail_type} enabled but api_key not configured")
elif self.mail_type == "cloudmail":
if not all([self.mail_cloudmail_api_url, self.mail_cloudmail_token, self.mail_cloudmail_domain]):
warnings.append(
"CloudMail enabled but config incomplete. "
"Required: MAIL_CLOUDMAIL_API_URL, MAIL_CLOUDMAIL_TOKEN, MAIL_CLOUDMAIL_DOMAIN"
)
# 检查 Sentinel 配置
if self.sentinel_enabled:
if self.sentinel_solver_type == "external_script" and not self.sentinel_script_path:
warnings.append("Sentinel external_script enabled but script_path not configured")
elif self.sentinel_solver_type == "api" and not self.sentinel_api_endpoint:
warnings.append("Sentinel API enabled but api_endpoint not configured")
elif self.sentinel_solver_type == "module" and not self.sentinel_module_name:
warnings.append("Sentinel module enabled but module_name not configured")
# 检查并发设置
if self.max_workers > 10 and not self.proxy_enabled:
warnings.append(
f"High concurrency ({self.max_workers} workers) without proxy may trigger rate limits"
)
return warnings
def print_summary(self):
"""打印配置摘要"""
from utils.logger import logger
logger.info("=" * 60)
logger.info("Configuration Summary")
logger.info("=" * 60)
logger.info(f"Proxy: {'Enabled' if self.proxy_enabled else 'Disabled'}")
if self.proxy_enabled:
logger.info(f" - Pool size: {len(self.proxy.pool)}")
logger.info(f" - Rotation: {self.proxy_rotation}")
logger.info(f"Mail: {'Enabled' if self.mail_enabled else 'Disabled'}")
if self.mail_enabled:
logger.info(f" - Type: {self.mail_type}")
logger.info(f"Sentinel: {'Enabled' if self.sentinel_enabled else 'Disabled'}")
if self.sentinel_enabled:
logger.info(f" - Solver: {self.sentinel_solver_type}")
logger.info(f"Concurrency: {self.max_workers} workers")
logger.info(f"Retry limit: {self.retry_limit}")
logger.info(f"Log level: {self.log_level}")
logger.info(f"TLS impersonate: {self.tls_impersonate}")
logger.info("=" * 60)
# 打印警告
warnings = self.validate_config()
if warnings:
logger.warning("Configuration warnings:")
for warning in warnings:
logger.warning(f" ⚠️ {warning}")
def load_config() -> AppConfig:
"""
加载配置
返回:
AppConfig 实例
示例:
config = load_config()
print(config.proxy.enabled)
print(config.mail.type)
"""
config = AppConfig()
return config
def create_default_env_file(path: str = ".env.example"):
"""
创建默认的 .env 示例文件
参数:
path: 文件保存路径
"""
content = """# OpenAI 账号自动注册系统 - 配置文件示例
# 复制此文件为 .env 并根据实际情况修改
# ========== 代理配置 ==========
# 是否启用代理true/false
PROXY_ENABLED=false
# 代理池(逗号分隔,支持 http/https/socks5
# 格式: protocol://[user:pass@]ip:port
# 示例: http://user:pass@1.2.3.4:8080,socks5://5.6.7.8:1080
PROXY_POOL=
# 代理轮换策略random: 随机选择, round_robin: 轮流使用)
PROXY_ROTATION=random
# ========== 并发配置 ==========
# 最大并发任务数(建议 1-5过高可能触发风控
MAX_WORKERS=1
# 失败重试次数
RETRY_LIMIT=3
# 重试延迟(秒)
RETRY_DELAY=5
# ========== 日志配置 ==========
# 日志级别DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO
# 是否记录日志到文件
LOG_TO_FILE=true
# ========== 邮箱配置 ==========
# 是否启用邮箱功能true/false
MAIL_ENABLED=false
# 邮箱类型imap, tempmail, api, cloudmail, manual
MAIL_TYPE=manual
# IMAP 配置(如果使用 IMAP
MAIL_IMAP_HOST=imap.gmail.com
MAIL_IMAP_PORT=993
MAIL_IMAP_USERNAME=your@email.com
MAIL_IMAP_PASSWORD=your_app_password
# 临时邮箱 API 配置(如果使用临时邮箱)
MAIL_API_KEY=
MAIL_API_ENDPOINT=
# CloudMail 配置(如果使用 CloudMail
# 1. 先通过 Cloud Mail 管理界面或 API 生成 Token
# 2. 将 Token 填入 MAIL_CLOUDMAIL_TOKEN
# 3. 填写你的邮箱域名(不带 @
# 4. Token 失效时需要手动更新
MAIL_CLOUDMAIL_API_URL=https://your-cloudmail-domain.com
MAIL_CLOUDMAIL_TOKEN=9f4e298e-7431-4c76-bc15-4931c3a73984
MAIL_CLOUDMAIL_DOMAIN=mygoband.com
# ========== Sentinel 配置 ==========
# 是否启用 Sentinel 解决器true/false
SENTINEL_ENABLED=false
# Sentinel 解决器类型external_script, api, module
SENTINEL_SOLVER_TYPE=external_script
# 外部脚本路径(如果使用 external_script
SENTINEL_SCRIPT_PATH=./sentinel_solver.js
# API 配置(如果使用 api
SENTINEL_API_ENDPOINT=http://localhost:8000/solve
SENTINEL_API_KEY=
# Python 模块名称(如果使用 module
SENTINEL_MODULE_NAME=
# ========== TLS 指纹配置 ==========
# 模拟的浏览器版本chrome110, chrome120, chrome124
TLS_IMPERSONATE=chrome124
# ========== 其他配置 ==========
# 成功账号保存文件路径
ACCOUNTS_OUTPUT_FILE=accounts.txt
# HTTP 请求超时时间(秒)
REQUEST_TIMEOUT=30
"""
Path(path).write_text(content, encoding="utf-8")
print(f"✅ Default .env.example created at: {path}")
# 导出主要接口
__all__ = [
"AppConfig",
"ProxyConfig",
"MailConfig",
"SentinelConfig",
"load_config",
"create_default_env_file",
]