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)
|
results.append(result)
|
||||||
_current_results.append(result)
|
|
||||||
|
|
||||||
# 更新进度: 账号完成
|
# 更新进度: 账号完成
|
||||||
is_success = result["status"] in ("success", "completed")
|
is_success = result["status"] in ("success", "completed")
|
||||||
|
|||||||
112
telegram_bot.py
112
telegram_bot.py
@@ -5,6 +5,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from telegram import Update, Bot, BotCommand
|
from telegram import Update, Bot, BotCommand
|
||||||
@@ -22,6 +23,8 @@ from config import (
|
|||||||
TEAMS,
|
TEAMS,
|
||||||
AUTH_PROVIDER,
|
AUTH_PROVIDER,
|
||||||
TEAM_JSON_FILE,
|
TEAM_JSON_FILE,
|
||||||
|
TEAM_TRACKER_FILE,
|
||||||
|
CSV_FILE,
|
||||||
TELEGRAM_CHECK_INTERVAL,
|
TELEGRAM_CHECK_INTERVAL,
|
||||||
TELEGRAM_LOW_STOCK_THRESHOLD,
|
TELEGRAM_LOW_STOCK_THRESHOLD,
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
@@ -114,6 +117,7 @@ class ProvisionerBot:
|
|||||||
("include_owners", self.cmd_include_owners),
|
("include_owners", self.cmd_include_owners),
|
||||||
("reload", self.cmd_reload),
|
("reload", self.cmd_reload),
|
||||||
("s2a_config", self.cmd_s2a_config),
|
("s2a_config", self.cmd_s2a_config),
|
||||||
|
("clean", self.cmd_clean),
|
||||||
]
|
]
|
||||||
for cmd, handler in handlers:
|
for cmd, handler in handlers:
|
||||||
self.app.add_handler(CommandHandler(cmd, handler))
|
self.app.add_handler(CommandHandler(cmd, handler))
|
||||||
@@ -219,6 +223,7 @@ class ProvisionerBot:
|
|||||||
/fingerprint - 开启/关闭随机指纹
|
/fingerprint - 开启/关闭随机指纹
|
||||||
/include_owners - 开启/关闭 Owner 入库
|
/include_owners - 开启/关闭 Owner 入库
|
||||||
/reload - 重载配置文件 (无需重启)
|
/reload - 重载配置文件 (无需重启)
|
||||||
|
/clean - 清理 team.json 和 tracker 数据
|
||||||
|
|
||||||
<b>📊 S2A 专属:</b>
|
<b>📊 S2A 专属:</b>
|
||||||
/dashboard - 查看 S2A 仪表盘
|
/dashboard - 查看 S2A 仪表盘
|
||||||
@@ -555,6 +560,113 @@ class ProvisionerBot:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await update.message.reply_text(f"❌ 重载配置失败: {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
|
@admin_only
|
||||||
async def cmd_s2a_config(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def cmd_s2a_config(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""配置 S2A 服务参数"""
|
"""配置 S2A 服务参数"""
|
||||||
|
|||||||
35
utils.py
35
utils.py
@@ -203,6 +203,14 @@ def print_summary(results: list):
|
|||||||
Args:
|
Args:
|
||||||
results: [{"team": "...", "email": "...", "status": "...", "crs_id": "..."}]
|
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.separator("=", 60)
|
||||||
log.header("执行摘要")
|
log.header("执行摘要")
|
||||||
log.separator("=", 60)
|
log.separator("=", 60)
|
||||||
@@ -212,30 +220,31 @@ def print_summary(results: list):
|
|||||||
|
|
||||||
log.info(f"总计: {len(results)} 个账号")
|
log.info(f"总计: {len(results)} 个账号")
|
||||||
log.success(f"成功: {success_count}")
|
log.success(f"成功: {success_count}")
|
||||||
log.error(f"失败: {failed_count}")
|
if failed_count > 0:
|
||||||
|
log.error(f"失败: {failed_count}")
|
||||||
|
|
||||||
# 按 Team 分组
|
# 按 Team 分组
|
||||||
teams = {}
|
teams = {}
|
||||||
for r in results:
|
for r in results:
|
||||||
team = r.get("team", "Unknown")
|
team = r.get("team", "Unknown")
|
||||||
if team not in teams:
|
if team not in teams:
|
||||||
teams[team] = {"success": 0, "failed": 0, "accounts": []}
|
teams[team] = {"success": [], "failed": []}
|
||||||
|
|
||||||
if r.get("status") == "success":
|
if r.get("status") == "success":
|
||||||
teams[team]["success"] += 1
|
teams[team]["success"].append(r)
|
||||||
else:
|
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():
|
for acc in failed_list:
|
||||||
log.info(f"{team_name}: 成功 {data['success']}, 失败 {data['failed']}", icon="team")
|
log.error(f" ✗ {acc.get('email', 'Unknown')}")
|
||||||
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')}")
|
|
||||||
|
|
||||||
log.separator("=", 60)
|
log.separator("=", 60)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user