Replace repository with grok-reg-protocol-cpa project

Overwrite previous grok-keygen contents with the local registration/CPA protocol codebase (secrets and runtime data excluded).
This commit is contained in:
chaos committed 2026-07-11 17:25:37 +08:00
commit 6f7c949681
33 files changed
+11125

No files matched your search

+674
View File
@@ -0,0 +1,674 @@
"""CLI wrapper for grok_register_ttk — multi-thread register + async CPA mint pipeline.
Architecture:
Register workers (R) → accounts_cli + mint_queue
Mint workers (M) → cpa_auths/xai-*.json + optional hotload
Browser lifecycle:
- One Chromium per register worker, reused via TabPool.clear_session
- Full recycle every N accounts or on error
- Register browser released BEFORE mint (mint always standalone Chromium)
- Peak browsers ≈ R + M (not 2×R)
"""
from __future__ import annotations
import argparse
import os
import queue
import sys
import threading
import time
import traceback
from typing import Any
# 强制走本目录的 grok_register_ttk
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import grok_register_ttk as reg # noqa: E402
# Linux 适配: DrissionPage 默认找 'chrome', 我们装的是 chromium
# 保留原版 slim flags + proxy,再补 chromium 路径与 turnstilePatch。
_orig_create_browser_options = reg.create_browser_options
def _patched_create_browser_options():
# Prefer original factory (proxy + CHROMIUM_SLIM_FLAGS + extension)
try:
opts = _orig_create_browser_options()
except Exception:
from DrissionPage import ChromiumOptions
opts = ChromiumOptions()
opts.auto_port()
opts.set_timeouts(base=1)
for flag in getattr(reg, "CHROMIUM_SLIM_FLAGS", ()) or ():
try:
opts.set_argument(flag)
except Exception:
pass
try:
opts.auto_port()
except Exception:
pass
try:
opts.set_timeouts(base=1)
except Exception:
pass
for cand in (
"/snap/bin/chromium",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
):
if os.path.isfile(cand):
try:
opts.set_browser_path(cand)
except Exception:
pass
break
ext_path = os.path.join(os.path.dirname(os.path.abspath(reg.__file__)), "turnstilePatch")
if os.path.isdir(ext_path):
try:
opts.add_extension(ext_path)
except Exception:
pass
return opts
reg.create_browser_options = _patched_create_browser_options
# ── 线程安全日志 ──
_log_queue: queue.Queue = queue.Queue()
def _log_writer():
while True:
msg = _log_queue.get()
if msg is None:
break
print(msg, flush=True)
def log(worker_id: int | str, msg: str) -> None:
_log_queue.put(f"[{time.strftime('%H:%M:%S')}] [W{worker_id}] {msg}")
# ── 统计 ──
_stats_lock = threading.Lock()
_stats = {
"reg_success": 0,
"reg_fail": 0,
"mint_success": 0,
"mint_fail": 0,
"mint_skip": 0,
}
def _inc(key: str, n: int = 1) -> None:
with _stats_lock:
_stats[key] = _stats.get(key, 0) + n
# forever 任务索引
_next_idx_lock = threading.Lock()
_next_idx = [1]
# mint 队列结束哨兵
_MINT_STOP = object()
def resolve_mint_workers(
*,
cli_value: int,
threads: int,
config: dict,
inline_mint: bool,
) -> int:
"""Resolve mint worker count.
Priority: --inline-mint > CLI --mint-workers (>=0) > config cpa_mint_workers > auto.
auto (-1): min(threads, 4) when CPA export enabled, else 0.
0: inline mint on register threads.
"""
if inline_mint:
return 0
if cli_value >= 0:
return max(0, min(int(cli_value), 10))
cfg_v = config.get("cpa_mint_workers", -1)
try:
cfg_v = int(cfg_v)
except Exception:
cfg_v = -1
if cfg_v >= 0:
return max(0, min(cfg_v, 10))
# auto
if config.get("cpa_export_enabled", True):
return max(1, min(int(threads), 4))
return 0
def resolve_mint_queue_max(config: dict, mint_workers: int, cli_value: int | None = None) -> int:
if cli_value is not None and cli_value >= 0:
return int(cli_value)
try:
v = int(config.get("cpa_mint_queue_max", 0) or 0)
except Exception:
v = 0
if v > 0:
return v
# default backpressure: 2 × mint workers (0 if no mint pool)
return max(0, mint_workers * 2) if mint_workers > 0 else 0
class DummyStop:
def __call__(self) -> bool:
return False
def _is_hotmail_provider() -> bool:
try:
provider = reg.get_email_provider()
except Exception:
provider = (getattr(reg, "config", {}) or {}).get("email_provider", "")
return str(provider or "").strip().lower() in {"hotmail", "outlook", "outlookmail", "microsoft"}
def _mark_email_stage_error(email: str, reason: str) -> None:
"""Persist failed Hotmail/Outlook aliases so the next run does not reuse them."""
if not email or not _is_hotmail_provider():
return
try:
reg.mark_error(email, reason=str(reason)[:120])
except Exception:
pass
def _ensure_browser(worker_id: int, force_recycle: bool = False):
"""Start browser if missing; optional full recycle."""
if force_recycle:
try:
reg.stop_browser()
except Exception:
pass
if reg.TabPool.get_browser() is None:
reg.start_browser(log_callback=lambda m: log(worker_id, m))
def register_one(
worker_id: int,
idx: int,
total: int,
accounts_file: str,
*,
do_mint_inline: bool = False,
mint_queue: queue.Queue | None = None,
) -> dict | None:
"""Run one registration. Enqueue CPA mint (default) instead of blocking.
Returns dict(email, sso, profile) or None.
"""
email = ""
dev_token = ""
try:
max_mail_retry = max(1, int((getattr(reg, "config", {}) or {}).get("mail_retry_count", 3) or 3))
except Exception:
max_mail_retry = 3
cancel = DummyStop()
try:
_ensure_browser(worker_id, force_recycle=False)
except Exception as exc:
log(worker_id, f"! 浏览器启动失败: {exc}")
return None
for mail_try in range(1, max_mail_retry + 1):
email = ""
dev_token = ""
try:
log(worker_id, f"--- 第 {idx}/{total} 个账号, 邮箱尝试 {mail_try}/{max_mail_retry} ---")
log(worker_id, "1. 打开注册页")
reg.open_signup_page(log_callback=lambda m: log(worker_id, m), cancel_callback=cancel)
log(worker_id, "2. 创建邮箱并提交")
email, dev_token = reg.fill_email_and_submit(
log_callback=lambda m: log(worker_id, m), cancel_callback=cancel
)
log(worker_id, f"邮箱: {email}")
log(worker_id, "3. 拉取验证码")
code = reg.fill_code_and_submit(
email,
dev_token,
log_callback=lambda m: log(worker_id, m),
cancel_callback=cancel,
)
log(worker_id, f"验证码: {code}")
break
except Exception as exc:
msg = str(exc)
if ("未收到验证码" in msg or "验证码" in msg) and mail_try < max_mail_retry:
log(worker_id, f"! 本邮箱未取到验证码,换邮箱重试: {msg}")
_mark_email_stage_error(email, msg)
try:
reg.restart_browser(log_callback=lambda m: log(worker_id, m))
except Exception:
pass
reg.sleep_with_cancel(1, cancel)
continue
log(worker_id, f"! 邮箱阶段失败: {msg}")
_mark_email_stage_error(email, msg)
traceback.print_exc()
_inc("reg_fail")
try:
reg.restart_browser(log_callback=lambda m: log(worker_id, m))
except Exception:
pass
return None
try:
log(worker_id, "4. 填写资料")
profile = reg.fill_profile_and_submit(
log_callback=lambda m: log(worker_id, m), cancel_callback=cancel
)
log(worker_id, f"资料已填: {profile.get('given_name')} {profile.get('family_name')}")
log(worker_id, "5. 等待 sso cookie")
sso = reg.wait_for_sso_cookie(
log_callback=lambda m: log(worker_id, m), cancel_callback=cancel
)
password = profile.get("password", "") or ""
line = f"{email}----{password}----{sso}\n"
with open(accounts_file, "a", encoding="utf-8") as f:
f.write(line)
log(worker_id, f"+ 注册成功: {email}")
reg.mark_used(email, password)
# Capture cookies BEFORE releasing browser (for mint cookie inject)
page = reg._get_page()
cookies = []
try:
import cpa_export as _cpa_exp
cookies = _cpa_exp.export_cookies_from_page(page) if page is not None else []
except Exception:
cookies = []
if cookies:
log(worker_id, f"[*] 导出 cookie {len(cookies)} 条供 mint 注入")
if page and reg.PERF_FLAGS.get("cookie_snapshot", True):
try:
reg.save_cookies_snapshot(page, "success", email)
except Exception:
pass
try:
reg.add_token_to_grok2api_pools(
sso, email=email, log_callback=lambda m: log(worker_id, m)
)
except Exception as exc:
log(worker_id, f"[Debug] grok2api: {exc}")
# Release / recycle register browser BEFORE mint so peak browsers ≈ R+M
try:
reg.prepare_browser_for_next_account(log_callback=lambda m: log(worker_id, m))
except Exception:
try:
reg.stop_browser()
except Exception:
pass
job = {
"email": email,
"password": password,
"sso": sso,
"profile": profile,
"idx": idx,
"cookies": cookies,
}
if do_mint_inline:
_run_mint_job(f"R{worker_id}", job, getattr(reg, "config", {}) or {})
elif mint_queue is not None:
# backpressure: wait while queue is saturated
qmax = int(getattr(mint_queue, "_reg_qmax", 0) or 0)
while qmax > 0 and mint_queue.qsize() >= qmax:
log(worker_id, f"[cpa] mint 队列背压 qsize={mint_queue.qsize()}≥{qmax},等待...")
time.sleep(1.0)
mint_queue.put(job)
log(worker_id, f"[cpa] enqueued mint for {email} (queue≈{mint_queue.qsize()})")
else:
log(worker_id, "[cpa] mint skipped (no queue / inline)")
_inc("reg_success")
return job
except Exception as exc:
log(worker_id, f"! 注册失败: {exc}")
reg.mark_error(email or "", reason=str(exc)[:120])
traceback.print_exc()
_inc("reg_fail")
try:
reg.restart_browser(log_callback=lambda m: log(worker_id, m))
except Exception:
pass
return None
def _run_mint_job(worker_id: int | str, job: dict[str, Any], config: dict) -> dict:
"""Standalone CPA mint (own Chromium). Never reuses register browser."""
email = job.get("email") or ""
password = job.get("password") or ""
if not email or not password:
_inc("mint_fail")
return {"ok": False, "error": "missing email/password", "email": email}
if not config.get("cpa_export_enabled", True):
_inc("mint_skip")
log(worker_id, f"[cpa] export disabled, skip {email}")
return {"ok": False, "skipped": True, "email": email}
try:
import cpa_export
# page=None always — force standalone path inside export
result = cpa_export.export_cpa_xai_for_account(
email,
password,
page=None,
cookies=job.get("cookies"),
sso=job.get("sso") or "",
config=config,
log_callback=lambda m: log(worker_id, m),
)
if result.get("ok"):
log(worker_id, f"+ CPA auth: {result.get('path')}")
_inc("mint_success")
elif result.get("skipped"):
_inc("mint_skip")
log(worker_id, f"[cpa] skipped: {result.get('reason')}")
else:
_inc("mint_fail")
log(worker_id, f"! CPA auth 未成功: {result.get('error') or result}")
return result
except Exception as exc:
_inc("mint_fail")
log(worker_id, f"! CPA export 异常: {exc}")
traceback.print_exc()
return {"ok": False, "error": str(exc), "email": email}
def _register_worker(
worker_id: int,
task_queue: queue.Queue,
total: int,
accounts_file: str,
mint_queue: queue.Queue | None,
forever: bool,
do_mint_inline: bool,
):
while True:
try:
idx = task_queue.get_nowait()
except queue.Empty:
if not forever:
break
with _next_idx_lock:
nxt = _next_idx[0]
_next_idx[0] = nxt + 5
for i in range(nxt, nxt + 5):
task_queue.put(i)
continue
retry = 0
while retry < 2:
try:
result = register_one(
worker_id,
idx,
total,
accounts_file,
do_mint_inline=do_mint_inline,
mint_queue=mint_queue,
)
if result:
break
retry += 1
if retry < 2:
log(worker_id, f"[retry] 账号 {idx} 失败,重试 {retry}/1")
try:
reg.restart_browser(log_callback=lambda m: log(worker_id, m))
except Exception:
pass
except Exception:
retry += 1
if retry < 2:
log(worker_id, f"[retry] 账号 {idx} 异常,重试 {retry}/1")
traceback.print_exc()
try:
reg.restart_browser(log_callback=lambda m: log(worker_id, m))
except Exception:
pass
if retry >= 2:
# register_one already counted fail on exception path; if both returned None, count once more only if needed
pass
# worker exit: free browser
try:
reg.stop_browser()
except Exception:
pass
log(worker_id, "register worker exit")
def _mint_worker(worker_id: str, mint_queue: queue.Queue, config: dict):
while True:
job = mint_queue.get()
try:
if job is _MINT_STOP:
break
if not isinstance(job, dict):
continue
_run_mint_job(worker_id, job, config)
finally:
mint_queue.task_done()
try:
from cpa_xai.browser_confirm import shutdown_mint_browsers
shutdown_mint_browsers()
except Exception:
pass
log(worker_id, "mint worker exit")
def main() -> int:
parser = argparse.ArgumentParser(description="CLI runner for grok_register_ttk (pipelined).")
parser.add_argument("--count", type=int, default=1, help="账号总数目标(0=不限;含已有)")
parser.add_argument(
"--extra",
type=int,
default=0,
help="在已有 accounts 基础上再新注册 N 个",
)
parser.add_argument("--threads", type=int, default=1, help="注册并发线程数(1-10)")
parser.add_argument(
"--mint-workers",
type=int,
default=-1,
help="CPA mint 并发:-1=用 config/auto;0=内联;1-10=固定。覆盖 config.cpa_mint_workers",
)
parser.add_argument(
"--mint-queue-max",
type=int,
default=-1,
help="mint 队列背压上限:-1=用 config/auto(2×workers);0=不限制",
)
parser.add_argument("--accounts-file", default=os.path.join(os.path.dirname(__file__), "accounts_cli.txt"))
parser.add_argument("--fast", action="store_true", default=True, help="快速模式(默认开):压缩 sleep、关截图")
parser.add_argument("--no-fast", action="store_true", help="关闭快速模式")
parser.add_argument("--no-browser-reuse", action="store_true", help="每号强制 quit 浏览器")
parser.add_argument("--browser-recycle-every", type=int, default=25, help="复用 N 次后完整回收")
parser.add_argument("--cookie-snapshot", action="store_true", help="注册成功写 cookie 快照(默认关,fast)")
parser.add_argument("--inline-mint", action="store_true", help="强制注册线程内联 mint(调试用)")
args = parser.parse_args()
reg.load_config()
cfg0 = getattr(reg, "config", {}) or {}
threads = max(1, min(args.threads, 10))
fast = bool(args.fast) and not bool(args.no_fast)
mint_workers = resolve_mint_workers(
cli_value=args.mint_workers,
threads=threads,
config=cfg0,
inline_mint=bool(args.inline_mint),
)
do_mint_inline = mint_workers == 0
mint_qmax = resolve_mint_queue_max(
cfg0,
mint_workers,
cli_value=(None if args.mint_queue_max < 0 else args.mint_queue_max),
)
# perf knobs
reg.configure_perf(
fast=fast,
sleep_scale=0.15 if fast else 1.0,
skip_debug_io=fast,
cookie_snapshot=bool(args.cookie_snapshot) or not fast,
async_side_effects=True,
browser_reuse=not args.no_browser_reuse,
browser_recycle_every=max(1, int(args.browser_recycle_every)),
)
# 断点续跑
done_count = 0
if os.path.exists(args.accounts_file):
with open(args.accounts_file) as f:
done_count = sum(1 for line in f if line.strip())
if args.extra and args.extra > 0:
target_total = done_count + args.extra
remaining = args.extra
print(
f"[*] 配置加载完成,额外新注册 {args.extra} 个(当前已有 {done_count} → 目标 {target_total}),"
f"注册线程={threads} mint_workers={mint_workers} mint_queue_max={mint_qmax} fast={fast}",
flush=True,
)
args.count = target_total
elif args.count == 0:
remaining = None
print(
f"[*] 配置加载完成,不限数量,注册线程={threads} mint_workers={mint_workers} mint_queue_max={mint_qmax} fast={fast}",
flush=True,
)
else:
remaining = max(0, args.count - done_count)
print(
f"[*] 配置加载完成,目标 {args.count} 个账号,注册线程={threads} "
f"mint_workers={mint_workers} mint_queue_max={mint_qmax} fast={fast}",
flush=True,
)
print(f"[*] accounts_file = {args.accounts_file}", flush=True)
if done_count > 0:
print(f"[*] 断点续跑:已完成 {done_count}", flush=True)
if remaining is not None and remaining <= 0:
print("[*] 所有账号已完成,无需继续(可用 --extra N 再注册)", flush=True)
return 0
log_thread = threading.Thread(target=_log_writer, daemon=True)
log_thread.start()
try:
reg.TabPool.init(reg.create_browser_options, log_callback=lambda m: log(0, m))
except Exception as exc:
print(f"[!] 浏览器初始化失败: {exc}", flush=True)
return 1
task_queue: queue.Queue = queue.Queue()
mint_queue: queue.Queue | None = queue.Queue() if not do_mint_inline else None
if mint_queue is not None:
mint_queue._reg_qmax = mint_qmax # type: ignore[attr-defined]
global _next_idx
_next_idx[0] = done_count + 1
if remaining is not None:
for i in range(done_count + 1, args.count + 1):
task_queue.put(i)
else:
for i in range(done_count + 1, done_count + threads * 5 + 1):
task_queue.put(i)
_next_idx[0] = done_count + threads * 5 + 1
forever = remaining is None
cfg = getattr(reg, "config", {}) or {}
# mint workers first (so queue consumers ready)
mint_threads: list[threading.Thread] = []
if mint_queue is not None and mint_workers > 0:
for i in range(1, mint_workers + 1):
wid = f"M{i}"
t = threading.Thread(
target=_mint_worker,
args=(wid, mint_queue, cfg),
daemon=True,
name=f"mint-{i}",
)
t.start()
mint_threads.append(t)
reg_threads: list[threading.Thread] = []
for wid in range(1, threads + 1):
t = threading.Thread(
target=_register_worker,
args=(wid, task_queue, args.count, args.accounts_file, mint_queue, forever, do_mint_inline),
daemon=True,
name=f"reg-{wid}",
)
t.start()
reg_threads.append(t)
try:
for t in reg_threads:
t.join()
except KeyboardInterrupt:
print("\n[!] 用户中断", flush=True)
# drain mint queue
if mint_queue is not None:
log(0, f"[cpa] 等待 mint 队列清空(qsize≈{mint_queue.qsize()})...")
mint_queue.join()
for _ in mint_threads:
mint_queue.put(_MINT_STOP)
for t in mint_threads:
t.join(timeout=600)
try:
reg.shutdown_browser()
except Exception:
pass
# stop side-effect pool
try:
pool = getattr(reg, "_side_effect_pool", None)
if pool is not None:
pool.shutdown(wait=False, cancel_futures=True)
except Exception:
pass
_log_queue.put(None)
log_thread.join(timeout=2)
with _stats_lock:
s = dict(_stats)
print(
f"=== 完成: 注册成功 {s.get('reg_success', 0)}, 注册失败 {s.get('reg_fail', 0)}, "
f"CPA成功 {s.get('mint_success', 0)}, CPA失败 {s.get('mint_fail', 0)}, "
f"CPA跳过 {s.get('mint_skip', 0)} ===",
flush=True,
)
return 0 if s.get("reg_success", 0) > 0 else 1
if __name__ == "__main__":
sys.exit(main())