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:
commit
6f7c949681
33 files changed
+11125
No files matched your search
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Batch mint CPA xai-*.json from register accounts_cli.txt.
|
||||
|
||||
Default: headed Chromium + turnstilePatch (headless is Cloudflare-blocked on
|
||||
accounts.x.ai). Token poll is source of truth; consent Allow uses real click.
|
||||
|
||||
Example (from grok_reg project root):
|
||||
export DISPLAY=:0
|
||||
uv run python -u scripts/backfill_cpa_xai_from_accounts.py \\
|
||||
--limit 1 --probe
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_ROOT))
|
||||
|
||||
from cpa_xai import existing_cpa_emails, mint_and_export, parse_accounts_file # noqa: E402
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument(
|
||||
"--accounts",
|
||||
default=str(_ROOT / "accounts_cli.txt"),
|
||||
)
|
||||
ap.add_argument(
|
||||
"--out-dir",
|
||||
default=str(_ROOT / "cpa_auths"),
|
||||
help="Primary output under register machine",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--cpa-dir",
|
||||
default="",
|
||||
help="Optional CPA hot-load auth-dir; files are copied here after success",
|
||||
)
|
||||
ap.add_argument("--limit", type=int, default=0, help="0 = all missing")
|
||||
ap.add_argument("--offset", type=int, default=0)
|
||||
ap.add_argument("--email", default="", help="Only this email")
|
||||
ap.add_argument("--skip-existing", action="store_true", default=True)
|
||||
ap.add_argument("--no-skip-existing", action="store_false", dest="skip_existing")
|
||||
ap.add_argument(
|
||||
"--headless",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Headless Chromium (usually blocked by Cloudflare on accounts.x.ai)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--headed",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Show browser (default; required for stable device consent)",
|
||||
)
|
||||
ap.add_argument("--probe", action="store_true", default=True)
|
||||
ap.add_argument("--no-probe", action="store_false", dest="probe")
|
||||
ap.add_argument("--probe-chat", action="store_true", default=False)
|
||||
ap.add_argument(
|
||||
"--proxy",
|
||||
default="",
|
||||
help="Outbound proxy. Empty → read register config.json cpa_proxy/proxy, else env",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--config",
|
||||
default=str(_ROOT / "config.json"),
|
||||
help="register config.json for cpa_proxy/proxy defaults",
|
||||
)
|
||||
ap.add_argument("--timeout", type=float, default=300.0)
|
||||
ap.add_argument("--sleep", type=float, default=3.0, help="Sleep between accounts")
|
||||
ap.add_argument(
|
||||
"--fail-log",
|
||||
default=str(_ROOT / "cpa_auths" / "backfill_failed.jsonl"),
|
||||
help="Append failures JSONL",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--force-standalone",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Always open fresh Chromium (default)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.headless:
|
||||
args.headed = False
|
||||
else:
|
||||
args.headless = False
|
||||
|
||||
# Resolve proxy: CLI > config cpa_proxy/proxy > env
|
||||
if not args.proxy:
|
||||
try:
|
||||
cfg_path = Path(args.config)
|
||||
if cfg_path.is_file():
|
||||
cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
|
||||
if isinstance(cfg, dict):
|
||||
cfg = {
|
||||
k: v
|
||||
for k, v in cfg.items()
|
||||
if not (isinstance(k, str) and (k.startswith("//") or k.startswith("#")))
|
||||
}
|
||||
args.proxy = (cfg.get("cpa_proxy") or cfg.get("proxy") or "").strip()
|
||||
if not args.cpa_dir:
|
||||
args.cpa_dir = (cfg.get("cpa_hotload_dir") or "").strip()
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"warn: read config proxy failed: {e}", flush=True)
|
||||
if not args.proxy:
|
||||
args.proxy = (
|
||||
os.environ.get("https_proxy")
|
||||
or os.environ.get("HTTPS_PROXY")
|
||||
or os.environ.get("http_proxy")
|
||||
or ""
|
||||
).strip()
|
||||
print(f"proxy={args.proxy or '(none)'}", flush=True)
|
||||
|
||||
accounts = parse_accounts_file(args.accounts)
|
||||
if args.email:
|
||||
accounts = [a for a in accounts if a.email.lower() == args.email.lower()]
|
||||
accounts = accounts[args.offset :]
|
||||
|
||||
have = set()
|
||||
if args.skip_existing:
|
||||
have |= {e.lower() for e in existing_cpa_emails(args.out_dir)}
|
||||
if args.cpa_dir:
|
||||
have |= {e.lower() for e in existing_cpa_emails(args.cpa_dir)}
|
||||
|
||||
todo = []
|
||||
for a in accounts:
|
||||
if args.skip_existing and a.email.lower() in have:
|
||||
continue
|
||||
todo.append(a)
|
||||
if args.limit and len(todo) >= args.limit:
|
||||
break
|
||||
|
||||
print(f"accounts total={len(parse_accounts_file(args.accounts))} todo={len(todo)} out={args.out_dir}")
|
||||
Path(args.out_dir).mkdir(parents=True, exist_ok=True)
|
||||
if args.cpa_dir:
|
||||
Path(args.cpa_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
ok_n = fail_n = 0
|
||||
results = []
|
||||
for i, acc in enumerate(todo, 1):
|
||||
print(f"\n=== [{i}/{len(todo)}] {acc.email} ===", flush=True)
|
||||
|
||||
def log(msg: str, _email=acc.email) -> None:
|
||||
print(f"[{time.strftime('%H:%M:%S')}] [{_email}] {msg}", flush=True)
|
||||
|
||||
r = mint_and_export(
|
||||
email=acc.email,
|
||||
password=acc.password,
|
||||
auth_dir=args.out_dir,
|
||||
page=None,
|
||||
proxy=args.proxy or None,
|
||||
headless=args.headless,
|
||||
probe=args.probe,
|
||||
probe_chat=args.probe_chat,
|
||||
browser_timeout_sec=args.timeout,
|
||||
force_standalone=args.force_standalone,
|
||||
sso=acc.sso or None,
|
||||
prefer_protocol=True,
|
||||
log=log,
|
||||
)
|
||||
results.append(r)
|
||||
if r.get("ok") and r.get("path"):
|
||||
ok_n += 1
|
||||
# mirror into CPA auth-dir
|
||||
if args.cpa_dir:
|
||||
src = Path(r["path"])
|
||||
dst = Path(args.cpa_dir) / src.name
|
||||
shutil.copy2(src, dst)
|
||||
os.chmod(dst, 0o600)
|
||||
print(f"copied -> {dst}", flush=True)
|
||||
else:
|
||||
fail_n += 1
|
||||
if args.fail_log:
|
||||
Path(args.fail_log).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(args.fail_log, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
||||
if args.sleep and i < len(todo):
|
||||
time.sleep(args.sleep)
|
||||
|
||||
print(f"\n=== done ok={ok_n} fail={fail_n} ===", flush=True)
|
||||
summary = Path(args.out_dir) / f"backfill_summary_{int(time.time())}.json"
|
||||
summary.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"summary {summary}")
|
||||
return 0 if ok_n > 0 or not todo else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user