diff --git a/telegram_bot.py b/telegram_bot.py index 8bc3f70..b6ae917 100644 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -149,7 +149,6 @@ 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)) @@ -181,10 +180,6 @@ 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( @@ -283,8 +278,6 @@ class ProvisionerBot: BotCommand("team_register", "GPT Team 自动注册"), # AutoGPTPlus BotCommand("autogptplus", "AutoGPTPlus 管理面板"), - # S2A - BotCommand("s2a", "S2A 服务管理面板"), ] try: await self.app.bot.set_my_commands(commands) @@ -3065,574 +3058,6 @@ 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 配置管理 - 交互式菜单"""