""" OpenAI 协议注册机 (Protocol Keygen) v5 — 全流程纯 HTTP 实现 ======================================================== 协议注册机实现 核心架构(全流程纯 HTTP,零浏览器依赖): 【注册流程】全步骤纯 HTTP: 步骤0:GET /oauth/authorize → 获取 login_session cookie(PKCE + screen_hint=signup) 步骤0:POST /api/accounts/authorize/continue → 提交邮箱(需 sentinel token) 步骤2:POST /api/accounts/user/register → 注册用户(username+password,需 sentinel) 步骤3:GET /api/accounts/email-otp/send → 触发验证码发送 步骤4:POST /api/accounts/email-otp/validate → 提交邮箱验证码 步骤5:POST /api/accounts/create_account → 提交姓名+生日完成注册 【OAuth 登录流程】纯 HTTP(perform_codex_oauth_login_http): 步骤1:GET /oauth/authorize → 获取 login_session 步骤2:POST /api/accounts/authorize/continue → 提交邮箱 步骤3:POST /api/accounts/password/verify → 提交密码 步骤4:consent 多步流程 → 提取 code → POST /oauth/token 换取 tokens Sentinel Token PoW 生成(纯 Python,逆向 SDK JS 的 PoW 算法): - FNV-1a 哈希 + xorshift 混合 - 伪造浏览器环境数据数组 - 暴力搜索直到哈希前缀 ≤ 难度阈值 - t 字段传空字符串(服务端不校验),c 字段从 sentinel API 实时获取 关键协议字段(逆向还原): - oai-client-auth-session: OAuth 流程中由服务端 Set-Cookie 设置的会话 cookie - openai-sentinel-token: JSON 对象 {p, t, c, id, flow} - Cookie 链式传递: 每步 Set-Cookie 自动累积 - oai-did: 设备唯一标识(UUID v4) 环境依赖: pip install requests """ import json import os import re import sys import time import uuid import math import random import string import secrets import hashlib import base64 import threading from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timezone, timedelta from urllib.parse import urlparse, parse_qs, urlencode, quote import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # =================== 配置加载 =================== def load_config(): """加载外部配置文件""" config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") if not os.path.exists(config_path): raise FileNotFoundError(f"config.json 未找到: {config_path}") with open(config_path, "r", encoding="utf-8") as f: return json.load(f) _config = load_config() # 基础配置 TOTAL_ACCOUNTS = _config.get("total_accounts", 30) CONCURRENT_WORKERS = _config.get("concurrent_workers", 1) # 并发数(默认串行) HEADLESS = _config.get("headless", False) # 是否无头模式运行浏览器 PROXY = _config.get("proxy", "") # 邮箱配置 CF_WORKER_DOMAIN = _config.get("cf_worker_domain", "email.tuxixilax.cfd") CF_EMAIL_DOMAIN = _config.get("cf_email_domain", "tuxixilax.cfd") CF_ADMIN_PASSWORD = _config.get("cf_admin_password", "") # OAuth 配置 OAUTH_ISSUER = _config.get("oauth_issuer", "https://auth.openai.com") OAUTH_CLIENT_ID = _config.get("oauth_client_id", "app_EMoamEEZ73f0CkXaXp7hrann") OAUTH_REDIRECT_URI = _config.get("oauth_redirect_uri", "http://localhost:1455/auth/callback") # 上传配置 UPLOAD_API_URL = _config.get("upload_api_url", "") UPLOAD_API_TOKEN = _config.get("upload_api_token", "") # 输出文件 ACCOUNTS_FILE = _config.get("accounts_file", "accounts.txt") CSV_FILE = _config.get("csv_file", "registered_accounts.csv") AK_FILE = _config.get("ak_file", "ak.txt") RK_FILE = _config.get("rk_file", "rk.txt") # 并发文件写入锁(多线程共享文件时防止数据竞争) _file_lock = threading.Lock() # OpenAI 认证域名 OPENAI_AUTH_BASE = "https://auth.openai.com" # ChatGPT 域名(用于 OAuth 登录获取 Token) CHATGPT_BASE = "https://chatgpt.com" # =================== HTTP 会话管理 =================== def create_session(): """创建带重试策略的 HTTP 会话""" session = requests.Session() retry = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504]) adapter = HTTPAdapter(max_retries=retry) session.mount("https://", adapter) session.mount("http://", adapter) if PROXY: session.proxies = {"http": PROXY, "https": PROXY} return session # 使用普通 session(全流程纯 HTTP,无需浏览器) # =================== 工具函数 =================== # 浏览器 UA(需与 sec-ch-ua 版本一致) USER_AGENT = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/145.0.0.0 Safari/537.36" ) # API 请求头模板(从 cURL 逆向提取) COMMON_HEADERS = { "accept": "application/json", "accept-language": "en-US,en;q=0.9", "content-type": "application/json", "origin": OPENAI_AUTH_BASE, "user-agent": USER_AGENT, "sec-ch-ua": '"Google Chrome";v="145", "Not?A_Brand";v="8", "Chromium";v="145"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", } # 页面导航请求头(用于 GET 类请求) NAVIGATE_HEADERS = { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "accept-language": "en-US,en;q=0.9", "user-agent": USER_AGENT, "sec-ch-ua": '"Google Chrome";v="145", "Not?A_Brand";v="8", "Chromium";v="145"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", } def generate_device_id(): """生成设备唯一标识(oai-did),UUID v4 格式""" return str(uuid.uuid4()) def generate_random_password(length=16): """生成符合 OpenAI 要求的随机密码""" chars = string.ascii_letters + string.digits + "!@#$%" pwd = list( random.choice(string.ascii_uppercase) + random.choice(string.ascii_lowercase) + random.choice(string.digits) + random.choice("!@#$%") + "".join(random.choice(chars) for _ in range(length - 4)) ) random.shuffle(pwd) return "".join(pwd) def generate_random_name(): """随机生成自然的英文姓名""" first = [ "James", "Robert", "John", "Michael", "David", "William", "Richard", "Mary", "Jennifer", "Linda", "Elizabeth", "Susan", "Jessica", "Sarah", "Emily", "Emma", "Olivia", "Sophia", "Liam", "Noah", "Oliver", "Ethan", ] last = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Martin", ] return random.choice(first), random.choice(last) def generate_random_birthday(): """生成随机生日字符串,格式 YYYY-MM-DD(20~30岁)""" year = random.randint(1996, 2006) month = random.randint(1, 12) day = random.randint(1, 28) return f"{year:04d}-{month:02d}-{day:02d}" def generate_datadog_trace(): """生成 Datadog APM 追踪头(从 cURL 中逆向提取的格式)""" trace_id = str(random.getrandbits(64)) parent_id = str(random.getrandbits(64)) trace_hex = format(int(trace_id), '016x') parent_hex = format(int(parent_id), '016x') return { "traceparent": f"00-0000000000000000{trace_hex}-{parent_hex}-01", "tracestate": "dd=s:1;o:rum", "x-datadog-origin": "rum", "x-datadog-parent-id": parent_id, "x-datadog-sampling-priority": "1", "x-datadog-trace-id": trace_id, } def generate_pkce(): """生成 PKCE code_verifier 和 code_challenge""" code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(64)).rstrip(b"=").decode("ascii") digest = hashlib.sha256(code_verifier.encode("ascii")).digest() code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") return code_verifier, code_challenge # =================== Sentinel Token 逆向生成 =================== # # 以下代码基于对 sentinel.openai.com 的 SDK JS 代码的逆向分析: # https://sentinel.openai.com/sentinel/20260124ceb8/sdk.js # # 核心算法: # 1. _getConfig() → 收集浏览器环境数据(18个元素的数组) # 2. _runCheck(startTime, seed, difficulty, config, nonce) → PoW 计算 # a) config[3] = nonce(第4个元素设为当前尝试次数) # b) config[9] = performance.now() - startTime(耗时) # c) data = base64(JSON.stringify(config)) # d) hash = fnv1a_32(seed + data) # e) 若 hash 的 hex 前缀 ≤ difficulty → 返回 data + "~S" # 3. 最终 token = "gAAAAAB" + answer # # FNV-1a 32位哈希: # offset_basis = 2166136261 # prime = 16777619 # for each byte: hash ^= byte; hash = (hash * prime) >>> 0 # 然后做 xorshift 混合 + 转 8 位 hex # class SentinelTokenGenerator: """ Sentinel Token 纯 Python 生成器 通过逆向 sentinel SDK 的 PoW 算法, 纯 Python 构造合法的 openai-sentinel-token。 """ MAX_ATTEMPTS = 500000 # 最大 PoW 尝试次数 ERROR_PREFIX = "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" # SDK 中的错误前缀常量 def __init__(self, device_id=None): self.device_id = device_id or generate_device_id() self.requirements_seed = str(random.random()) self.sid = str(uuid.uuid4()) @staticmethod def _fnv1a_32(text): """ FNV-1a 32位哈希算法(从 SDK JS 逆向还原) 逆向来源:SDK 中的匿名函数,特征码: e = 2166136261 (FNV offset basis) e ^= t.charCodeAt(r) e = Math.imul(e, 16777619) >>> 0 (FNV prime) 最后做 xorshift 混合(murmurhash3 风格的 finalizer): e ^= e >>> 16 e = Math.imul(e, 2246822507) >>> 0 e ^= e >>> 13 e = Math.imul(e, 3266489909) >>> 0 e ^= e >>> 16 """ h = 2166136261 # FNV offset basis for ch in text: code = ord(ch) h ^= code # Math.imul(h, 16777619) >>> 0 模拟无符号32位乘法 h = ((h * 16777619) & 0xFFFFFFFF) # xorshift 混合(murmurhash3 finalizer) h ^= (h >> 16) h = ((h * 2246822507) & 0xFFFFFFFF) h ^= (h >> 13) h = ((h * 3266489909) & 0xFFFFFFFF) h ^= (h >> 16) h = h & 0xFFFFFFFF # 转为8位 hex 字符串,左补零 return format(h, '08x') def _get_config(self): """ 构造浏览器环境数据数组(_getConfig 方法逆向还原) SDK 中的元素对应关系(按索引): [0] screen.width + screen.height → "1920x1080" 格式 [1] new Date().toString() → 时间字符串 [2] performance.memory.jsHeapSizeLimit → 内存限制 [3] Math.random() → 随机数(后被 nonce 覆盖) [4] navigator.userAgent → UA [5] 随机 script src → 随机选一个页面 script 的 src [6] 脚本版本匹配 → script src 匹配 c/[^/]*/_ [7] document.documentElement.data-build → 构建版本 [8] navigator.language → 语言 [9] navigator.languages.join(',') → 语言列表(后被耗时覆盖) [10] Math.random() → 随机数 [11] 随机 navigator 属性 → 随机取 navigator 原型链上的一个属性 [12] Object.keys(document) 随机一个 → document 属性 [13] Object.keys(window) 随机一个 → window 属性 [14] performance.now() → 高精度时间 [15] self.sid → 会话标识 UUID [16] URLSearchParams 参数 → URL 搜索参数 [17] navigator.hardwareConcurrency → CPU 核心数 [18] performance.timeOrigin → 时间起点 """ # 模拟真实的浏览器环境数据 screen_info = f"1920x1080" now = datetime.now(timezone.utc) # 格式化为 JS Date.toString() 格式 date_str = now.strftime("%a %b %d %Y %H:%M:%S GMT+0000 (Coordinated Universal Time)") js_heap_limit = 4294705152 # Chrome 典型值 nav_random1 = random.random() ua = USER_AGENT # 模拟 sentinel SDK 的 script src script_src = "https://sentinel.openai.com/sentinel/20260124ceb8/sdk.js" # 匹配 c/[^/]*/_ script_version = None data_build = None language = "en-US" languages = "en-US,en" nav_random2 = random.random() # 模拟随机 navigator 属性 nav_props = [ "vendorSub", "productSub", "vendor", "maxTouchPoints", "scheduling", "userActivation", "doNotTrack", "geolocation", "connection", "plugins", "mimeTypes", "pdfViewerEnabled", "webkitTemporaryStorage", "webkitPersistentStorage", "hardwareConcurrency", "cookieEnabled", "credentials", "mediaDevices", "permissions", "locks", "ink", ] nav_prop = random.choice(nav_props) # 模拟属性值 nav_val = f"{nav_prop}−undefined" # SDK 用 − (U+2212) 而非 - (U+002D) doc_key = random.choice(["location", "implementation", "URL", "documentURI", "compatMode"]) win_key = random.choice(["Object", "Function", "Array", "Number", "parseFloat", "undefined"]) perf_now = random.uniform(1000, 50000) hardware_concurrency = random.choice([4, 8, 12, 16]) # 模拟 performance.timeOrigin(毫秒级 Unix 时间戳) time_origin = time.time() * 1000 - perf_now config = [ screen_info, # [0] 屏幕尺寸 date_str, # [1] 时间 js_heap_limit, # [2] 内存限制 nav_random1, # [3] 占位,后被 nonce 替换 ua, # [4] UserAgent script_src, # [5] script src script_version, # [6] 脚本版本 data_build, # [7] 构建版本 language, # [8] 语言 languages, # [9] 占位,后被耗时替换 nav_random2, # [10] 随机数 nav_val, # [11] navigator 属性 doc_key, # [12] document key win_key, # [13] window key perf_now, # [14] performance.now self.sid, # [15] 会话 UUID "", # [16] URL 参数 hardware_concurrency, # [17] CPU 核心数 time_origin, # [18] 时间起点 ] return config @staticmethod def _base64_encode(data): """ 模拟 SDK 的 E() 函数:JSON.stringify → TextEncoder.encode → btoa """ json_str = json.dumps(data, separators=(',', ':'), ensure_ascii=False) encoded = json_str.encode('utf-8') return base64.b64encode(encoded).decode('ascii') def _run_check(self, start_time, seed, difficulty, config, nonce): """ 单次 PoW 检查(_runCheck 方法逆向还原) 参数: start_time: 起始时间(秒) seed: PoW 种子字符串 difficulty: 难度字符串(hex 前缀阈值) config: 环境配置数组 nonce: 当前尝试序号 返回: 成功时返回 base64(config) + "~S" 失败时返回 None """ # 设置 nonce 和耗时 config[3] = nonce config[9] = round((time.time() - start_time) * 1000) # 毫秒 # base64 编码环境数据 data = self._base64_encode(config) # 计算 FNV-1a 哈希:hash(seed + data) hash_input = seed + data hash_hex = self._fnv1a_32(hash_input) # 难度校验:哈希前缀 ≤ 难度值 diff_len = len(difficulty) if hash_hex[:diff_len] <= difficulty: return data + "~S" return None def generate_token(self, seed=None, difficulty=None): """ 生成 sentinel token(完整 PoW 流程) 参数: seed: PoW 种子(来自服务端的 proofofwork.seed) difficulty: 难度值(来自服务端的 proofofwork.difficulty) 返回: 格式为 "gAAAAAB..." 的 sentinel token 字符串 """ # 如果没有服务端提供的 seed/difficulty,使用 requirements token 模式 if seed is None: seed = self.requirements_seed difficulty = difficulty or "0" start_time = time.time() config = self._get_config() for i in range(self.MAX_ATTEMPTS): result = self._run_check(start_time, seed, difficulty, config, i) if result: elapsed = time.time() - start_time print(f" ✅ PoW 完成: {i+1} 次迭代, 耗时 {elapsed:.2f}s") return "gAAAAAB" + result # PoW 失败(超过最大尝试次数),返回错误 token print(f" ⚠️ PoW 超过最大尝试次数 ({self.MAX_ATTEMPTS})") return "gAAAAAB" + self.ERROR_PREFIX + self._base64_encode(str(None)) def generate_requirements_token(self): """ 生成 requirements token(不需要服务端参数) 这是 SDK 中 getRequirementsToken() 的还原。 用于不需要服务端 seed 的场景(如注册页面初始化)。 """ config = self._get_config() config[3] = 1 config[9] = round(random.uniform(5, 50)) # 模拟小延迟 data = self._base64_encode(config) return "gAAAAAC" + data # 注意前缀是 C 不是 B # =================== Cloudflare 临时邮箱 =================== def create_temp_email(session): """通过 Cloudflare Worker 创建临时邮箱""" print("📧 创建临时邮箱...") name_len = random.randint(10, 14) name_chars = list(random.choices(string.ascii_lowercase, k=name_len)) for _ in range(random.choice([1, 2])): pos = random.randint(2, len(name_chars) - 1) name_chars.insert(pos, random.choice(string.digits)) name = "".join(name_chars) try: res = session.post( f"https://{CF_WORKER_DOMAIN}/admin/new_address", json={"enablePrefix": True, "name": name, "domain": CF_EMAIL_DOMAIN}, headers={"x-admin-auth": CF_ADMIN_PASSWORD, "Content-Type": "application/json"}, timeout=10, verify=False, ) if res.status_code == 200: data = res.json() email = data.get("address") token = data.get("jwt") if email: print(f" ✅ 邮箱: {email}") return email, token print(f" ❌ 创建失败: {res.status_code}") except Exception as e: print(f" ❌ 异常: {e}") return None, None def fetch_emails(session, email, cf_token): """获取邮箱中的邮件""" try: res = session.get( f"https://{CF_WORKER_DOMAIN}/api/mails", params={"limit": 10, "offset": 0}, headers={"Authorization": f"Bearer {cf_token}"}, verify=False, timeout=30, ) if res.status_code == 200: return res.json().get("results", []) except Exception: pass return [] def extract_verification_code(content): """从邮件内容提取6位验证码""" if not content: return None # 策略1:HTML body 样式匹配 m = re.search(r'background-color:\s*#F3F3F3[^>]*>[\s\S]*?(\d{6})[\s\S]*?

', content) if m: return m.group(1) # 策略2:Subject m = re.search(r'Subject:.*?(\d{6})', content) if m and m.group(1) != "177010": return m.group(1) # 策略3:通用正则 for pat in [r'>\s*(\d{6})\s*<', r'(? 0: org_id = ws_orgs[0].get("id") projects = ws_orgs[0].get("projects", []) if projects: project_id = projects[0].get("id") print(f" ✅ org_id: {org_id}") print(f" ✅ project_id: {project_id}") if org_id: print(f" [4c] POST organization/select...") body = {"org_id": org_id} if project_id: body["project_id"] = project_id h_org = dict(COMMON_HEADERS) h_org["referer"] = org_url h_org["oai-device-id"] = device_id h_org.update(generate_datadog_trace()) resp = session.post( f"{OAUTH_ISSUER}/api/accounts/organization/select", json=body, headers=h_org, verify=False, timeout=30, allow_redirects=False, ) print(f" 状态码: {resp.status_code}") if resp.status_code in (301, 302, 303, 307, 308): loc = resp.headers.get("Location", "") auth_code = _extract_code_from_url(loc) if auth_code: print(f" ✅ organization/select 获取到 code(长度: {len(auth_code)})") else: # 继续跟踪重定向链 auth_code = _follow_and_extract_code(session, loc) if auth_code: print(f" ✅ 跟踪重定向获取到 code(长度: {len(auth_code)})") elif resp.status_code == 200: org_data = resp.json() org_next = org_data.get("continue_url", "") print(f" org continue_url: {org_next}") if org_next: full_next = org_next if org_next.startswith("http") else f"{OAUTH_ISSUER}{org_next}" auth_code = _follow_and_extract_code(session, full_next) if auth_code: print(f" ✅ 跟踪获取到 code(长度: {len(auth_code)})") else: print(f" ⚠️ 未找到 org_id,尝试直接跟踪 consent URL...") auth_code = _follow_and_extract_code(session, org_url) if auth_code: print(f" ✅ 直接跟踪获取到 code(长度: {len(auth_code)})") else: # workspace/select 返回了非 organization 的 continue_url,直接跟踪 if ws_next: full_next = ws_next if ws_next.startswith("http") else f"{OAUTH_ISSUER}{ws_next}" auth_code = _follow_and_extract_code(session, full_next) if auth_code: print(f" ✅ 跟踪获取到 code(长度: {len(auth_code)})") except Exception as e: print(f" ⚠️ workspace/select 异常: {e}") import traceback traceback.print_exc() # ----- 步骤4d: 备用策略 — allow_redirects=True 捕获 ConnectionError ----- if not auth_code: print(" [4d] 备用策略: GET consent (allow_redirects=True)...") try: resp = session.get(consent_url, headers=NAVIGATE_HEADERS, verify=False, timeout=30, allow_redirects=True) print(f" 最终: {resp.status_code}, URL: {resp.url[:200]}") auth_code = _extract_code_from_url(resp.url) if auth_code: print(f" ✅ 最终 URL 中提取到 code") # 检查重定向链 if not auth_code and resp.history: for r in resp.history: loc = r.headers.get("Location", "") auth_code = _extract_code_from_url(loc) if auth_code: print(f" ✅ 重定向链中提取到 code") break except requests.exceptions.ConnectionError as e: url_match = re.search(r'(https?://localhost[^\s\'"]+)', str(e)) if url_match: auth_code = _extract_code_from_url(url_match.group(1)) if auth_code: print(f" ✅ ConnectionError 中提取到 code") except Exception as e: print(f" ⚠️ 备用策略异常: {e}") if not auth_code: print(" ❌ 未获取到 authorization code") return None # 用 code 换 token(复用已有的 codex_exchange_code 函数) return codex_exchange_code(auth_code, code_verifier) # =================== Codex OAuth 登录 + CPA 回调(浏览器版,作为 fallback) =================== def perform_codex_oauth_login(email, password, registrar_session=None): """ 注册成功后,通过浏览器混合模式执行 Codex OAuth 登录获取 Token。 混合架构: 浏览器层:完成 OAuth 登录全流程(邮箱+密码提交) - sentinel SDK 在浏览器内自动生成 t/c 字段(反机器人遥测+challenge response) - 通过 CDP 网络事件监听捕获 authorization code HTTP 层:用 code 换取 tokens(POST /oauth/token,无需 sentinel) 使用 Codex 专用配置(来自 config.json): client_id: app_EMoamEEZ73f0CkXaXp7hrann(Codex CLI) redirect_uri: http://localhost:1455/auth/callback scope: openid profile email offline_access 参数: email: 注册的邮箱 password: 注册的密码 registrar_session: 注册时的 requests.Session(含 CF cookies,可选,本模式暂未使用) 返回: dict: tokens 字典(含 access_token/refresh_token/id_token),失败返回 None """ print("\n🔐 执行 Codex OAuth 登录获取 Token(浏览器混合模式)...") # 1. 构造 PKCE 参数 code_verifier, code_challenge = generate_pkce() state = secrets.token_urlsafe(32) authorize_params = { "response_type": "code", "client_id": OAUTH_CLIENT_ID, "redirect_uri": OAUTH_REDIRECT_URI, "scope": "openid profile email offline_access", "code_challenge": code_challenge, "code_challenge_method": "S256", "state": state, } authorize_url = f"{OAUTH_ISSUER}/oauth/authorize?{urlencode(authorize_params)}" try: import undetected_chromedriver as uc from selenium.webdriver.common.by import By except ImportError: print(" ❌ 需要安装 undetected-chromedriver:") print(" pip install undetected-chromedriver selenium") return None driver = None try: # 2. 启动浏览器(带 CDP 网络事件监听) mode_str = "无头模式" if HEADLESS else "有头模式" print(f" 🌐 启动浏览器执行 OAuth 登录({mode_str},sentinel SDK 自动处理 t/c 字段)...") options = uc.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--disable-gpu") options.add_argument("--window-size=800,600") options.add_argument(f"--user-agent={USER_AGENT}") if HEADLESS: options.add_argument("--headless=new") if PROXY: options.add_argument(f"--proxy-server={PROXY}") driver = uc.Chrome(version_main=145, options=options, use_subprocess=True) # 启用 CDP 网络事件监听(捕获请求中的 authorization code 回调) driver.execute_cdp_cmd("Network.enable", {}) # 注入 JS Hook:拦截所有导航/请求,捕获回调 URL 中的 code # 由于 redirect_uri 是 localhost:1455(不可达),浏览器会导航失败但 URL 仍可读取 # 同时注入 sentinel token 拦截 Hook(调试用,可查看 t/c 内容) hook_js = """ // 拦截 XHR 请求头,捕获 sentinel token(调试用) (function() { window.__sentinel_tokens = []; const origOpen = XMLHttpRequest.prototype.open; const origSetHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.setRequestHeader = function(name, value) { if (name === 'openai-sentinel-token') { try { window.__sentinel_tokens.push(JSON.parse(value)); console.log('SENTINEL_CAPTURED:', value.substring(0, 80)); } catch(e) {} } return origSetHeader.call(this, name, value); }; // 同时拦截 fetch const origFetch = window.fetch; window.fetch = function(input, init) { if (init && init.headers) { let sentinel = null; if (init.headers instanceof Headers) { sentinel = init.headers.get('openai-sentinel-token'); } else if (typeof init.headers === 'object') { sentinel = init.headers['openai-sentinel-token']; } if (sentinel) { try { window.__sentinel_tokens.push(JSON.parse(sentinel)); console.log('SENTINEL_CAPTURED_FETCH:', sentinel.substring(0, 80)); } catch(e) {} } } return origFetch.apply(this, arguments); }; })(); """ # 在新文档加载前注入 Hook driver.execute_cdp_cmd( "Page.addScriptToEvaluateOnNewDocument", {"source": hook_js} ) # 3. 导航到 OAuth authorize URL print(f" 📡 访问 OAuth authorize URL...") driver.get(authorize_url) # 4. 等待 Cloudflare Challenge 完成 + 页面加载 print(" ⏳ 等待 Cloudflare Challenge + 登录页面加载...") for i in range(60): try: current_url = driver.current_url # 检查是否已到达回调(极快通过的情况) if "localhost" in current_url and "code=" in current_url: print(f" ✅ 快速到达回调(第 {i+1}s)") break # 检查是否有输入框或按钮(登录页加载完成) inputs = driver.find_elements(By.CSS_SELECTOR, "input") if inputs: print(f" ✅ 登录页面加载完成(第 {i+1}s)") break except Exception: pass if i % 15 == 0 and i > 0: print(f" ... 已等待 {i}s") time.sleep(1) time.sleep(1) # 辅助函数:检测并点击错误页面的重试按钮 def _check_and_retry_error(): """检测 OAuth 错误页面并点击重试按钮""" try: buttons = driver.find_elements(By.TAG_NAME, "button") for btn in buttons: try: btn_text = btn.text.strip().lower() if btn_text in ["重试", "retry", "try again", "重新尝试"]: if btn.is_displayed(): driver.execute_script("arguments[0].click();", btn) print(f" 🔁 检测到错误页面,已点击重试") time.sleep(3) return True except Exception: continue except Exception: pass return False # 5. 自动化 OAuth 登录流程(邮箱 → 密码 → 确认) auth_code = None max_steps = 30 # 最大步骤数(防止无限循环) for step_i in range(max_steps): try: current_url = driver.current_url # ===== 检查是否已到达回调 URL ===== if ("localhost" in current_url or "callback" in current_url) and "code=" in current_url: parsed = urlparse(current_url) params = parse_qs(parsed.query) auth_code = params.get("code", [None])[0] if auth_code: print(f" ✅ 获取到 authorization code(URL 回调,长度: {len(auth_code)})") break # ===== 检是否是错误页面 ===== if _check_and_retry_error(): continue # ===== 邮箱输入页面 ===== email_inputs = driver.find_elements( By.CSS_SELECTOR, 'input[type="email"], input[name="email"], input[name="username"], input[id="email"]' ) visible_email = [e for e in email_inputs if e.is_displayed()] if visible_email: print(f" 📧 [OAuth] 输入邮箱: {email}") inp = visible_email[0] inp.clear() inp.send_keys(email) time.sleep(0.5) # 点击 Continue/Submit 按钮 submit_btns = driver.find_elements(By.CSS_SELECTOR, 'button[type="submit"]') if submit_btns: driver.execute_script("arguments[0].click();", submit_btns[0]) else: # 回退:查找任何按钮 buttons = driver.find_elements(By.TAG_NAME, "button") for btn in buttons: text = btn.text.strip().lower() if text in ("continue", "继续", "next", "sign in", "log in"): driver.execute_script("arguments[0].click();", btn) break print(" ✅ 邮箱已提交") time.sleep(3) continue # ===== 密码输入页面 ===== pwd_inputs = driver.find_elements( By.CSS_SELECTOR, 'input[type="password"], input[name="password"]' ) visible_pwd = [e for e in pwd_inputs if e.is_displayed()] if visible_pwd: print(" 🔑 [OAuth] 输入密码...") inp = visible_pwd[0] inp.clear() # 逐字符输入密码(模拟真实打字,避免反机器人检测) for char in password: inp.send_keys(char) time.sleep(0.03) time.sleep(0.5) # 点击 Submit submit_btns = driver.find_elements(By.CSS_SELECTOR, 'button[type="submit"]') if submit_btns: driver.execute_script("arguments[0].click();", submit_btns[0]) else: buttons = driver.find_elements(By.TAG_NAME, "button") for btn in buttons: text = btn.text.strip().lower() if text in ("continue", "继续", "log in", "sign in"): driver.execute_script("arguments[0].click();", btn) break print(" ✅ 密码已提交") time.sleep(3) continue # ===== 授权确认页面 / Continue 按钮 ===== buttons = driver.find_elements(By.TAG_NAME, "button") clicked_consent = False for btn in buttons: try: btn_text = btn.text.strip().lower() if btn_text in ("continue", "继续", "allow", "approve", "accept", "authorize"): if btn.is_displayed() and btn.is_enabled(): driver.execute_script("arguments[0].click();", btn) print(f" ✅ [OAuth] 已点击确认按钮: '{btn.text.strip()}'") clicked_consent = True time.sleep(3) break except Exception: continue if clicked_consent: continue # ===== 没有可操作的元素,等待页面变化 ===== time.sleep(2) except Exception as e: print(f" ⚠️ OAuth 步骤异常: {e}") time.sleep(2) # 6. 如果通过 URL 未获取到 code,尝试从网络日志中获取 if not auth_code: print(" 🔍 尝试从浏览器网络日志中提取 authorization code...") try: # 检查 performance log(如果可用) logs = driver.get_log("performance") for entry in logs: try: msg = json.loads(entry["message"]) method = msg.get("message", {}).get("method", "") if method in ("Network.requestWillBeSent", "Network.responseReceived"): url = (msg.get("message", {}).get("params", {}) .get("request", {}).get("url", "") or msg.get("message", {}).get("params", {}) .get("response", {}).get("url", "")) if "code=" in url and "localhost" in url: parsed = urlparse(url) params = parse_qs(parsed.query) auth_code = params.get("code", [None])[0] if auth_code: print(f" ✅ 从网络日志中获取到 code(长度: {len(auth_code)})") break except Exception: continue except Exception: pass # 7. 最后尝试:直接读取当前 URL if not auth_code: try: final_url = driver.current_url if "code=" in final_url: parsed = urlparse(final_url) params = parse_qs(parsed.query) auth_code = params.get("code", [None])[0] if auth_code: print(f" ✅ 从最终 URL 获取到 code(长度: {len(auth_code)})") except Exception: pass # 调试:打印捕获到的 sentinel tokens(如果有) try: captured = driver.execute_script("return window.__sentinel_tokens || [];") if captured: print(f" 📋 调试: 共捕获 {len(captured)} 个 sentinel tokens") for idx, st in enumerate(captured[:3]): # 最多打印3个 t_val = st.get("t", "") c_val = st.get("c", "") flow = st.get("flow", "") print(f" [{idx}] flow={flow}, t长度={len(t_val)}, c长度={len(c_val)}") except Exception: pass # 8. 用 authorization code 换取 tokens if auth_code: return codex_exchange_code(auth_code, code_verifier) print(" ❌ 未获取到 authorization code") try: print(f" 最终 URL: {driver.current_url[:200]}") except Exception: pass return None except Exception as e: print(f" ❌ Codex OAuth 登录异常: {e}") import traceback traceback.print_exc() return None finally: if driver: try: driver.quit() print(" 🔒 OAuth 浏览器已关闭") except (OSError, Exception): pass def codex_exchange_code(code, code_verifier): """ 用 authorization code 换取 Codex tokens POST https://auth.openai.com/oauth/token Content-Type: application/x-www-form-urlencoded """ print(" 🔄 换取 Codex Token...") session = create_session() for attempt in range(2): try: resp = session.post( f"{OAUTH_ISSUER}/oauth/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, data={ "grant_type": "authorization_code", "code": code, "redirect_uri": OAUTH_REDIRECT_URI, "client_id": OAUTH_CLIENT_ID, "code_verifier": code_verifier, }, verify=False, timeout=60, ) break except Exception as e: if attempt == 0: print(f" ⚠️ Token 交换超时,重试...") time.sleep(2) continue print(f" ❌ Token 交换失败: {e}") return None if resp.status_code == 200: data = resp.json() print(f" ✅ Codex Token 获取成功!") print(f" Access Token 长度: {len(data.get('access_token', ''))}") print(f" Refresh Token: {'✅' if data.get('refresh_token') else '❌'}") print(f" ID Token: {'✅' if data.get('id_token') else '❌'}") return data else: print(f" ❌ Token 交换失败: {resp.status_code}") print(f" 响应: {resp.text[:300]}") return None # =================== Token JSON 保存 + CPA 上传 =================== def decode_jwt_payload(token): """解析 JWT token 的 payload 部分""" try: parts = token.split(".") if len(parts) != 3: return {} payload = parts[1] # 补齐 base64 padding padding = 4 - len(payload) % 4 if padding != 4: payload += "=" * padding decoded = base64.urlsafe_b64decode(payload) return json.loads(decoded) except Exception: return {} def save_token_json(email, access_token, refresh_token=None, id_token=None): """ 保存完整的 Token JSON 文件(格式兼容 Codex),并自动上传到 CPA 管理平台。 JSON 格式与 codex_ultimate.py 一致: { "type": "codex", "email": "xxx@xxx.com", "expired": "2026-02-20T15:30:00+08:00", "id_token": "...", "account_id": "...", "access_token": "...", "last_refresh": "2026-02-18T15:30:00+08:00", "refresh_token": "..." } """ try: from datetime import datetime, timezone, timedelta payload = decode_jwt_payload(access_token) # 提取 account_id auth_info = payload.get("https://api.openai.com/auth", {}) account_id = auth_info.get("chatgpt_account_id", "") # 计算过期时间 exp_timestamp = payload.get("exp", 0) if exp_timestamp: exp_dt = datetime.fromtimestamp(exp_timestamp, tz=timezone(timedelta(hours=8))) expired_str = exp_dt.strftime("%Y-%m-%dT%H:%M:%S+08:00") else: expired_str = "" now = datetime.now(tz=timezone(timedelta(hours=8))) last_refresh_str = now.strftime("%Y-%m-%dT%H:%M:%S+08:00") token_data = { "type": "codex", "email": email, "expired": expired_str, "id_token": id_token or "", "account_id": account_id, "access_token": access_token, "last_refresh": last_refresh_str, "refresh_token": refresh_token or "", } filename = f"{email}.json" with open(filename, "w", encoding="utf-8") as f: json.dump(token_data, f, ensure_ascii=False) print(f" ✅ Token JSON 已保存到 {filename}") # 上传到 CPA 管理平台 if UPLOAD_API_URL: upload_token_json(filename) except Exception as e: print(f" ❌ 保存 Token JSON 失败: {e}") def upload_token_json(filename): """上传 Token JSON 文件到 CPA 管理平台""" try: session = create_session() with open(filename, "rb") as f: files = {"file": (filename, f, "application/json")} headers = {"Authorization": f"Bearer {UPLOAD_API_TOKEN}"} resp = session.post( UPLOAD_API_URL, files=files, headers=headers, verify=False, timeout=30, ) if resp.status_code == 200: print(f" ✅ Token JSON 已上传到 CPA 管理平台") else: print(f" ❌ CPA 上传失败: {resp.status_code} - {resp.text[:200]}") except Exception as e: print(f" ❌ CPA 上传异常: {e}") def save_tokens(email, tokens): """保存 tokens 到所有目标(txt + JSON + CPA 上传),线程安全""" access_token = tokens.get("access_token", "") refresh_token = tokens.get("refresh_token", "") id_token = tokens.get("id_token", "") with _file_lock: if access_token: with open(AK_FILE, "a", encoding="utf-8") as f: f.write(f"{access_token}\n") if refresh_token: with open(RK_FILE, "a", encoding="utf-8") as f: f.write(f"{refresh_token}\n") if access_token: save_token_json(email, access_token, refresh_token, id_token) # =================== 账号持久化 =================== def save_account(email, password): """保存账号信息(线程安全)""" try: with _file_lock: with open(ACCOUNTS_FILE, "a", encoding="utf-8") as f: f.write(f"{email}:{password}\n") file_exists = os.path.exists(CSV_FILE) with open(CSV_FILE, "a", newline="", encoding="utf-8") as f: import csv w = csv.writer(f) if not file_exists: w.writerow(["email", "password", "timestamp"]) w.writerow([email, password, time.strftime("%Y-%m-%d %H:%M:%S")]) print(f" ✅ 账号已保存") except Exception as e: print(f" ⚠️ 保存失败: {e}") # =================== 批量执行入口 =================== def register_one(worker_id=0, task_index=0, total=1): """ 注册单个账号的完整流程(线程安全) 返回: (email, password, success, reg_time, total_time) """ tag = f"[W{worker_id}]" if CONCURRENT_WORKERS > 1 else "" t_start = time.time() session = create_session() # 1. 创建临时邮箱 email, cf_token = create_temp_email(session) if not email: return None, None, False, 0, 0 password = generate_random_password() # 2. 协议注册 registrar = ProtocolRegistrar() success, email, password = registrar.register(email, cf_token, password) save_account(email, password) t_reg = time.time() - t_start # 注册耗时 if not success: return email, password, False, t_reg, t_reg print(f" 📝 注册耗时: {t_reg:.1f}s") # 3. Codex OAuth 登录 tokens = None try: tokens = perform_codex_oauth_login_http( email, password, registrar_session=registrar.session, cf_token=cf_token, ) if not tokens: print(f"{tag} ❌ 纯 HTTP OAuth 失败") t_total = time.time() - t_start if tokens: save_tokens(email, tokens) print(f"{tag} ✅ {email} | 注册 {t_reg:.1f}s + OAuth {t_total - t_reg:.1f}s = 总 {t_total:.1f}s") else: print(f"{tag} ⚠️ OAuth 失败(注册已成功)") except Exception as e: t_total = time.time() - t_start print(f"{tag} ⚠️ OAuth 异常: {e}") return email, password, True, t_reg, t_total def run_batch(): """批量注册入口(支持并发)""" workers = max(1, CONCURRENT_WORKERS) batch_start = time.time() print(f"\n🚀 协议注册机 v5 — {TOTAL_ACCOUNTS} 个账号 | 并发 {workers} | 域名 {CF_EMAIL_DOMAIN}") ok = 0 fail = 0 results_lock = threading.Lock() reg_times = [] # 注册耗时列表 total_times = [] # 总耗时列表 if workers == 1: for i in range(TOTAL_ACCOUNTS): print(f"\n--- [{i+1}/{TOTAL_ACCOUNTS}] ---") email, password, success, t_reg, t_total = register_one( worker_id=0, task_index=i + 1, total=TOTAL_ACCOUNTS ) if success: ok += 1 reg_times.append(t_reg) total_times.append(t_total) else: fail += 1 wall = time.time() - batch_start throughput = wall / ok if ok > 0 else 0 print(f"📊 {i+1}/{TOTAL_ACCOUNTS} | ✅{ok} ❌{fail} | 吞吐 {throughput:.1f}s/个 | 已用 {wall:.0f}s") if i < TOTAL_ACCOUNTS - 1: wait = random.randint(3, 8) time.sleep(wait) else: print(f"🔀 启动 {workers} 个并发 worker...\n") def _worker_task(task_index, worker_id): if task_index > 1: jitter = random.uniform(1, 3) * worker_id time.sleep(jitter) try: email, password, success, t_reg, t_total = register_one( worker_id=worker_id, task_index=task_index, total=TOTAL_ACCOUNTS ) return task_index, email, password, success, t_reg, t_total except Exception as e: print(f"[W{worker_id}] ❌ 异常: {e}") return task_index, None, None, False, 0, 0 with ThreadPoolExecutor(max_workers=workers) as executor: futures = {} for i in range(TOTAL_ACCOUNTS): worker_id = (i % workers) + 1 future = executor.submit(_worker_task, i + 1, worker_id) futures[future] = i + 1 for future in as_completed(futures): task_idx = futures[future] try: _, email, password, success, t_reg, t_total = future.result() with results_lock: if success: ok += 1 reg_times.append(t_reg) total_times.append(t_total) else: fail += 1 done = ok + fail wall = time.time() - batch_start throughput = wall / ok if ok > 0 else 0 print(f"📊 {done}/{TOTAL_ACCOUNTS} | ✅{ok} ❌{fail} | 吞吐 {throughput:.1f}s/个 | 已用 {wall:.0f}s") except Exception as e: with results_lock: fail += 1 print(f"❌ 任务 {task_idx} 异常: {e}") elapsed = time.time() - batch_start throughput = elapsed / ok if ok > 0 else 0 avg_reg = sum(reg_times) / len(reg_times) if reg_times else 0 avg_total = sum(total_times) / len(total_times) if total_times else 0 print(f"\n🏁 完成: ✅{ok} ❌{fail} | 总耗时 {elapsed:.1f}s | 吞吐 {throughput:.1f}s/个 | 单号(注册 {avg_reg:.1f}s + OAuth {avg_total - avg_reg:.1f}s = {avg_total:.1f}s)") if __name__ == "__main__": run_batch()