feat: Implement result deduplication by email and enhance summary reporting to detail only failed accounts.

This commit is contained in:
2026-01-18 06:59:35 +08:00
parent 49cb2430c8
commit 95fa705768
3 changed files with 135 additions and 15 deletions

1
run.py
View File

@@ -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")

View File

@@ -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 数据
<b>📊 S2A 专属:</b>
/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"<b>⚠️ 确认清理数据?</b>\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<b>此操作不可恢复!</b>\n\n"
f"确认清理请发送:\n"
f"<code>/clean confirm</code>\n\n"
f"如需同时清理 CSV:\n"
f"<code>/clean all confirm</code>"
)
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"<b>⚠️ 部分清理失败</b>\n\n"
f"已清理: {', '.join(cleaned) or ''}\n"
f"失败: {'; '.join(errors)}",
parse_mode="HTML"
)
else:
await update.message.reply_text(
f"<b>✅ 清理完成</b>\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 服务参数"""

View File

@@ -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)
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)}")
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')}")
# 只显示失败的账号详情
for acc in failed_list:
log.error(f"{acc.get('email', 'Unknown')}")
log.separator("=", 60)