Files
AutoDoneTeam/tg_bot.py
2026-01-26 23:43:31 +08:00

886 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()