From f2acd3592a67d9ea9b563974de338c4297cca40e Mon Sep 17 00:00:00 2001 From: Ttungx <1323593614@qq.com> Date: Fri, 6 Mar 2026 11:19:56 +0800 Subject: [PATCH] Prepare public codex auto register repo --- .gitignore | 35 ++ README.md | 150 +++++--- codex/README.md | 88 +++-- codex/config.example.json | 18 + codex/config.json | 21 -- codex/protocol_keygen.py | 721 ++++++++++++++++++++++++++------------ config.example.json | 17 + config.json | 22 -- duckmaildoc.md | 228 ++++++++++++ 9 files changed, 935 insertions(+), 365 deletions(-) create mode 100644 .gitignore create mode 100644 codex/config.example.json delete mode 100644 codex/config.json create mode 100644 config.example.json delete mode 100644 config.json create mode 100644 duckmaildoc.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d31f5f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.python-version +.venv/ +venv/ +env/ + +.vscode/ +.idea/ +.DS_Store +Thumbs.db + +config.json +codex/config.json + +registered_accounts.txt +accounts.txt +ak.txt +rk.txt +registered_accounts.csv + +codex/accounts.txt +codex/ak.txt +codex/rk.txt +codex/registered_accounts.csv + +codex/codex_tokens/ +codex/codex_accounts_tokens/ +rename_tokens.py + +*.log +*.tmp + diff --git a/README.md b/README.md index b31fb7d..cdba283 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,123 @@ -# ChatGPT 批量自动注册工具 +# codex_auto_register -> 使用 DuckMail 临时邮箱,并发自动注册 ChatGPT 账号 +基于 DuckMail 的 ChatGPT / Codex 自动注册与 OAuth Token 生成工具集。 -## 功能 +## 致谢 -- 📨 自动创建临时邮箱 (DuckMail) -- 📥 自动获取 OTP 验证码 -- ⚡ 支持并发注册多个账号 -- 🔄 自动处理 OAuth 登录 -- ☁️ 支持代理配置 -- 📤 支持上传账号到 Codex / CPA 面板 +本项目基于原项目 https://github.com/adminlove520/chatgpt_register 改造而来。 -## 环境 +当前仓库的主要差异: + +- 将注册codex邮箱服务从原先方案替换为 DuckMail API +- 保留并扩展 Codex 协议 OAuth 流程 +- 输出 CLIProxyAPI v6 可识别的 Codex auth files +- 增加更适合公开仓库的示例配置与忽略规则 + +## 包含内容 + +- `chatgpt_register.py`:根目录下的 DuckMail 注册脚本 +- `codex/protocol_keygen.py`:纯 HTTP 的 Codex OAuth 注册与 token 生成脚本 +- `duckmaildoc.md`:DuckMail API 参考文档(https://raw.githubusercontent.com/MoonWeSif/DuckMail/main/public/llm-api-docs.txt) + +## 环境依赖 + +根目录脚本: ```bash pip install curl_cffi ``` -## 配置 (config.json) +Codex 脚本: -```json -{ - "total_accounts": 5, - "duckmail_api_base": "https://api.duckmail.sbs", - "duckmail_bearer": "你的 DuckMail API Token", - "proxy": "http://127.0.0.1:7890", - "output_file": "registered_accounts.txt", - "enable_oauth": true, - "oauth_redirect_uri": "http://localhost:1455/auth/callback", - "ak_file": "ak.txt", - "rk_file": "rk.txt" -} +```bash +pip install requests urllib3 ``` -| 配置项 | 说明 | -|--------|------| -| total_accounts | 注册账号数量 | -| duckmail_bearer | DuckMail API Token | -| proxy | 代理地址 (可选) | -| output_file | 输出账号文件 | -| enable_oauth | 启用 OAuth 登录 | -| ak_file | Access Key 文件 | -| rk_file | Refresh Key 文件 | +## 配置方式 -## CPA 面板集成 +仓库只提交示例配置,不提交真实配置。 -注册完成后,可以自动上传账号到 CPA 面板: +使用前复制: -| 配置项 | 说明 | 参考 | -|--------|------|------| -| upload_api_url | CPA 面板上传 API 地址 | https://help.router-for.me/cn/ | -| upload_api_token | CPA 面板登录密码 | 你的 CPA 面板密码 | +```bash +copy config.example.json config.json +copy codex\config.example.json codex\config.json +``` -> CPA 面板仓库: https://github.com/dongshuyan/CPA-Dashboard +然后把你自己的 DuckMail、代理和 CPA 参数填进去。 -## 使用 +## 根目录脚本 + +运行: ```bash python chatgpt_register.py ``` -## 输出 +对应示例配置见 `config.example.json`。 -注册成功的账号会保存到 `registered_accounts.txt` +主要配置项: -## 目录结构 +| 配置项 | 说明 | +| ----------------- | ------------------------ | +| total_accounts | 注册账号数量 | +| duckmail_api_base | DuckMail API 地址 | +| duckmail_bearer | DuckMail Bearer Token | +| proxy | HTTP/HTTPS 代理 | +| output_file | 注册结果输出文件 | +| enable_oauth | 是否执行 OAuth | +| oauth_required | 是否要求 OAuth 成功 | +| upload_api_url | 可选,上传到 CPA 的接口 | +| upload_api_token | 可选,CPA 管理接口 Token | +## Codex 协议脚本 + +运行: + +```bash +python codex\protocol_keygen.py ``` + +对应示例配置见 `codex/config.example.json`。 + +该脚本会: + +- 使用 DuckMail 创建临时邮箱 +- 完成 ChatGPT 注册流程 +- 执行 Codex OAuth 登录并换取 token +- 生成 CLIProxyAPI v6 兼容文件名的 token JSON +- 可选上传到 CPA 管理接口 + +## 输出说明 + +运行过程中通常会生成以下本地文件,这些都已加入 `.gitignore`,不会进入新仓库: + +- `config.json` +- `codex/config.json` +- `registered_accounts.txt` +- `codex/accounts.txt` +- `codex/ak.txt` +- `codex/rk.txt` +- `codex/registered_accounts.csv` +- `codex/codex_tokens/` +- `codex/codex_accounts_tokens/` + +## 仓库结构 + +```text chatgpt_register/ -├── chatgpt_register.py # 主程序 -├── config.json # 配置文件 -├── README.md # 本文档 -├── codex/ # Codex 协议密钥生成 -│ ├── config.json -│ └── protocol_keygen.py -├── registered_accounts.txt # 输出的账号 -├── ak.txt # Access Keys -└── rk.txt # Refresh Keys +├── chatgpt_register.py +├── config.example.json +├── duckmaildoc.md +├── README.md +└── codex/ + ├── config.example.json + ├── protocol_keygen.py + └── README.md ``` -## 注意事项 +## 说明 -- 需要有效的代理才能注册成功 -- DuckMail API Token 需要从 https://duckmail.sbs 获取 -- 建议使用代理避免 IP 被封 -- 使用 CPA 面板需要先部署面板服务 +- 需要可用代理,否则注册、OAuth 和 CPA 自动刷新都会失败 +- `config.json` 与 `codex/config.json` 仅保留在本地使用,不应提交 +- 如果你使用 CLIProxyAPI,建议保持 `refresh_token` 与 token JSON 文件完整保存 diff --git a/codex/README.md b/codex/README.md index 710e545..cb7486a 100644 --- a/codex/README.md +++ b/codex/README.md @@ -1,60 +1,74 @@ # Codex 协议密钥生成工具 -> 为 ChatGPT 注册生成 Codex 协议所需的 Access Key 和 Refresh Key +该目录提供基于 DuckMail 的纯 HTTP Codex OAuth 注册与 token 生成脚本。 -## 功能 +## 主要能力 -- 🔑 自动生成 Access Key (ak) -- 🔄 自动生成 Refresh Key (rk) -- 📤 支持上传到 Codex / CPA 面板 -- ⚡ 支持并发生成 +- 纯 HTTP 注册 ChatGPT 账号 +- 通过 DuckMail API 创建邮箱并读取验证码 +- 获取 `access_token`、`refresh_token`、`id_token` +- 生成 CLIProxyAPI v6 兼容的 token JSON 文件名 +- 可选上传到 CPA 管理接口 -## 配置 (config.json) +## 配置文件 + +只提交示例文件: + +```bash +copy config.example.json config.json +``` + +示例配置字段: ```json { - "total_accounts": 800, - "concurrent_workers": 8, + "total_accounts": 10, + "concurrent_workers": 2, "headless": false, - "proxy": "http://127.0.0.1:7890", - "cf_worker_domain": "你的 Cloudflare Worker 域名", - "cf_email_domain": "你的 Cloudflare 邮箱域名", - "cf_admin_password": "你的 Cloudflare 管理密码", - "upload_api_url": "https://你的CPA地址/v0/management/auth-files", - "upload_api_token": "你的CPA密码", - "cli_proxy_api_base": "你的CPA基础URL", - "cli_proxy_management_url": "http://你的CPA地址/management.html#/oauth", - "cli_proxy_password": "你的CPA密码" + "proxy": "http://127.0.0.1:7897", + "duckmail_api_base": "https://api.duckmail.sbs", + "duckmail_api_key": "", + "oauth_issuer": "https://auth.openai.com", + "oauth_client_id": "app_EMoamEEZ73f0CkXaXp7hrann", + "oauth_redirect_uri": "http://localhost:1455/auth/callback", + "upload_api_url": "http://localhost:8317/v0/management/auth-files", + "upload_api_token": "", + "accounts_file": "accounts.txt", + "csv_file": "registered_accounts.csv", + "ak_file": "ak.txt", + "rk_file": "rk.txt", + "token_json_dir": "codex_accounts_tokens" } ``` -| 配置项 | 说明 | -|--------|------| -| total_accounts | 生成账号数量 | -| concurrent_workers | 并发数 | -| proxy | 代理地址 | -| cf_worker_domain | Cloudflare Worker 域名 | -| upload_api_url | CPA 上传 API | -| cli_proxy_api_base | CPA CLI 代理 API | - ## 使用 ```bash python protocol_keygen.py ``` -## 输出 +## 输出文件 -- `ak.txt` - Access Keys -- `rk.txt` - Refresh Keys -- `registered_accounts.csv` - CSV 格式账号 +运行后会在本地生成: -## 接入 CPA 面板 +- `accounts.txt` +- `registered_accounts.csv` +- `ak.txt` +- `rk.txt` +- `codex_accounts_tokens/` 或你配置的 token 输出目录 -生成后可以自动上传到 CPA 面板: +这些文件都应保留本地使用,不应提交到仓库。 -1. 部署 CPA 面板: https://github.com/dongshuyan/CPA-Dashboard -2. 配置 `upload_api_url` 和 `upload_api_token` -3. 运行后自动上传 +## 与原项目的差异 -> 文档: https://help.router-for.me/cn/ +相比上游项目,本目录的核心改动是将邮箱接收能力切换为 DuckMail: + +- 使用 `POST /accounts` 创建临时邮箱 +- 使用 `POST /token` 获取 DuckMail Bearer Token +- 使用 `GET /messages` 和 `GET /messages/{id}` 轮询验证码邮件 + +## CPA 使用说明 + +- 生成的 token JSON 适配 CLIProxyAPI v6 命名格式 +- `refresh_token` 会一并保存,供 CPA 自动刷新 access token +- 如果配置了 `upload_api_url`,脚本会自动上传生成的 auth file diff --git a/codex/config.example.json b/codex/config.example.json new file mode 100644 index 0000000..eae978b --- /dev/null +++ b/codex/config.example.json @@ -0,0 +1,18 @@ +{ + "total_accounts": 10, + "concurrent_workers": 2, + "headless": false, + "proxy": "http://127.0.0.1:7897", + "duckmail_api_base": "https://api.duckmail.sbs", + "duckmail_api_key": "", + "oauth_issuer": "https://auth.openai.com", + "oauth_client_id": "app_EMoamEEZ73f0CkXaXp7hrann", + "oauth_redirect_uri": "http://localhost:1455/auth/callback", + "upload_api_url": "", + "upload_api_token": "", + "accounts_file": "accounts.txt", + "csv_file": "registered_accounts.csv", + "ak_file": "ak.txt", + "rk_file": "rk.txt", + "token_json_dir": "codex_accounts_tokens" +} \ No newline at end of file diff --git a/codex/config.json b/codex/config.json deleted file mode 100644 index a9c0ad9..0000000 --- a/codex/config.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "total_accounts": 800, - "concurrent_workers": 8, - "headless": false, - "proxy": "", - "cf_worker_domain": "", - "cf_email_domain": "", - "cf_admin_password": "", - "oauth_issuer": "https://auth.openai.com", - "oauth_client_id": "app_EMoamEEZ73f0CkXaXp7hrann", - "oauth_redirect_uri": "http://localhost:1455/auth/callback", - "upload_api_url": "https://你的CPA地址/v0/management/auth-files", - "upload_api_token": "你的CPA密码", - "cli_proxy_api_base": "你的CPA基础URL", - "cli_proxy_management_url": "http://你的CPA地址/management.html#/oauth", - "cli_proxy_password": "你的CPA密码", - "accounts_file": "accounts.txt", - "csv_file": "registered_accounts.csv", - "ak_file": "ak.txt", - "rk_file": "rk.txt" -} \ No newline at end of file diff --git a/codex/protocol_keygen.py b/codex/protocol_keygen.py index 1d9acc0..6eaa138 100644 --- a/codex/protocol_keygen.py +++ b/codex/protocol_keygen.py @@ -62,9 +62,12 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # =================== 配置加载 =================== + def load_config(): """加载外部配置文件""" - config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") + 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: @@ -79,15 +82,16 @@ 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", "") +# 邮箱配置(DuckMail) +DUCKMAIL_API_BASE = _config.get("duckmail_api_base", "https://api.duckmail.sbs") +DUCKMAIL_API_KEY = _config.get("duckmail_api_key", "") # 可选,私有域名时填写 dk_xxx # 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") +OAUTH_REDIRECT_URI = _config.get( + "oauth_redirect_uri", "http://localhost:1455/auth/callback" +) # 上传配置 UPLOAD_API_URL = _config.get("upload_api_url", "") @@ -98,6 +102,11 @@ 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") +# 单账号 Token JSON 输出目录(相对于脚本所在目录) +_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +TOKEN_JSON_DIR = os.path.join( + _SCRIPT_DIR, _config.get("token_json_dir", "codex_accounts_tokens") +) # 并发文件写入锁(多线程共享文件时防止数据竞争) _file_lock = threading.Lock() @@ -111,6 +120,7 @@ CHATGPT_BASE = "https://chatgpt.com" # =================== HTTP 会话管理 =================== + def create_session(): """创建带重试策略的 HTTP 会话""" session = requests.Session() @@ -188,13 +198,44 @@ def generate_random_password(length=16): 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", + "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", + "Smith", + "Johnson", + "Williams", + "Brown", + "Jones", + "Garcia", + "Miller", + "Davis", + "Wilson", + "Anderson", + "Thomas", + "Taylor", + "Moore", + "Martin", ] return random.choice(first), random.choice(last) @@ -211,8 +252,8 @@ 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') + 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", @@ -225,14 +266,16 @@ def generate_datadog_trace(): def generate_pkce(): """生成 PKCE code_verifier 和 code_challenge""" - code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(64)).rstrip(b"=").decode("ascii") + 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 # @@ -241,7 +284,7 @@ def generate_pkce(): # 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)) +# c) data = base64(JSON.stringify(config)) # d) hash = fnv1a_32(seed + data) # e) 若 hash 的 hex 前缀 ≤ difficulty → 返回 data + "~S" # 3. 最终 token = "gAAAAAB" + answer @@ -253,10 +296,11 @@ def generate_pkce(): # 然后做 xorshift 混合 + 转 8 位 hex # + class SentinelTokenGenerator: """ Sentinel Token 纯 Python 生成器 - + 通过逆向 sentinel SDK 的 PoW 算法, 纯 Python 构造合法的 openai-sentinel-token。 """ @@ -273,12 +317,12 @@ class SentinelTokenGenerator: 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 @@ -291,23 +335,23 @@ class SentinelTokenGenerator: code = ord(ch) h ^= code # Math.imul(h, 16777619) >>> 0 模拟无符号32位乘法 - h = ((h * 16777619) & 0xFFFFFFFF) + 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 >> 16 + h = (h * 2246822507) & 0xFFFFFFFF + h ^= h >> 13 + h = (h * 3266489909) & 0xFFFFFFFF + h ^= h >> 16 h = h & 0xFFFFFFFF # 转为8位 hex 字符串,左补零 - return format(h, '08x') + return format(h, "08x") def _get_config(self): """ 构造浏览器环境数据数组(_getConfig 方法逆向还原) - + SDK 中的元素对应关系(按索引): [0] screen.width + screen.height → "1920x1080" 格式 [1] new Date().toString() → 时间字符串 @@ -333,7 +377,9 @@ class SentinelTokenGenerator: 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)") + 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 @@ -347,43 +393,62 @@ class SentinelTokenGenerator: 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", + "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"]) + 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 参数 + 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] 时间起点 + time_origin, # [18] 时间起点 ] return config @@ -392,21 +457,21 @@ class SentinelTokenGenerator: """ 模拟 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') + 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 @@ -432,11 +497,11 @@ class SentinelTokenGenerator: def generate_token(self, seed=None, difficulty=None): """ 生成 sentinel token(完整 PoW 流程) - + 参数: seed: PoW 种子(来自服务端的 proofofwork.seed) difficulty: 难度值(来自服务端的 proofofwork.difficulty) - + 返回: 格式为 "gAAAAAB..." 的 sentinel token 字符串 """ @@ -445,7 +510,6 @@ class SentinelTokenGenerator: seed = self.requirements_seed difficulty = difficulty or "0" - start_time = time.time() config = self._get_config() @@ -464,7 +528,7 @@ class SentinelTokenGenerator: def generate_requirements_token(self): """ 生成 requirements token(不需要服务端参数) - + 这是 SDK 中 getRequirementsToken() 的还原。 用于不需要服务端 seed 的场景(如注册页面初始化)。 """ @@ -475,80 +539,139 @@ class SentinelTokenGenerator: return "gAAAAAC" + data # 注意前缀是 C 不是 B -# =================== Cloudflare 临时邮箱 =================== +# =================== DuckMail 临时邮箱 =================== + + +def _get_duckmail_domains(session): + """获取 DuckMail 可用域名列表""" + headers = {"Accept": "application/json"} + if DUCKMAIL_API_KEY: + headers["Authorization"] = f"Bearer {DUCKMAIL_API_KEY}" + try: + res = session.get(f"{DUCKMAIL_API_BASE}/domains", headers=headers, timeout=10) + if res.status_code == 200: + members = res.json().get("hydra:member", []) + return [m["domain"] for m in members if m.get("isVerified")] + except Exception as e: + print(f" ⚠️ 获取域名列表失败: {e}") + return ["duckmail.sbs"] + def create_temp_email(session): - """通过 Cloudflare Worker 创建临时邮箱""" - print("📧 创建临时邮箱...") + """通过 DuckMail API 创建临时邮箱账号,返回 (email, dm_token)""" + print("📧 创建 DuckMail 临时邮箱...") 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) + username = "".join(name_chars) + + domains = _get_duckmail_domains(session) + domain = random.choice(domains) + email = f"{username}@{domain}" + mail_password = generate_random_password() + + create_headers = {"Content-Type": "application/json", "Accept": "application/json"} + if DUCKMAIL_API_KEY: + create_headers["Authorization"] = f"Bearer {DUCKMAIL_API_KEY}" 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, + f"{DUCKMAIL_API_BASE}/accounts", + json={"address": email, "password": mail_password}, + headers=create_headers, + timeout=15, ) - 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}") + if res.status_code == 201: + tok_res = session.post( + f"{DUCKMAIL_API_BASE}/token", + json={"address": email, "password": mail_password}, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + timeout=10, + ) + if tok_res.status_code == 200: + token = tok_res.json().get("token") + if token: + print(f" ✅ 邮箱: {email}") + return email, token + print(f" ❌ 获取 token 失败: {tok_res.status_code} {tok_res.text[:100]}") + else: + print(f" ❌ 创建邮箱失败: {res.status_code} {res.text[:100]}") except Exception as e: print(f" ❌ 异常: {e}") return None, None -def fetch_emails(session, email, cf_token): - """获取邮箱中的邮件""" +def fetch_emails(session, email, dm_token): + """获取 DuckMail 收件箱邮件列表(不含正文)""" 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, + f"{DUCKMAIL_API_BASE}/messages", + headers={ + "Authorization": f"Bearer {dm_token}", + "Accept": "application/json", + }, + timeout=30, ) if res.status_code == 200: - return res.json().get("results", []) + return res.json().get("hydra:member", []) except Exception: pass return [] +def fetch_email_body(session, msg_id, dm_token): + """获取 DuckMail 邮件详情(含 text/html 正文)""" + try: + res = session.get( + f"{DUCKMAIL_API_BASE}/messages/{msg_id}", + headers={ + "Authorization": f"Bearer {dm_token}", + "Accept": "application/json", + }, + timeout=30, + ) + if res.status_code == 200: + detail = res.json() + # 优先返回 text,其次拼接 html + return detail.get("text") or "".join(detail.get("html", [])) + 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) + 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) + 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'(?\s*(\d{6})\s*<", r"(? 0 else 0 - print(f"📊 {i+1}/{TOTAL_ACCOUNTS} | ✅{ok} ❌{fail} | 吞吐 {throughput:.1f}s/个 | 已用 {wall:.0f}s") + print( + f"📊 {i+1}/{TOTAL_ACCOUNTS} | ✅{ok} ❌{fail} | 吞吐 {throughput:.1f}s/个 | 已用 {wall:.0f}s" + ) if i < TOTAL_ACCOUNTS - 1: wait = random.randint(3, 8) @@ -2230,9 +2493,7 @@ def run_batch(): time.sleep(jitter) try: email, password, success, t_reg, t_total = register_one( - worker_id=worker_id, - task_index=task_index, - total=TOTAL_ACCOUNTS + 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: @@ -2260,7 +2521,9 @@ def run_batch(): 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") + print( + f"📊 {done}/{TOTAL_ACCOUNTS} | ✅{ok} ❌{fail} | 吞吐 {throughput:.1f}s/个 | 已用 {wall:.0f}s" + ) except Exception as e: with results_lock: fail += 1 @@ -2270,7 +2533,9 @@ def run_batch(): 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)") + 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__": diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..88b5a5d --- /dev/null +++ b/config.example.json @@ -0,0 +1,17 @@ +{ + "total_accounts": 3, + "duckmail_api_base": "https://api.duckmail.sbs", + "duckmail_bearer": "", + "proxy": "http://127.0.0.1:7897", + "output_file": "registered_accounts.txt", + "enable_oauth": true, + "oauth_required": true, + "oauth_issuer": "https://auth.openai.com", + "oauth_client_id": "app_EMoamEEZ73f0CkXaXp7hrann", + "oauth_redirect_uri": "http://localhost:1455/auth/callback", + "ak_file": "ak.txt", + "rk_file": "rk.txt", + "token_json_dir": "codex_tokens", + "upload_api_url": "", + "upload_api_token": "" +} \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index eb65acc..0000000 --- a/config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "_comment": "ChatGPT(DuckMail)", - - "total_accounts": 5, - - "duckmail_api_base": "https://api.duckmail.sbs", - "duckmail_bearer": "", - - "proxy": "", - - "output_file": "registered_accounts.txt", - "enable_oauth": true, - "oauth_required": true, - "oauth_issuer": "https://auth.openai.com", - "oauth_client_id": "app_EMoamEEZ73f0CkXaXp7hrann", - "oauth_redirect_uri": "http://localhost:1455/auth/callback", - "ak_file": "ak.txt", - "rk_file": "rk.txt", - "token_json_dir": "codex_tokens", - "upload_api_url": "http://localhost:8317/v0/management/auth-files", - "upload_api_token": "你的cpa面板登录密码" -} diff --git a/duckmaildoc.md b/duckmaildoc.md new file mode 100644 index 0000000..8a065ae --- /dev/null +++ b/duckmaildoc.md @@ -0,0 +1,228 @@ +``` +# DuckMail API Reference +# Base URL: https://api.duckmail.sbs +# Authentication: Bearer Token or API Key (dk_xxx) +# This file is designed to be sent to AI assistants for integration help. + +--- + +## Authentication + +### Bearer Token +Obtain a token via POST /token with email address and password. +Include in requests: Authorization: Bearer