""" 配置管理模块 使用 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="模块名称") # Sentinel 内部配置 sentinel_debug: bool = Field(default=False, description="Sentinel 调试模式") sentinel_sdk_path: str = Field( default="sdk/sdk.js", description="Sentinel SDK JS 文件路径(相对于项目根目录)" ) # ========== 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 请求超时时间(秒)" ) # ========== Telegram Bot 配置 ========== telegram_bot_token: Optional[str] = Field( default=None, description="Telegram Bot Token (从 @BotFather 获取)" ) telegram_allowed_users: str = Field( default="", description="允许使用 Bot 的用户 ID(逗号分隔)" ) telegram_admin_users: str = Field( default="", description="管理员用户 ID(逗号分隔,拥有更高权限)" ) telegram_bot_enabled: bool = Field( default=True, description="是否启用 Telegram Bot" ) @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, ) @property def fingerprint_config(self) -> Dict[str, Any]: """获取指纹配置""" return { "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", "screen_width": 1920, "screen_height": 1080, "languages": ["en-US", "en"], "hardware_concurrency": 8, } 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", ]