diff --git a/run.py b/run.py
index 5f72581..1c9c696 100644
--- a/run.py
+++ b/run.py
@@ -87,10 +87,12 @@ def _signal_handler(signum, frame):
sys.exit(0)
-# 注册信号处理器
-signal.signal(signal.SIGINT, _signal_handler)
-signal.signal(signal.SIGTERM, _signal_handler)
-atexit.register(_save_state)
+# 注册信号处理器 (仅在主线程中)
+import threading
+if threading.current_thread() is threading.main_thread():
+ signal.signal(signal.SIGINT, _signal_handler)
+ signal.signal(signal.SIGTERM, _signal_handler)
+ atexit.register(_save_state)
def process_single_team(team: dict) -> tuple[list, list]:
diff --git a/telegram_bot.py b/telegram_bot.py
index 13685aa..a367667 100644
--- a/telegram_bot.py
+++ b/telegram_bot.py
@@ -38,7 +38,7 @@ def admin_only(func):
async def wrapper(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
if user_id not in TELEGRAM_ADMIN_CHAT_IDS:
- await update.message.reply_text("Unauthorized. Your ID is not in admin list.")
+ await update.message.reply_text("⛔ 无权限,你的 ID 不在管理员列表中")
return
return await func(self, update, context)
return wrapper
@@ -108,7 +108,7 @@ class ProvisionerBot:
log.info(f"Admin Chat IDs: {TELEGRAM_ADMIN_CHAT_IDS}")
# 发送启动通知
- await self.notifier.notify("Bot Started\nReady for commands. Send /help for usage.")
+ await self.notifier.notify("🤖 Bot 已启动\n准备就绪,发送 /help 查看帮助")
# 运行 Bot
await self.app.initialize()
@@ -131,29 +131,29 @@ class ProvisionerBot:
@admin_only
async def cmd_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""显示帮助信息"""
- help_text = """OpenAI Team Provisioner Bot
+ help_text = """🤖 OpenAI Team 批量注册 Bot
-Commands:
-/status - View all teams status
-/team <n> - View team N details
-/run <n> - Start processing team N
-/run_all - Start processing all teams
-/stop - Stop current task
-/logs [n] - View recent n logs (default 10)
-/dashboard - View S2A dashboard stats
-/stock - Check account stock
-/import - Upload accounts to team.json
-/help - Show this help
+📋 命令列表:
+/status - 查看所有 Team 状态
+/team <n> - 查看第 n 个 Team 详情
+/run <n> - 开始处理第 n 个 Team
+/run_all - 开始处理所有 Team
+/stop - 停止当前任务
+/logs [n] - 查看最近 n 条日志 (默认 10)
+/dashboard - 查看 S2A 仪表盘
+/stock - 查看账号库存
+/import - 导入账号到 team.json
+/help - 显示此帮助
-Upload Accounts:
-Send a JSON file or use /import with JSON data:
-[{"account":"email","password":"pwd","token":"jwt"},...]
-Then use /run to process them.
+📤 上传账号:
+直接发送 JSON 文件,或使用 /import 加 JSON 数据:
+[{"account":"邮箱","password":"密码","token":"jwt"},...]
+上传后使用 /run 开始处理
-Examples:
-/run 0 - Process first team
-/team 1 - View second team status
-/logs 20 - View last 20 logs"""
+💡 示例:
+/run 0 - 处理第一个 Team
+/team 1 - 查看第二个 Team 状态
+/logs 20 - 查看最近 20 条日志"""
await update.message.reply_text(help_text, parse_mode="HTML")
@admin_only
@@ -163,25 +163,25 @@ Then use /run to process them.
teams_data = tracker.get("teams", {})
if not teams_data:
- await update.message.reply_text("No data yet. Run tasks first.")
+ await update.message.reply_text("📭 暂无数据,请先运行任务")
return
- lines = ["Teams Status\n"]
+ lines = ["📊 Team 状态总览\n"]
for team_name, accounts in teams_data.items():
total = len(accounts)
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
- status_icon = "OK" if completed == total else ("FAIL" if failed > 0 else "...")
+ status_icon = "✅" if completed == total else ("❌" if failed > 0 else "⏳")
lines.append(
- f"[{status_icon}] {team_name}: {completed}/{total} "
- f"(F:{failed} P:{pending})"
+ f"{status_icon} {team_name}: {completed}/{total} "
+ f"(失败:{failed} 待处理:{pending})"
)
# 当前任务状态
if self.current_task and not self.current_task.done():
- lines.append(f"\nRunning: {self.current_team or 'Unknown'}")
+ lines.append(f"\n🔄 运行中: {self.current_team or '未知'}")
await update.message.reply_text("\n".join(lines), parse_mode="HTML")
@@ -189,17 +189,17 @@ Then use /run to process them.
async def cmd_team(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""查看指定 Team 详情"""
if not context.args:
- await update.message.reply_text("Usage: /team \nExample: /team 0")
+ await update.message.reply_text("用法: /team <序号>\n示例: /team 0")
return
try:
team_idx = int(context.args[0])
except ValueError:
- await update.message.reply_text("Invalid team index. Must be a number.")
+ await update.message.reply_text("❌ 无效的序号,必须是数字")
return
if team_idx < 0 or team_idx >= len(TEAMS):
- await update.message.reply_text(f"Team index out of range. Valid: 0-{len(TEAMS)-1}")
+ await update.message.reply_text(f"❌ 序号超出范围,有效范围: 0-{len(TEAMS)-1}")
return
team = TEAMS[team_idx]
@@ -208,22 +208,22 @@ Then use /run to process them.
tracker = load_team_tracker()
accounts = tracker.get("teams", {}).get(team_name, [])
- lines = [f"Team {team_idx}: {team_name}\n"]
- lines.append(f"Owner: {team.get('owner_email', 'N/A')}")
- lines.append(f"Accounts: {len(accounts)}\n")
+ lines = [f"📁 Team {team_idx}: {team_name}\n"]
+ lines.append(f"👤 Owner: {team.get('owner_email', '无')}")
+ lines.append(f"📊 账号数: {len(accounts)}\n")
if accounts:
for acc in accounts:
email = acc.get("email", "")
status = acc.get("status", "unknown")
role = acc.get("role", "member")
- icon = {"completed": "OK", "authorized": "AUTH", "registered": "REG"}.get(
- status, "FAIL" if "fail" in status.lower() else "..."
+ icon = {"completed": "✅", "authorized": "🔐", "registered": "📝"}.get(
+ status, "❌" if "fail" in status.lower() else "⏳"
)
- role_tag = " [O]" if role == "owner" else ""
- lines.append(f"[{icon}] {email}{role_tag}")
+ role_tag = " [Owner]" if role == "owner" else ""
+ lines.append(f"{icon} {email}{role_tag}")
else:
- lines.append("No accounts processed yet.")
+ lines.append("📭 暂无已处理的账号")
await update.message.reply_text("\n".join(lines), parse_mode="HTML")
@@ -232,28 +232,28 @@ Then use /run to process them.
"""启动处理指定 Team"""
if self.current_task and not self.current_task.done():
await update.message.reply_text(
- f"Task already running: {self.current_team}\nUse /stop to cancel."
+ f"⚠️ 任务正在运行: {self.current_team}\n使用 /stop 停止"
)
return
if not context.args:
- await update.message.reply_text("Usage: /run \nExample: /run 0")
+ await update.message.reply_text("用法: /run <序号>\n示例: /run 0")
return
try:
team_idx = int(context.args[0])
except ValueError:
- await update.message.reply_text("Invalid team index. Must be a number.")
+ await update.message.reply_text("❌ 无效的序号,必须是数字")
return
if team_idx < 0 or team_idx >= len(TEAMS):
- await update.message.reply_text(f"Team index out of range. Valid: 0-{len(TEAMS)-1}")
+ await update.message.reply_text(f"❌ 序号超出范围,有效范围: 0-{len(TEAMS)-1}")
return
team_name = TEAMS[team_idx].get("name", f"Team{team_idx}")
self.current_team = team_name
- await update.message.reply_text(f"Starting task for Team {team_idx}: {team_name}...")
+ await update.message.reply_text(f"🚀 开始处理 Team {team_idx}: {team_name}...")
# 在后台线程执行任务
loop = asyncio.get_event_loop()
@@ -271,12 +271,12 @@ Then use /run to process them.
"""启动处理所有 Team"""
if self.current_task and not self.current_task.done():
await update.message.reply_text(
- f"Task already running: {self.current_team}\nUse /stop to cancel."
+ f"⚠️ 任务正在运行: {self.current_team}\n使用 /stop 停止"
)
return
- self.current_team = "ALL"
- await update.message.reply_text(f"Starting task for ALL teams ({len(TEAMS)} teams)...")
+ self.current_team = "全部"
+ await update.message.reply_text(f"🚀 开始处理所有 Team (共 {len(TEAMS)} 个)...")
loop = asyncio.get_event_loop()
self.current_task = loop.run_in_executor(
@@ -284,7 +284,7 @@ Then use /run to process them.
self._run_all_teams_task
)
- self.current_task = asyncio.ensure_future(self._wrap_task(self.current_task, "ALL"))
+ self.current_task = asyncio.ensure_future(self._wrap_task(self.current_task, "全部"))
async def _wrap_task(self, task, team_name: str):
"""包装任务以处理完成通知"""
@@ -294,7 +294,7 @@ Then use /run to process them.
failed = len(result or []) - success
await self.notifier.notify_task_completed(team_name, success, failed)
except Exception as e:
- await self.notifier.notify_error(f"Task failed: {team_name}", str(e))
+ await self.notifier.notify_error(f"任务失败: {team_name}", str(e))
finally:
self.current_team = None
# 清理进度跟踪
@@ -315,14 +315,14 @@ Then use /run to process them.
async def cmd_stop(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""停止当前任务"""
if not self.current_task or self.current_task.done():
- await update.message.reply_text("No task is running.")
+ await update.message.reply_text("📭 当前没有运行中的任务")
return
# 注意: 由于任务在线程池中运行,无法直接取消
# 这里只能发送信号
await update.message.reply_text(
- f"Requesting stop for: {self.current_team}\n"
- "Note: Current account processing will complete before stopping."
+ f"🛑 正在停止: {self.current_team}\n"
+ "注意: 当前账号处理完成后才会停止"
)
# 设置全局停止标志
@@ -346,7 +346,7 @@ Then use /run to process them.
from config import BASE_DIR
log_file = BASE_DIR / "logs" / "app.log"
if not log_file.exists():
- await update.message.reply_text("No log file found.")
+ await update.message.reply_text("📭 日志文件不存在")
return
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
@@ -354,7 +354,7 @@ Then use /run to process them.
recent = lines[-n:] if len(lines) >= n else lines
if not recent:
- await update.message.reply_text("Log file is empty.")
+ await update.message.reply_text("📭 日志文件为空")
return
# 格式化日志 (移除 ANSI 颜色码)
@@ -369,19 +369,19 @@ Then use /run to process them.
await update.message.reply_text(f"{log_text}", parse_mode="HTML")
except Exception as e:
- await update.message.reply_text(f"Error reading logs: {e}")
+ await update.message.reply_text(f"❌ 读取日志失败: {e}")
@admin_only
async def cmd_dashboard(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""查看 S2A 仪表盘统计"""
if AUTH_PROVIDER != "s2a":
await update.message.reply_text(
- f"Dashboard only available for S2A provider.\n"
- f"Current provider: {AUTH_PROVIDER}"
+ f"⚠️ 仪表盘仅支持 S2A 模式\n"
+ f"当前模式: {AUTH_PROVIDER}"
)
return
- await update.message.reply_text("Fetching dashboard stats...")
+ await update.message.reply_text("⏳ 正在获取仪表盘数据...")
try:
stats = s2a_get_dashboard_stats()
@@ -390,25 +390,25 @@ Then use /run to process them.
await update.message.reply_text(text, parse_mode="HTML")
else:
await update.message.reply_text(
- "Failed to fetch dashboard stats.\n"
- "Check S2A configuration and API connection."
+ "❌ 获取仪表盘数据失败\n"
+ "请检查 S2A 配置和 API 连接"
)
except Exception as e:
- await update.message.reply_text(f"Error: {e}")
+ await update.message.reply_text(f"❌ 错误: {e}")
@admin_only
async def cmd_stock(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""查看账号存货"""
if AUTH_PROVIDER != "s2a":
await update.message.reply_text(
- f"Stock check only available for S2A provider.\n"
- f"Current provider: {AUTH_PROVIDER}"
+ f"⚠️ 库存查询仅支持 S2A 模式\n"
+ f"当前模式: {AUTH_PROVIDER}"
)
return
stats = s2a_get_dashboard_stats()
if not stats:
- await update.message.reply_text("Failed to fetch stock info.")
+ await update.message.reply_text("❌ 获取库存信息失败")
return
text = self._format_stock_message(stats)
@@ -453,35 +453,35 @@ Then use /run to process them.
# 状态图标
if normal <= TELEGRAM_LOW_STOCK_THRESHOLD:
- status_icon = "LOW STOCK"
+ status_icon = "⚠️ 库存不足"
status_line = f"{status_icon}"
elif health_pct >= 80:
- status_icon = "OK"
+ status_icon = "✅ 正常"
status_line = f"{status_icon}"
elif health_pct >= 50:
- status_icon = "WARN"
+ status_icon = "⚠️ 警告"
status_line = f"{status_icon}"
else:
- status_icon = "CRITICAL"
+ status_icon = "🔴 严重"
status_line = f"{status_icon}"
- title = "LOW STOCK ALERT" if is_alert else "Account Stock"
+ title = "🚨 库存不足警报" if is_alert else "📦 账号库存"
lines = [
f"{title}",
"",
- f"Status: {status_line}",
- f"Health: {health_pct:.1f}%",
+ f"状态: {status_line}",
+ f"健康度: {health_pct:.1f}%",
"",
- f"Normal: {normal}",
- f"Error: {error}",
- f"RateLimit: {ratelimit}",
- f"Total: {total}",
+ f"正常: {normal}",
+ f"异常: {error}",
+ f"限流: {ratelimit}",
+ f"总计: {total}",
]
if is_alert:
lines.append("")
- lines.append(f"Threshold: {TELEGRAM_LOW_STOCK_THRESHOLD}")
+ lines.append(f"预警阈值: {TELEGRAM_LOW_STOCK_THRESHOLD}")
return "\n".join(lines)
@@ -491,13 +491,13 @@ Then use /run to process them.
# 获取命令后的 JSON 数据
if not context.args:
await update.message.reply_text(
- "Upload Accounts to team.json\n\n"
- "Usage:\n"
- "1. Send a JSON file directly\n"
- "2. /import followed by JSON data\n\n"
- "JSON format:\n"
- "[{\"account\":\"email\",\"password\":\"pwd\",\"token\":\"jwt\"},...]\n\n"
- "After upload, use /run to start processing.",
+ "📤 导入账号到 team.json\n\n"
+ "用法:\n"
+ "1. 直接发送 JSON 文件\n"
+ "2. /import 后跟 JSON 数据\n\n"
+ "JSON 格式:\n"
+ "[{\"account\":\"邮箱\",\"password\":\"密码\",\"token\":\"jwt\"},...]\n\n"
+ "导入后使用 /run 开始处理",
parse_mode="HTML"
)
return
@@ -512,14 +512,14 @@ Then use /run to process them.
# 检查是否是管理员
user_id = update.effective_user.id
if user_id not in TELEGRAM_ADMIN_CHAT_IDS:
- await update.message.reply_text("Unauthorized.")
+ await update.message.reply_text("⛔ 无权限")
return
document = update.message.document
if not document:
return
- await update.message.reply_text("Processing JSON file...")
+ await update.message.reply_text("⏳ 正在处理 JSON 文件...")
try:
# 下载文件
@@ -530,7 +530,7 @@ Then use /run to process them.
await self._process_import_json(update, json_text)
except Exception as e:
- await update.message.reply_text(f"Error reading file: {e}")
+ await update.message.reply_text(f"❌ 读取文件失败: {e}")
async def _process_import_json(self, update: Update, json_text: str):
"""处理导入的 JSON 数据,保存到 team.json"""
@@ -540,7 +540,7 @@ Then use /run to process them.
try:
new_accounts = json.loads(json_text)
except json.JSONDecodeError as e:
- await update.message.reply_text(f"Invalid JSON format: {e}")
+ await update.message.reply_text(f"❌ JSON 格式错误: {e}")
return
if not isinstance(new_accounts, list):
@@ -548,7 +548,7 @@ Then use /run to process them.
new_accounts = [new_accounts]
if not new_accounts:
- await update.message.reply_text("No accounts in JSON data")
+ await update.message.reply_text("📭 JSON 数据中没有账号")
return
# 验证格式
@@ -569,7 +569,7 @@ Then use /run to process them.
})
if not valid_accounts:
- await update.message.reply_text("No valid accounts found (need account/email and token)")
+ await update.message.reply_text("❌ 未找到有效账号 (需要 account/email 和 token 字段)")
return
# 读取现有 team.json
@@ -608,16 +608,16 @@ Then use /run to process them.
json.dump(existing_accounts, f, ensure_ascii=False, indent=2)
await update.message.reply_text(
- f"Upload Complete\n\n"
- f"Added: {added}\n"
- f"Skipped (duplicate): {skipped}\n"
- f"Total in team.json: {len(existing_accounts)}\n\n"
- f"Use /run_all or /run <n> to start processing.",
+ f"✅ 导入完成\n\n"
+ f"新增: {added}\n"
+ f"跳过 (重复): {skipped}\n"
+ f"team.json 总数: {len(existing_accounts)}\n\n"
+ f"使用 /run_all 或 /run <n> 开始处理",
parse_mode="HTML"
)
except Exception as e:
- await update.message.reply_text(f"Error saving to team.json: {e}")
+ await update.message.reply_text(f"❌ 保存到 team.json 失败: {e}")
async def main():