Initial commit: grok-free-register-oss
Open-source Grok free registration CLI, xai_enroller auth pipeline, local auth service, tests and docs.
This commit is contained in:
commit
d10009d639
72 files changed
+18752
No files matched your search
Executable
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""通过 SSH 导出不含密码的注册会话;兼容历史裸 SSO 记录。"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
COOKIE_FIELDS = frozenset(
|
||||
{
|
||||
"name",
|
||||
"value",
|
||||
"url",
|
||||
"domain",
|
||||
"path",
|
||||
"expires",
|
||||
"httpOnly",
|
||||
"secure",
|
||||
"sameSite",
|
||||
}
|
||||
)
|
||||
MAX_RECORD_BYTES = 256 * 1024
|
||||
LEGACY_COOKIE_SCOPE = {
|
||||
"name": "sso",
|
||||
"domain": "accounts.x.ai",
|
||||
"path": "/",
|
||||
"secure": True,
|
||||
"httpOnly": True,
|
||||
"sameSite": "Lax",
|
||||
}
|
||||
|
||||
|
||||
def _decode_json_line(raw, label):
|
||||
if len(raw) > MAX_RECORD_BYTES:
|
||||
raise ValueError(f"invalid {label} record")
|
||||
try:
|
||||
document = json.loads(raw.decode("utf-8"))
|
||||
email = document["email"]
|
||||
cookies = document["cookies"]
|
||||
except (UnicodeDecodeError, TypeError, ValueError, KeyError) as exc:
|
||||
raise ValueError(f"invalid {label} record") from exc
|
||||
if not isinstance(email, str) or not email or not isinstance(cookies, list) or not cookies:
|
||||
raise ValueError(f"invalid {label} record")
|
||||
try:
|
||||
email.encode("utf-8")
|
||||
except UnicodeEncodeError as exc:
|
||||
raise ValueError(f"invalid {label} record") from exc
|
||||
normalized = []
|
||||
for cookie in cookies:
|
||||
if not isinstance(cookie, dict):
|
||||
raise ValueError(f"invalid {label} record")
|
||||
filtered = {key: cookie[key] for key in COOKIE_FIELDS if key in cookie}
|
||||
if not all(
|
||||
isinstance(filtered.get(key), str) and filtered[key]
|
||||
for key in ("name", "value")
|
||||
):
|
||||
raise ValueError(f"invalid {label} record")
|
||||
scope = filtered.get("domain") or filtered.get("url")
|
||||
if not isinstance(scope, str) or not scope:
|
||||
raise ValueError(f"invalid {label} record")
|
||||
try:
|
||||
filtered["name"].encode("utf-8")
|
||||
filtered["value"].encode("utf-8")
|
||||
scope.encode("utf-8")
|
||||
except UnicodeEncodeError as exc:
|
||||
raise ValueError(f"invalid {label} record") from exc
|
||||
normalized.append(filtered)
|
||||
return {"email": email, "cookies": normalized}
|
||||
|
||||
|
||||
def _complete_lines(data):
|
||||
"""Return newline-terminated records and the unconsumed trailing bytes."""
|
||||
parts = data.split(b"\n")
|
||||
return parts[:-1], parts[-1]
|
||||
|
||||
|
||||
def _read_complete_file(path):
|
||||
if not path.exists():
|
||||
return []
|
||||
lines, _incomplete = _complete_lines(path.read_bytes())
|
||||
return [line for line in lines if line]
|
||||
|
||||
|
||||
def load_snapshots(path, *, raw_lines=None):
|
||||
snapshots = {}
|
||||
scopes = {}
|
||||
lines = _read_complete_file(path) if raw_lines is None else raw_lines
|
||||
for line in lines:
|
||||
document = _decode_json_line(line, "session")
|
||||
email = document["email"]
|
||||
cookies = document["cookies"]
|
||||
if email in snapshots:
|
||||
continue
|
||||
snapshots[email] = {"email": email, "cookies": cookies}
|
||||
for cookie in cookies:
|
||||
name = cookie.get("name")
|
||||
domain = cookie.get("domain")
|
||||
if name in {"sso", "sso-rw"} and domain:
|
||||
scopes[(name, domain)] = {
|
||||
"name": name,
|
||||
"domain": domain,
|
||||
"path": cookie.get("path", "/"),
|
||||
"secure": bool(cookie.get("secure", True)),
|
||||
"httpOnly": bool(cookie.get("httpOnly", True)),
|
||||
"sameSite": cookie.get("sameSite", "Lax"),
|
||||
}
|
||||
return snapshots, scopes
|
||||
|
||||
|
||||
def export_sessions(sessions_path, accounts_path, *, session_lines=None):
|
||||
snapshots, scopes = load_snapshots(sessions_path, raw_lines=session_lines)
|
||||
for document in snapshots.values():
|
||||
yield document
|
||||
if not accounts_path.exists():
|
||||
return
|
||||
legacy_scopes = list(scopes.values()) or [LEGACY_COOKIE_SCOPE]
|
||||
for raw in _read_complete_file(accounts_path):
|
||||
try:
|
||||
email, _password, sso = raw.decode("utf-8").rsplit(":", 2)
|
||||
except (UnicodeDecodeError, ValueError) as exc:
|
||||
raise ValueError("invalid account record") from exc
|
||||
if not email or not sso or email in snapshots:
|
||||
continue
|
||||
cookies = [{**scope, "value": sso} for scope in legacy_scopes]
|
||||
yield {"email": email, "cookies": cookies}
|
||||
|
||||
|
||||
def _write_document(document):
|
||||
payload = json.dumps(document, separators=(",", ":"))
|
||||
if len(payload.encode("utf-8")) > MAX_RECORD_BYTES:
|
||||
raise ValueError("invalid session record")
|
||||
print(payload, flush=False)
|
||||
|
||||
|
||||
def export_and_follow(sessions_path, accounts_path, *, poll_seconds=0.25):
|
||||
"""Emit a complete snapshot, then losslessly follow the same JSONL fd."""
|
||||
sessions_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not sessions_path.exists():
|
||||
fd = os.open(sessions_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
||||
os.close(fd)
|
||||
os.chmod(sessions_path, 0o600)
|
||||
stream = sessions_path.open("rb")
|
||||
|
||||
with stream:
|
||||
opened = os.fstat(stream.fileno())
|
||||
initial_lines, pending = _complete_lines(stream.read())
|
||||
if len(pending) > MAX_RECORD_BYTES:
|
||||
raise ValueError("invalid session record")
|
||||
for document in export_sessions(
|
||||
sessions_path,
|
||||
accounts_path,
|
||||
session_lines=[line for line in initial_lines if line],
|
||||
):
|
||||
_write_document(document)
|
||||
sys.stdout.flush()
|
||||
|
||||
while True:
|
||||
chunk = stream.read()
|
||||
if chunk:
|
||||
complete, pending = _complete_lines(pending + chunk)
|
||||
if len(pending) > MAX_RECORD_BYTES:
|
||||
raise ValueError("invalid session record")
|
||||
for raw in complete:
|
||||
if raw:
|
||||
_write_document(_decode_json_line(raw, "session"))
|
||||
sys.stdout.flush()
|
||||
continue
|
||||
|
||||
try:
|
||||
current = sessions_path.stat()
|
||||
except FileNotFoundError:
|
||||
return 3
|
||||
if (
|
||||
current.st_dev != opened.st_dev
|
||||
or current.st_ino != opened.st_ino
|
||||
or current.st_size < stream.tell()
|
||||
):
|
||||
return 3
|
||||
time.sleep(poll_seconds)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--follow", action="store_true")
|
||||
parser.add_argument("sessions_path", type=Path)
|
||||
parser.add_argument("accounts_path", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.follow:
|
||||
raise SystemExit(export_and_follow(args.sessions_path, args.accounts_path))
|
||||
for document in export_sessions(args.sessions_path, args.accounts_path):
|
||||
_write_document(document)
|
||||
except ValueError:
|
||||
raise SystemExit(4) from None
|
||||
except BrokenPipeError:
|
||||
try:
|
||||
sys.stdout.close()
|
||||
finally:
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user