diff --git a/auto_gpt_team.py b/auto_gpt_team.py
index 8fe6c3c..68d5d22 100644
--- a/auto_gpt_team.py
+++ b/auto_gpt_team.py
@@ -315,40 +315,103 @@ def get_email_domains():
all_domains = file_domains | config_domains
return sorted(all_domains) if all_domains else []
+def validate_domain_format(domain: str) -> tuple:
+ """验证域名格式是否正确
+
+ Args:
+ domain: 要验证的域名 (带或不带@前缀)
+
+ Returns:
+ tuple: (是否有效, 标准化的域名或错误信息)
+ """
+ domain = domain.strip().lower()
+
+ # 移除开头的引号和尾部特殊字符
+ domain = domain.strip('"\'')
+
+ # 确保以 @ 开头
+ if not domain.startswith("@"):
+ domain = "@" + domain
+
+ # 移除尾部可能的引号或逗号
+ domain = domain.rstrip('",\'')
+
+ # 基本长度检查 (至少 @x.y)
+ if len(domain) < 4:
+ return False, "域名太短"
+
+ # 提取 @ 后面的部分进行验证
+ domain_part = domain[1:] # 去掉 @
+
+ # 检查是否包含至少一个点
+ if "." not in domain_part:
+ return False, "域名缺少点号"
+
+ # 检查点的位置 (不能在开头或结尾)
+ if domain_part.startswith(".") or domain_part.endswith("."):
+ return False, "点号位置不正确"
+
+ # 检查不能有连续的点
+ if ".." in domain_part:
+ return False, "不能有连续的点号"
+
+ # 检查每个部分是否有效
+ parts = domain_part.split(".")
+ for part in parts:
+ if not part:
+ return False, "域名部分为空"
+ # 检查是否只包含有效字符 (字母、数字、连字符)
+ if not all(c.isalnum() or c == "-" for c in part):
+ return False, f"域名包含无效字符"
+ # 不能以连字符开头或结尾
+ if part.startswith("-") or part.endswith("-"):
+ return False, "域名部分不能以连字符开头或结尾"
+
+ # 顶级域名至少2个字符
+ if len(parts[-1]) < 2:
+ return False, "顶级域名太短"
+
+ return True, domain
+
+
def add_email_domains(new_domains: list) -> tuple:
"""添加域名到列表
-
+
Args:
new_domains: 新的域名列表
-
+
Returns:
- tuple: (添加数量, 跳过数量, 当前总数)
+ tuple: (添加数量, 跳过数量, 无效数量, 当前总数)
"""
# 获取当前所有域名(文件 + 配置)
current = set(load_domains_from_file())
config_domains = set(EMAIL_DOMAINS) if EMAIL_DOMAINS else set()
all_existing = current | config_domains
-
+
added = 0
skipped = 0
-
+ invalid = 0
+
for domain in new_domains:
- domain = domain.strip().lower()
- # 确保以 @ 开头
- if not domain.startswith("@"):
- domain = "@" + domain
- if not domain or len(domain) < 4: # 至少 @x.y
+ # 验证域名格式
+ is_valid, result = validate_domain_format(domain)
+
+ if not is_valid:
+ invalid += 1
continue
+
+ domain = result # 使用标准化后的域名
+
if domain in all_existing:
skipped += 1
else:
current.add(domain)
all_existing.add(domain)
added += 1
-
+
# 只保存通过 Bot 添加的域名到文件
save_domains_to_file(sorted(current))
- return added, skipped, len(all_existing)
+ return added, skipped, invalid, len(all_existing)
def remove_email_domain(domain: str) -> bool:
"""删除指定域名 (只能删除通过 Bot 添加的域名)
@@ -371,11 +434,21 @@ def remove_email_domain(domain: str) -> bool:
return True
return False
-def clear_email_domains():
- """清空域名列表"""
+def get_file_domains_count() -> int:
+ """获取txt文件中的域名数量 (不包含config配置的)"""
+ return len(load_domains_from_file())
+
+
+def clear_email_domains() -> int:
+ """清空域名列表 (只清空txt文件,保留config配置)
+
+ Returns:
+ int: 被清空的域名数量
+ """
+ count = len(load_domains_from_file())
if DOMAIN_FILE.exists():
DOMAIN_FILE.unlink()
- return True
+ return count
# ================= 固定配置 =================
TARGET_URL = "https://chatgpt.com"
diff --git a/telegram_bot.py b/telegram_bot.py
index bb4f900..8bc3f70 100644
--- a/telegram_bot.py
+++ b/telegram_bot.py
@@ -149,6 +149,7 @@ class ProvisionerBot:
("clean_teams", self.cmd_clean_teams),
("keys_usage", self.cmd_keys_usage),
("autogptplus", self.cmd_autogptplus),
+ ("s2a", self.cmd_s2a_panel),
]
for cmd, handler in handlers:
self.app.add_handler(CommandHandler(cmd, handler))
@@ -180,6 +181,10 @@ class ProvisionerBot:
self.callback_autogptplus,
pattern="^autogptplus:"
))
+ self.app.add_handler(CallbackQueryHandler(
+ self.callback_s2a_panel,
+ pattern="^s2a:"
+ ))
# 注册自定义数量输入处理器 (GPT Team 注册)
self.app.add_handler(MessageHandler(
@@ -231,6 +236,7 @@ class ProvisionerBot:
async def _set_commands(self):
"""设置 Bot 命令菜单提示"""
commands = [
+ # 基础信息
BotCommand("help", "查看帮助信息"),
BotCommand("list", "查看 team.json 账号列表"),
BotCommand("status", "查看任务处理状态"),
@@ -239,24 +245,46 @@ class ProvisionerBot:
BotCommand("logs", "查看最近日志"),
BotCommand("logs_live", "启用实时日志推送"),
BotCommand("logs_stop", "停止实时日志推送"),
+ # 任务控制
BotCommand("run", "处理指定 Team"),
BotCommand("run_all", "处理所有 Team"),
BotCommand("resume", "继续处理未完成账号"),
BotCommand("stop", "停止当前任务"),
+ # 配置管理
BotCommand("fingerprint", "开启/关闭随机指纹"),
BotCommand("include_owners", "开启/关闭 Owner 入库"),
BotCommand("reload", "重载配置文件"),
- BotCommand("clean", "清理数据文件"),
+ # 清理管理
+ BotCommand("clean", "清理已完成账号"),
+ BotCommand("clean_errors", "清理错误状态账号"),
+ BotCommand("clean_teams", "清理已完成 Team"),
+ # S2A
BotCommand("dashboard", "查看 S2A 仪表盘"),
BotCommand("keys_usage", "查看 API 密钥用量"),
BotCommand("stock", "查看账号库存"),
BotCommand("s2a_config", "配置 S2A 参数"),
BotCommand("import", "导入账号到 team.json"),
+ # GPTMail
BotCommand("gptmail_keys", "查看 GPTMail API Keys"),
BotCommand("gptmail_add", "添加 GPTMail API Key"),
BotCommand("gptmail_del", "删除 GPTMail API Key"),
BotCommand("test_email", "测试邮箱创建"),
+ # IBAN 管理
+ BotCommand("iban_list", "查看 IBAN 列表"),
+ BotCommand("iban_add", "添加 IBAN"),
+ BotCommand("iban_clear", "清空 IBAN 列表"),
+ # 域名管理
+ BotCommand("domain_list", "查看邮箱域名列表"),
+ BotCommand("domain_add", "添加邮箱域名"),
+ BotCommand("domain_del", "删除指定域名"),
+ BotCommand("domain_clear", "清空域名列表"),
+ # GPT Team
+ BotCommand("team_fingerprint", "GPT Team 随机指纹"),
+ BotCommand("team_register", "GPT Team 自动注册"),
+ # AutoGPTPlus
BotCommand("autogptplus", "AutoGPTPlus 管理面板"),
+ # S2A
+ BotCommand("s2a", "S2A 服务管理面板"),
]
try:
await self.app.bot.set_my_commands(commands)
@@ -2526,33 +2554,39 @@ class ProvisionerBot:
"/domain_add @example.com\n"
"/domain_add @a.com,@b.com\n\n"
"支持空格或逗号分隔\n"
- "@ 符号可省略,会自动添加",
+ "@ 符号可省略,会自动添加\n\n"
+ "格式要求:\n"
+ "• 域名需包含至少一个点号\n"
+ "• 只能包含字母、数字、连字符\n"
+ "• 顶级域名至少2个字符",
parse_mode="HTML"
)
return
-
+
try:
from auto_gpt_team import add_email_domains
-
+
# 解析输入 (支持空格、逗号、换行分隔)
raw_input = " ".join(context.args)
# 替换逗号和换行为空格,然后按空格分割
domains = [s.strip() for s in raw_input.replace(",", " ").replace("\n", " ").split() if s.strip()]
-
+
if not domains:
await update.message.reply_text("❌ 未提供有效的域名")
return
-
- added, skipped, total = add_email_domains(domains)
-
- await update.message.reply_text(
- f"✅ 域名导入完成\n\n"
- f"新增: {added}\n"
- f"跳过 (重复): {skipped}\n"
- f"当前总数: {total}",
- parse_mode="HTML"
- )
-
+
+ added, skipped, invalid, total = add_email_domains(domains)
+
+ # 构建响应消息
+ lines = ["✅ 域名导入完成\n"]
+ lines.append(f"新增: {added}")
+ lines.append(f"跳过 (重复): {skipped}")
+ if invalid > 0:
+ lines.append(f"无效 (格式错误): {invalid}")
+ lines.append(f"当前总数: {total}")
+
+ await update.message.reply_text("\n".join(lines), parse_mode="HTML")
+
except ImportError:
await update.message.reply_text("❌ auto_gpt_team 模块未找到")
except Exception as e:
@@ -2597,28 +2631,47 @@ class ProvisionerBot:
@admin_only
async def cmd_domain_clear(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
- """清空域名列表"""
+ """清空域名列表 (只清空txt文件中的域名,保留config配置)"""
# 需要确认
if not context.args or context.args[0].lower() != "confirm":
try:
- from auto_gpt_team import get_email_domains
- count = len(get_email_domains())
+ from auto_gpt_team import get_file_domains_count, EMAIL_DOMAINS
+ file_count = get_file_domains_count()
+ config_count = len(EMAIL_DOMAINS) if EMAIL_DOMAINS else 0
except:
- count = 0
-
+ file_count = 0
+ config_count = 0
+
+ if file_count == 0:
+ await update.message.reply_text(
+ "📧 域名列表\n\n"
+ "txt文件中没有可清空的域名\n"
+ f"config配置中的域名: {config_count} 个 (不会被清空)",
+ parse_mode="HTML"
+ )
+ return
+
await update.message.reply_text(
f"⚠️ 确认清空域名列表?\n\n"
- f"当前共有 {count} 个域名\n\n"
+ f"将清空txt文件中的域名: {file_count} 个\n"
+ f"config配置中的域名: {config_count} 个 (不会被清空)\n\n"
f"确认请发送:\n"
f"/domain_clear confirm",
parse_mode="HTML"
)
return
-
+
try:
- from auto_gpt_team import clear_email_domains
- clear_email_domains()
- await update.message.reply_text("✅ 域名列表已清空", parse_mode="HTML")
+ from auto_gpt_team import clear_email_domains, EMAIL_DOMAINS
+ cleared_count = clear_email_domains()
+ config_count = len(EMAIL_DOMAINS) if EMAIL_DOMAINS else 0
+
+ await update.message.reply_text(
+ f"✅ 域名列表已清空\n\n"
+ f"已清空: {cleared_count} 个域名\n"
+ f"保留 (config配置): {config_count} 个",
+ parse_mode="HTML"
+ )
except ImportError:
await update.message.reply_text("❌ auto_gpt_team 模块未找到")
except Exception as e:
@@ -3012,21 +3065,600 @@ class ProvisionerBot:
self.current_task = None
self.current_team = None
+ # ==================== S2A 管理面板 ====================
+
+ @admin_only
+ async def cmd_s2a_panel(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """S2A 服务管理面板"""
+ if AUTH_PROVIDER != "s2a":
+ await update.message.reply_text(
+ f"⚠️ S2A 面板仅在 S2A 模式下可用\n\n"
+ f"当前模式: {AUTH_PROVIDER}\n\n"
+ f"请在 config.toml 中设置:\n"
+ f"auth_provider = \"s2a\"",
+ parse_mode="HTML"
+ )
+ return
+
+ keyboard = self._get_s2a_main_keyboard()
+ await update.message.reply_text(
+ "📊 S2A 服务管理面板\n\n"
+ "Shared Account 服务配置管理\n\n"
+ "请选择功能:",
+ parse_mode="HTML",
+ reply_markup=keyboard
+ )
+
+ def _get_s2a_main_keyboard(self):
+ """获取 S2A 主菜单键盘"""
+ return InlineKeyboardMarkup([
+ [
+ InlineKeyboardButton("📊 仪表盘", callback_data="s2a:dashboard"),
+ InlineKeyboardButton("📦 库存查询", callback_data="s2a:stock"),
+ ],
+ [
+ InlineKeyboardButton("🔑 密钥用量", callback_data="s2a:keys_usage"),
+ InlineKeyboardButton("⚙️ 服务配置", callback_data="s2a:config"),
+ ],
+ [
+ InlineKeyboardButton("🧹 清理错误", callback_data="s2a:clean_errors"),
+ InlineKeyboardButton("🗑️ 清理Teams", callback_data="s2a:clean_teams"),
+ ],
+ [
+ InlineKeyboardButton("📤 导入账号", callback_data="s2a:import"),
+ InlineKeyboardButton("🔄 测试连接", callback_data="s2a:test"),
+ ],
+ [
+ InlineKeyboardButton("🚀 开始处理", callback_data="s2a:run"),
+ ],
+ ])
+
+ async def callback_s2a_panel(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """处理 S2A 面板回调"""
+ query = update.callback_query
+
+ # 权限检查
+ user_id = update.effective_user.id
+ if user_id not in TELEGRAM_ADMIN_CHAT_IDS:
+ await query.answer("⛔ 无权限", show_alert=True)
+ return
+
+ await query.answer()
+
+ data = query.data.split(":")
+ action = data[1] if len(data) > 1 else ""
+ sub_action = data[2] if len(data) > 2 else ""
+
+ if action == "dashboard":
+ await self._s2a_show_dashboard(query)
+ elif action == "stock":
+ await self._s2a_show_stock(query)
+ elif action == "keys_usage":
+ await self._s2a_show_keys_usage(query, sub_action)
+ elif action == "config":
+ await self._s2a_show_config(query)
+ elif action == "clean_errors":
+ await self._s2a_clean_errors(query, sub_action)
+ elif action == "clean_teams":
+ await self._s2a_clean_teams(query, sub_action)
+ elif action == "import":
+ await self._s2a_show_import(query)
+ elif action == "test":
+ await self._s2a_test_connection(query)
+ elif action == "run":
+ await self._s2a_show_run_options(query)
+ elif action == "run_team":
+ await self._s2a_run_team(query, context, sub_action)
+ elif action == "run_all":
+ await self._s2a_run_all(query, context)
+ elif action == "resume":
+ await self._s2a_resume(query, context)
+ elif action == "back":
+ await query.edit_message_text(
+ "📊 S2A 服务管理面板\n\n"
+ "Shared Account 服务配置管理\n\n"
+ "请选择功能:",
+ parse_mode="HTML",
+ reply_markup=self._get_s2a_main_keyboard()
+ )
+
+ async def _s2a_show_dashboard(self, query):
+ """显示 S2A 仪表盘"""
+ await query.edit_message_text("⏳ 正在获取仪表盘数据...")
+
+ try:
+ stats = s2a_get_dashboard_stats()
+ if stats:
+ text = format_dashboard_stats(stats)
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(text, parse_mode="HTML", reply_markup=reply_markup)
+ else:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "❌ 获取仪表盘数据失败\n请检查 S2A 配置和 API 连接",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(f"❌ 错误: {e}", reply_markup=reply_markup)
+
+ async def _s2a_show_stock(self, query):
+ """显示库存信息"""
+ await query.edit_message_text("⏳ 正在获取库存数据...")
+
+ try:
+ stats = s2a_get_dashboard_stats()
+ if not stats:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text("❌ 获取库存信息失败", reply_markup=reply_markup)
+ return
+
+ text = self._format_stock_message(stats)
+ keyboard = [
+ [InlineKeyboardButton("🔄 刷新", callback_data="s2a:stock")],
+ [InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(text, parse_mode="HTML", reply_markup=reply_markup)
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(f"❌ 错误: {e}", reply_markup=reply_markup)
+
+ async def _s2a_show_keys_usage(self, query, period: str = ""):
+ """显示密钥用量"""
+ if not period:
+ # 显示时间选择菜单
+ keyboard = [
+ [
+ InlineKeyboardButton("今日", callback_data="s2a:keys_usage:today"),
+ InlineKeyboardButton("本周", callback_data="s2a:keys_usage:week"),
+ ],
+ [
+ InlineKeyboardButton("本月", callback_data="s2a:keys_usage:month"),
+ InlineKeyboardButton("全部", callback_data="s2a:keys_usage:all"),
+ ],
+ [InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "🔑 API 密钥用量查询\n\n选择时间范围:",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ return
+
+ await query.edit_message_text("⏳ 正在获取用量数据...")
+
+ try:
+ keys_data = s2a_get_keys_with_usage(period)
+ if keys_data:
+ text = format_keys_usage(keys_data, period)
+ keyboard = [
+ [InlineKeyboardButton("🔄 刷新", callback_data=f"s2a:keys_usage:{period}")],
+ [InlineKeyboardButton("◀️ 返回时间选择", callback_data="s2a:keys_usage")],
+ [InlineKeyboardButton("◀️ 返回主菜单", callback_data="s2a:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(text, parse_mode="HTML", reply_markup=reply_markup)
+ else:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:keys_usage")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text("❌ 获取用量数据失败", reply_markup=reply_markup)
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:keys_usage")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(f"❌ 错误: {e}", reply_markup=reply_markup)
+
+ async def _s2a_show_config(self, query):
+ """显示 S2A 配置"""
+ # 脱敏显示 API Key
+ key_display = "未配置"
+ if S2A_ADMIN_KEY:
+ if len(S2A_ADMIN_KEY) > 10:
+ key_display = f"{S2A_ADMIN_KEY[:4]}...{S2A_ADMIN_KEY[-4:]}"
+ else:
+ key_display = S2A_ADMIN_KEY[:4] + "..."
+
+ groups_display = ", ".join(S2A_GROUP_NAMES) if S2A_GROUP_NAMES else "默认分组"
+ group_ids_display = ", ".join(str(x) for x in S2A_GROUP_IDS) if S2A_GROUP_IDS else "无"
+
+ lines = [
+ "⚙️ S2A 服务配置",
+ "",
+ f"API 地址: {S2A_API_BASE or '未配置'}",
+ f"CPA 地址: {CPA_API_BASE or '未配置'}",
+ f"CRS 地址: {CRS_API_BASE or '未配置'}",
+ f"Admin Key: {key_display}",
+ "",
+ f"并发数: {S2A_CONCURRENCY}",
+ f"优先级: {S2A_PRIORITY}",
+ f"分组名称: {groups_display}",
+ f"分组 ID: {group_ids_display}",
+ "",
+ "💡 使用命令修改配置:",
+ "/s2a_config concurrency 10",
+ "/s2a_config priority 50",
+ ]
+
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "\n".join(lines),
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_clean_errors(self, query, sub_action: str = ""):
+ """清理错误账号"""
+ if sub_action == "confirm":
+ await query.edit_message_text("⏳ 正在清理错误账号...")
+
+ try:
+ # 获取错误账号
+ error_accounts = s2a_get_error_accounts()
+ if not error_accounts:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ 没有需要清理的错误账号",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 批量删除
+ deleted, failed = s2a_batch_delete_error_accounts(error_accounts)
+
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"✅ 清理完成\n\n"
+ f"已删除: {deleted} 个\n"
+ f"失败: {failed} 个",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(f"❌ 清理失败: {e}", reply_markup=reply_markup)
+ return
+
+ # 显示确认菜单
+ try:
+ error_accounts = s2a_get_error_accounts()
+ count = len(error_accounts) if error_accounts else 0
+ except:
+ count = 0
+
+ if count == 0:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ 没有错误状态的账号需要清理",
+ reply_markup=reply_markup
+ )
+ return
+
+ keyboard = [
+ [
+ InlineKeyboardButton("✅ 确认清理", callback_data="s2a:clean_errors:confirm"),
+ InlineKeyboardButton("❌ 取消", callback_data="s2a:back"),
+ ],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ f"🧹 清理错误账号\n\n"
+ f"发现 {count} 个错误状态账号\n\n"
+ f"确认要清理这些账号吗?",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_clean_teams(self, query, sub_action: str = ""):
+ """清理已完成 Teams"""
+ if sub_action == "confirm":
+ await query.edit_message_text("⏳ 正在清理已完成 Teams...")
+
+ try:
+ completed_teams = get_completed_teams()
+ if not completed_teams:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ 没有需要清理的已完成 Team",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 批量删除
+ removed_count = batch_remove_completed_teams()
+
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"✅ 清理完成\n\n"
+ f"已清理: {removed_count} 个 Team",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(f"❌ 清理失败: {e}", reply_markup=reply_markup)
+ return
+
+ # 显示确认菜单
+ try:
+ completed_teams = get_completed_teams()
+ count = len(completed_teams) if completed_teams else 0
+ except:
+ count = 0
+
+ if count == 0:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ 没有已完成的 Team 需要清理",
+ reply_markup=reply_markup
+ )
+ return
+
+ keyboard = [
+ [
+ InlineKeyboardButton("✅ 确认清理", callback_data="s2a:clean_teams:confirm"),
+ InlineKeyboardButton("❌ 取消", callback_data="s2a:back"),
+ ],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ f"🗑️ 清理已完成 Teams\n\n"
+ f"发现 {count} 个已完成的 Team\n\n"
+ f"确认要从 tracker 中清理这些记录吗?",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_show_import(self, query):
+ """显示导入说明"""
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "📤 导入账号到 team.json\n\n"
+ "方式一: 使用命令\n"
+ "/import <json内容>\n\n"
+ "方式二: 发送 JSON 文件\n"
+ "直接发送 .json 文件即可自动导入\n\n"
+ "JSON 格式:\n"
+ "[{\"email\":\"...\",\"password\":\"...\"}]",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_test_connection(self, query):
+ """测试 S2A API 连接"""
+ await query.edit_message_text("⏳ 正在测试 API 连接...")
+
+ import requests
+
+ results = []
+ apis = [
+ ("S2A", S2A_API_BASE),
+ ("CPA", CPA_API_BASE),
+ ("CRS", CRS_API_BASE),
+ ]
+
+ for name, base_url in apis:
+ if not base_url:
+ results.append(f" {name}: ⚠️ 未配置")
+ continue
+
+ try:
+ start = time.time()
+ resp = requests.get(f"{base_url}/health", timeout=5)
+ elapsed = (time.time() - start) * 1000
+
+ if resp.status_code == 200:
+ results.append(f" {name}: ✅ 正常 ({elapsed:.0f}ms)")
+ else:
+ results.append(f" {name}: ⚠️ 状态 {resp.status_code}")
+ except requests.exceptions.ConnectionError:
+ results.append(f" {name}: ❌ 连接失败")
+ except requests.exceptions.Timeout:
+ results.append(f" {name}: ❌ 超时")
+ except Exception as e:
+ results.append(f" {name}: ❌ {str(e)[:20]}")
+
+ keyboard = [
+ [InlineKeyboardButton("🔄 重新测试", callback_data="s2a:test")],
+ [InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "🔄 API 连接测试\n\n" +
+ "\n".join(results),
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_show_run_options(self, query):
+ """显示运行选项"""
+ # 获取 Team 列表
+ team_count = len(TEAMS) if TEAMS else 0
+
+ if team_count == 0:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "⚠️ 没有可处理的 Team\n\n"
+ "请先在 team.json 中添加账号",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 显示前几个 Team 的快捷按钮
+ team_buttons = []
+ for i, team in enumerate(TEAMS[:6]):
+ name = team.get("name", f"Team{i}")
+ team_buttons.append(
+ InlineKeyboardButton(f"{i}: {name[:8]}", callback_data=f"s2a:run_team:{i}")
+ )
+
+ # 每行2个按钮
+ keyboard = []
+ for i in range(0, len(team_buttons), 2):
+ row = team_buttons[i:i+2]
+ keyboard.append(row)
+
+ keyboard.append([
+ InlineKeyboardButton("🚀 处理全部", callback_data="s2a:run_all"),
+ InlineKeyboardButton("🔄 继续未完成", callback_data="s2a:resume"),
+ ])
+ keyboard.append([InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")])
+
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ f"🚀 开始处理任务\n\n"
+ f"共有 {team_count} 个 Team\n\n"
+ f"选择要处理的 Team:",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _s2a_run_team(self, query, context, team_idx_str: str):
+ """运行指定 Team"""
+ try:
+ team_idx = int(team_idx_str)
+ if team_idx < 0 or team_idx >= len(TEAMS):
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:run")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 无效的 Team 序号: {team_idx}",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 检查是否有任务在运行
+ if self.current_task and not self.current_task.done():
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:run")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"⚠️ 当前有任务在运行: {self.current_team}\n\n"
+ f"请先使用 /stop 停止当前任务",
+ reply_markup=reply_markup
+ )
+ return
+
+ team = TEAMS[team_idx]
+ team_name = team.get("name", f"Team{team_idx}")
+
+ await query.edit_message_text(f"🚀 开始处理 Team: {team_name}\n\n请查看日志了解进度")
+
+ # 启动任务
+ self.current_team = team_name
+ self.current_task = asyncio.create_task(
+ self._run_single_team_task(team_idx, query.message.chat_id)
+ )
+
+ except ValueError:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:run")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text("❌ 无效的参数", reply_markup=reply_markup)
+
+ async def _s2a_run_all(self, query, context):
+ """运行所有 Team"""
+ if self.current_task and not self.current_task.done():
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:run")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"⚠️ 当前有任务在运行: {self.current_team}\n\n"
+ f"请先使用 /stop 停止当前任务",
+ reply_markup=reply_markup
+ )
+ return
+
+ if not TEAMS:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text("⚠️ 没有可处理的 Team", reply_markup=reply_markup)
+ return
+
+ await query.edit_message_text(f"🚀 开始处理所有 {len(TEAMS)} 个 Team\n\n请查看日志了解进度")
+
+ self.current_team = "全部"
+ self.current_task = asyncio.create_task(
+ self._run_all_teams_task(query.message.chat_id)
+ )
+
+ async def _s2a_resume(self, query, context):
+ """继续处理未完成账号"""
+ if self.current_task and not self.current_task.done():
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:run")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"⚠️ 当前有任务在运行: {self.current_team}\n\n"
+ f"请先使用 /stop 停止当前任务",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 检查未完成账号
+ incomplete = get_all_incomplete_accounts()
+ if not incomplete:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="s2a:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ 没有未完成的账号\n\n所有账号都已处理完成",
+ reply_markup=reply_markup
+ )
+ return
+
+ await query.edit_message_text(f"🔄 继续处理 {len(incomplete)} 个未完成账号\n\n请查看日志了解进度")
+
+ self.current_team = "恢复"
+ self.current_task = asyncio.create_task(
+ self._run_resume_task(query.message.chat_id)
+ )
+
+ # ==================== AutoGPTPlus 管理面板 ====================
+
@admin_only
async def cmd_autogptplus(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""AutoGPTPlus 配置管理 - 交互式菜单"""
keyboard = [
[
InlineKeyboardButton("📋 查看配置", callback_data="autogptplus:config"),
- InlineKeyboardButton("� 设置 Token", callback_data="autogptplus:set_token"),
+ InlineKeyboardButton("🔑 设置 Token", callback_data="autogptplus:set_token"),
],
[
- InlineKeyboardButton("� 测试邮件", callback_data="autogptplus:test_email"),
+ InlineKeyboardButton("📧 域名管理", callback_data="autogptplus:domains"),
+ InlineKeyboardButton("💳 IBAN 管理", callback_data="autogptplus:ibans"),
+ ],
+ [
+ InlineKeyboardButton("🎭 随机指纹", callback_data="autogptplus:fingerprint"),
+ InlineKeyboardButton("📊 统计信息", callback_data="autogptplus:stats"),
+ ],
+ [
+ InlineKeyboardButton("📧 测试邮件", callback_data="autogptplus:test_email"),
InlineKeyboardButton("🔄 测试 API", callback_data="autogptplus:test_api"),
],
+ [
+ InlineKeyboardButton("🚀 开始注册", callback_data="autogptplus:register"),
+ ],
]
reply_markup = InlineKeyboardMarkup(keyboard)
-
+
await update.message.reply_text(
"🤖 AutoGPTPlus 管理面板\n\n"
"ChatGPT 订阅自动化配置管理\n\n"
@@ -3035,21 +3667,46 @@ class ProvisionerBot:
reply_markup=reply_markup
)
+ def _get_autogptplus_main_keyboard(self):
+ """获取 AutoGPTPlus 主菜单键盘"""
+ return InlineKeyboardMarkup([
+ [
+ InlineKeyboardButton("📋 查看配置", callback_data="autogptplus:config"),
+ InlineKeyboardButton("🔑 设置 Token", callback_data="autogptplus:set_token"),
+ ],
+ [
+ InlineKeyboardButton("📧 域名管理", callback_data="autogptplus:domains"),
+ InlineKeyboardButton("💳 IBAN 管理", callback_data="autogptplus:ibans"),
+ ],
+ [
+ InlineKeyboardButton("🎭 随机指纹", callback_data="autogptplus:fingerprint"),
+ InlineKeyboardButton("📊 统计信息", callback_data="autogptplus:stats"),
+ ],
+ [
+ InlineKeyboardButton("📧 测试邮件", callback_data="autogptplus:test_email"),
+ InlineKeyboardButton("🔄 测试 API", callback_data="autogptplus:test_api"),
+ ],
+ [
+ InlineKeyboardButton("🚀 开始注册", callback_data="autogptplus:register"),
+ ],
+ ])
+
async def callback_autogptplus(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""处理 AutoGPTPlus 回调"""
query = update.callback_query
-
+
# 权限检查
user_id = update.effective_user.id
if user_id not in TELEGRAM_ADMIN_CHAT_IDS:
await query.answer("⛔ 无权限", show_alert=True)
return
-
+
await query.answer()
-
+
data = query.data.split(":")
action = data[1] if len(data) > 1 else ""
-
+ sub_action = data[2] if len(data) > 2 else ""
+
if action == "config":
await self._show_autogptplus_config(query)
elif action == "set_token":
@@ -3058,26 +3715,24 @@ class ProvisionerBot:
await self._test_autogptplus_email(query)
elif action == "test_api":
await self._test_autogptplus_api(query)
+ elif action == "domains":
+ await self._show_autogptplus_domains(query, sub_action)
+ elif action == "ibans":
+ await self._show_autogptplus_ibans(query, sub_action)
+ elif action == "fingerprint":
+ await self._toggle_autogptplus_fingerprint(query)
+ elif action == "stats":
+ await self._show_autogptplus_stats(query)
+ elif action == "register":
+ await self._start_autogptplus_register(query, context)
elif action == "back":
# 返回主菜单
- keyboard = [
- [
- InlineKeyboardButton("📋 查看配置", callback_data="autogptplus:config"),
- InlineKeyboardButton("� 设置 Token", callback_data="autogptplus:set_token"),
- ],
- [
- InlineKeyboardButton("📧 测试邮件", callback_data="autogptplus:test_email"),
- InlineKeyboardButton("🔄 测试 API", callback_data="autogptplus:test_api"),
- ],
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
await query.edit_message_text(
"🤖 AutoGPTPlus 管理面板\n\n"
"ChatGPT 订阅自动化配置管理\n\n"
"请选择功能:",
parse_mode="HTML",
- reply_markup=reply_markup
+ reply_markup=self._get_autogptplus_main_keyboard()
)
async def _prompt_autogptplus_token(self, query, context: ContextTypes.DEFAULT_TYPE):
@@ -3423,6 +4078,339 @@ class ProvisionerBot:
reply_markup=reply_markup
)
+ async def _show_autogptplus_domains(self, query, sub_action: str = ""):
+ """显示/管理域名"""
+ try:
+ from auto_gpt_team import (
+ get_email_domains, get_file_domains_count, EMAIL_DOMAINS,
+ clear_email_domains
+ )
+
+ if sub_action == "clear":
+ # 清空域名
+ cleared = clear_email_domains()
+ config_count = len(EMAIL_DOMAINS) if EMAIL_DOMAINS else 0
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:domains")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"✅ 域名已清空\n\n"
+ f"已清空: {cleared} 个\n"
+ f"保留 (config): {config_count} 个",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 显示域名列表
+ domains = get_email_domains()
+ file_count = get_file_domains_count()
+ config_count = len(EMAIL_DOMAINS) if EMAIL_DOMAINS else 0
+
+ lines = ["📧 邮箱域名管理\n"]
+ lines.append(f"总计: {len(domains)} 个")
+ lines.append(f" • txt文件: {file_count} 个")
+ lines.append(f" • config配置: {config_count} 个\n")
+
+ if domains:
+ lines.append("域名列表:")
+ for i, domain in enumerate(domains[:15], 1):
+ lines.append(f" {i}. {domain}")
+ if len(domains) > 15:
+ lines.append(f" ... 还有 {len(domains) - 15} 个")
+
+ keyboard = [
+ [
+ InlineKeyboardButton("🗑️ 清空txt域名", callback_data="autogptplus:domains:clear"),
+ ],
+ [InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "\n".join(lines),
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ except ImportError:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "❌ 模块未找到",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 操作失败\n\n{e}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _show_autogptplus_ibans(self, query, sub_action: str = ""):
+ """显示/管理 IBAN"""
+ try:
+ from auto_gpt_team import get_sepa_ibans, load_ibans_from_file, SEPA_IBANS, clear_sepa_ibans
+
+ if sub_action == "clear":
+ # 清空 IBAN
+ clear_sepa_ibans()
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:ibans")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "✅ IBAN 列表已清空",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 显示 IBAN 列表
+ ibans = get_sepa_ibans()
+ file_ibans = load_ibans_from_file()
+ config_count = len(SEPA_IBANS) if SEPA_IBANS else 0
+
+ lines = ["💳 IBAN 管理\n"]
+ lines.append(f"总计: {len(ibans)} 个")
+ lines.append(f" • txt文件: {len(file_ibans)} 个")
+ lines.append(f" • config配置: {config_count} 个\n")
+
+ if ibans:
+ lines.append("IBAN 列表:")
+ for i, iban in enumerate(ibans[:10], 1):
+ # 脱敏显示
+ masked = f"{iban[:8]}...{iban[-4:]}" if len(iban) > 12 else iban
+ lines.append(f" {i}. {masked}")
+ if len(ibans) > 10:
+ lines.append(f" ... 还有 {len(ibans) - 10} 个")
+
+ keyboard = [
+ [
+ InlineKeyboardButton("🗑️ 清空IBAN", callback_data="autogptplus:ibans:clear"),
+ ],
+ [InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "\n".join(lines),
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ except ImportError:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "❌ 模块未找到",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 操作失败\n\n{e}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _toggle_autogptplus_fingerprint(self, query):
+ """切换随机指纹"""
+ import tomli_w
+
+ try:
+ # 读取当前配置
+ with open(CONFIG_FILE, "rb") as f:
+ import tomllib
+ config = tomllib.load(f)
+
+ # 确保 autogptplus section 存在
+ if "autogptplus" not in config:
+ config["autogptplus"] = {}
+
+ # 获取当前状态并切换
+ current = config.get("autogptplus", {}).get("random_fingerprint", True)
+ new_value = not current
+
+ # 更新配置
+ config["autogptplus"]["random_fingerprint"] = new_value
+
+ # 写回文件
+ with open(CONFIG_FILE, "wb") as f:
+ tomli_w.dump(config, f)
+
+ # 重新加载模块
+ import importlib
+ import auto_gpt_team
+ importlib.reload(auto_gpt_team)
+
+ status = "✅ 已开启" if new_value else "❌ 已关闭"
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ f"🎭 随机指纹设置\n\n"
+ f"状态: {status}\n\n"
+ f"{'每次注册将使用随机浏览器指纹' if new_value else '将使用固定浏览器指纹'}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 设置失败\n\n{e}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _show_autogptplus_stats(self, query):
+ """显示统计信息"""
+ try:
+ from auto_gpt_team import get_email_domains, get_sepa_ibans, RANDOM_FINGERPRINT
+ import json
+ from pathlib import Path
+
+ # 读取账号文件统计
+ accounts_file = Path("accounts.json")
+ accounts_count = 0
+ if accounts_file.exists():
+ try:
+ with open(accounts_file, "r", encoding="utf-8") as f:
+ accounts = json.load(f)
+ accounts_count = len(accounts)
+ except:
+ pass
+
+ domains = get_email_domains()
+ ibans = get_sepa_ibans()
+
+ lines = ["📊 AutoGPTPlus 统计信息\n"]
+
+ # 资源统计
+ lines.append("📦 可用资源:")
+ lines.append(f" • 邮箱域名: {len(domains)} 个")
+ lines.append(f" • IBAN: {len(ibans)} 个")
+ lines.append(f" • 随机指纹: {'开启' if RANDOM_FINGERPRINT else '关闭'}")
+ lines.append("")
+
+ # 账号统计
+ lines.append("👥 已注册账号:")
+ lines.append(f" • 总计: {accounts_count} 个")
+
+ # 配置状态
+ lines.append("")
+ lines.append("⚙️ 配置状态:")
+ if len(domains) > 0 and len(ibans) > 0:
+ lines.append(" ✅ 已就绪,可以开始注册")
+ else:
+ if len(domains) == 0:
+ lines.append(" ⚠️ 缺少邮箱域名")
+ if len(ibans) == 0:
+ lines.append(" ⚠️ 缺少 IBAN")
+
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "\n".join(lines),
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ except ImportError:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "❌ 模块未找到",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 获取统计失败\n\n{e}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ async def _start_autogptplus_register(self, query, context):
+ """快速开始注册 (跳转到 team_register)"""
+ try:
+ from auto_gpt_team import get_email_domains, get_sepa_ibans, MAIL_API_TOKEN, MAIL_API_BASE
+
+ # 检查配置
+ domains = get_email_domains()
+ ibans = get_sepa_ibans()
+
+ missing = []
+ if not MAIL_API_TOKEN:
+ missing.append("mail_api_token")
+ if not MAIL_API_BASE:
+ missing.append("mail_api_base")
+ if not domains:
+ missing.append("邮箱域名")
+ if not ibans:
+ missing.append("IBAN")
+
+ if missing:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "⚠️ 配置不完整\n\n"
+ "缺少以下配置:\n" +
+ "\n".join(f" • {m}" for m in missing) +
+ "\n\n请先完成配置后再开始注册",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ return
+
+ # 显示注册选项
+ keyboard = [
+ [
+ InlineKeyboardButton("1️⃣ 注册1个", callback_data="team_reg:1"),
+ InlineKeyboardButton("3️⃣ 注册3个", callback_data="team_reg:3"),
+ ],
+ [
+ InlineKeyboardButton("5️⃣ 注册5个", callback_data="team_reg:5"),
+ InlineKeyboardButton("🔢 自定义", callback_data="team_reg:custom"),
+ ],
+ [InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")],
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ await query.edit_message_text(
+ "🚀 开始 ChatGPT Team 注册\n\n"
+ f"可用资源:\n"
+ f" • 邮箱域名: {len(domains)} 个\n"
+ f" • IBAN: {len(ibans)} 个\n\n"
+ "选择注册数量:",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
+ except ImportError:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ "❌ 模块未找到",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+ except Exception as e:
+ keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+ await query.edit_message_text(
+ f"❌ 操作失败\n\n{e}",
+ parse_mode="HTML",
+ reply_markup=reply_markup
+ )
+
@admin_only
async def handle_team_custom_count(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""处理文本输入 (GPT Team 自定义数量 / AutoGPTPlus Token)"""