886 lines
29 KiB
Python
886 lines
29 KiB
Python
#!/usr/bin/env python3
|
||
"""Telegram Bot for OpenAI Account Registration (Refactored)
|
||
|
||
Features:
|
||
- /start - 🏠 主菜单
|
||
- /register - 🆕 快速注册账号
|
||
- /sepa - 💳 SEPA德国注册
|
||
- /settings - ⚙️ 设置
|
||
- /cancel - ❌ 取消当前任务
|
||
- /help - ❓ 帮助
|
||
|
||
Registration Modes:
|
||
1. Basic - 注册 + 邮箱验证 + Access Token
|
||
2. SEPA - 注册 + 邮箱验证 + SEPA 支付方式
|
||
|
||
Output Modes:
|
||
1. Notion - 自动保存到 Notion 数据库
|
||
2. JSON - 仅发送 JSON 文件
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import asyncio
|
||
import secrets
|
||
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, BotCommand
|
||
from telegram.ext import (
|
||
Application,
|
||
CommandHandler,
|
||
CallbackQueryHandler,
|
||
ContextTypes,
|
||
MessageHandler,
|
||
filters
|
||
)
|
||
|
||
# Local modules
|
||
from modules.tempmail import TempMailClient
|
||
from modules.register import OpenAIRegistrar
|
||
from flow import CompleteRegistrationFlow
|
||
from config import TEMPMAIL_CONFIG, DEBUG
|
||
|
||
# ============================================================
|
||
# Configuration
|
||
# ============================================================
|
||
|
||
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '')
|
||
ALLOWED_USER_IDS = os.getenv('ALLOWED_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!")
|
||
|
||
# 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:
|
||
"""生成随机密码"""
|
||
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'
|
||
return ''.join(secrets.choice(chars) for _ in range(length))
|
||
|
||
|
||
def check_authorization(user_id: int) -> bool:
|
||
"""检查用户是否有权限"""
|
||
if not ALLOWED_USERS:
|
||
return True
|
||
return user_id in ALLOWED_USERS
|
||
|
||
|
||
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
|
||
|
||
if not check_authorization(user.id):
|
||
await update.message.reply_text(
|
||
"❌ 你没有权限使用此机器人。\n"
|
||
"请联系管理员获取访问权限。"
|
||
)
|
||
return
|
||
|
||
# 初始化用户设置
|
||
get_user_settings(context)
|
||
|
||
await update.message.reply_text(
|
||
f"👋 你好 {user.first_name}!\n\n"
|
||
"🤖 **OpenAI 账号注册机器人**\n\n"
|
||
"请选择操作:",
|
||
reply_markup=build_main_menu_keyboard(),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
|
||
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 - 🆕 快速注册账号
|
||
/sepa - 💳 SEPA德国注册
|
||
/settings - ⚙️ 设置
|
||
/cancel - ❌ 取消当前任务
|
||
/help - ❓ 帮助
|
||
|
||
**注册模式:**
|
||
• 🆕 **快速注册** - 注册 + 邮箱验证 + Token
|
||
• 💳 **SEPA德国** - 快速注册 + SEPA支付方式
|
||
|
||
**输出格式:**
|
||
• **Notion** - 自动保存到数据库,显示摘要
|
||
• **JSON** - 仅发送文件,格式如下:
|
||
```json
|
||
{
|
||
"account": "email",
|
||
"password": "xxx",
|
||
"accesstoken": "sk-xxx"
|
||
}
|
||
```
|
||
|
||
**并发模式:**
|
||
• 开启后批量注册将并行执行(最多5个)
|
||
• 大幅提升批量注册速度
|
||
|
||
**注意事项:**
|
||
• 发送 /cancel 可取消正在进行的任务
|
||
• 账号密码自动生成,请保存好信息
|
||
"""
|
||
|
||
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')
|
||
|
||
|
||
# ============================================================
|
||
# Callback Handler
|
||
# ============================================================
|
||
|
||
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)
|
||
|
||
# ---- Main Menu ----
|
||
if data == "menu_register":
|
||
await query.message.edit_text(
|
||
"🆕 **快速注册账号**\n\n"
|
||
"选择注册数量:",
|
||
reply_markup=build_count_keyboard("register"),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
elif data == "menu_sepa":
|
||
await query.message.edit_text(
|
||
"💳 **SEPA 德国注册**\n\n"
|
||
"包含:注册 + 邮箱验证 + SEPA支付方式\n\n"
|
||
"选择注册数量:",
|
||
reply_markup=build_count_keyboard("sepa"),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
elif data == "menu_settings":
|
||
await query.message.edit_text(
|
||
"⚙️ **设置**\n\n"
|
||
"点击选项进行切换:",
|
||
reply_markup=build_settings_keyboard(settings),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
elif data == "menu_help":
|
||
await cmd_help(update, context)
|
||
|
||
elif data == "back_main":
|
||
await query.message.edit_text(
|
||
"🤖 **OpenAI 账号注册机器人**\n\n"
|
||
"请选择操作:",
|
||
reply_markup=build_main_menu_keyboard(),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
# ---- Settings ----
|
||
elif data == "settings_label":
|
||
# 忽略标签点击
|
||
pass
|
||
|
||
elif data.startswith("set_region_"):
|
||
settings['region'] = data.replace("set_region_", "")
|
||
await query.message.edit_text(
|
||
"⚙️ **设置**\n\n"
|
||
"点击选项进行切换:",
|
||
reply_markup=build_settings_keyboard(settings),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
elif data.startswith("set_output_"):
|
||
settings['output_format'] = data.replace("set_output_", "")
|
||
await query.message.edit_text(
|
||
"⚙️ **设置**\n\n"
|
||
"点击选项进行切换:",
|
||
reply_markup=build_settings_keyboard(settings),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
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 == "set_concurrent_off":
|
||
settings['concurrent'] = False
|
||
await query.message.edit_text(
|
||
"⚙️ **设置**\n\n"
|
||
"点击选项进行切换:",
|
||
reply_markup=build_settings_keyboard(settings),
|
||
parse_mode='Markdown'
|
||
)
|
||
|
||
# ---- 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'
|
||
)
|
||
|
||
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)
|
||
|
||
|
||
# ============================================================
|
||
# Text Message Handler
|
||
# ============================================================
|
||
|
||
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_custom_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_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(
|
||
"❌ 请输入有效的数字 (1-20)\n\n"
|
||
"请重新输入:"
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# 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(
|
||
"❌ 发生错误,请稍后重试。\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"""
|
||
if not BOT_TOKEN:
|
||
print("❌ Error: TELEGRAM_BOT_TOKEN not set!")
|
||
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")
|
||
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).post_init(post_init).build()
|
||
|
||
# 添加处理器
|
||
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)
|
||
|
||
# 启动
|
||
print("✅ Bot started successfully!")
|
||
print("Press Ctrl+C to stop")
|
||
|
||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|