import os import sys import time import json import re import uuid import random import string import secrets import hashlib import base64 import threading import argparse import urllib.parse import urllib.request import urllib.error from datetime import datetime from typing import Any, Dict, Optional from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs from dataclasses import dataclass from curl_cffi import requests INBOX_DATA = {} YOUR_DOMAIN = "example.com" class CFGatewayHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): # 仅在需要调试时打印日志 pass def _send_json(self, data, status=200): self.send_response(status) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(json.dumps(data).encode("utf-8")) def do_POST(self): parsed = urlparse(self.path) # 处理来自 Cloudflare 的 Webhook if parsed.path == "/webhook": try: content_length = int(self.headers["Content-Length"]) post_data = json.loads(self.rfile.read(content_length)) raw_content = post_data.get("raw", "") recipient = post_data.get("to", "").lower().strip() sender = post_data.get("from", "").lower().strip() # 校验是否来自 OpenAI (发件人或内容包含 openai) if "openai" not in sender and "openai" not in raw_content.lower(): self._send_json({"status": "ignored"}) return # 使用 email 库解析邮件 import email from email import policy msg = email.message_from_string(raw_content, policy=policy.default) def extract_codes(text): if not text: return [] # 匹配 6 位数字,排除前后紧邻的数字 return re.findall(r"(?]+>", " ", payload) matches.extend(extract_codes(clean_text)) else: matches.extend(extract_codes(payload)) # 兜底:如果解析失败,退回到原始正则匹配 if not matches: matches = extract_codes(raw_content) if matches: code = matches[-1] if recipient in INBOX_DATA: INBOX_DATA[recipient]["code"] = code # print(f"[*] [Gateway] 捕获验证码: {code} 来自 {sender} -> {recipient}") self._send_json({"status": "ok"}) except Exception as e: print(f"[!] [Gateway] Webhook 处理出错: {e}") self._send_json({"status": "error"}, 500) # 处理注册逻辑的申请邮箱请求 elif parsed.path == "/v2/inbox/create": email = f"{uuid.uuid4().hex[:10]}@{YOUR_DOMAIN}" token = uuid.uuid4().hex INBOX_DATA[email] = {"token": token, "code": None, "timestamp": time.time()} self._send_json({"address": email, "token": token}) def do_GET(self): # 处理注册逻辑的查验证码请求 if self.path.startswith("/v2/inbox"): params = parse_qs(urlparse(self.path).query) token = params.get("token", [""])[0] emails_resp = [] for email, data in INBOX_DATA.items(): if data["token"] == token and data["code"]: emails_resp.append( { "from": "openai", "subject": "Verification Code", "body": f"Your code is {data['code']}", "date": int(time.time()), } ) self._send_json({"emails": emails_resp}) def start_gateway_server(): server = HTTPServer(("0.0.0.0", 8080), CFGatewayHandler) server.serve_forever() CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" AUTH_URL = "https://auth.openai.com/oauth/authorize" TOKEN_URL = "https://auth.openai.com/oauth/token" DEFAULT_REDIRECT_URI = "http://localhost:1455/auth/callback" DEFAULT_SCOPE = "openid email profile offline_access" @dataclass(frozen=True) class OAuthStart: auth_url: str state: str code_verifier: str redirect_uri: str def _b64url_no_pad(raw: bytes) -> str: return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") def _sha256_b64url_no_pad(s: str) -> str: return _b64url_no_pad(hashlib.sha256(s.encode("ascii")).digest()) def _random_state(nbytes: int = 16) -> str: return secrets.token_urlsafe(nbytes) def _pkce_verifier() -> str: return secrets.token_urlsafe(64) def _generate_password(length: int = 12) -> str: chars = string.ascii_letters + string.digits return "".join(secrets.choice(chars) for _ in range(length)) def _jwt_claims_no_verify(id_token: str) -> Dict[str, Any]: if not id_token or id_token.count(".") < 2: return {} payload_b64 = id_token.split(".")[1] pad = "=" * ((4 - (len(payload_b64) % 4)) % 4) try: payload = base64.urlsafe_b64decode((payload_b64 + pad).encode("ascii")) return json.loads(payload.decode("utf-8")) except: return {} def _decode_jwt_segment(seg: str) -> Dict[str, Any]: raw = (seg or "").strip() if not raw: return {} pad = "=" * ((4 - (len(raw) % 4)) % 4) try: decoded = base64.urlsafe_b64decode((raw + pad).encode("ascii")) return json.loads(decoded.decode("utf-8")) except: return {} def _parse_callback_url(callback_url: str) -> Dict[str, str]: parsed = urllib.parse.urlparse(callback_url) query = urllib.parse.parse_qs(parsed.query, keep_blank_values=True) fragment = urllib.parse.parse_qs(parsed.fragment, keep_blank_values=True) for k, v in fragment.items(): if k not in query: query[k] = v return { "code": (query.get("code", [""])[0] or "").strip(), "state": (query.get("state", [""])[0] or "").strip(), "error": (query.get("error", [""])[0] or "").strip(), "error_description": (query.get("error_description", [""])[0] or "").strip(), } class ChatGPTManager: def __init__(self, args): self.base_url = args.base_url.rstrip("/") self.mgmt_key = args.mgmt_key self.target = args.target self.check_interval = args.check_interval self.reg_delay_min = args.reg_delay_min self.reg_delay_max = args.reg_delay_max self.proxy = args.proxy self.current_reg_delay = random.randint(self.reg_delay_min, self.reg_delay_max) self.headers = { "Authorization": f"Bearer {self.mgmt_key}", "Content-Type": "application/json", "Accept": "application/json", } def log(self, msg): timestamp = time.strftime("%Y-%m-%d %H:%M:%S") print(f"[{timestamp}] {msg}") def get_remote_accounts(self): try: url = f"{self.base_url}/v0/management/auth-files" resp = requests.get( url, headers=self.headers, impersonate="chrome", timeout=20 ) if resp.status_code == 200: return resp.json().get("files", []) return [] except Exception as e: self.log(f"[!] 获取账号列表出错: {e}") return [] def delete_remote_account(self, name): try: url = f"{self.base_url}/v0/management/auth-files" resp = requests.delete( url, headers=self.headers, params={"name": name}, impersonate="chrome" ) return resp.status_code in (200, 204) except: return False def check_and_cleanup(self): self.log("[*] 开始执行账号健康状态扫描...") accounts = self.get_remote_accounts() if not accounts: return 0 invalid_count = 0 for acc in accounts: email = acc.get("email") auth_index = acc.get("auth_index") filename = acc.get("name") account_id = acc.get("id_token", {}).get("chatgpt_account_id") if not auth_index: continue payload = { "authIndex": auth_index, "method": "GET", "url": "https://chatgpt.com/backend-api/wham/usage", "header": { "Authorization": "Bearer $TOKEN$", "Content-Type": "application/json", "User-Agent": "codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal", "Chatgpt-Account-Id": account_id if account_id else "", }, } try: resp = requests.post( f"{self.base_url}/v0/management/api-call", headers=self.headers, json=payload, impersonate="chrome", timeout=15, ) data = resp.json() status = data.get("status_code") if not status and "body" in data: try: status = json.loads(data["body"]).get("status") except: pass if status == 401: self.log(f" [-] 账号 {email} 已失效 (401),正在删除...") if self.delete_remote_account(filename): invalid_count += 1 except: pass self.log(f"[+] 扫描完成,共清理 {invalid_count} 个失效账号。") return len(accounts) - invalid_count def upload_token_data(self, token_json): try: data = json.loads(token_json) email = data.get("email", "unknown") filename = f"token_{email.replace('@', '_')}_{int(time.time())}.json" url = f"{self.base_url}/v0/management/auth-files?name={filename}" resp = requests.post( url, headers={ "Authorization": f"Bearer {self.mgmt_key}", "Content-Type": "application/json", }, data=token_json, impersonate="chrome", ) return resp.status_code == 200 except Exception as e: self.log(f"[!] 上传 Token 失败: {e}") return False def register_one(self): proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None s = requests.Session(proxies=proxies, impersonate="chrome") email = None try: # 1. 申请邮箱 (直接调用内存接口) email_info = { "address": f"{uuid.uuid4().hex[:10]}@{YOUR_DOMAIN}", "token": uuid.uuid4().hex, } email = email_info["address"] INBOX_DATA[email] = { "token": email_info["token"], "code": None, "timestamp": time.time(), } self.log(f"[*] 注册邮箱: {email}") # 2. 初始化 OAuth state = _random_state() code_verifier = _pkce_verifier() code_challenge = _sha256_b64url_no_pad(code_verifier) params = { "client_id": CLIENT_ID, "response_type": "code", "redirect_uri": DEFAULT_REDIRECT_URI, "scope": DEFAULT_SCOPE, "state": state, "code_challenge": code_challenge, "code_challenge_method": "S256", "prompt": "login", "id_token_add_organizations": "true", "codex_cli_simplified_flow": "true", } auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}" # 3. 访问并获取 did s.get(auth_url, timeout=15) did = s.cookies.get("oai-did") if not did: return None # 4. Sentinel sen_req_body = f'{{"p":"","id":"{did}","flow":"authorize_continue"}}' sen_resp = s.post( "https://sentinel.openai.com/backend-api/sentinel/req", headers={ "origin": "https://sentinel.openai.com", "content-type": "text/plain;charset=UTF-8", }, data=sen_req_body, timeout=15, ) sen_token = sen_resp.json()["token"] sentinel = f'{{"p": "", "t": "", "c": "{sen_token}", "id": "{did}", "flow": "authorize_continue"}}' # 5. Continue signup_body = f'{{"username":{{"value":"{email}","kind":"email"}},"screen_hint":"signup"}}' s.post( "https://auth.openai.com/api/accounts/authorize/continue", headers={ "openai-sentinel-token": sentinel, "content-type": "application/json", }, data=signup_body, ) # 6. Password password = _generate_password() s.post( "https://auth.openai.com/api/accounts/user/register", headers={"content-type": "application/json"}, data=json.dumps({"password": password, "username": email}), ) # 7. Send OTP s.get("https://auth.openai.com/api/accounts/email-otp/send") # 8. Wait Code self.log("[*] 等待验证码...") code = None for _ in range(30): if INBOX_DATA.get(email, {}).get("code"): code = INBOX_DATA[email]["code"] break time.sleep(5) if not code: return None self.log(f"[+] 捕获验证码: {code}") # 9. Validate val_resp = s.post( "https://auth.openai.com/api/accounts/email-otp/validate", headers={ "accept": "application/json", "content-type": "application/json", "referer": "https://auth.openai.com/email-verification", "origin": "https://auth.openai.com", }, data=json.dumps({"code": code}), ) self.log(f"[*] 验证码校验状态: {val_resp.status_code}") if val_resp.status_code != 200: self.log(f"[!] 校验失败响应: {val_resp.text}") return None # 10. Create create_resp = s.post( "https://auth.openai.com/api/accounts/create_account", headers={ "accept": "application/json", "content-type": "application/json", "referer": "https://auth.openai.com/about-you", "origin": "https://auth.openai.com", }, data='{"name":"Neo","birthdate":"2000-02-20"}', ) self.log(f"[*] 账户创建状态: {create_resp.status_code}") if create_resp.status_code != 200: self.log(f"[!] 账户创建失败详情: {create_resp.text}") return None # 11. 获取 Workspace ID (三重保险提取法) auth_cookie = s.cookies.get("oai-client-auth-session") or "" workspace_id = None resp_text = create_resp.text # 尝试解析 JSON try: rj = create_resp.json() if isinstance(rj, dict): ws_info = rj.get("workspaces") or [] if ws_info and isinstance(ws_info, list): workspace_id = ws_info[0].get("id") elif str(rj.get("id", "")).startswith("ws-"): workspace_id = rj.get("id") except: pass # 辅助提取:从 Cookie 字符串正则匹配 if not workspace_id and auth_cookie: ws_match = re.search(r"ws-[a-zA-Z0-9]+", auth_cookie) if ws_match: workspace_id = ws_match.group(0) # 辅助提取:从 JWT Cookie Payload 提取 if not workspace_id and auth_cookie: for seg in auth_cookie.split("."): try: decoded = _decode_jwt_segment(seg) if isinstance(decoded, dict): ws_list = decoded.get("workspaces") or [] if ws_list: workspace_id = ws_list[0].get("id") break except: continue if not workspace_id: self.log(f"[!] 无法锁定 Workspace ID. 响应长度: {len(resp_text)}") if resp_text.strip().startswith("= self.check_interval: current_count = self.check_and_cleanup() last_check_time = now else: current_count = len(self.get_remote_accounts()) self.log(f"[*] 当前存量: {current_count} / 目标: {self.target}") if current_count < self.target: token_json = self.register_one() if token_json and self.upload_token_data(token_json): self.log("[+] 注册成功并已上传") self.current_reg_delay = random.randint( self.reg_delay_min, self.reg_delay_max ) else: self.log("[!] 注册失败,退避等待") self.current_reg_delay = min(self.current_reg_delay * 2, 3600) time.sleep(self.current_reg_delay) else: time.sleep(60) if __name__ == "__main__": parser = argparse.ArgumentParser( description="ChatGPT 账号全自动管理脚本 (单一脚本版)" ) parser.add_argument( "--base-url", default="http://localhost:8317", help="CLIProxyAPI 地址" ) parser.add_argument("--mgmt-key", required=True, help="管理密钥") parser.add_argument("--target", type=int, default=100, help="账号目标数量") parser.add_argument("--check-interval", type=int, default=3600, help="检测间隔") parser.add_argument("--reg-delay-min", type=int, default=60, help="最小延迟") parser.add_argument("--reg-delay-max", type=int, default=120, help="最大延迟") parser.add_argument("--proxy", default=None, help="代理") parser.add_argument("--domain", default="example.com", help="邮箱域名") args = parser.parse_args() # 更新全局域名变量 YOUR_DOMAIN = args.domain try: ChatGPTManager(args).start() except KeyboardInterrupt: print("\n[*] 已停止。")