feat: Implement result deduplication by email and enhance summary reporting to detail only failed accounts.
This commit is contained in:
1
run.py
1
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")
|
||||
|
||||
112
telegram_bot.py
112
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 数据
|
||||
|
||||
<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 服务参数"""
|
||||
|
||||
35
utils.py
35
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)
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user