From 768963666a021edb17650821913efc3c97b3af4f Mon Sep 17 00:00:00 2001 From: kyx236 Date: Sat, 14 Feb 2026 02:36:51 +0800 Subject: [PATCH] feat: Add a thread-safe account store module for managing accounts and recording statistics, and integrate it into the bot. --- bot.py | 160 +++++++++++++++++++++++++++++++++++++++++- core/account_store.py | 32 +++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 0f5148f..4ef9321 100644 --- a/bot.py +++ b/bot.py @@ -646,7 +646,7 @@ async def cmd_delete(update: Update, context: ContextTypes.DEFAULT_TYPE): @restricted async def cmd_verify(update: Update, context: ContextTypes.DEFAULT_TYPE): - """/verify — 检测已保存的 Session Key 是否仍然有效""" + """/verify — 检测已保存的 Session Key 是否仍然有效,自动删除被封禁的账号""" accounts = account_store.read_all() if not accounts: @@ -693,6 +693,12 @@ async def cmd_verify(update: Update, context: ContextTypes.DEFAULT_TYPE): valid = sum(1 for r in results if r["ok"]) invalid = len(results) - valid + # 自动删除被封禁的账号 + banned_emails = [r["email"] for r in results if r["reason"] == "被封禁"] + deleted_count = 0 + if banned_emails: + deleted_count = account_store.delete_by_emails(banned_emails) + text = ( f"🔑 账号验证结果\n\n" f"✅ 有效: {valid} ❌ 无效: {invalid}\n\n" @@ -701,6 +707,10 @@ async def cmd_verify(update: Update, context: ContextTypes.DEFAULT_TYPE): icon = "✅" if r["ok"] else "❌" text += f"{i}. {icon} {r['email']} — {r['reason']}\n" + if deleted_count > 0: + text += f"\n🗑 已自动删除 {deleted_count} 个封禁账号\n" + text += f"📦 剩余 {account_store.count()} 个账号" + if len(text) > 4000: text = text[:4000] + "\n...(已截断)" @@ -975,6 +985,112 @@ def _register_worker(loop: asyncio.AbstractEventLoop, status_msg, count: int): _clear_task() +# ============================================================ +# 自动注册辅助函数 +# ============================================================ + +def _auto_register_sync(loop: asyncio.AbstractEventLoop, status_msg, count: int = 5) -> bool: + """同步执行注册流程,返回是否至少注册了 1 个账号。 + + 此函数在后台线程中被调用,用于 /check 和文件上传时自动注册账号。 + """ + success_count = 0 + + try: + mail_pool = MailPool(MAIL_SYSTEMS) + if mail_pool.count == 0: + asyncio.run_coroutine_threadsafe( + _edit_or_send(status_msg, "❌ 没有可用的邮箱系统,无法自动注册!"), + loop, + ).result(timeout=10) + return False + + asyncio.run_coroutine_threadsafe( + _edit_or_send( + status_msg, + f"📭 没有可用账号,正在自动注册 {count} 个...\n" + f"📧 邮箱系统就绪({mail_pool.count} 个)", + ), + loop, + ).result(timeout=10) + + for i in range(count): + if _is_stopped(): + break + + bar = _progress_bar(i, count) + asyncio.run_coroutine_threadsafe( + _edit_or_send( + status_msg, + f"📭 自动注册中...\n{bar}\n✅ 成功: {success_count}", + ), + loop, + ).result(timeout=10) + + # Step 1: 创建邮箱 + 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: + account_store.record_register(False, "邮箱创建失败") + continue + + if _is_stopped(): + break + + # Step 2: 发送 Magic Link + if not attack_claude(target_email): + account_store.record_register(False, "Magic Link 发送失败") + continue + + # Step 3: 等待邮件 + email_content = mail_sys.wait_for_email(target_email, stop_check=_is_stopped) + if _is_stopped(): + break + if not email_content: + account_store.record_register(False, "邮件接收超时") + continue + + magic_link = extract_magic_link(email_content) + if not magic_link: + account_store.record_register(False, "Magic Link 解析失败") + continue + + if _is_stopped(): + break + + # Step 4: 交换 SessionKey + account = finalize_login(magic_link) + if account: + success_count += 1 + account_store.append(account.email, account.session_key, account.org_uuid) + account_store.record_register(True) + else: + account_store.record_register(False, "SessionKey 交换失败") + + # 间隔防止限流 + if i < count - 1: + time.sleep(2) + + asyncio.run_coroutine_threadsafe( + _edit_or_send( + status_msg, + f"{'⏹ 自动注册已中断' if _is_stopped() else '✅ 自动注册完成'}\n" + f"成功注册 {success_count} 个账号,继续执行检查...", + ), + loop, + ).result(timeout=10) + + except Exception as e: + logger.exception("自动注册异常") + asyncio.run_coroutine_threadsafe( + _edit_or_send(status_msg, f"💥 自动注册异常:{e}"), + loop, + ) + return False + + return success_count > 0 + + # ============================================================ # /check — CC 检查 # ============================================================ @@ -1010,6 +1126,27 @@ async def cmd_check(update: Update, context: ContextTypes.DEFAULT_TYPE): ) return + # 检查是否有账号,若无则自动注册 5 个 + if account_store.count() == 0: + if not _set_task("自动注册 5 个账号"): + await update.message.reply_text(f"⚠️ 已有任务在运行:{_task_name}") + return + + status_msg = await update.message.reply_text( + "📭 没有可用账号,正在自动注册 5 个...", + parse_mode="HTML", + ) + + loop = asyncio.get_event_loop() + reg_ok = await loop.run_in_executor( + None, _auto_register_sync, loop, status_msg, 5 + ) + _clear_task() + + if not reg_ok: + await _edit_or_send(status_msg, "❌ 自动注册失败,无法执行 CC 检查。") + return + # 从账号池获取空闲账号 if len(cards) == 1: acquired = account_store.acquire(1) @@ -1141,6 +1278,27 @@ async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("❌ 文件太大(最大 1MB)") return + # 检查是否有账号,若无则自动注册 5 个 + if account_store.count() == 0: + if not _set_task("自动注册 5 个账号"): + await update.message.reply_text(f"⚠️ 已有任务在运行:{_task_name}") + return + + reg_status_msg = await update.message.reply_text( + "📭 没有可用账号,正在自动注册 5 个...", + parse_mode="HTML", + ) + + loop_reg = asyncio.get_event_loop() + reg_ok = await loop_reg.run_in_executor( + None, _auto_register_sync, loop_reg, reg_status_msg, 5 + ) + _clear_task() + + if not reg_ok: + await _edit_or_send(reg_status_msg, "❌ 自动注册失败,无法执行 CC 检查。") + return + # 从账号池获取空闲账号 acquired = account_store.acquire() # 获取所有空闲 diff --git a/core/account_store.py b/core/account_store.py index cc9a1a1..cd49713 100644 --- a/core/account_store.py +++ b/core/account_store.py @@ -180,6 +180,38 @@ def delete_by_email(email: str) -> dict | None: return removed +def delete_by_emails(emails: list[str]) -> int: + """批量按邮箱删除账号,返回实际删除数量""" + if not emails: + return 0 + + email_set = set(emails) + with _lock: + try: + with open(_ACCOUNTS_FILE, "r", encoding="utf-8") as f: + lines = [line.strip() for line in f if line.strip()] + except FileNotFoundError: + return 0 + + remaining = [] + deleted = 0 + for line in lines: + parts = line.split("|") + if parts[0] in email_set: + deleted += 1 + # 同时从 busy 集合中移除 + _busy.discard(line) + else: + remaining.append(line) + + if deleted > 0: + with open(_ACCOUNTS_FILE, "w", encoding="utf-8") as f: + for line in remaining: + f.write(line + "\n") + + return deleted + + # ====== 统计数据 ====== def _load_stats() -> dict: