feat: Implement thread-safe account and statistics management and integrate proxy support for all external requests.

This commit is contained in:
2026-02-13 03:32:27 +08:00
parent 1c58279292
commit ef23318090
12 changed files with 1041 additions and 84 deletions

537
bot.py
View File

@@ -5,16 +5,19 @@ autoClaude Telegram Bot
"""
import asyncio
import json
import logging
import os
import random
import string
import time
import threading
from functools import wraps
from telegram import Update, BotCommand
from telegram import Update, BotCommand, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
Application,
CallbackQueryHandler,
CommandHandler,
MessageHandler,
ContextTypes,
@@ -30,6 +33,8 @@ from mail_service import MailPool, extract_magic_link
from stripe_token import StripeTokenizer
from gift_checker import GiftChecker
from claude_auth import attack_claude, finalize_login
import account_store
import proxy_pool
# --- 日志配置 ---
logging.basicConfig(
@@ -37,11 +42,14 @@ logging.basicConfig(
level=logging.INFO,
)
logger = logging.getLogger(__name__)
# 降低 httpx 轮询日志级别,减少刷屏
logging.getLogger("httpx").setLevel(logging.WARNING)
# --- 全局状态 ---
_task_lock = threading.Lock()
_task_running = False
_task_name = ""
_stop_event = threading.Event()
# ============================================================
@@ -68,6 +76,7 @@ def _set_task(name: str) -> bool:
return False
_task_running = True
_task_name = name
_stop_event.clear()
return True
@@ -77,6 +86,12 @@ def _clear_task():
with _task_lock:
_task_running = False
_task_name = ""
_stop_event.clear()
def _is_stopped() -> bool:
"""检查是否收到停止信号"""
return _stop_event.is_set()
async def _edit_or_send(msg, text: str):
@@ -87,6 +102,16 @@ async def _edit_or_send(msg, text: str):
pass
def _progress_bar(current: int, total: int, width: int = 12) -> str:
"""生成可视化进度条 ▓▓▓▓░░░░ 3/10 (30%)"""
if total <= 0:
return ""
pct = current / total
filled = round(width * pct)
bar = "" * filled + "" * (width - filled)
return f"{bar} {current}/{total} ({round(pct * 100)}%)"
# ============================================================
# 命令处理
# ============================================================
@@ -97,12 +122,17 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
welcome = (
"🤖 <b>autoClaude Bot</b>\n\n"
"可用命令:\n"
" /register [N] — 注册 Claude 账号(默认 1 个)\n"
" /check &lt;卡号|月|年|CVC&gt; — 单张 CC 检查\n"
" /register [N] — 注册 Claude 账号\n"
" /check &lt;卡号|月|年|CVC&gt; — CC 检查\n"
" 📎 发送 .txt 文件 — 批量 CC 检查\n"
" /accounts — 查看已注册账号\n"
" /status — 当前任务状态\n"
" /help — 帮助\n\n"
" /accounts — 查看账号 | /delete — 删除账号\n"
" /verify — 验证 SK 有效性\n"
" /stats — 统计面板\n"
" /stop — 中断当前任务\n"
" /proxy on|off — 代理开关\n"
" /proxytest — 测试代理 | /proxystatus — 代理状态\n"
" /mailstatus — 邮件系统状态\n"
" /status — 任务状态 | /help — 帮助\n\n"
f"👤 你的用户 ID: <code>{update.effective_user.id}</code>"
)
await update.message.reply_text(welcome, parse_mode="HTML")
@@ -113,19 +143,23 @@ async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/help — 命令列表"""
text = (
"📖 <b>命令说明</b>\n\n"
"<b>/register [N]</b>\n"
" 注册 N 个 Claude 账号(默认 1\n"
" 流程:创建邮箱 → 发送 Magic Link → 等待邮件 → 交换 SessionKey\n"
" 注册结果自动保存到 accounts.txt\n\n"
"<b>/check &lt;CARD|MM|YY|CVC&gt;</b>\n"
" 单张信用卡检查。\n\n"
"<b>📎 发送 .txt 文件</b>\n"
" 批量 CC 检查,文件每行一张卡:\n"
" <code>卡号|月|年|CVC</code>\n\n"
"<b>/accounts</b>\n"
" 列出 accounts.txt 中保存的所有账号。\n\n"
"<b>/status</b>\n"
" 查看当前是否有后台任务在运行。\n"
"<b>📝 注册与账号</b>\n"
" /register [N] — 注册 N 个账号\n"
" /accounts — 查看已注册账号\n"
" /delete <序号|邮箱> — 删除账号\n"
" /verify — 验证 SK 有效性\n\n"
"<b>💳 CC 检查</b>\n"
" /check <CARD|MM|YY|CVC> — 单张检查\n"
" 📎 发送 .txt 文件 — 批量检查\n\n"
"<b>🛠 工具与状态</b>\n"
" /stop — 中断当前任务\n"
" /stats — 统计面板\n"
" /status — 任务状态\n\n"
"<b>🌐 代理与邮件</b>\n"
" /proxy on|off — 开关代理\n"
" /proxytest — 测试代理\n"
" /proxystatus — 代理池状态\n"
" /mailstatus — 邮件系统状态\n"
)
await update.message.reply_text(text, parse_mode="HTML")
@@ -144,30 +178,301 @@ async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
@restricted
async def cmd_accounts(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/accounts — 列出已注册账号"""
try:
with open("accounts.txt", "r") as f:
lines = [l.strip() for l in f if l.strip()]
except FileNotFoundError:
await update.message.reply_text("📭 尚无已注册账号accounts.txt 不存在)。")
async def cmd_stop(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/stop — 中断当前运行的任务"""
with _task_lock:
if not _task_running:
await update.message.reply_text("✅ 当前没有运行中的任务。")
return
_stop_event.set()
await update.message.reply_text(
f"⏹ 正在停止任务:<b>{_task_name}</b>\n"
"任务将在当前步骤完成后安全退出...",
parse_mode="HTML",
)
@restricted
async def cmd_mailstatus(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/mailstatus — 检查邮箱系统连通性"""
status_msg = await update.message.reply_text("🔍 正在检测邮件系统...")
mail_pool = MailPool(MAIL_SYSTEMS)
if mail_pool.count == 0:
await _edit_or_send(status_msg, "❌ 没有可用的邮箱系统!请检查 config.toml 配置。")
return
if not lines:
await update.message.reply_text("📭 accounts.txt 为空。")
text = f"📬 <b>邮件系统状态(共 {mail_pool.count} 个)</b>\n\n"
for i, ms in enumerate(mail_pool.systems, 1):
health = ms.check_health()
icon = "" if health["ok"] else ""
domains = ", ".join(ms.domains)
text += f"{i}. {icon} <code>{ms.base_url}</code>\n"
text += f" 域名: {domains}\n"
text += f" 状态: {health['message']}\n"
await _edit_or_send(status_msg, text)
@restricted
async def cmd_proxytest(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/proxytest — 测试所有代理的连通性"""
pp = proxy_pool.pool
if pp.count == 0:
await update.message.reply_text("❌ 代理池为空proxy.txt 不存在或无有效代理)")
return
text = f"📋 <b>已注册账号(共 {len(lines)} 个)</b>\n\n"
for i, line in enumerate(lines, 1):
parts = line.split("|")
email = parts[0] if len(parts) > 0 else "?"
sk = parts[1][:12] + "..." if len(parts) > 1 and len(parts[1]) > 12 else (parts[1] if len(parts) > 1 else "?")
text += f"{i}. <code>{email}</code>\n SK: <code>{sk}</code>\n"
status_msg = await update.message.reply_text(
f"🔍 正在测试 {pp.count} 个代理...(可能需要一些时间)"
)
# 在线程中执行测试避免阻塞事件循环
loop = asyncio.get_event_loop()
results = await loop.run_in_executor(None, pp.test_all)
# 构建结果报告
ok_count = sum(1 for r in results if r["ok"])
fail_count = len(results) - ok_count
text = (
f"🎯 <b>代理测试结果</b>\n\n"
f"✅ 通过: {ok_count} ❌ 失败: {fail_count} "
f"🚧 剩余可用: {pp.active_count}\n\n"
)
for r in results:
icon = "" if r["ok"] else ""
latency = f"{r['latency_ms']}ms" if r['latency_ms'] > 0 else "-"
prio = r.get('priority', '-')
text += f"{icon} <code>{r['proxy']}</code>\n"
text += f" 延迟: {latency} | 优先级: {prio}\n"
if not r["ok"]:
text += f" 错误: {r.get('error', '?')}\n"
# Telegram 消息限制 4096 字符
if len(text) > 4000:
text = text[:4000] + "\n...(已截断)"
await _edit_or_send(status_msg, text)
@restricted
async def cmd_proxystatus(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/proxystatus — 查看代理池状态"""
pp = proxy_pool.pool
if pp.count == 0:
await update.message.reply_text("❌ 代理池为空proxy.txt 不存在或无有效代理)")
return
items = pp.status_list()
text = f"🌐 <b>代理池状态(共 {len(items)} 个)</b>\n\n"
for i, item in enumerate(items, 1):
icon = "" if item["last_ok"] else ""
text += (
f"{i}. {icon} <code>{item['proxy']}</code>\n"
f" 优先级: {item['priority']} | "
f"延迟: {item['latency_ms']}ms | "
f"{item['success']}{item['fail']}\n"
)
if len(text) > 4000:
text = text[:4000] + "\n...(已截断)"
await update.message.reply_text(text, parse_mode="HTML")
@restricted
async def cmd_proxy(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/proxy on|off — 开关代理"""
pp = proxy_pool.pool
if not context.args:
# 无参数:显示当前状态
status = "✅ 已开启" if pp.enabled else "❌ 已关闭"
await update.message.reply_text(
f"🌐 <b>代理状态</b>: {status}\n"
f"📦 代理池: {pp.count} 个(活跃 {pp.active_count} 个)\n\n"
f"用法: <code>/proxy on</code> 或 <code>/proxy off</code>",
parse_mode="HTML",
)
return
arg = context.args[0].lower()
if arg == "on":
pp.enabled = True
await update.message.reply_text(
f"✅ 代理已开启(池中 {pp.active_count} 个可用)"
)
elif arg == "off":
pp.enabled = False
await update.message.reply_text("❌ 代理已关闭,所有请求将直连")
else:
await update.message.reply_text("❌ 用法: /proxy on 或 /proxy off")
@restricted
async def cmd_accounts(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/accounts — 列出已注册账号"""
accounts = account_store.read_all()
if not accounts:
await update.message.reply_text("📭 尚无已注册账号。")
return
text = f"📋 <b>已注册账号(共 {len(accounts)} 个)</b>\n\n"
for i, acc in enumerate(accounts, 1):
text += f"{i}. <code>{acc['email']}</code>\n SK: <code>{acc['session_key']}</code>\n"
if len(text) > 4000:
text = text[:4000] + "\n...(已截断,请点击下方按钮导出完整数据)"
keyboard = [[InlineKeyboardButton("📥 导出 JSON 文件", callback_data="export_accounts_json")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(text, parse_mode="HTML", reply_markup=reply_markup)
@restricted
async def cmd_delete(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/delete <序号|邮箱> — 删除指定账号"""
user_id = update.effective_user.id
if not context.args:
await update.message.reply_text(
"❌ 用法:\n"
" <code>/delete 3</code> — 按序号删除\n"
" <code>/delete user@example.com</code> — 按邮箱删除\n\n"
"💡 使用 /accounts 查看序号",
parse_mode="HTML",
)
return
arg = context.args[0]
removed = None
# 尝试按序号删除
try:
index = int(arg)
removed = account_store.delete_by_index(index)
if not removed:
total = account_store.count()
await update.message.reply_text(f"❌ 无效序号。当前共 {total} 个账号。")
return
except ValueError:
# 按邮箱删除
removed = account_store.delete_by_email(arg)
if not removed:
await update.message.reply_text(f"❌ 未找到邮箱:{arg}")
return
await update.message.reply_text(
f"🗑 已删除账号:\n"
f"📧 <code>{removed['email']}</code>\n"
f"剩余 {account_store.count()} 个账号",
parse_mode="HTML",
)
@restricted
async def cmd_verify(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/verify — 检测已保存的 Session Key 是否仍然有效"""
accounts = account_store.read_all()
if not accounts:
await update.message.reply_text("📭 尚无已注册账号。")
return
status_msg = await update.message.reply_text(
f"🔍 正在验证 {len(accounts)} 个账号的 Session Key..."
)
from curl_cffi import requests as cffi_requests
from config import get_proxy
results = []
for i, acc in enumerate(accounts, 1):
sk = acc.get("session_key", "")
email = acc.get("email", "?")
if not sk:
results.append({"email": email, "ok": False, "reason": "SK 为空"})
continue
try:
resp = cffi_requests.get(
"https://claude.ai/api/organizations",
headers={
"Cookie": f"sessionKey={sk}",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
impersonate="chrome124",
proxies=get_proxy(),
timeout=15,
)
if resp.status_code == 200:
results.append({"email": email, "ok": True, "reason": "有效"})
elif resp.status_code == 401:
results.append({"email": email, "ok": False, "reason": "已过期"})
elif resp.status_code == 403:
results.append({"email": email, "ok": False, "reason": "被封禁"})
else:
results.append({"email": email, "ok": False, "reason": f"HTTP {resp.status_code}"})
except Exception as e:
results.append({"email": email, "ok": False, "reason": str(e)[:50]})
valid = sum(1 for r in results if r["ok"])
invalid = len(results) - valid
text = (
f"🔑 <b>账号验证结果</b>\n\n"
f"✅ 有效: {valid} ❌ 无效: {invalid}\n\n"
)
for i, r in enumerate(results, 1):
icon = "" if r["ok"] else ""
text += f"{i}. {icon} <code>{r['email']}</code> — {r['reason']}\n"
if len(text) > 4000:
text = text[:4000] + "\n...(已截断)"
await _edit_or_send(status_msg, text)
@restricted
async def cmd_stats(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""/stats — 展示历史统计数据"""
stats = account_store.get_stats()
reg_total = stats.get("register_total", 0)
reg_ok = stats.get("register_success", 0)
reg_fail = stats.get("register_fail", 0)
reg_rate = f"{round(reg_ok / reg_total * 100)}%" if reg_total > 0 else "-"
cc_total = stats.get("cc_total", 0)
cc_ok = stats.get("cc_pass", 0)
cc_fail = stats.get("cc_fail", 0)
cc_rate = f"{round(cc_ok / cc_total * 100)}%" if cc_total > 0 else "-"
text = (
"📊 <b>统计面板</b>\n\n"
f"<b>📝 注册统计</b>\n"
f" 总计: {reg_total} | ✅ {reg_ok} | ❌ {reg_fail}\n"
f" 成功率: {reg_rate}\n"
)
# 失败原因分布
reasons = stats.get("register_fail_reasons", {})
if reasons:
text += " <b>失败原因:</b>\n"
for reason, cnt in sorted(reasons.items(), key=lambda x: -x[1]):
text += f"{reason}: {cnt}\n"
text += (
f"\n<b>💳 CC 检查统计</b>\n"
f" 总计: {cc_total} | ✅ {cc_ok} | ❌ {cc_fail}\n"
f" 通过率: {cc_rate}\n"
)
text += f"\n<b>📦 当前账号数</b>: {account_store.count()}"
await update.message.reply_text(text, parse_mode="HTML")
@@ -210,7 +515,7 @@ async def cmd_register(update: Update, context: ContextTypes.DEFAULT_TYPE):
def _register_worker(loop: asyncio.AbstractEventLoop, status_msg, count: int):
"""注册工作线程"""
results = {"success": 0, "fail": 0, "accounts": []}
results = {"success": 0, "fail": 0, "accounts": [], "fail_reasons": []}
try:
# 初始化邮箱系统池
@@ -233,99 +538,159 @@ def _register_worker(loop: asyncio.AbstractEventLoop, status_msg, count: int):
).result(timeout=10)
for i in range(count):
progress = f"[{i + 1}/{count}]"
bar = _progress_bar(i, count)
step_header = f"📊 {bar}\n"
# Step 1: 创建邮箱(轮询选系统)
asyncio.run_coroutine_threadsafe(
_edit_or_send(
status_msg,
f"{progress} 创建临时邮箱...\n\n"
f"{step_header} 创建临时邮箱...\n\n"
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
),
loop,
).result(timeout=10)
# 检查停止信号
if _is_stopped():
break
random_prefix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
target_email, mail_sys = mail_pool.create_user(random_prefix)
if not target_email or not mail_sys:
results["fail"] += 1
reason = "邮箱创建失败"
results["fail_reasons"].append(reason)
account_store.record_register(False, reason)
continue
# 检查停止信号
if _is_stopped():
break
# Step 2: 发送 Magic Link
asyncio.run_coroutine_threadsafe(
_edit_or_send(
status_msg,
f"{progress} 发送 Magic Link...\n"
f"{step_header} 发送 Magic Link...\n"
f"📧 {target_email}\n\n"
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
),
loop,
).result(timeout=10)
if _is_stopped():
break
if not attack_claude(target_email):
results["fail"] += 1
reason = "Magic Link 发送失败"
results["fail_reasons"].append(reason)
account_store.record_register(False, reason)
continue
# Step 3: 等待邮件(使用创建时对应的系统)
asyncio.run_coroutine_threadsafe(
_edit_or_send(
status_msg,
f"{progress} 等待 Claude 邮件...\n"
f"{step_header} 等待 Claude 邮件...\n"
f"📧 {target_email}\n\n"
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
),
loop,
).result(timeout=10)
email_content = mail_sys.wait_for_email(target_email)
email_content = mail_sys.wait_for_email(target_email, stop_check=_is_stopped)
if _is_stopped():
break
if not email_content:
results["fail"] += 1
reason = "邮件接收超时"
results["fail_reasons"].append(reason)
account_store.record_register(False, reason)
continue
magic_link = extract_magic_link(email_content)
if not magic_link:
results["fail"] += 1
reason = "Magic Link 解析失败"
results["fail_reasons"].append(reason)
account_store.record_register(False, reason)
continue
# Step 4: 交换 SessionKey
asyncio.run_coroutine_threadsafe(
_edit_or_send(
status_msg,
f"{progress} 交换 SessionKey...\n"
f"{step_header} 交换 SessionKey...\n"
f"📧 {target_email}\n\n"
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
),
loop,
).result(timeout=10)
if _is_stopped():
break
account = finalize_login(magic_link)
if account:
results["success"] += 1
results["accounts"].append(account)
# 保存到文件
with open("accounts.txt", "a") as f:
f.write(f"{account.email}|{account.session_key}|{account.org_uuid}\n")
account_store.append(account.email, account.session_key, account.org_uuid)
account_store.record_register(True)
else:
results["fail"] += 1
reason = "SessionKey 交换失败"
results["fail_reasons"].append(reason)
account_store.record_register(False, reason)
# 间隔防止限流
if i < count - 1:
time.sleep(2)
# 最终汇报
stopped = _is_stopped()
done_bar = _progress_bar(results["success"] + results["fail"], count)
title = "⏹ <b>注册已中断</b>" if stopped else "🏁 <b>注册完成</b>"
report = (
f"🏁 <b>注册完成</b>\n\n"
f"{title}\n"
f"📊 {done_bar}\n\n"
f"✅ 成功: {results['success']}\n"
f"❌ 失败: {results['fail']}\n"
)
# 失败原因汇总
if results["fail_reasons"]:
from collections import Counter
reason_counts = Counter(results["fail_reasons"])
report += "\n<b>失败原因:</b>\n"
for reason, cnt in reason_counts.most_common():
report += f"{reason}: {cnt}\n"
if results["accounts"]:
report += "\n<b>新注册账号:</b>\n"
for acc in results["accounts"]:
report += f"• <code>{acc.email}</code>\n"
report += (
f"• <code>{acc.email}</code>\n"
f" SK: <code>{acc.session_key}</code>\n"
)
# Telegram 消息限制 4096 字符
if len(report) > 4000:
report = report[:4000] + "\n...(已截断,使用 /accounts 查看完整列表)"
keyboard = [[InlineKeyboardButton("📥 导出 JSON 文件", callback_data="export_accounts_json")]]
reply_markup = InlineKeyboardMarkup(keyboard)
async def _send_final():
try:
await status_msg.edit_text(report, parse_mode="HTML", reply_markup=reply_markup)
except Exception:
pass
asyncio.run_coroutine_threadsafe(
_edit_or_send(status_msg, report),
_send_final(),
loop,
).result(timeout=10)
@@ -361,13 +726,9 @@ async def cmd_check(update: Update, context: ContextTypes.DEFAULT_TYPE):
return
# 读取可用账号
try:
with open("accounts.txt", "r") as f:
lines = [l.strip() for l in f if l.strip()]
except FileNotFoundError:
lines = []
acc_lines = account_store.read_lines()
if not lines:
if not acc_lines:
await update.message.reply_text("❌ 没有可用账号,请先 /register 注册一个。")
return
@@ -383,7 +744,7 @@ async def cmd_check(update: Update, context: ContextTypes.DEFAULT_TYPE):
loop = asyncio.get_event_loop()
threading.Thread(
target=_check_worker,
args=(loop, status_msg, card_line, lines[-1]),
args=(loop, status_msg, card_line, acc_lines[-1]),
daemon=True,
).start()
@@ -477,11 +838,7 @@ async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE):
return
# 读取可用账号
try:
with open("accounts.txt", "r") as f:
acc_lines = [l.strip() for l in f if l.strip()]
except FileNotFoundError:
acc_lines = []
acc_lines = account_store.read_lines()
if not acc_lines:
await update.message.reply_text("❌ 没有可用账号,请先 /register 注册一个。")
@@ -548,6 +905,9 @@ def _batch_check_worker(loop: asyncio.AbstractEventLoop, status_msg, cards: list
checker = GiftChecker(account)
for i, card_line in enumerate(cards):
if _is_stopped():
break
cc, mm, yy, cvc = card_line.split("|")
masked = f"{cc[:4]}****{cc[-4:]}"
@@ -615,6 +975,40 @@ def _batch_check_worker(loop: asyncio.AbstractEventLoop, status_msg, cards: list
_clear_task()
# ============================================================
# 回调处理 — 导出 JSON
# ============================================================
async def callback_export_json(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""处理导出 JSON 文件的回调"""
query = update.callback_query
await query.answer()
accounts = account_store.read_all()
if not accounts:
await query.message.reply_text("📭 没有账号数据。")
return
json_data = json.dumps(accounts, indent=2, ensure_ascii=False)
json_path = "accounts_export.json"
with open(json_path, "w", encoding="utf-8") as f:
f.write(json_data)
with open(json_path, "rb") as f:
await query.message.reply_document(
document=f,
filename="accounts.json",
caption=f"📋 共 {len(accounts)} 个账号",
)
# 清理临时文件
try:
os.remove(json_path)
except OSError:
pass
# ============================================================
# 启动 Bot
# ============================================================
@@ -624,9 +1018,17 @@ async def post_init(application: Application):
commands = [
BotCommand("start", "欢迎信息"),
BotCommand("register", "注册 Claude 账号 [数量]"),
BotCommand("check", "CC 检查 <卡号|月|年|CVC>"),
BotCommand("accounts", "看已注册账号"),
BotCommand("status", "当前任务状态"),
BotCommand("stop", "中断当前任务"),
BotCommand("check", "CC 检"),
BotCommand("accounts", "查看账号"),
BotCommand("delete", "删除账号"),
BotCommand("verify", "验证 SK 有效性"),
BotCommand("stats", "统计面板"),
BotCommand("mailstatus", "邮件系统状态"),
BotCommand("proxy", "代理开关 on/off"),
BotCommand("proxytest", "测试代理"),
BotCommand("proxystatus", "代理池状态"),
BotCommand("status", "任务状态"),
BotCommand("help", "命令帮助"),
]
await application.bot.set_my_commands(commands)
@@ -645,9 +1047,18 @@ def main():
app.add_handler(CommandHandler("start", cmd_start))
app.add_handler(CommandHandler("help", cmd_help))
app.add_handler(CommandHandler("register", cmd_register))
app.add_handler(CommandHandler("stop", cmd_stop))
app.add_handler(CommandHandler("check", cmd_check))
app.add_handler(CommandHandler("accounts", cmd_accounts))
app.add_handler(CommandHandler("delete", cmd_delete))
app.add_handler(CommandHandler("verify", cmd_verify))
app.add_handler(CommandHandler("stats", cmd_stats))
app.add_handler(CommandHandler("mailstatus", cmd_mailstatus))
app.add_handler(CommandHandler("proxytest", cmd_proxytest))
app.add_handler(CommandHandler("proxystatus", cmd_proxystatus))
app.add_handler(CommandHandler("proxy", cmd_proxy))
app.add_handler(CommandHandler("status", cmd_status))
app.add_handler(CallbackQueryHandler(callback_export_json, pattern="^export_accounts_json$"))
app.add_handler(MessageHandler(filters.Document.ALL, handle_document))
logger.info("🤖 Bot 启动中...")