update
This commit is contained in:
85
config.py
85
config.py
@@ -214,6 +214,91 @@ def save_team_json():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def remove_team_by_name(team_name: str) -> bool:
|
||||||
|
"""从 team.json 中删除指定的 Team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_name: Team 名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功删除
|
||||||
|
"""
|
||||||
|
global _raw_teams, TEAMS
|
||||||
|
|
||||||
|
# 查找要删除的 team 索引
|
||||||
|
team_idx = None
|
||||||
|
for i, team in enumerate(TEAMS):
|
||||||
|
if team.get("name") == team_name:
|
||||||
|
team_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if team_idx is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 从 TEAMS 和 _raw_teams 中删除
|
||||||
|
TEAMS.pop(team_idx)
|
||||||
|
_raw_teams.pop(team_idx)
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
try:
|
||||||
|
with open(TEAM_JSON_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(_raw_teams, f, ensure_ascii=False, indent=2)
|
||||||
|
_log_config("INFO", "team.json", f"已删除 Team: {team_name}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
_log_config("ERROR", "team.json", "保存失败", str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def batch_remove_teams_by_names(team_names: list) -> dict:
|
||||||
|
"""批量从 team.json 中删除指定的 Teams
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_names: Team 名称列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: {"success": 成功数, "failed": 失败数, "total": 总数}
|
||||||
|
"""
|
||||||
|
global _raw_teams, TEAMS
|
||||||
|
|
||||||
|
results = {"success": 0, "failed": 0, "total": len(team_names)}
|
||||||
|
|
||||||
|
# 收集要保留的 teams
|
||||||
|
names_to_remove = set(team_names)
|
||||||
|
new_teams = []
|
||||||
|
new_raw_teams = []
|
||||||
|
removed_count = 0
|
||||||
|
|
||||||
|
for i, team in enumerate(TEAMS):
|
||||||
|
if team.get("name") in names_to_remove:
|
||||||
|
removed_count += 1
|
||||||
|
else:
|
||||||
|
new_teams.append(team)
|
||||||
|
new_raw_teams.append(_raw_teams[i])
|
||||||
|
|
||||||
|
if removed_count == 0:
|
||||||
|
return results
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
try:
|
||||||
|
with open(TEAM_JSON_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(new_raw_teams, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# 更新内存中的数据
|
||||||
|
TEAMS.clear()
|
||||||
|
TEAMS.extend(new_teams)
|
||||||
|
_raw_teams.clear()
|
||||||
|
_raw_teams.extend(new_raw_teams)
|
||||||
|
|
||||||
|
results["success"] = removed_count
|
||||||
|
_log_config("INFO", "team.json", f"已删除 {removed_count} 个 Team")
|
||||||
|
except Exception as e:
|
||||||
|
results["failed"] = len(team_names)
|
||||||
|
_log_config("ERROR", "team.json", "批量删除失败", str(e))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def reload_config() -> dict:
|
def reload_config() -> dict:
|
||||||
"""重新加载配置文件 (config.toml 和 team.json)
|
"""重新加载配置文件 (config.toml 和 team.json)
|
||||||
|
|
||||||
|
|||||||
161
telegram_bot.py
161
telegram_bot.py
@@ -48,8 +48,9 @@ from config import (
|
|||||||
S2A_GROUP_IDS,
|
S2A_GROUP_IDS,
|
||||||
S2A_ADMIN_KEY,
|
S2A_ADMIN_KEY,
|
||||||
BROWSER_RANDOM_FINGERPRINT,
|
BROWSER_RANDOM_FINGERPRINT,
|
||||||
|
batch_remove_teams_by_names,
|
||||||
)
|
)
|
||||||
from utils import load_team_tracker, get_all_incomplete_accounts
|
from utils import load_team_tracker, get_all_incomplete_accounts, save_team_tracker, get_completed_teams, batch_remove_completed_teams
|
||||||
from bot_notifier import BotNotifier, set_notifier, progress_finish
|
from bot_notifier import BotNotifier, set_notifier, progress_finish
|
||||||
from s2a_service import (
|
from s2a_service import (
|
||||||
s2a_get_dashboard_stats, format_dashboard_stats, s2a_get_keys_with_usage, format_keys_usage,
|
s2a_get_dashboard_stats, format_dashboard_stats, s2a_get_keys_with_usage, format_keys_usage,
|
||||||
@@ -135,6 +136,7 @@ class ProvisionerBot:
|
|||||||
("s2a_config", self.cmd_s2a_config),
|
("s2a_config", self.cmd_s2a_config),
|
||||||
("clean", self.cmd_clean),
|
("clean", self.cmd_clean),
|
||||||
("clean_errors", self.cmd_clean_errors),
|
("clean_errors", self.cmd_clean_errors),
|
||||||
|
("clean_teams", self.cmd_clean_teams),
|
||||||
("keys_usage", self.cmd_keys_usage),
|
("keys_usage", self.cmd_keys_usage),
|
||||||
]
|
]
|
||||||
for cmd, handler in handlers:
|
for cmd, handler in handlers:
|
||||||
@@ -155,6 +157,10 @@ class ProvisionerBot:
|
|||||||
self.callback_clean_errors,
|
self.callback_clean_errors,
|
||||||
pattern="^clean_errors:"
|
pattern="^clean_errors:"
|
||||||
))
|
))
|
||||||
|
self.app.add_handler(CallbackQueryHandler(
|
||||||
|
self.callback_clean_teams,
|
||||||
|
pattern="^clean_teams:"
|
||||||
|
))
|
||||||
|
|
||||||
# 注册定时检查任务
|
# 注册定时检查任务
|
||||||
if TELEGRAM_CHECK_INTERVAL > 0 and AUTH_PROVIDER == "s2a":
|
if TELEGRAM_CHECK_INTERVAL > 0 and AUTH_PROVIDER == "s2a":
|
||||||
@@ -265,6 +271,10 @@ class ProvisionerBot:
|
|||||||
/s2a_config - 配置 S2A 参数
|
/s2a_config - 配置 S2A 参数
|
||||||
/clean_errors - 清理错误状态账号
|
/clean_errors - 清理错误状态账号
|
||||||
|
|
||||||
|
<b>🧹 清理管理:</b>
|
||||||
|
/clean - 清理已完成账号 (team.json)
|
||||||
|
/clean_teams - 清理已完成 Team (tracker)
|
||||||
|
|
||||||
<b>📤 导入账号:</b>
|
<b>📤 导入账号:</b>
|
||||||
/import - 导入账号到 team.json
|
/import - 导入账号到 team.json
|
||||||
或直接发送 JSON 文件
|
或直接发送 JSON 文件
|
||||||
@@ -1584,6 +1594,155 @@ class ProvisionerBot:
|
|||||||
|
|
||||||
await query.edit_message_text("\n".join(lines), parse_mode="HTML")
|
await query.edit_message_text("\n".join(lines), parse_mode="HTML")
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
async def cmd_clean_teams(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""清理处理成功的 Team"""
|
||||||
|
# 获取已完成的 teams
|
||||||
|
tracker = load_team_tracker()
|
||||||
|
completed_teams = get_completed_teams(tracker)
|
||||||
|
|
||||||
|
if not completed_teams:
|
||||||
|
await update.message.reply_text("✅ 没有处理成功的 Team 需要清理")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 存储数据供分页使用
|
||||||
|
context.bot_data["clean_teams_data"] = completed_teams
|
||||||
|
context.bot_data["clean_teams_total"] = len(completed_teams)
|
||||||
|
|
||||||
|
# 显示第一页
|
||||||
|
text, keyboard = self._build_clean_teams_page(completed_teams, page=0)
|
||||||
|
await update.message.reply_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
|
|
||||||
|
def _build_clean_teams_page(self, teams: list, page: int = 0, page_size: int = 10):
|
||||||
|
"""构建已完成 Team 预览页面"""
|
||||||
|
total = len(teams)
|
||||||
|
total_pages = (total + page_size - 1) // page_size
|
||||||
|
start_idx = page * page_size
|
||||||
|
end_idx = min(start_idx + page_size, total)
|
||||||
|
page_teams = teams[start_idx:end_idx]
|
||||||
|
|
||||||
|
# 统计总账号数
|
||||||
|
total_accounts = sum(t["total"] for t in teams)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"<b>🧹 清理已完成 Team (预览)</b>",
|
||||||
|
"",
|
||||||
|
f"共发现 <b>{total}</b> 个已完成的 Team",
|
||||||
|
f"涉及 <b>{total_accounts}</b> 个账号记录",
|
||||||
|
"",
|
||||||
|
f"<b>Team 列表 (第 {page + 1}/{total_pages} 页):</b>",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 显示当前页的 Team
|
||||||
|
for i, team in enumerate(page_teams, start=start_idx + 1):
|
||||||
|
name = team["name"][:25]
|
||||||
|
count = team["total"]
|
||||||
|
lines.append(f"{i}. ✅ {name} ({count} 个账号)")
|
||||||
|
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
"⚠️ <b>此操作将同时清理:</b>",
|
||||||
|
"• team_tracker.json (账号处理记录)",
|
||||||
|
"• team.json (Team 配置)",
|
||||||
|
])
|
||||||
|
|
||||||
|
text = "\n".join(lines)
|
||||||
|
|
||||||
|
# 构建分页按钮
|
||||||
|
nav_buttons = []
|
||||||
|
if page > 0:
|
||||||
|
nav_buttons.append(InlineKeyboardButton("⬅️ 上一页", callback_data=f"clean_teams:page:{page - 1}"))
|
||||||
|
if page < total_pages - 1:
|
||||||
|
nav_buttons.append(InlineKeyboardButton("下一页 ➡️", callback_data=f"clean_teams:page:{page + 1}"))
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
nav_buttons,
|
||||||
|
[InlineKeyboardButton(f"🧹 确认清理全部 ({total})", callback_data="clean_teams:confirm")],
|
||||||
|
[InlineKeyboardButton("❌ 取消", callback_data="clean_teams:cancel")],
|
||||||
|
]
|
||||||
|
# 过滤空行
|
||||||
|
keyboard = [row for row in keyboard if row]
|
||||||
|
|
||||||
|
return text, InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
async def callback_clean_teams(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""处理清理已完成 Team 的回调"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
# 验证权限
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
if user_id not in TELEGRAM_ADMIN_CHAT_IDS:
|
||||||
|
await query.edit_message_text("⛔ 无权限")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 解析回调数据
|
||||||
|
data = query.data.replace("clean_teams:", "")
|
||||||
|
|
||||||
|
if data.startswith("page:"):
|
||||||
|
# 分页浏览
|
||||||
|
page = int(data.replace("page:", ""))
|
||||||
|
teams = context.bot_data.get("clean_teams_data", [])
|
||||||
|
|
||||||
|
if not teams:
|
||||||
|
await query.edit_message_text("❌ 数据已过期,请重新使用 /clean_teams")
|
||||||
|
return
|
||||||
|
|
||||||
|
text, keyboard = self._build_clean_teams_page(teams, page)
|
||||||
|
await query.edit_message_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
|
|
||||||
|
elif data == "cancel":
|
||||||
|
# 取消操作
|
||||||
|
context.bot_data.pop("clean_teams_data", None)
|
||||||
|
context.bot_data.pop("clean_teams_total", None)
|
||||||
|
await query.edit_message_text("✅ 已取消清理操作")
|
||||||
|
|
||||||
|
elif data == "confirm":
|
||||||
|
# 执行清理
|
||||||
|
teams_data = context.bot_data.get("clean_teams_data", [])
|
||||||
|
total = context.bot_data.get("clean_teams_total", 0)
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
f"<b>🧹 正在清理 {total} 个已完成 Team...</b>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取要删除的 team 名称列表
|
||||||
|
team_names = [t["name"] for t in teams_data]
|
||||||
|
|
||||||
|
# 1. 清理 tracker
|
||||||
|
tracker = load_team_tracker()
|
||||||
|
tracker_results = batch_remove_completed_teams(tracker)
|
||||||
|
save_team_tracker(tracker)
|
||||||
|
|
||||||
|
# 2. 清理 team.json
|
||||||
|
json_results = batch_remove_teams_by_names(team_names)
|
||||||
|
|
||||||
|
# 清理缓存数据
|
||||||
|
context.bot_data.pop("clean_teams_data", None)
|
||||||
|
context.bot_data.pop("clean_teams_total", None)
|
||||||
|
|
||||||
|
# 统计清理的账号数
|
||||||
|
total_accounts = sum(d.get("accounts", 0) for d in tracker_results.get("details", []) if d.get("status") == "success")
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
lines = [
|
||||||
|
"<b>✅ 清理完成</b>",
|
||||||
|
"",
|
||||||
|
f"清理 Team: <b>{tracker_results['success']}</b>",
|
||||||
|
f"清理账号记录: <b>{total_accounts}</b>",
|
||||||
|
"",
|
||||||
|
"<b>📁 文件清理:</b>",
|
||||||
|
f"• tracker: {tracker_results['success']} 个 Team",
|
||||||
|
f"• team.json: {json_results['success']} 个 Team",
|
||||||
|
]
|
||||||
|
|
||||||
|
if tracker_results['failed'] > 0 or json_results['failed'] > 0:
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"<b>失败:</b> tracker={tracker_results['failed']}, json={json_results['failed']}")
|
||||||
|
|
||||||
|
await query.edit_message_text("\n".join(lines), parse_mode="HTML")
|
||||||
|
|
||||||
@admin_only
|
@admin_only
|
||||||
async def cmd_import(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def cmd_import(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""上传账号到 team.json"""
|
"""上传账号到 team.json"""
|
||||||
|
|||||||
90
utils.py
90
utils.py
@@ -370,3 +370,93 @@ def add_team_owners_to_tracker(tracker: dict, password: str) -> int:
|
|||||||
log.info(f"已添加 {added_count} 个 Team Owner 到 tracker", icon="sync")
|
log.info(f"已添加 {added_count} 个 Team Owner 到 tracker", icon="sync")
|
||||||
|
|
||||||
return added_count
|
return added_count
|
||||||
|
|
||||||
|
|
||||||
|
def get_completed_teams(tracker: dict) -> list:
|
||||||
|
"""获取所有处理成功的 Team 列表
|
||||||
|
|
||||||
|
处理成功的定义:所有账号状态都是 completed,且没有失败的账号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tracker: team_tracker 数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: [{"name": "team_name", "total": 4, "completed": 4, "failed": 0, "pending": 0}]
|
||||||
|
"""
|
||||||
|
completed_teams = []
|
||||||
|
teams_data = tracker.get("teams", {})
|
||||||
|
|
||||||
|
for team_name, accounts in teams_data.items():
|
||||||
|
total = len(accounts)
|
||||||
|
if total == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
completed = sum(1 for a in accounts if a.get("status") == "completed")
|
||||||
|
failed = sum(1 for a in accounts if "fail" in a.get("status", "").lower())
|
||||||
|
pending = total - completed - failed
|
||||||
|
|
||||||
|
# 全部完成且没有失败的才算成功
|
||||||
|
if completed == total and failed == 0:
|
||||||
|
completed_teams.append({
|
||||||
|
"name": team_name,
|
||||||
|
"total": total,
|
||||||
|
"completed": completed,
|
||||||
|
"failed": failed,
|
||||||
|
"pending": pending
|
||||||
|
})
|
||||||
|
|
||||||
|
return completed_teams
|
||||||
|
|
||||||
|
|
||||||
|
def remove_team_from_tracker(tracker: dict, team_name: str) -> bool:
|
||||||
|
"""从 tracker 中删除整个 Team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tracker: team_tracker 数据
|
||||||
|
team_name: Team 名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功删除
|
||||||
|
"""
|
||||||
|
if team_name in tracker.get("teams", {}):
|
||||||
|
del tracker["teams"][team_name]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def batch_remove_completed_teams(tracker: dict) -> dict:
|
||||||
|
"""批量删除所有处理成功的 Team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tracker: team_tracker 数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: {"success": 删除成功数, "failed": 删除失败数, "total": 总数, "details": [...]}
|
||||||
|
"""
|
||||||
|
completed_teams = get_completed_teams(tracker)
|
||||||
|
|
||||||
|
results = {
|
||||||
|
"success": 0,
|
||||||
|
"failed": 0,
|
||||||
|
"total": len(completed_teams),
|
||||||
|
"details": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for team_info in completed_teams:
|
||||||
|
team_name = team_info["name"]
|
||||||
|
if remove_team_from_tracker(tracker, team_name):
|
||||||
|
results["success"] += 1
|
||||||
|
results["details"].append({
|
||||||
|
"name": team_name,
|
||||||
|
"status": "success",
|
||||||
|
"accounts": team_info["total"]
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
results["failed"] += 1
|
||||||
|
results["details"].append({
|
||||||
|
"name": team_name,
|
||||||
|
"status": "failed",
|
||||||
|
"message": "删除失败"
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|||||||
Reference in New Issue
Block a user