diff --git a/run.py b/run.py index 4c4a5d2..4bc5fa4 100644 --- a/run.py +++ b/run.py @@ -521,7 +521,6 @@ def process_accounts(accounts: list, team_name: str, team_index: int = 0, ) results.append(result) - _current_results.append(result) # 更新进度: 账号完成 is_success = result["status"] in ("success", "completed") diff --git a/telegram_bot.py b/telegram_bot.py index b4ae9dd..3b53e6c 100644 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -5,6 +5,7 @@ import asyncio import sys from concurrent.futures import ThreadPoolExecutor from functools import wraps +from pathlib import Path from typing import Optional from telegram import Update, Bot, BotCommand @@ -22,6 +23,8 @@ from config import ( TEAMS, AUTH_PROVIDER, TEAM_JSON_FILE, + TEAM_TRACKER_FILE, + CSV_FILE, TELEGRAM_CHECK_INTERVAL, TELEGRAM_LOW_STOCK_THRESHOLD, CONFIG_FILE, @@ -114,6 +117,7 @@ class ProvisionerBot: ("include_owners", self.cmd_include_owners), ("reload", self.cmd_reload), ("s2a_config", self.cmd_s2a_config), + ("clean", self.cmd_clean), ] for cmd, handler in handlers: self.app.add_handler(CommandHandler(cmd, handler)) @@ -219,6 +223,7 @@ class ProvisionerBot: /fingerprint - 开启/关闭随机指纹 /include_owners - 开启/关闭 Owner 入库 /reload - 重载配置文件 (无需重启) +/clean - 清理 team.json 和 tracker 数据 📊 S2A 专属: /dashboard - 查看 S2A 仪表盘 @@ -555,6 +560,113 @@ class ProvisionerBot: except Exception as e: await update.message.reply_text(f"❌ 重载配置失败: {e}") + @admin_only + async def cmd_clean(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + """清理 team.json 和 team_tracker.json 数据""" + # 检查是否有任务正在运行 + if self.current_task and not self.current_task.done(): + await update.message.reply_text( + f"⚠️ 有任务正在运行: {self.current_team}\n" + "请等待任务完成或使用 /stop 停止后再清理" + ) + return + + # 解析参数 + args = [a.lower() for a in context.args] if context.args else [] + include_csv = "all" in args + confirmed = "confirm" in args + + # 需要确认参数 + if not confirmed: + # 统计当前数据 + team_count = len(TEAMS) + tracker = load_team_tracker() + tracker_accounts = sum(len(accs) for accs in tracker.get("teams", {}).values()) + + # 统计 CSV 行数 + csv_count = 0 + csv_path = Path(CSV_FILE) + if csv_path.exists(): + try: + with open(csv_path, "r", encoding="utf-8") as f: + csv_count = max(0, sum(1 for _ in f) - 1) # 减去表头 + except: + pass + + msg = ( + f"⚠️ 确认清理数据?\n\n" + f"将清空以下文件:\n" + f"• team.json ({team_count} 个 Team)\n" + f"• team_tracker.json ({tracker_accounts} 个账号记录)\n" + ) + + if include_csv: + msg += f"• accounts.csv ({csv_count} 条记录)\n" + + msg += ( + f"\n此操作不可恢复!\n\n" + f"确认清理请发送:\n" + f"/clean confirm\n\n" + f"如需同时清理 CSV:\n" + f"/clean all confirm" + ) + + await update.message.reply_text(msg, parse_mode="HTML") + return + + # 执行清理 + cleaned = [] + errors = [] + + # 清理 team.json + try: + if TEAM_JSON_FILE.exists(): + with open(TEAM_JSON_FILE, "w", encoding="utf-8") as f: + f.write("[]") + cleaned.append("team.json") + except Exception as e: + errors.append(f"team.json: {e}") + + # 清理 team_tracker.json + try: + tracker_file = Path(TEAM_TRACKER_FILE) + if tracker_file.exists(): + with open(tracker_file, "w", encoding="utf-8") as f: + f.write('{"teams": {}}') + cleaned.append("team_tracker.json") + except Exception as e: + errors.append(f"team_tracker.json: {e}") + + # 清理 accounts.csv (可选) + if include_csv: + try: + csv_path = Path(CSV_FILE) + if csv_path.exists(): + with open(csv_path, "w", encoding="utf-8") as f: + f.write("email,password,team,status,crs_id,timestamp\n") + cleaned.append("accounts.csv") + except Exception as e: + errors.append(f"accounts.csv: {e}") + + # 重载配置以清空内存中的 TEAMS + reload_config() + + # 返回结果 + if errors: + await update.message.reply_text( + f"⚠️ 部分清理失败\n\n" + f"已清理: {', '.join(cleaned) or '无'}\n" + f"失败: {'; '.join(errors)}", + parse_mode="HTML" + ) + else: + await update.message.reply_text( + f"✅ 清理完成\n\n" + f"已清空: {', '.join(cleaned)}\n\n" + f"现在可以导入新的 team.json 了", + parse_mode="HTML" + ) + @admin_only async def cmd_s2a_config(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """配置 S2A 服务参数""" diff --git a/utils.py b/utils.py index f23a365..f014207 100644 --- a/utils.py +++ b/utils.py @@ -203,6 +203,14 @@ def print_summary(results: list): Args: results: [{"team": "...", "email": "...", "status": "...", "crs_id": "..."}] """ + # 去重 (按 email 去重,保留最后一条记录) + seen = {} + for r in results: + email = r.get("email", "") + if email: + seen[email] = r + results = list(seen.values()) + log.separator("=", 60) log.header("执行摘要") log.separator("=", 60) @@ -212,30 +220,31 @@ def print_summary(results: list): log.info(f"总计: {len(results)} 个账号") log.success(f"成功: {success_count}") - log.error(f"失败: {failed_count}") + if failed_count > 0: + log.error(f"失败: {failed_count}") # 按 Team 分组 teams = {} for r in results: team = r.get("team", "Unknown") if team not in teams: - teams[team] = {"success": 0, "failed": 0, "accounts": []} + teams[team] = {"success": [], "failed": []} if r.get("status") == "success": - teams[team]["success"] += 1 + teams[team]["success"].append(r) else: - teams[team]["failed"] += 1 + teams[team]["failed"].append(r) - teams[team]["accounts"].append(r) - - log.info("按 Team 统计:") - for team_name, data in teams.items(): - log.info(f"{team_name}: 成功 {data['success']}, 失败 {data['failed']}", icon="team") - for acc in data["accounts"]: - if acc.get("status") == "success": - log.success(f"{acc.get('email', 'Unknown')}") - else: - log.error(f"{acc.get('email', 'Unknown')}") + if teams: + log.info("按 Team 统计:") + for team_name, data in teams.items(): + success_list = data["success"] + failed_list = data["failed"] + log.info(f" {team_name}: 成功 {len(success_list)}, 失败 {len(failed_list)}") + + # 只显示失败的账号详情 + for acc in failed_list: + log.error(f" ✗ {acc.get('email', 'Unknown')}") log.separator("=", 60)