From 80667718561110812dd15eeac2ad0f944dc2885d Mon Sep 17 00:00:00 2001 From: dela Date: Mon, 26 Jan 2026 23:43:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.example.py | 4 +- flow/flow_email_token.py | 4 +- modules/__init__.py | 24 +- modules/pow_solver.py | 114 ---- modules/register.py | 11 +- modules/sentinel_solver.py | 27 +- tg_bot.py | 1291 +++++++++++++++++++----------------- tg_bot_old.py | 784 ++++++++++++++++++++++ 8 files changed, 1522 insertions(+), 737 deletions(-) delete mode 100644 modules/pow_solver.py create mode 100644 tg_bot_old.py diff --git a/config.example.py b/config.example.py index e49e049..6c2c384 100644 --- a/config.example.py +++ b/config.example.py @@ -47,8 +47,8 @@ POW_CONFIG = { # Sentinel 求解器配置 SENTINEL_CONFIG = { - # 使用纯 Python 实现(不需要 Node.js) - # True: 使用 sentinel_native.py(推荐,无外部依赖) + # 使用纯 Python 原生实现(默认,推荐) + # True: 使用 sentinel_native.py(无需 Node.js,推荐) # False: 使用 js_executor.py + sdk.js(需要 Node.js) 'use_native': True, } diff --git a/flow/flow_email_token.py b/flow/flow_email_token.py index 77c2a85..3cde778 100644 --- a/flow/flow_email_token.py +++ b/flow/flow_email_token.py @@ -29,7 +29,7 @@ try: from modules.fingerprint import BrowserFingerprint from modules.http_client import HTTPClient from modules.sentinel_solver import SentinelSolver - from modules.pow_solver import ProofOfWorkSolver + from modules.sentinel_native import PowSolver MODULES_AVAILABLE = True print("✅ Project modules loaded") except ImportError as e: @@ -57,7 +57,7 @@ class OpenAILogin: self.session = self.http_client.session # 保持兼容性 use_native = SENTINEL_CONFIG.get('use_native', True) self.sentinel_solver = SentinelSolver(self.fingerprint, use_native=use_native) - self.pow_solver = ProofOfWorkSolver() + self.pow_solver = PowSolver() print(f"✅ Using HTTPClient with project modules (native={use_native})") else: # 降级使用原始 session diff --git a/modules/__init__.py b/modules/__init__.py index 854e564..a7069e5 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,13 +1,15 @@ # modules/__init__.py -"""Sentinel Bypass 模块包""" +"""Sentinel Bypass 模块包 + +默认使用纯 Python 原生实现(无需 Node.js) +""" from .fingerprint import BrowserFingerprint -from .js_executor import JSExecutor from .sentinel_solver import SentinelSolver from .http_client import HTTPClient from .register import OpenAIRegistrar -# 纯 Python 实现(不依赖 Node.js) +# 纯 Python 实现(默认推荐) from .sentinel_native import ( NativeSentinelSolver, PowSolver, @@ -18,14 +20,20 @@ from .sentinel_native import ( generate_requirements_token_simple, ) +# Node.js 实现(可选,需要安装 Node.js) +try: + from .js_executor import JSExecutor + _HAS_JS_EXECUTOR = True +except ImportError: + _HAS_JS_EXECUTOR = False + __all__ = [ - # 原有组件(依赖 Node.js) + # 核心组件 'BrowserFingerprint', - 'JSExecutor', 'SentinelSolver', 'HTTPClient', 'OpenAIRegistrar', - # 纯 Python 组件 + # 纯 Python 组件(推荐) 'NativeSentinelSolver', 'PowSolver', 'TurnstileSolver', @@ -34,3 +42,7 @@ __all__ = [ 'solve_pow_simple', 'generate_requirements_token_simple', ] + +# 仅当 Node.js 可用时导出 JSExecutor +if _HAS_JS_EXECUTOR: + __all__.append('JSExecutor') diff --git a/modules/pow_solver.py b/modules/pow_solver.py deleted file mode 100644 index 976c527..0000000 --- a/modules/pow_solver.py +++ /dev/null @@ -1,114 +0,0 @@ -import time -import json -import base64 -from typing import List - -DEBUG = True - -class ProofOfWorkSolver: - """解决 OpenAI Sentinel 的 Proof of Work challenge""" - - def __init__(self): - # FNV-1a 常量 - self.FNV_OFFSET = 2166136261 - self.FNV_PRIME = 16777619 - - def fnv1a_hash(self, data: str) -> str: - """FNV-1a hash 算法""" - hash_value = self.FNV_OFFSET - - for char in data: - hash_value ^= ord(char) - hash_value = (hash_value * self.FNV_PRIME) & 0xFFFFFFFF - - # 额外的混合步骤(从 JS 代码复制) - hash_value ^= hash_value >> 16 - hash_value = (hash_value * 2246822507) & 0xFFFFFFFF - hash_value ^= hash_value >> 13 - hash_value = (hash_value * 3266489909) & 0xFFFFFFFF - hash_value ^= hash_value >> 16 - - # 转为 8 位十六进制字符串 - return format(hash_value, '08x') - - def serialize_array(self, arr: List) -> str: - """模拟 JS 的 T() 函数:JSON.stringify + Base64""" - json_str = json.dumps(arr, separators=(',', ':')) - return base64.b64encode(json_str.encode()).decode() - - def build_fingerprint_array(self, nonce: int, elapsed_ms: int) -> List: - """构建指纹数组(简化版)""" - return [ - 0, # [0] screen dimensions - "", # [1] timestamp - 0, # [2] memory - nonce, # [3] nonce ← 关键 - "", # [4] user agent - "", # [5] random element - "", # [6] script src - "", # [7] language - "", # [8] languages - elapsed_ms, # [9] elapsed time ← 关键 - "", # [10] random function - "", # [11] keys - "", # [12] window keys - 0, # [13] performance.now() - "", # [14] uuid - "", # [15] URL params - 0, # [16] hardware concurrency - 0 # [17] timeOrigin - ] - - def solve(self, seed: str, difficulty: str, max_iterations: int = 10000000) -> str: - """ - 解决 PoW challenge - - Args: - seed: Challenge seed - difficulty: 目标难度(十六进制字符串) - max_iterations: 最大尝试次数 - - Returns: - 序列化的答案(包含 nonce) - """ - if DEBUG: - print(f"[PoW] Solving challenge:") - print(f" Seed: {seed}") - print(f" Difficulty: {difficulty}") - - start_time = time.time() - - for nonce in range(max_iterations): - elapsed_ms = int((time.time() - start_time) * 1000) - - # 构建指纹数组 - fingerprint = self.build_fingerprint_array(nonce, elapsed_ms) - - # 序列化 - serialized = self.serialize_array(fingerprint) - - # 计算 hash(seed + serialized) - hash_input = seed + serialized - hash_result = self.fnv1a_hash(hash_input) - - # 检查是否满足难度要求 - # 比较方式:hash 的前 N 位(作为整数)<= difficulty(作为整数) - difficulty_len = len(difficulty) - hash_prefix = hash_result[:difficulty_len] - - if hash_prefix <= difficulty: - elapsed = time.time() - start_time - if DEBUG: - print(f"[PoW] ✓ Found solution in {elapsed:.2f}s") - print(f" Nonce: {nonce}") - print(f" Hash: {hash_result}") - print(f" Serialized: {serialized[:100]}...") - - # 返回 serialized + "~S" (表示成功) - return serialized + "~S" - - # 每 100k 次迭代打印进度 - if DEBUG and nonce > 0 and nonce % 100000 == 0: - print(f"[PoW] Tried {nonce:,} iterations...") - - raise Exception(f"Failed to solve PoW after {max_iterations:,} iterations") diff --git a/modules/register.py b/modules/register.py index 7fd46b5..55d9ee6 100644 --- a/modules/register.py +++ b/modules/register.py @@ -13,7 +13,7 @@ from .sentinel_solver import SentinelSolver from .http_client import HTTPClient from .stripe_payment import StripePaymentHandler from config import AUTH_BASE_URL, DEBUG, TEMPMAIL_CONFIG -from modules.pow_solver import ProofOfWorkSolver +from .sentinel_native import PowSolver from modules.tempmail import TempMailClient # 导入 Sentinel 配置 @@ -34,7 +34,7 @@ class OpenAIRegistrar: self.solver = SentinelSolver(self.fingerprint, use_native=use_native) self.http_client = HTTPClient(self.fingerprint) - self.pow_solver = ProofOfWorkSolver() # 新增 + self.pow_solver = PowSolver() # 使用 sentinel_native 中的 PowSolver self.tempmail_client = tempmail_client # 临时邮箱客户端(可选) def _step1_init_through_chatgpt(self, email: str): @@ -310,8 +310,11 @@ class OpenAIRegistrar: if not seed or not difficulty: raise Exception(f"Missing PoW parameters") - # 解 PoW - self.pow_answer = self.pow_solver.solve(seed, difficulty) + # 获取指纹配置数组 + config = self.fingerprint.get_config_array() + + # 解 PoW (静态方法,需要传入 config) + self.pow_answer = PowSolver.solve(seed, difficulty, config) if DEBUG: print(f"✅ [2.6] PoW solved (difficulty: {difficulty})") diff --git a/modules/sentinel_solver.py b/modules/sentinel_solver.py index 25bb622..66eda38 100644 --- a/modules/sentinel_solver.py +++ b/modules/sentinel_solver.py @@ -1,9 +1,8 @@ # modules/sentinel_solver.py """Sentinel 挑战求解器 -支持两种模式: -1. Node.js 模式 (默认): 使用 sdk.js 执行 -2. Native 模式: 纯 Python 实现,无需 Node.js +默认使用纯 Python 原生实现(无需 Node.js) +如需使用 Node.js 模式,设置 use_native=False """ import json @@ -13,30 +12,30 @@ from typing import Dict, Optional from .fingerprint import BrowserFingerprint from config import DEBUG -# 尝试导入 JSExecutor(需要 Node.js) -try: - from .js_executor import JSExecutor - HAS_JS_EXECUTOR = True -except Exception: - HAS_JS_EXECUTOR = False - -# 尝试导入纯 Python 实现 +# 优先导入纯 Python 实现(推荐) try: from .sentinel_native import NativeSentinelSolver, PowSolver HAS_NATIVE_SOLVER = True except ImportError: HAS_NATIVE_SOLVER = False +# 尝试导入 JSExecutor(需要 Node.js,作为备选) +try: + from .js_executor import JSExecutor + HAS_JS_EXECUTOR = True +except Exception: + HAS_JS_EXECUTOR = False + class SentinelSolver: """协调指纹生成和求解器,生成完整的 Sentinel tokens 支持两种模式: - - use_native=False (默认): 使用 Node.js + sdk.js - - use_native=True: 使用纯 Python 实现 + - use_native=True (默认): 使用纯 Python 实现,无需 Node.js + - use_native=False: 使用 Node.js + sdk.js(需安装 Node.js) """ - def __init__(self, fingerprint: BrowserFingerprint, use_native: bool = False): + def __init__(self, fingerprint: BrowserFingerprint, use_native: bool = True): self.fingerprint = fingerprint self.use_native = use_native diff --git a/tg_bot.py b/tg_bot.py index 03fd381..ebb6405 100644 --- a/tg_bot.py +++ b/tg_bot.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 -"""Telegram Bot for OpenAI Account Registration +"""Telegram Bot for OpenAI Account Registration (Refactored) Features: -- /start - 开始使用机器人 -- /register - 注册单个账号 -- /batch - 批量注册账号 -- /payment - 完整注册(含支付) -- /help - 帮助信息 +- /start - 🏠 主菜单 +- /register - 🆕 快速注册账号 +- /sepa - 💳 SEPA德国注册 +- /settings - ⚙️ 设置 +- /cancel - ❌ 取消当前任务 +- /help - ❓ 帮助 Registration Modes: -1. Basic - 注册 + 邮箱验证 -2. Billing - 注册 + 邮箱验证 + 欧洲账单 URL -3. Payment - 注册 + 邮箱验证 + 账单 URL + SEPA 支付方式 +1. Basic - 注册 + 邮箱验证 + Access Token +2. SEPA - 注册 + 邮箱验证 + SEPA 支付方式 + +Output Modes: +1. Notion - 自动保存到 Notion 数据库 +2. JSON - 仅发送 JSON 文件 """ import os @@ -19,12 +23,13 @@ import sys import json import asyncio import secrets -import string -from typing import Optional +import tempfile +from typing import Optional, List, Dict, Any from datetime import datetime +from concurrent.futures import ThreadPoolExecutor # Telegram Bot -from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, BotCommand from telegram.ext import ( Application, CommandHandler, @@ -36,12 +41,16 @@ from telegram.ext import ( # Local modules from modules.tempmail import TempMailClient +from modules.register import OpenAIRegistrar from flow import CompleteRegistrationFlow from config import TEMPMAIL_CONFIG, DEBUG -# Bot configuration +# ============================================================ +# Configuration +# ============================================================ + BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '') -ALLOWED_USER_IDS = os.getenv('ALLOWED_USER_IDS', '') # Comma-separated user IDs +ALLOWED_USER_IDS = os.getenv('ALLOWED_USER_IDS', '') # Parse allowed users ALLOWED_USERS = set() @@ -51,6 +60,22 @@ if ALLOWED_USER_IDS: except ValueError: print("⚠️ Warning: Invalid ALLOWED_USER_IDS format. Bot will be public!") +# Default settings +DEFAULT_SETTINGS = { + 'region': 'de', # de / us + 'output_format': 'notion', # notion / json + 'concurrent': True, # 是否启用并发 + 'max_concurrent': 5 # 最大并发数 +} + +# Progress bar characters +PROGRESS_FILLED = '▓' +PROGRESS_EMPTY = '░' +PROGRESS_LENGTH = 10 + +# ============================================================ +# Utility Functions +# ============================================================ def generate_random_password(length: int = 16) -> str: """生成随机密码""" @@ -59,13 +84,155 @@ def generate_random_password(length: int = 16) -> str: def check_authorization(user_id: int) -> bool: - """检查用户是否有权限使用 bot""" + """检查用户是否有权限""" if not ALLOWED_USERS: - return True # 如果未配置白名单,允许所有用户 + return True return user_id in ALLOWED_USERS -async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): +def get_user_settings(context: ContextTypes.DEFAULT_TYPE) -> dict: + """获取用户设置""" + if 'settings' not in context.user_data: + context.user_data['settings'] = DEFAULT_SETTINGS.copy() + return context.user_data['settings'] + + +def create_progress_bar(current: int, total: int, concurrent_count: int = 0) -> str: + """创建进度条""" + if total == 0: + return PROGRESS_EMPTY * PROGRESS_LENGTH + + filled = int((current / total) * PROGRESS_LENGTH) + bar = PROGRESS_FILLED * filled + PROGRESS_EMPTY * (PROGRESS_LENGTH - filled) + + progress_text = f"[{bar}] {current}/{total}" + if concurrent_count > 1: + progress_text += f" 🚀×{concurrent_count}" + + return progress_text + + +def get_tempmail_client() -> TempMailClient: + """获取临时邮箱客户端""" + api_base_url = TEMPMAIL_CONFIG.get('api_base_url') + username = TEMPMAIL_CONFIG.get('username') + password_cfg = TEMPMAIL_CONFIG.get('password') + admin_token = TEMPMAIL_CONFIG.get('admin_token') + + if username and password_cfg: + return TempMailClient( + api_base_url=api_base_url, + username=username, + password=password_cfg + ) + elif admin_token: + return TempMailClient( + api_base_url=api_base_url, + admin_token=admin_token + ) + else: + raise ValueError("临时邮箱配置错误") + + +def get_region_info(region: str) -> dict: + """获取地区信息""" + regions = { + 'de': { + "name": "Hans Mueller", + "address_line1": "Hauptstraße 123", + "city": "Berlin", + "postal_code": "10115", + "state": "BE", + "country": "DE" + }, + 'us': { + "name": "John Doe", + "address_line1": "123 Main Street", + "city": "New York", + "postal_code": "10001", + "state": "NY", + "country": "US" + } + } + return regions.get(region, regions['de']) + + +# ============================================================ +# Keyboard Builders +# ============================================================ + +def build_main_menu_keyboard() -> InlineKeyboardMarkup: + """构建主菜单键盘""" + keyboard = [ + [InlineKeyboardButton("🆕 注册账号", callback_data="menu_register")], + [InlineKeyboardButton("💳 SEPA德国", callback_data="menu_sepa")], + [InlineKeyboardButton("⚙️ 高级选项", callback_data="menu_settings")], + [InlineKeyboardButton("❓ 帮助", callback_data="menu_help")] + ] + return InlineKeyboardMarkup(keyboard) + + +def build_count_keyboard(mode: str) -> InlineKeyboardMarkup: + """构建数量选择键盘""" + keyboard = [ + [ + InlineKeyboardButton("1", callback_data=f"{mode}_count_1"), + InlineKeyboardButton("3", callback_data=f"{mode}_count_3"), + InlineKeyboardButton("5", callback_data=f"{mode}_count_5"), + InlineKeyboardButton("10", callback_data=f"{mode}_count_10") + ], + [InlineKeyboardButton("📝 自定义数量", callback_data=f"{mode}_count_custom")], + [InlineKeyboardButton("◀️ 返回主菜单", callback_data="back_main")] + ] + return InlineKeyboardMarkup(keyboard) + + +def build_settings_keyboard(settings: dict) -> InlineKeyboardMarkup: + """构建设置键盘""" + region_de = "✓ " if settings['region'] == 'de' else "" + region_us = "✓ " if settings['region'] == 'us' else "" + output_notion = "✓ " if settings['output_format'] == 'notion' else "" + output_json = "✓ " if settings['output_format'] == 'json' else "" + concurrent_on = "✓ " if settings['concurrent'] else "" + concurrent_off = "✓ " if not settings['concurrent'] else "" + + keyboard = [ + [InlineKeyboardButton(f"🌍 地区", callback_data="settings_label")], + [ + InlineKeyboardButton(f"{region_de}🇩🇪 德国", callback_data="set_region_de"), + InlineKeyboardButton(f"{region_us}🇺🇸 美国", callback_data="set_region_us") + ], + [InlineKeyboardButton(f"📤 输出格式", callback_data="settings_label")], + [ + InlineKeyboardButton(f"{output_notion}Notion", callback_data="set_output_notion"), + InlineKeyboardButton(f"{output_json}JSON文件", callback_data="set_output_json") + ], + [InlineKeyboardButton(f"🚀 并发模式 (max={settings['max_concurrent']})", callback_data="settings_label")], + [ + InlineKeyboardButton(f"{concurrent_on}开启", callback_data="set_concurrent_on"), + InlineKeyboardButton(f"{concurrent_off}关闭", callback_data="set_concurrent_off") + ], + [InlineKeyboardButton("◀️ 返回主菜单", callback_data="back_main")] + ] + return InlineKeyboardMarkup(keyboard) + + +def build_quick_action_keyboard(mode: str) -> InlineKeyboardMarkup: + """构建快捷操作键盘""" + keyboard = [ + [ + InlineKeyboardButton("🔄 再来一个", callback_data=f"quick_{mode}"), + InlineKeyboardButton("🏠 主菜单", callback_data="back_main") + ] + ] + return InlineKeyboardMarkup(keyboard) + + +# ============================================================ +# Command Handlers +# ============================================================ + +async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE): """处理 /start 命令""" user = update.effective_user @@ -76,72 +243,113 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): ) return - keyboard = [ - [InlineKeyboardButton("📝 注册单个账号", callback_data="register_single")], - [InlineKeyboardButton("📦 批量注册", callback_data="register_batch")], - [InlineKeyboardButton("💳 完整注册(含支付)", callback_data="register_payment")], - [InlineKeyboardButton("❓ 帮助", callback_data="help")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) + # 初始化用户设置 + get_user_settings(context) await update.message.reply_text( f"👋 你好 {user.first_name}!\n\n" - "🤖 OpenAI 账号注册机器人\n\n" - "功能:\n" - "• 自动注册 OpenAI 账号\n" - "• 自动验证邮箱\n" - "• 获取 Access Token\n" - "• 生成欧洲账单 URL (可选)\n" - "• 自动添加支付方式 SEPA (可选)\n\n" - "请选择操作:", - reply_markup=reply_markup + "🤖 **OpenAI 账号注册机器人**\n\n" + "请选择操作:", + reply_markup=build_main_menu_keyboard(), + parse_mode='Markdown' ) -async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): +async def cmd_register(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /register 命令 - 快速注册""" + if not check_authorization(update.effective_user.id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + await update.message.reply_text( + "🆕 **快速注册账号**\n\n" + "选择注册数量:", + reply_markup=build_count_keyboard("register"), + parse_mode='Markdown' + ) + + +async def cmd_sepa(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /sepa 命令 - SEPA 注册""" + if not check_authorization(update.effective_user.id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + await update.message.reply_text( + "💳 **SEPA 德国注册**\n\n" + "包含:注册 + 邮箱验证 + SEPA支付方式\n\n" + "选择注册数量:", + reply_markup=build_count_keyboard("sepa"), + parse_mode='Markdown' + ) + + +async def cmd_settings(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /settings 命令""" + if not check_authorization(update.effective_user.id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + settings = get_user_settings(context) + await update.message.reply_text( + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), + parse_mode='Markdown' + ) + + +async def cmd_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /cancel 命令""" + if not check_authorization(update.effective_user.id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + # 设置取消标志 + context.user_data['cancel_flag'] = True + + await update.message.reply_text( + "⚠️ **取消请求已发送**\n\n" + "当前任务将在完成进行中的注册后停止。", + parse_mode='Markdown' + ) + + +async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE): """处理 /help 命令""" help_text = """ 📖 **使用帮助** -**命令列表:** -/start - 开始使用 -/register - 注册单个账号 -/batch <数量> - 批量注册 (例: /batch 5) -/payment - 完整注册(含支付) -/help - 显示此帮助 +**命令列表:** +/start - 🏠 主菜单 +/register - 🆕 快速注册账号 +/sepa - 💳 SEPA德国注册 +/settings - ⚙️ 设置 +/cancel - ❌ 取消当前任务 +/help - ❓ 帮助 -**注册模式:** -1️⃣ **基础注册** - 仅注册账号和验证邮箱 -2️⃣ **账单注册** - 注册 + 生成欧洲账单 URL -3️⃣ **完整注册** - 注册 + 账单 + 自动添加 SEPA 支付 +**注册模式:** +• 🆕 **快速注册** - 注册 + 邮箱验证 + Token +• 💳 **SEPA德国** - 快速注册 + SEPA支付方式 -**注册流程:** -1. 选择注册模式 (单个/批量/完整) -2. 选择是否生成账单 URL -3. 如需支付,选择地区信息(德国/美国/自定义) -4. 等待注册完成 (通常 30-90秒) -5. 接收账号信息 +**输出格式:** +• **Notion** - 自动保存到数据库,显示摘要 +• **JSON** - 仅发送文件,格式如下: +```json +{ + "account": "email", + "password": "xxx", + "accesstoken": "sk-xxx" +} +``` -**账号信息包含:** -• 邮箱地址 -• 密码 -• Access Token -• 账单 URL (如已选择) -• IBAN (如已添加支付) +**并发模式:** +• 开启后批量注册将并行执行(最多5个) +• 大幅提升批量注册速度 -**支付说明:** -• 自动生成德国 IBAN(符合 ISO 7064 标准) -• 使用 SEPA 支付方式 -• 每个账号使用唯一 IBAN -• 支持自定义账单地址信息 - -**注意事项:** -• 账号密码会自动生成 -• 邮箱使用临时邮箱服务 -• 请保存好收到的账号信息 -• 批量注册建议不超过 20 个 - -如有问题,请联系管理员。 +**注意事项:** +• 发送 /cancel 可取消正在进行的任务 +• 账号密码自动生成,请保存好信息 """ if update.message: @@ -150,543 +358,144 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.callback_query.message.reply_text(help_text, parse_mode='Markdown') -async def register_single(update: Update, context: ContextTypes.DEFAULT_TYPE): - """处理单个账号注册""" - user_id = update.effective_user.id +# ============================================================ +# Callback Handler +# ============================================================ - if not check_authorization(user_id): - await update.message.reply_text("❌ 你没有权限使用此机器人。") - return - - # 询问是否需要生成账单 - keyboard = [ - [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_single_with_billing")], - [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_single_no_billing")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) - - await update.message.reply_text( - "🔹 单个账号注册\n\n" - "是否需要生成欧洲账单 URL?", - reply_markup=reply_markup - ) - - -async def batch_register(update: Update, context: ContextTypes.DEFAULT_TYPE): - """处理批量注册命令""" - user_id = update.effective_user.id - - if not check_authorization(user_id): - await update.message.reply_text("❌ 你没有权限使用此机器人。") - return - - # 解析数量 - try: - count = int(context.args[0]) if context.args else 0 - - if count <= 0 or count > 20: - await update.message.reply_text( - "❌ 请提供有效的数量 (1-20)\n\n" - "示例: /batch 5" - ) - return - - # 保存数量到上下文 - context.user_data['batch_count'] = count - - # 询问是否需要生成账单 - keyboard = [ - [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_batch_with_billing")], - [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_batch_no_billing")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) - - await update.message.reply_text( - f"🔹 批量注册 {count} 个账号\n\n" - "是否需要生成欧洲账单 URL?", - reply_markup=reply_markup - ) - - except (IndexError, ValueError): - await update.message.reply_text( - "❌ 请提供有效的数量\n\n" - "示例: /batch 5" - ) - - -async def payment_register(update: Update, context: ContextTypes.DEFAULT_TYPE): - """处理完整注册命令(含支付)""" - user_id = update.effective_user.id - - if not check_authorization(user_id): - await update.message.reply_text("❌ 你没有权限使用此机器人。") - return - - # 询问数量 - keyboard = [ - [InlineKeyboardButton("1️⃣ 单个账号", callback_data="payment_count_1")], - [InlineKeyboardButton("5️⃣ 5个账号", callback_data="payment_count_5")], - [InlineKeyboardButton("🔟 10个账号", callback_data="payment_count_10")], - [InlineKeyboardButton("📝 自定义数量", callback_data="payment_count_custom")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) - - await update.message.reply_text( - "💳 **完整注册(含支付)**\n\n" - "此模式将执行:\n" - "✅ 注册 OpenAI 账号\n" - "✅ 验证邮箱\n" - "✅ 生成欧洲账单 URL\n" - "✅ 自动添加 SEPA 支付方式\n" - "✅ 生成唯一德国 IBAN\n\n" - "请选择注册数量:", - reply_markup=reply_markup, - parse_mode='Markdown' - ) - - -async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): - """处理按钮回调""" +async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理所有回调""" query = update.callback_query await query.answer() user_id = query.from_user.id - if not check_authorization(user_id): await query.message.reply_text("❌ 你没有权限使用此机器人。") return data = query.data + settings = get_user_settings(context) - # Payment registration callbacks - if data == "register_payment": - keyboard = [ - [InlineKeyboardButton("1️⃣ 单个账号", callback_data="payment_count_1")], - [InlineKeyboardButton("5️⃣ 5个账号", callback_data="payment_count_5")], - [InlineKeyboardButton("🔟 10个账号", callback_data="payment_count_10")], - [InlineKeyboardButton("📝 自定义数量", callback_data="payment_count_custom")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) + # ---- Main Menu ---- + if data == "menu_register": await query.message.edit_text( - "💳 **完整注册(含支付)**\n\n" - "此模式将执行:\n" - "✅ 注册 OpenAI 账号\n" - "✅ 验证邮箱\n" - "✅ 生成欧洲账单 URL\n" - "✅ 自动添加 SEPA 支付方式\n" - "✅ 生成唯一德国 IBAN\n\n" - "请选择注册数量:", - reply_markup=reply_markup, + "🆕 **快速注册账号**\n\n" + "选择注册数量:", + reply_markup=build_count_keyboard("register"), parse_mode='Markdown' ) - elif data.startswith("payment_count_"): - # 处理数量选择 - if data == "payment_count_custom": - await query.message.edit_text( - "📝 **自定义数量**\n\n" - "请发送数量 (1-20):\n" - "直接回复一个数字即可", - parse_mode='Markdown' - ) - context.user_data['awaiting_payment_count'] = True - return - - # 提取数量 - count_map = { - "payment_count_1": 1, - "payment_count_5": 5, - "payment_count_10": 10 - } - count = count_map.get(data, 1) - context.user_data['payment_count'] = count - - # 询问地区 - keyboard = [ - [InlineKeyboardButton("🇩🇪 德国地址", callback_data="payment_region_de")], - [InlineKeyboardButton("🇺🇸 美国地址", callback_data="payment_region_us")], - [InlineKeyboardButton("🌍 使用默认", callback_data="payment_region_default")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) + elif data == "menu_sepa": await query.message.edit_text( - f"✅ **注册数量:{count} 个**\n\n" - "请选择账单地址信息:\n\n" - "🇩🇪 德国地址 - 使用德国信息\n" - "🇺🇸 美国地址 - 使用美国信息\n" - "🌍 使用默认 - 使用默认配置", - reply_markup=reply_markup, + "💳 **SEPA 德国注册**\n\n" + "包含:注册 + 邮箱验证 + SEPA支付方式\n\n" + "选择注册数量:", + reply_markup=build_count_keyboard("sepa"), parse_mode='Markdown' ) - elif data.startswith("payment_region_"): - count = context.user_data.get('payment_count', 1) - - # 保存地区选择 - context.user_data['payment_region'] = data - - # 显示地区名称 - region_name = { - "payment_region_de": "🇩🇪 德国", - "payment_region_us": "🇺🇸 美国", - "payment_region_default": "🌍 默认" - }.get(data, "🌍 默认") - - # 询问输出方式 - keyboard = [ - [InlineKeyboardButton("📝 Notion 数据库", callback_data="payment_output_notion")], - [InlineKeyboardButton("📄 JSON 文件", callback_data="payment_output_json")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) + elif data == "menu_settings": await query.message.edit_text( - f"✅ **地址信息:{region_name}**\n\n" - f"请选择输出方式:\n\n" - f"📝 **Notion 数据库** - 自动保存到 Notion\n" - f"📄 **JSON 文件** - 生成 JSON 格式输出\n\n" - f"JSON 格式示例:\n" - f"```json\n" - f'{{\n' - f' "account": "email@example.com",\n' - f' "password": "yourpassword",\n' - f' "token": "eyJ..."\n' - f'}}\n' - f"```", - reply_markup=reply_markup, + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), parse_mode='Markdown' ) - elif data.startswith("payment_output_"): - count = context.user_data.get('payment_count', 1) - region_data = context.user_data.get('payment_region', 'payment_region_default') - - # 保存输出格式选择 - output_format = "json" if data == "payment_output_json" else "notion" - context.user_data['output_format'] = output_format - - # 显示地区和输出方式 - region_name = { - "payment_region_de": "🇩🇪 德国", - "payment_region_us": "🇺🇸 美国", - "payment_region_default": "🌍 默认" - }.get(region_data, "🌍 默认") - - output_name = "📝 Notion 数据库" if output_format == "notion" else "📄 JSON 文件" + elif data == "menu_help": + await cmd_help(update, context) + elif data == "back_main": await query.message.edit_text( - f"⚙️ **配置完成**\n\n" - f"注册数量:{count} 个\n" - f"地址信息:{region_name}\n" - f"输出方式:{output_name}\n\n" - f"即将开始完整注册流程...", + "🤖 **OpenAI 账号注册机器人**\n\n" + "请选择操作:", + reply_markup=build_main_menu_keyboard(), parse_mode='Markdown' ) - # 根据地区设置默认信息 - region_info = { - "payment_region_de": { - "name": "Hans Mueller", - "address_line1": "Hauptstraße 123", - "city": "Berlin", - "postal_code": "10115", - "state": "BE", - "country": "DE" - }, - "payment_region_us": { - "name": "John Doe", - "address_line1": "123 Main Street", - "city": "New York", - "postal_code": "10001", - "state": "NY", - "country": "US" - }, - "payment_region_default": { - "name": "John Doe", - "address_line1": "123 Main Street", - "city": "New York", - "postal_code": "10001", - "state": "NY", - "country": "US" - } - } + # ---- Settings ---- + elif data == "settings_label": + # 忽略标签点击 + pass - payment_info = region_info.get(region_data, region_info["payment_region_default"]) - await perform_registration( - query.message, - count, - generate_billing=True, - add_payment=True, - payment_info=payment_info, - output_format=output_format - ) - - # Original callbacks - elif data == "register_single": - keyboard = [ - [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_single_with_billing")], - [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_single_no_billing")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) + elif data.startswith("set_region_"): + settings['region'] = data.replace("set_region_", "") await query.message.edit_text( - "📝 **单个账号注册**\n\n" - "是否需要生成欧洲账单 URL?\n\n" - "✅ 生成账单 - 包含账单链接\n" - "❌ 不生成 - 仅注册和验证", - reply_markup=reply_markup, + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), parse_mode='Markdown' ) - elif data == "register_batch": + elif data.startswith("set_output_"): + settings['output_format'] = data.replace("set_output_", "") await query.message.edit_text( - "📦 **批量注册**\n\n" - "请使用命令: `/batch <数量>`\n\n" - "示例: `/batch 5`", + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), parse_mode='Markdown' ) - elif data == "help": - await help_command(update, context) + elif data == "set_concurrent_on": + settings['concurrent'] = True + await query.message.edit_text( + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), + parse_mode='Markdown' + ) - elif data.startswith("reg_single_"): - generate_billing = "with_billing" in data - await perform_registration(query.message, 1, generate_billing) + elif data == "set_concurrent_off": + settings['concurrent'] = False + await query.message.edit_text( + "⚙️ **设置**\n\n" + "点击选项进行切换:", + reply_markup=build_settings_keyboard(settings), + parse_mode='Markdown' + ) - elif data.startswith("reg_batch_"): - count = context.user_data.get('batch_count', 0) - if count <= 0: - await query.message.reply_text( - "❌ 请先使用 /batch <数量> 命令" - ) - return + # ---- Count Selection ---- + elif data.endswith("_count_custom"): + # 自定义数量 + mode = data.split("_")[0] # register or sepa + context.user_data['awaiting_custom_count'] = True + context.user_data['custom_count_mode'] = mode + + await query.message.edit_text( + "📝 **自定义数量**\n\n" + "请输入注册数量 (1-20):\n\n" + "直接发送数字即可", + parse_mode='Markdown' + ) - generate_billing = "with_billing" in data - await perform_registration(query.message, count, generate_billing) + elif data.startswith("register_count_") or data.startswith("sepa_count_"): + parts = data.split("_") + mode = parts[0] # register or sepa + count = int(parts[2]) + + # 保存当前模式和数量 + context.user_data['current_mode'] = mode + context.user_data['current_count'] = count + + # 清除取消标志 + context.user_data['cancel_flag'] = False + + # 开始执行注册 + await execute_registration(query.message, context, mode, count) + + # ---- Quick Actions ---- + elif data.startswith("quick_"): + mode = data.replace("quick_", "") + count = context.user_data.get('current_count', 1) + + # 清除取消标志 + context.user_data['cancel_flag'] = False + + await execute_registration(query.message, context, mode, count) -async def perform_registration(message, count: int, generate_billing: bool, add_payment: bool = False, payment_info: dict = None, output_format: str = "notion"): - """执行注册流程(使用 CompleteRegistrationFlow)""" - # 发送开始消息 - status_text = ( - f"🔄 开始注册 {count} 个账号...\n" - f"{'✅ 将生成账单 URL' if generate_billing else '❌ 不生成账单'}\n" - ) - if add_payment: - status_text += "✅ 将添加支付方式\n" - if output_format == "notion": - status_text += "✅ 将保存到 Notion\n" - else: - status_text += "📄 将输出 JSON 格式\n" - status_text += "\n⏳ 请稍候..." - - status_msg = await message.reply_text(status_text) - - # 初始化临时邮箱客户端 - try: - api_base_url = TEMPMAIL_CONFIG.get('api_base_url') - username = TEMPMAIL_CONFIG.get('username') - password_cfg = TEMPMAIL_CONFIG.get('password') - admin_token = TEMPMAIL_CONFIG.get('admin_token') - - if username and password_cfg: - tempmail_client = TempMailClient( - api_base_url=api_base_url, - username=username, - password=password_cfg - ) - elif admin_token: - tempmail_client = TempMailClient( - api_base_url=api_base_url, - admin_token=admin_token - ) - else: - await status_msg.edit_text("❌ 临时邮箱配置错误,请联系管理员") - return - - except Exception as e: - await status_msg.edit_text(f"❌ 初始化失败: {str(e)}") - return - - # 创建完整流程管理器 - try: - flow = CompleteRegistrationFlow(tempmail_client) - if DEBUG: - print(f"✅ CompleteRegistrationFlow 初始化成功") - except Exception as e: - await status_msg.edit_text(f"❌ 流程管理器初始化失败: {str(e)}") - return - - # 注册账号 - success_accounts = [] - failed_accounts = [] - - for i in range(1, count + 1): - try: - # 更新状态 - progress_bar = "█" * i + "░" * (count - i) - await status_msg.edit_text( - f"🔄 **注册进度** [{i}/{count}]\n" - f"{progress_bar}\n\n" - f"✅ 成功: {len(success_accounts)}\n" - f"❌ 失败: {len(failed_accounts)}\n\n" - f"⏳ 正在处理第 {i} 个账号...", - parse_mode='Markdown' - ) - - # 生成密码 - password = generate_random_password() - - # 根据模式执行不同的注册流程 - if add_payment: - # 完整流程:注册 + 账单 + 支付 + Notion/JSON - result = flow.register_with_payment( - password=password, - payment_info=payment_info, - save_to_notion=True, - output_format=output_format - ) - elif generate_billing: - # 账单流程:注册 + 账单 - result = flow.register_with_billing(password=password) - else: - # 基础流程:仅注册 - result = flow.register_basic(password=password) - - if not result.get('success'): - failed_accounts.append({ - 'email': result.get('email', 'N/A'), - 'error': result.get('error', 'Unknown error') - }) - continue - - success_accounts.append(result) - - except Exception as e: - failed_accounts.append({ - 'email': 'N/A', - 'error': str(e) - }) - - # 发送结果摘要 - progress_bar = "█" * count - await status_msg.edit_text( - f"🎉 **注册完成!** [{count}/{count}]\n" - f"{progress_bar}\n\n" - f"✅ 成功: **{len(success_accounts)}**\n" - f"❌ 失败: **{len(failed_accounts)}**\n\n" - f"📨 正在发送账号信息...", - parse_mode='Markdown' - ) - - # 发送每个成功的账号 - for idx, acc in enumerate(success_accounts, 1): - # 判断输出格式 - if output_format == "json": - # JSON 格式输出 - json_output = { - "account": acc.get('email'), - "password": acc.get('password'), - "token": acc.get('access_token', '') - } - - import json as json_module - json_text = json_module.dumps(json_output, indent=2, ensure_ascii=False) - - account_text = ( - f"╔═══════════════════\n" - f"║ 📄 **账号 #{idx} (JSON)**\n" - f"╚═══════════════════\n\n" - f"```json\n{json_text}\n```" - ) - else: - # Notion 格式输出(原来的显示方式) - account_text = ( - f"╔═══════════════════\n" - f"║ 🎯 **账号 #{idx}**\n" - f"╚═══════════════════\n\n" - f"📧 **邮箱**\n" - f"`{acc['email']}`\n\n" - f"🔑 **密码**\n" - f"`{acc['password']}`\n" - ) - - if 'access_token' in acc: - account_text += f"\n🎫 **Access Token**\n`{acc['access_token'][:50]}...`\n" - - if 'checkout_url' in acc: - account_text += f"\n💳 **账单链接**\n[点击打开支付页面]({acc['checkout_url']})\n" - elif 'billing_error' in acc: - account_text += f"\n⚠️ 账单: {acc['billing_error']}\n" - - # 支付信息 - if 'payment_added' in acc and acc['payment_added']: - account_text += f"\n✅ **支付方式**\nSEPA 已添加\n" - if 'iban' in acc: - account_text += f"\n🏦 **IBAN**\n`{acc['iban']}`\n" - elif 'payment_error' in acc: - account_text += f"\n⚠️ 支付: {acc['payment_error']}\n" - - # Notion 保存状态 - if 'notion_saved' in acc and acc['notion_saved']: - account_text += f"\n📝 **Notion**\n✅ 已保存到数据库\n" - elif 'notion_error' in acc: - account_text += f"\n⚠️ Notion: {acc['notion_error']}\n" - - account_text += "\n━━━━━━━━━━━━━━━━━━━" - - await message.reply_text(account_text, parse_mode='Markdown') - - # 如果有失败的账号 - if failed_accounts: - failed_text = "⚠️ **失败列表**\n\n" - for idx, acc in enumerate(failed_accounts, 1): - failed_text += f"`{idx}.` {acc['email']}\n❌ {acc['error']}\n\n" - await message.reply_text(failed_text, parse_mode='Markdown') - - # 如果是 JSON 格式且批量注册(>=2个账号),生成并发送完整的 JSON 文件 - if output_format == "json" and count >= 2 and success_accounts: - import json as json_module - import tempfile - from datetime import datetime - - # 构建完整的 JSON 数据 - json_data = [] - for acc in success_accounts: - json_data.append({ - "account": acc.get('email'), - "password": acc.get('password'), - "token": acc.get('access_token', '') - }) - - # 创建临时文件 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - temp_file = tempfile.NamedTemporaryFile(mode='w', suffix=f'_accounts_{timestamp}.json', delete=False, encoding='utf-8') - json_module.dump(json_data, temp_file, indent=2, ensure_ascii=False) - temp_file.close() - - # 发送文件 - try: - with open(temp_file.name, 'rb') as f: - await message.reply_document( - document=f, - filename=f"openai_accounts_{timestamp}.json", - caption=f"📦 **批量注册完整 JSON 文件**\n\n✅ 包含 {len(success_accounts)} 个成功账号" - ) - - # 删除临时文件 - import os - os.unlink(temp_file.name) - except Exception as e: - await message.reply_text(f"⚠️ 发送 JSON 文件失败: {str(e)}") - - # 更新最终状态消息 - await status_msg.edit_text( - f"✅ **全部完成!**\n\n" - f"📊 **统计**\n" - f"• 总数: {count}\n" - f"• 成功: {len(success_accounts)}\n" - f"• 失败: {len(failed_accounts)}\n\n" - f"{'✅ 账号信息已发送' if success_accounts else '❌ 没有成功的账号'}", - parse_mode='Markdown' - ) - +# ============================================================ +# Text Message Handler +# ============================================================ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE): """处理文本消息(用于自定义数量输入)""" @@ -695,34 +504,31 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE if not check_authorization(user_id): return - # 检查是否在等待支付数量输入 - if context.user_data.get('awaiting_payment_count'): + # 检查是否在等待自定义数量输入 + if context.user_data.get('awaiting_custom_count'): try: count = int(update.message.text.strip()) if count <= 0 or count > 20: await update.message.reply_text( - "❌ 请提供有效的数量 (1-20)\n\n" + "❌ 请输入有效的数量 (1-20)\n\n" "请重新输入数字:" ) return # 清除等待标志 - context.user_data['awaiting_payment_count'] = False - context.user_data['payment_count'] = count - - # 询问地区 - keyboard = [ - [InlineKeyboardButton("🇩🇪 德国地址", callback_data="payment_region_de")], - [InlineKeyboardButton("🇺🇸 美国地址", callback_data="payment_region_us")], - [InlineKeyboardButton("🌍 使用默认", callback_data="payment_region_default")] - ] - reply_markup = InlineKeyboardMarkup(keyboard) - await update.message.reply_text( - f"✅ 将注册 {count} 个完整账号\n\n" - "请选择账单地址信息:", - reply_markup=reply_markup - ) + context.user_data['awaiting_custom_count'] = False + mode = context.user_data.get('custom_count_mode', 'register') + + # 保存当前模式和数量 + context.user_data['current_mode'] = mode + context.user_data['current_count'] = count + + # 清除取消标志 + context.user_data['cancel_flag'] = False + + # 开始执行注册 + await execute_registration(update.message, context, mode, count) except ValueError: await update.message.reply_text( @@ -731,25 +537,319 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE ) +# ============================================================ +# Registration Execution +# ============================================================ + +async def execute_registration(message, context: ContextTypes.DEFAULT_TYPE, mode: str, count: int): + """执行注册流程""" + settings = get_user_settings(context) + add_payment = (mode == "sepa") + output_format = settings['output_format'] + use_concurrent = settings['concurrent'] and count > 1 + max_concurrent = settings['max_concurrent'] + region = settings['region'] + payment_info = get_region_info(region) + + # 发送初始状态 + mode_name = "SEPA德国" if add_payment else "快速注册" + status_msg = await message.reply_text( + f"⏳ 正在注册... {create_progress_bar(0, count)}\n\n" + f"📋 模式: {mode_name}\n" + f"📊 数量: {count}\n" + f"🚀 并发: {'开启' if use_concurrent else '关闭'}", + parse_mode='Markdown' + ) + + # 初始化 + try: + tempmail_client = get_tempmail_client() + except Exception as e: + await status_msg.edit_text(f"❌ 初始化失败: {str(e)}") + return + + success_accounts = [] + failed_accounts = [] + completed = 0 + lock = asyncio.Lock() + + async def update_progress(concurrent_active: int = 0): + """更新进度条""" + nonlocal completed + try: + await status_msg.edit_text( + f"⏳ 正在注册... {create_progress_bar(completed, count, concurrent_active)}\n\n" + f"✅ 成功: {len(success_accounts)}\n" + f"❌ 失败: {len(failed_accounts)}", + parse_mode='Markdown' + ) + except Exception: + pass # 忽略消息编辑错误 + + async def register_one(task_id: int, semaphore: asyncio.Semaphore = None): + """注册单个账号""" + nonlocal completed + + # 检查取消标志 + if context.user_data.get('cancel_flag'): + return None + + if semaphore: + async with semaphore: + return await do_register(task_id) + else: + return await do_register(task_id) + + async def do_register(task_id: int): + """实际执行注册""" + nonlocal completed + + try: + password = generate_random_password() + + # 使用线程池执行同步注册代码 + loop = asyncio.get_event_loop() + with ThreadPoolExecutor() as executor: + if add_payment: + # SEPA 完整流程 + flow = CompleteRegistrationFlow(tempmail_client) + result = await loop.run_in_executor( + executor, + lambda: flow.register_with_payment( + password=password, + payment_info=payment_info, + save_to_notion=(output_format == 'notion'), + output_format=output_format + ) + ) + else: + # 基础注册流程 + flow = CompleteRegistrationFlow(tempmail_client) + result = await loop.run_in_executor( + executor, + lambda: flow.register_basic(password=password) + ) + + async with lock: + completed += 1 + if result.get('success'): + success_accounts.append(result) + else: + failed_accounts.append({ + 'email': result.get('email', 'N/A'), + 'error': result.get('error', 'Unknown error') + }) + + # 更新进度 + await update_progress() + + return result + + except Exception as e: + async with lock: + completed += 1 + failed_accounts.append({ + 'email': 'N/A', + 'error': str(e) + }) + await update_progress() + return None + + # 执行注册 + if use_concurrent and count > 1: + # 并发模式 + semaphore = asyncio.Semaphore(max_concurrent) + tasks = [register_one(i, semaphore) for i in range(1, count + 1)] + await asyncio.gather(*tasks) + else: + # 顺序模式 + for i in range(1, count + 1): + if context.user_data.get('cancel_flag'): + break + await register_one(i) + + # 检查是否被取消 + was_cancelled = context.user_data.get('cancel_flag', False) + context.user_data['cancel_flag'] = False + + # 发送结果 + await send_results( + message, + status_msg, + success_accounts, + failed_accounts, + count, + mode, + output_format, + was_cancelled + ) + + +async def send_results( + message, + status_msg, + success_accounts: List[Dict], + failed_accounts: List[Dict], + count: int, + mode: str, + output_format: str, + was_cancelled: bool = False +): + """发送注册结果""" + + # 更新状态消息 + status_text = "⚠️ **任务已取消**" if was_cancelled else "✅ **注册完成**" + await status_msg.edit_text( + f"{status_text}\n\n" + f"📊 **统计**\n" + f"• 总数: {count}\n" + f"• 成功: {len(success_accounts)}\n" + f"• 失败: {len(failed_accounts)}", + parse_mode='Markdown' + ) + + if not success_accounts: + # 没有成功的账号 + await message.reply_text( + "❌ 没有成功注册的账号\n\n" + "请稍后重试。", + reply_markup=build_quick_action_keyboard(mode) + ) + return + + # 根据输出格式处理 + if output_format == "json": + # JSON 模式:仅发送文件 + await send_json_file(message, success_accounts, mode) + else: + # Notion 模式:显示摘要 + await send_notion_summary(message, success_accounts, mode) + + # 发送失败列表(如果有) + if failed_accounts: + failed_text = "⚠️ **失败列表**\n\n" + for idx, acc in enumerate(failed_accounts[:5], 1): # 最多显示5个 + failed_text += f"{idx}. {acc.get('email', 'N/A')}\n └ {acc['error'][:50]}\n" + if len(failed_accounts) > 5: + failed_text += f"\n... 还有 {len(failed_accounts) - 5} 个失败" + await message.reply_text(failed_text, parse_mode='Markdown') + + +async def send_json_file(message, accounts: List[Dict], mode: str): + """发送 JSON 文件""" + # 构建 JSON 数据 + json_data = [] + for acc in accounts: + json_data.append({ + "account": acc.get('email', ''), + "password": acc.get('password', ''), + "accesstoken": acc.get('access_token', '') + }) + + # 创建临时文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"accounts_{timestamp}.json" + + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f: + json.dump(json_data, f, indent=2, ensure_ascii=False) + temp_path = f.name + + # 发送文件 + try: + with open(temp_path, 'rb') as f: + await message.reply_document( + document=f, + filename=filename, + caption=f"📦 共 {len(accounts)} 个账号", + reply_markup=build_quick_action_keyboard(mode) + ) + finally: + # 删除临时文件 + try: + os.unlink(temp_path) + except Exception: + pass + + +async def send_notion_summary(message, accounts: List[Dict], mode: str): + """发送 Notion 摘要""" + for idx, acc in enumerate(accounts, 1): + account_text = ( + f"╔═══════════════════\n" + f"║ 🎯 **账号 #{idx}**\n" + f"╚═══════════════════\n\n" + f"📧 **邮箱**\n" + f"`{acc.get('email', 'N/A')}`\n\n" + f"🔑 **密码**\n" + f"`{acc.get('password', 'N/A')}`\n" + ) + + if acc.get('access_token'): + token = acc['access_token'] + display_token = token[:50] + "..." if len(token) > 50 else token + account_text += f"\n🎫 **Access Token**\n`{display_token}`\n" + + if acc.get('payment_added'): + account_text += f"\n✅ **SEPA 支付**\n已添加\n" + if acc.get('iban'): + account_text += f"\n🏦 **IBAN**\n`{acc['iban']}`\n" + + if acc.get('notion_saved'): + account_text += f"\n📝 **Notion**\n✅ 已保存\n" + + # 最后一个账号添加快捷操作按钮 + if idx == len(accounts): + await message.reply_text( + account_text, + parse_mode='Markdown', + reply_markup=build_quick_action_keyboard(mode) + ) + else: + await message.reply_text(account_text, parse_mode='Markdown') + + +# ============================================================ +# Error Handler +# ============================================================ + async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """全局错误处理""" print(f"❌ Error: {context.error}") if update and update.effective_message: await update.effective_message.reply_text( - f"❌ 发生错误: {str(context.error)}\n\n" - "请稍后重试或联系管理员。" + "❌ 发生错误,请稍后重试。\n\n" + "如问题持续,请联系管理员。" ) +# ============================================================ +# Bot Setup +# ============================================================ + +async def post_init(application): + """Bot 启动后初始化""" + # 设置命令菜单 + commands = [ + BotCommand("start", "🏠 主菜单"), + BotCommand("register", "🆕 快速注册账号"), + BotCommand("sepa", "💳 SEPA德国注册"), + BotCommand("settings", "⚙️ 设置"), + BotCommand("cancel", "❌ 取消当前任务"), + BotCommand("help", "❓ 帮助"), + ] + await application.bot.set_my_commands(commands) + print("✅ 命令菜单已设置") + + def main(): - """启动 bot""" + """启动 Bot""" if not BOT_TOKEN: print("❌ Error: TELEGRAM_BOT_TOKEN not set!") - print("Please set environment variable: export TELEGRAM_BOT_TOKEN='your_token'") + print("Please set: export TELEGRAM_BOT_TOKEN='your_token'") sys.exit(1) - # 检查配置 + # 检查临时邮箱配置 api_base_url = TEMPMAIL_CONFIG.get('api_base_url') if not api_base_url or 'your.tempmail.domain' in api_base_url: print("❌ Error: TEMPMAIL_CONFIG not configured in config.py") @@ -759,21 +859,22 @@ def main(): print(f"📋 Allowed Users: {len(ALLOWED_USERS) if ALLOWED_USERS else 'ALL (Public)'}") # 创建应用 - application = Application.builder().token(BOT_TOKEN).build() + application = Application.builder().token(BOT_TOKEN).post_init(post_init).build() # 添加处理器 - application.add_handler(CommandHandler("start", start)) - application.add_handler(CommandHandler("help", help_command)) - application.add_handler(CommandHandler("register", register_single)) - application.add_handler(CommandHandler("batch", batch_register)) - application.add_handler(CommandHandler("payment", payment_register)) - application.add_handler(CallbackQueryHandler(button_callback)) + application.add_handler(CommandHandler("start", cmd_start)) + application.add_handler(CommandHandler("register", cmd_register)) + application.add_handler(CommandHandler("sepa", cmd_sepa)) + application.add_handler(CommandHandler("settings", cmd_settings)) + application.add_handler(CommandHandler("cancel", cmd_cancel)) + application.add_handler(CommandHandler("help", cmd_help)) + application.add_handler(CallbackQueryHandler(callback_handler)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text_message)) # 错误处理 application.add_error_handler(error_handler) - # 启动 bot + # 启动 print("✅ Bot started successfully!") print("Press Ctrl+C to stop") diff --git a/tg_bot_old.py b/tg_bot_old.py new file mode 100644 index 0000000..03fd381 --- /dev/null +++ b/tg_bot_old.py @@ -0,0 +1,784 @@ +#!/usr/bin/env python3 +"""Telegram Bot for OpenAI Account Registration + +Features: +- /start - 开始使用机器人 +- /register - 注册单个账号 +- /batch - 批量注册账号 +- /payment - 完整注册(含支付) +- /help - 帮助信息 + +Registration Modes: +1. Basic - 注册 + 邮箱验证 +2. Billing - 注册 + 邮箱验证 + 欧洲账单 URL +3. Payment - 注册 + 邮箱验证 + 账单 URL + SEPA 支付方式 +""" + +import os +import sys +import json +import asyncio +import secrets +import string +from typing import Optional +from datetime import datetime + +# Telegram Bot +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram.ext import ( + Application, + CommandHandler, + CallbackQueryHandler, + ContextTypes, + MessageHandler, + filters +) + +# Local modules +from modules.tempmail import TempMailClient +from flow import CompleteRegistrationFlow +from config import TEMPMAIL_CONFIG, DEBUG + +# Bot configuration +BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '') +ALLOWED_USER_IDS = os.getenv('ALLOWED_USER_IDS', '') # Comma-separated user IDs + +# Parse allowed users +ALLOWED_USERS = set() +if ALLOWED_USER_IDS: + try: + ALLOWED_USERS = set(int(uid.strip()) for uid in ALLOWED_USER_IDS.split(',') if uid.strip()) + except ValueError: + print("⚠️ Warning: Invalid ALLOWED_USER_IDS format. Bot will be public!") + + +def generate_random_password(length: int = 16) -> str: + """生成随机密码""" + chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*' + return ''.join(secrets.choice(chars) for _ in range(length)) + + +def check_authorization(user_id: int) -> bool: + """检查用户是否有权限使用 bot""" + if not ALLOWED_USERS: + return True # 如果未配置白名单,允许所有用户 + return user_id in ALLOWED_USERS + + +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /start 命令""" + user = update.effective_user + + if not check_authorization(user.id): + await update.message.reply_text( + "❌ 你没有权限使用此机器人。\n" + "请联系管理员获取访问权限。" + ) + return + + keyboard = [ + [InlineKeyboardButton("📝 注册单个账号", callback_data="register_single")], + [InlineKeyboardButton("📦 批量注册", callback_data="register_batch")], + [InlineKeyboardButton("💳 完整注册(含支付)", callback_data="register_payment")], + [InlineKeyboardButton("❓ 帮助", callback_data="help")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + f"👋 你好 {user.first_name}!\n\n" + "🤖 OpenAI 账号注册机器人\n\n" + "功能:\n" + "• 自动注册 OpenAI 账号\n" + "• 自动验证邮箱\n" + "• 获取 Access Token\n" + "• 生成欧洲账单 URL (可选)\n" + "• 自动添加支付方式 SEPA (可选)\n\n" + "请选择操作:", + reply_markup=reply_markup + ) + + +async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /help 命令""" + help_text = """ +📖 **使用帮助** + +**命令列表:** +/start - 开始使用 +/register - 注册单个账号 +/batch <数量> - 批量注册 (例: /batch 5) +/payment - 完整注册(含支付) +/help - 显示此帮助 + +**注册模式:** +1️⃣ **基础注册** - 仅注册账号和验证邮箱 +2️⃣ **账单注册** - 注册 + 生成欧洲账单 URL +3️⃣ **完整注册** - 注册 + 账单 + 自动添加 SEPA 支付 + +**注册流程:** +1. 选择注册模式 (单个/批量/完整) +2. 选择是否生成账单 URL +3. 如需支付,选择地区信息(德国/美国/自定义) +4. 等待注册完成 (通常 30-90秒) +5. 接收账号信息 + +**账号信息包含:** +• 邮箱地址 +• 密码 +• Access Token +• 账单 URL (如已选择) +• IBAN (如已添加支付) + +**支付说明:** +• 自动生成德国 IBAN(符合 ISO 7064 标准) +• 使用 SEPA 支付方式 +• 每个账号使用唯一 IBAN +• 支持自定义账单地址信息 + +**注意事项:** +• 账号密码会自动生成 +• 邮箱使用临时邮箱服务 +• 请保存好收到的账号信息 +• 批量注册建议不超过 20 个 + +如有问题,请联系管理员。 + """ + + if update.message: + await update.message.reply_text(help_text, parse_mode='Markdown') + elif update.callback_query: + await update.callback_query.message.reply_text(help_text, parse_mode='Markdown') + + +async def register_single(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理单个账号注册""" + user_id = update.effective_user.id + + if not check_authorization(user_id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + # 询问是否需要生成账单 + keyboard = [ + [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_single_with_billing")], + [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_single_no_billing")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + "🔹 单个账号注册\n\n" + "是否需要生成欧洲账单 URL?", + reply_markup=reply_markup + ) + + +async def batch_register(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理批量注册命令""" + user_id = update.effective_user.id + + if not check_authorization(user_id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + # 解析数量 + try: + count = int(context.args[0]) if context.args else 0 + + if count <= 0 or count > 20: + await update.message.reply_text( + "❌ 请提供有效的数量 (1-20)\n\n" + "示例: /batch 5" + ) + return + + # 保存数量到上下文 + context.user_data['batch_count'] = count + + # 询问是否需要生成账单 + keyboard = [ + [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_batch_with_billing")], + [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_batch_no_billing")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + f"🔹 批量注册 {count} 个账号\n\n" + "是否需要生成欧洲账单 URL?", + reply_markup=reply_markup + ) + + except (IndexError, ValueError): + await update.message.reply_text( + "❌ 请提供有效的数量\n\n" + "示例: /batch 5" + ) + + +async def payment_register(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理完整注册命令(含支付)""" + user_id = update.effective_user.id + + if not check_authorization(user_id): + await update.message.reply_text("❌ 你没有权限使用此机器人。") + return + + # 询问数量 + keyboard = [ + [InlineKeyboardButton("1️⃣ 单个账号", callback_data="payment_count_1")], + [InlineKeyboardButton("5️⃣ 5个账号", callback_data="payment_count_5")], + [InlineKeyboardButton("🔟 10个账号", callback_data="payment_count_10")], + [InlineKeyboardButton("📝 自定义数量", callback_data="payment_count_custom")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + "💳 **完整注册(含支付)**\n\n" + "此模式将执行:\n" + "✅ 注册 OpenAI 账号\n" + "✅ 验证邮箱\n" + "✅ 生成欧洲账单 URL\n" + "✅ 自动添加 SEPA 支付方式\n" + "✅ 生成唯一德国 IBAN\n\n" + "请选择注册数量:", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + + +async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理按钮回调""" + query = update.callback_query + await query.answer() + + user_id = query.from_user.id + + if not check_authorization(user_id): + await query.message.reply_text("❌ 你没有权限使用此机器人。") + return + + data = query.data + + # Payment registration callbacks + if data == "register_payment": + keyboard = [ + [InlineKeyboardButton("1️⃣ 单个账号", callback_data="payment_count_1")], + [InlineKeyboardButton("5️⃣ 5个账号", callback_data="payment_count_5")], + [InlineKeyboardButton("🔟 10个账号", callback_data="payment_count_10")], + [InlineKeyboardButton("📝 自定义数量", callback_data="payment_count_custom")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.message.edit_text( + "💳 **完整注册(含支付)**\n\n" + "此模式将执行:\n" + "✅ 注册 OpenAI 账号\n" + "✅ 验证邮箱\n" + "✅ 生成欧洲账单 URL\n" + "✅ 自动添加 SEPA 支付方式\n" + "✅ 生成唯一德国 IBAN\n\n" + "请选择注册数量:", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + + elif data.startswith("payment_count_"): + # 处理数量选择 + if data == "payment_count_custom": + await query.message.edit_text( + "📝 **自定义数量**\n\n" + "请发送数量 (1-20):\n" + "直接回复一个数字即可", + parse_mode='Markdown' + ) + context.user_data['awaiting_payment_count'] = True + return + + # 提取数量 + count_map = { + "payment_count_1": 1, + "payment_count_5": 5, + "payment_count_10": 10 + } + count = count_map.get(data, 1) + context.user_data['payment_count'] = count + + # 询问地区 + keyboard = [ + [InlineKeyboardButton("🇩🇪 德国地址", callback_data="payment_region_de")], + [InlineKeyboardButton("🇺🇸 美国地址", callback_data="payment_region_us")], + [InlineKeyboardButton("🌍 使用默认", callback_data="payment_region_default")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.message.edit_text( + f"✅ **注册数量:{count} 个**\n\n" + "请选择账单地址信息:\n\n" + "🇩🇪 德国地址 - 使用德国信息\n" + "🇺🇸 美国地址 - 使用美国信息\n" + "🌍 使用默认 - 使用默认配置", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + + elif data.startswith("payment_region_"): + count = context.user_data.get('payment_count', 1) + + # 保存地区选择 + context.user_data['payment_region'] = data + + # 显示地区名称 + region_name = { + "payment_region_de": "🇩🇪 德国", + "payment_region_us": "🇺🇸 美国", + "payment_region_default": "🌍 默认" + }.get(data, "🌍 默认") + + # 询问输出方式 + keyboard = [ + [InlineKeyboardButton("📝 Notion 数据库", callback_data="payment_output_notion")], + [InlineKeyboardButton("📄 JSON 文件", callback_data="payment_output_json")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.message.edit_text( + f"✅ **地址信息:{region_name}**\n\n" + f"请选择输出方式:\n\n" + f"📝 **Notion 数据库** - 自动保存到 Notion\n" + f"📄 **JSON 文件** - 生成 JSON 格式输出\n\n" + f"JSON 格式示例:\n" + f"```json\n" + f'{{\n' + f' "account": "email@example.com",\n' + f' "password": "yourpassword",\n' + f' "token": "eyJ..."\n' + f'}}\n' + f"```", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + + elif data.startswith("payment_output_"): + count = context.user_data.get('payment_count', 1) + region_data = context.user_data.get('payment_region', 'payment_region_default') + + # 保存输出格式选择 + output_format = "json" if data == "payment_output_json" else "notion" + context.user_data['output_format'] = output_format + + # 显示地区和输出方式 + region_name = { + "payment_region_de": "🇩🇪 德国", + "payment_region_us": "🇺🇸 美国", + "payment_region_default": "🌍 默认" + }.get(region_data, "🌍 默认") + + output_name = "📝 Notion 数据库" if output_format == "notion" else "📄 JSON 文件" + + await query.message.edit_text( + f"⚙️ **配置完成**\n\n" + f"注册数量:{count} 个\n" + f"地址信息:{region_name}\n" + f"输出方式:{output_name}\n\n" + f"即将开始完整注册流程...", + parse_mode='Markdown' + ) + + # 根据地区设置默认信息 + region_info = { + "payment_region_de": { + "name": "Hans Mueller", + "address_line1": "Hauptstraße 123", + "city": "Berlin", + "postal_code": "10115", + "state": "BE", + "country": "DE" + }, + "payment_region_us": { + "name": "John Doe", + "address_line1": "123 Main Street", + "city": "New York", + "postal_code": "10001", + "state": "NY", + "country": "US" + }, + "payment_region_default": { + "name": "John Doe", + "address_line1": "123 Main Street", + "city": "New York", + "postal_code": "10001", + "state": "NY", + "country": "US" + } + } + + payment_info = region_info.get(region_data, region_info["payment_region_default"]) + await perform_registration( + query.message, + count, + generate_billing=True, + add_payment=True, + payment_info=payment_info, + output_format=output_format + ) + + # Original callbacks + elif data == "register_single": + keyboard = [ + [InlineKeyboardButton("✅ 生成账单 URL", callback_data="reg_single_with_billing")], + [InlineKeyboardButton("❌ 不生成账单", callback_data="reg_single_no_billing")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.message.edit_text( + "📝 **单个账号注册**\n\n" + "是否需要生成欧洲账单 URL?\n\n" + "✅ 生成账单 - 包含账单链接\n" + "❌ 不生成 - 仅注册和验证", + reply_markup=reply_markup, + parse_mode='Markdown' + ) + + elif data == "register_batch": + await query.message.edit_text( + "📦 **批量注册**\n\n" + "请使用命令: `/batch <数量>`\n\n" + "示例: `/batch 5`", + parse_mode='Markdown' + ) + + elif data == "help": + await help_command(update, context) + + elif data.startswith("reg_single_"): + generate_billing = "with_billing" in data + await perform_registration(query.message, 1, generate_billing) + + elif data.startswith("reg_batch_"): + count = context.user_data.get('batch_count', 0) + if count <= 0: + await query.message.reply_text( + "❌ 请先使用 /batch <数量> 命令" + ) + return + + generate_billing = "with_billing" in data + await perform_registration(query.message, count, generate_billing) + + +async def perform_registration(message, count: int, generate_billing: bool, add_payment: bool = False, payment_info: dict = None, output_format: str = "notion"): + """执行注册流程(使用 CompleteRegistrationFlow)""" + # 发送开始消息 + status_text = ( + f"🔄 开始注册 {count} 个账号...\n" + f"{'✅ 将生成账单 URL' if generate_billing else '❌ 不生成账单'}\n" + ) + if add_payment: + status_text += "✅ 将添加支付方式\n" + if output_format == "notion": + status_text += "✅ 将保存到 Notion\n" + else: + status_text += "📄 将输出 JSON 格式\n" + status_text += "\n⏳ 请稍候..." + + status_msg = await message.reply_text(status_text) + + # 初始化临时邮箱客户端 + try: + api_base_url = TEMPMAIL_CONFIG.get('api_base_url') + username = TEMPMAIL_CONFIG.get('username') + password_cfg = TEMPMAIL_CONFIG.get('password') + admin_token = TEMPMAIL_CONFIG.get('admin_token') + + if username and password_cfg: + tempmail_client = TempMailClient( + api_base_url=api_base_url, + username=username, + password=password_cfg + ) + elif admin_token: + tempmail_client = TempMailClient( + api_base_url=api_base_url, + admin_token=admin_token + ) + else: + await status_msg.edit_text("❌ 临时邮箱配置错误,请联系管理员") + return + + except Exception as e: + await status_msg.edit_text(f"❌ 初始化失败: {str(e)}") + return + + # 创建完整流程管理器 + try: + flow = CompleteRegistrationFlow(tempmail_client) + if DEBUG: + print(f"✅ CompleteRegistrationFlow 初始化成功") + except Exception as e: + await status_msg.edit_text(f"❌ 流程管理器初始化失败: {str(e)}") + return + + # 注册账号 + success_accounts = [] + failed_accounts = [] + + for i in range(1, count + 1): + try: + # 更新状态 + progress_bar = "█" * i + "░" * (count - i) + await status_msg.edit_text( + f"🔄 **注册进度** [{i}/{count}]\n" + f"{progress_bar}\n\n" + f"✅ 成功: {len(success_accounts)}\n" + f"❌ 失败: {len(failed_accounts)}\n\n" + f"⏳ 正在处理第 {i} 个账号...", + parse_mode='Markdown' + ) + + # 生成密码 + password = generate_random_password() + + # 根据模式执行不同的注册流程 + if add_payment: + # 完整流程:注册 + 账单 + 支付 + Notion/JSON + result = flow.register_with_payment( + password=password, + payment_info=payment_info, + save_to_notion=True, + output_format=output_format + ) + elif generate_billing: + # 账单流程:注册 + 账单 + result = flow.register_with_billing(password=password) + else: + # 基础流程:仅注册 + result = flow.register_basic(password=password) + + if not result.get('success'): + failed_accounts.append({ + 'email': result.get('email', 'N/A'), + 'error': result.get('error', 'Unknown error') + }) + continue + + success_accounts.append(result) + + except Exception as e: + failed_accounts.append({ + 'email': 'N/A', + 'error': str(e) + }) + + # 发送结果摘要 + progress_bar = "█" * count + await status_msg.edit_text( + f"🎉 **注册完成!** [{count}/{count}]\n" + f"{progress_bar}\n\n" + f"✅ 成功: **{len(success_accounts)}**\n" + f"❌ 失败: **{len(failed_accounts)}**\n\n" + f"📨 正在发送账号信息...", + parse_mode='Markdown' + ) + + # 发送每个成功的账号 + for idx, acc in enumerate(success_accounts, 1): + # 判断输出格式 + if output_format == "json": + # JSON 格式输出 + json_output = { + "account": acc.get('email'), + "password": acc.get('password'), + "token": acc.get('access_token', '') + } + + import json as json_module + json_text = json_module.dumps(json_output, indent=2, ensure_ascii=False) + + account_text = ( + f"╔═══════════════════\n" + f"║ 📄 **账号 #{idx} (JSON)**\n" + f"╚═══════════════════\n\n" + f"```json\n{json_text}\n```" + ) + else: + # Notion 格式输出(原来的显示方式) + account_text = ( + f"╔═══════════════════\n" + f"║ 🎯 **账号 #{idx}**\n" + f"╚═══════════════════\n\n" + f"📧 **邮箱**\n" + f"`{acc['email']}`\n\n" + f"🔑 **密码**\n" + f"`{acc['password']}`\n" + ) + + if 'access_token' in acc: + account_text += f"\n🎫 **Access Token**\n`{acc['access_token'][:50]}...`\n" + + if 'checkout_url' in acc: + account_text += f"\n💳 **账单链接**\n[点击打开支付页面]({acc['checkout_url']})\n" + elif 'billing_error' in acc: + account_text += f"\n⚠️ 账单: {acc['billing_error']}\n" + + # 支付信息 + if 'payment_added' in acc and acc['payment_added']: + account_text += f"\n✅ **支付方式**\nSEPA 已添加\n" + if 'iban' in acc: + account_text += f"\n🏦 **IBAN**\n`{acc['iban']}`\n" + elif 'payment_error' in acc: + account_text += f"\n⚠️ 支付: {acc['payment_error']}\n" + + # Notion 保存状态 + if 'notion_saved' in acc and acc['notion_saved']: + account_text += f"\n📝 **Notion**\n✅ 已保存到数据库\n" + elif 'notion_error' in acc: + account_text += f"\n⚠️ Notion: {acc['notion_error']}\n" + + account_text += "\n━━━━━━━━━━━━━━━━━━━" + + await message.reply_text(account_text, parse_mode='Markdown') + + # 如果有失败的账号 + if failed_accounts: + failed_text = "⚠️ **失败列表**\n\n" + for idx, acc in enumerate(failed_accounts, 1): + failed_text += f"`{idx}.` {acc['email']}\n❌ {acc['error']}\n\n" + await message.reply_text(failed_text, parse_mode='Markdown') + + # 如果是 JSON 格式且批量注册(>=2个账号),生成并发送完整的 JSON 文件 + if output_format == "json" and count >= 2 and success_accounts: + import json as json_module + import tempfile + from datetime import datetime + + # 构建完整的 JSON 数据 + json_data = [] + for acc in success_accounts: + json_data.append({ + "account": acc.get('email'), + "password": acc.get('password'), + "token": acc.get('access_token', '') + }) + + # 创建临时文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + temp_file = tempfile.NamedTemporaryFile(mode='w', suffix=f'_accounts_{timestamp}.json', delete=False, encoding='utf-8') + json_module.dump(json_data, temp_file, indent=2, ensure_ascii=False) + temp_file.close() + + # 发送文件 + try: + with open(temp_file.name, 'rb') as f: + await message.reply_document( + document=f, + filename=f"openai_accounts_{timestamp}.json", + caption=f"📦 **批量注册完整 JSON 文件**\n\n✅ 包含 {len(success_accounts)} 个成功账号" + ) + + # 删除临时文件 + import os + os.unlink(temp_file.name) + except Exception as e: + await message.reply_text(f"⚠️ 发送 JSON 文件失败: {str(e)}") + + # 更新最终状态消息 + await status_msg.edit_text( + f"✅ **全部完成!**\n\n" + f"📊 **统计**\n" + f"• 总数: {count}\n" + f"• 成功: {len(success_accounts)}\n" + f"• 失败: {len(failed_accounts)}\n\n" + f"{'✅ 账号信息已发送' if success_accounts else '❌ 没有成功的账号'}", + parse_mode='Markdown' + ) + + +async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理文本消息(用于自定义数量输入)""" + user_id = update.effective_user.id + + if not check_authorization(user_id): + return + + # 检查是否在等待支付数量输入 + if context.user_data.get('awaiting_payment_count'): + try: + count = int(update.message.text.strip()) + + if count <= 0 or count > 20: + await update.message.reply_text( + "❌ 请提供有效的数量 (1-20)\n\n" + "请重新输入数字:" + ) + return + + # 清除等待标志 + context.user_data['awaiting_payment_count'] = False + context.user_data['payment_count'] = count + + # 询问地区 + keyboard = [ + [InlineKeyboardButton("🇩🇪 德国地址", callback_data="payment_region_de")], + [InlineKeyboardButton("🇺🇸 美国地址", callback_data="payment_region_us")], + [InlineKeyboardButton("🌍 使用默认", callback_data="payment_region_default")] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await update.message.reply_text( + f"✅ 将注册 {count} 个完整账号\n\n" + "请选择账单地址信息:", + reply_markup=reply_markup + ) + + except ValueError: + await update.message.reply_text( + "❌ 请输入有效的数字 (1-20)\n\n" + "请重新输入:" + ) + + +async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): + """全局错误处理""" + print(f"❌ Error: {context.error}") + + if update and update.effective_message: + await update.effective_message.reply_text( + f"❌ 发生错误: {str(context.error)}\n\n" + "请稍后重试或联系管理员。" + ) + + +def main(): + """启动 bot""" + if not BOT_TOKEN: + print("❌ Error: TELEGRAM_BOT_TOKEN not set!") + print("Please set environment variable: export TELEGRAM_BOT_TOKEN='your_token'") + sys.exit(1) + + # 检查配置 + api_base_url = TEMPMAIL_CONFIG.get('api_base_url') + if not api_base_url or 'your.tempmail.domain' in api_base_url: + print("❌ Error: TEMPMAIL_CONFIG not configured in config.py") + sys.exit(1) + + print("🤖 Starting Telegram Bot...") + print(f"📋 Allowed Users: {len(ALLOWED_USERS) if ALLOWED_USERS else 'ALL (Public)'}") + + # 创建应用 + application = Application.builder().token(BOT_TOKEN).build() + + # 添加处理器 + application.add_handler(CommandHandler("start", start)) + application.add_handler(CommandHandler("help", help_command)) + application.add_handler(CommandHandler("register", register_single)) + application.add_handler(CommandHandler("batch", batch_register)) + application.add_handler(CommandHandler("payment", payment_register)) + application.add_handler(CallbackQueryHandler(button_callback)) + application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text_message)) + + # 错误处理 + application.add_error_handler(error_handler) + + # 启动 bot + print("✅ Bot started successfully!") + print("Press Ctrl+C to stop") + + application.run_polling(allowed_updates=Update.ALL_TYPES) + + +if __name__ == '__main__': + main()