trytax commited on
Commit
d12c7eb
·
verified ·
1 Parent(s): db1e0f7

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +17 -0
  2. README.md +13 -10
  3. app.py +221 -0
  4. register.py +700 -0
  5. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ # 安装 Playwright 系统依赖和浏览器
9
+ RUN playwright install-deps chromium && playwright install chromium
10
+
11
+ COPY app.py register.py .
12
+
13
+ RUN mkdir -p /app/tokens
14
+
15
+ EXPOSE 7860
16
+
17
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,13 @@
1
- ---
2
- title: Try1
3
- emoji: 👁
4
- colorFrom: yellow
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
+ ---
2
+ title: OpenAI 自动注册器
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_file: app.py
8
+ pinned: false
9
+ ---
10
+
11
+ # OpenAI 自动注册系统
12
+
13
+ 使用 Playwright 自动化注册 OpenAI 账号,支持代理。点击按钮开始注册,成功后会显示邮箱和 Account ID,并保存 Token 文件。
app.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import asyncio
3
+ import sys
4
+ import json
5
+ import random
6
+ from pathlib import Path
7
+ import zipfile
8
+ from datetime import datetime
9
+
10
+ TOKEN_DIR = Path("/app/tokens")
11
+ TOKEN_DIR.mkdir(exist_ok=True)
12
+ ACCOUNTS_FILE = TOKEN_DIR / "accounts.txt"
13
+
14
+ # 全局状态
15
+ current_process = None
16
+
17
+ async def run_registration(proxy: str, loop_mode: bool = False, progress=gr.Progress()):
18
+ """
19
+ 执行注册,loop_mode=False 表示单次(带 --once),loop_mode=True 表示无限循环(不带 --once)
20
+ 返回实时日志的异步生成器
21
+ """
22
+ global current_process
23
+ cmd = [sys.executable, "/app/register.py"]
24
+ if not loop_mode:
25
+ cmd.append("--once")
26
+ if proxy and proxy.strip():
27
+ cmd.extend(["--proxy", proxy.strip()])
28
+
29
+ process = await asyncio.create_subprocess_exec(
30
+ *cmd,
31
+ stdout=asyncio.subprocess.PIPE,
32
+ stderr=asyncio.subprocess.STDOUT,
33
+ )
34
+ current_process = process
35
+
36
+ lines = []
37
+ async for line in process.stdout:
38
+ decoded = line.decode('utf-8', errors='replace').rstrip()
39
+ lines.append(decoded)
40
+ if len(lines) % 10 == 0:
41
+ progress(0.5, desc="运行中...")
42
+ yield "\n".join(lines)
43
+
44
+ await process.wait()
45
+ current_process = None
46
+ progress(1.0, desc="进程结束")
47
+ yield "\n".join(lines) + "\n\n✅ 进程已结束。"
48
+
49
+ def stop_registration():
50
+ global current_process
51
+ if current_process and current_process.returncode is None:
52
+ current_process.terminate()
53
+ return "⏹️ 已发送停止信号"
54
+ return "没有正在运行的进程"
55
+
56
+ def list_token_files():
57
+ files = list(TOKEN_DIR.glob("token_*.json"))
58
+ return [str(f) for f in files]
59
+
60
+ def get_accounts_content():
61
+ if ACCOUNTS_FILE.exists():
62
+ return ACCOUNTS_FILE.read_text(encoding='utf-8')
63
+ return "暂无账号信息"
64
+
65
+ def download_accounts():
66
+ if ACCOUNTS_FILE.exists():
67
+ return str(ACCOUNTS_FILE)
68
+ return None
69
+
70
+ def download_all_files():
71
+ token_files = list(TOKEN_DIR.glob("token_*.json"))
72
+ if not token_files and not ACCOUNTS_FILE.exists():
73
+ return None
74
+
75
+ zip_path = TOKEN_DIR / f"all_files_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
76
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
77
+ for f in token_files:
78
+ zipf.write(f, arcname=f.name)
79
+ if ACCOUNTS_FILE.exists():
80
+ zipf.write(ACCOUNTS_FILE, arcname="accounts.txt")
81
+ return str(zip_path)
82
+
83
+ def download_latest():
84
+ files = list(TOKEN_DIR.glob("token_*.json"))
85
+ if files:
86
+ latest = max(files, key=lambda p: p.stat().st_mtime)
87
+ return latest
88
+ return None
89
+
90
+ def update_file_list():
91
+ files = list_token_files()
92
+ count = len(files)
93
+ return gr.update(value=files), gr.update(value=count)
94
+
95
+ # 构建 Gradio 界面
96
+ with gr.Blocks(title="OpenAI 自动注册 (双模式版)") as demo:
97
+ gr.Markdown("""
98
+ # 🤖 OpenAI 自动注册 (双模式版)
99
+ - **单次注册**:执行一次注册,完成后停止。
100
+ - **无限循环**:启动后 `register.py` 自己无限循环注册,可随时停止。
101
+ - 所有生成的 token 文件及 `accounts.txt` 均可单独下载或打包下载全部。
102
+ """)
103
+
104
+ with gr.Row():
105
+ proxy_input = gr.Textbox(
106
+ label="代理地址 (可选)",
107
+ placeholder="例如 http://127.0.0.1:7890"
108
+ )
109
+
110
+ with gr.Row():
111
+ single_btn = gr.Button("▶️ 单次注册", variant="primary")
112
+ loop_btn = gr.Button("🔄 无限循环", variant="primary")
113
+ stop_btn = gr.Button("⏹️ 停止", variant="secondary")
114
+
115
+ output = gr.Textbox(label="实时日志", lines=20, interactive=False)
116
+ progress_bar = gr.Progress()
117
+
118
+ gr.Markdown("---\n### 📁 生成的 Token 文件")
119
+ with gr.Row():
120
+ file_count = gr.Number(value=0, label="当前 Token 数量", interactive=False)
121
+ file_list = gr.Files(label="所有 Token 文件", file_count="multiple")
122
+ with gr.Row():
123
+ refresh_btn = gr.Button("🔄 刷新文件列表")
124
+ download_latest_btn = gr.Button("📥 下载最新 Token")
125
+ download_all_btn = gr.Button("📦 下载全部文件")
126
+
127
+ gr.Markdown("---\n### 📋 账号信息 (accounts.txt)")
128
+ with gr.Row():
129
+ accounts_display = gr.Textbox(label="accounts.txt 内容", lines=10, interactive=False)
130
+ refresh_accounts_btn = gr.Button("🔄 刷新账号列表")
131
+ download_accounts_btn = gr.Button("📥 下载 accounts.txt")
132
+
133
+ # 单次注册
134
+ async def single_run(proxy):
135
+ yield gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=True), None
136
+ logs = ""
137
+ async for log in run_registration(proxy, loop_mode=False):
138
+ logs = log
139
+ yield gr.update(), gr.update(), gr.update(), logs
140
+ yield gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=False), logs
141
+
142
+ single_btn.click(
143
+ fn=single_run,
144
+ inputs=proxy_input,
145
+ outputs=[single_btn, loop_btn, stop_btn, output],
146
+ queue=True
147
+ ).then(
148
+ fn=update_file_list,
149
+ outputs=[file_list, file_count]
150
+ ).then(
151
+ fn=get_accounts_content,
152
+ outputs=accounts_display
153
+ )
154
+
155
+ # 无限循环
156
+ async def loop_run(proxy):
157
+ yield gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=True), None
158
+ logs = ""
159
+ async for log in run_registration(proxy, loop_mode=True):
160
+ logs = log
161
+ yield gr.update(), gr.update(), gr.update(), logs
162
+ yield gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=False), logs
163
+
164
+ loop_btn.click(
165
+ fn=loop_run,
166
+ inputs=proxy_input,
167
+ outputs=[single_btn, loop_btn, stop_btn, output],
168
+ queue=True
169
+ ).then(
170
+ fn=update_file_list,
171
+ outputs=[file_list, file_count]
172
+ ).then(
173
+ fn=get_accounts_content,
174
+ outputs=accounts_display
175
+ )
176
+
177
+ # 停止
178
+ def stop_fn():
179
+ msg = stop_registration()
180
+ return gr.update(interactive=False), msg
181
+
182
+ stop_btn.click(
183
+ fn=stop_fn,
184
+ outputs=[stop_btn, output]
185
+ ).then(
186
+ fn=update_file_list,
187
+ outputs=[file_list, file_count]
188
+ ).then(
189
+ fn=get_accounts_content,
190
+ outputs=accounts_display
191
+ )
192
+
193
+ # 刷新文件列表
194
+ refresh_btn.click(
195
+ fn=update_file_list,
196
+ outputs=[file_list, file_count]
197
+ )
198
+
199
+ download_latest_btn.click(
200
+ fn=download_latest,
201
+ outputs=gr.File(label="下载最新 Token")
202
+ )
203
+
204
+ download_all_btn.click(
205
+ fn=download_all_files,
206
+ outputs=gr.File(label="下载全部文件")
207
+ )
208
+
209
+ # 账号相关
210
+ refresh_accounts_btn.click(
211
+ fn=get_accounts_content,
212
+ outputs=accounts_display
213
+ )
214
+
215
+ download_accounts_btn.click(
216
+ fn=download_accounts,
217
+ outputs=gr.File(label="下载 accounts.txt")
218
+ )
219
+
220
+ demo.queue()
221
+ demo.launch(server_name="0.0.0.0", server_port=7860)
register.py ADDED
@@ -0,0 +1,700 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ OpenAI 自动注册脚本(异步最终版,含弹窗关闭)
5
+ """
6
+ import json
7
+ import os
8
+ import re
9
+ import sys
10
+ import time
11
+ import random
12
+ import string
13
+ import secrets
14
+ import hashlib
15
+ import base64
16
+ import argparse
17
+ import asyncio
18
+ from pathlib import Path
19
+ from datetime import datetime, timedelta
20
+ from dataclasses import dataclass
21
+ from typing import Any, Dict, Optional, List, Tuple
22
+
23
+ import urllib.parse
24
+ import urllib.request
25
+ import urllib.error
26
+
27
+ from curl_cffi import requests
28
+ from playwright.async_api import async_playwright
29
+
30
+ # ==========================================
31
+ # 常量配置
32
+ # ==========================================
33
+ OUT_DIR = Path(__file__).parent.resolve()
34
+ TOKEN_DIR = OUT_DIR / "tokens"
35
+ TOKEN_DIR.mkdir(parents=True, exist_ok=True)
36
+
37
+ AUTH_URL = "https://auth.openai.com/oauth/authorize"
38
+ TOKEN_URL = "https://auth.openai.com/oauth/token"
39
+ CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
40
+ DEFAULT_REDIRECT_URI = "http://localhost:1455/auth/callback"
41
+ DEFAULT_SCOPE = "openid email profile offline_access"
42
+
43
+ # ==========================================
44
+ # 辅助函数
45
+ # ==========================================
46
+ def _gen_password() -> str:
47
+ alphabet = string.ascii_letters + string.digits
48
+ special = "!@#$%^&*.-"
49
+ base = [
50
+ random.choice(string.ascii_lowercase),
51
+ random.choice(string.ascii_uppercase),
52
+ random.choice(string.digits),
53
+ random.choice(special),
54
+ ]
55
+ base += [random.choice(alphabet + special) for _ in range(12)]
56
+ random.shuffle(base)
57
+ return "".join(base)
58
+
59
+ def _random_name() -> str:
60
+ letters = string.ascii_lowercase
61
+ n = random.randint(5, 9)
62
+ s = ''.join(random.choice(letters) for _ in range(n))
63
+ return s.capitalize()
64
+
65
+ def _random_birthdate() -> str:
66
+ start = datetime(1970,1,1); end = datetime(1999,12,31)
67
+ delta = end - start
68
+ d = start + timedelta(days=random.randrange(delta.days + 1))
69
+ return d.strftime('%Y-%m-%d')
70
+
71
+ def _b64url_no_pad(raw: bytes) -> str:
72
+ return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
73
+
74
+ def _sha256_b64url_no_pad(s: str) -> str:
75
+ return _b64url_no_pad(hashlib.sha256(s.encode("ascii")).digest())
76
+
77
+ def _random_state(nbytes: int = 16) -> str:
78
+ return secrets.token_urlsafe(nbytes)
79
+
80
+ def _pkce_verifier() -> str:
81
+ return secrets.token_urlsafe(64)
82
+
83
+ def _parse_callback_url(callback_url: str) -> Dict[str, Any]:
84
+ candidate = callback_url.strip()
85
+ if not candidate:
86
+ return {"code": "","state": "","error": "","error_description": ""}
87
+ if "://" not in candidate:
88
+ if candidate.startswith("?"):
89
+ candidate = f"http://localhost{candidate}"
90
+ elif any(ch in candidate for ch in "/?#") or ":" in candidate:
91
+ candidate = f"http://{candidate}"
92
+ elif "=" in candidate:
93
+ candidate = f"http://localhost/?{candidate}"
94
+ parsed = urllib.parse.urlparse(candidate)
95
+ query = urllib.parse.parse_qs(parsed.query, keep_blank_values=True)
96
+ fragment = urllib.parse.parse_qs(parsed.fragment, keep_blank_values=True)
97
+ for key, values in fragment.items():
98
+ if key not in query or not query[key] or not (query[key][0] or "").strip():
99
+ query[key] = values
100
+ def get1(k: str) -> str:
101
+ v = query.get(k, [""])
102
+ return (v[0] or "").strip()
103
+ code = get1("code"); state = get1("state")
104
+ error = get1("error"); error_description = get1("error_description")
105
+ if code and not state and "#" in code:
106
+ code, state = code.split("#",1)
107
+ if not error and error_description:
108
+ error, error_description = error_description, ""
109
+ return {"code": code,"state": state,"error": error,"error_description": error_description}
110
+
111
+ def _jwt_claims_no_verify(id_token: str) -> Dict[str, Any]:
112
+ if not id_token or id_token.count(".") < 2:
113
+ return {}
114
+ payload_b64 = id_token.split(".")[1]
115
+ pad = "=" * ((4 - (len(payload_b64) % 4)) % 4)
116
+ try:
117
+ payload = base64.urlsafe_b64decode((payload_b64 + pad).encode("ascii"))
118
+ return json.loads(payload.decode("utf-8"))
119
+ except Exception:
120
+ return {}
121
+
122
+ def _decode_jwt_segment(seg: str) -> Dict[str, Any]:
123
+ raw = (seg or "").strip()
124
+ if not raw: return {}
125
+ pad = "=" * ((4 - (len(raw) % 4)) % 4)
126
+ try:
127
+ decoded = base64.urlsafe_b64decode((raw + pad).encode("ascii"))
128
+ return json.loads(decoded.decode("utf-8"))
129
+ except Exception:
130
+ return {}
131
+
132
+ def _to_int(v: Any) -> int:
133
+ try: return int(v)
134
+ except (TypeError, ValueError): return 0
135
+
136
+ def _post_form(url: str, data: Dict[str, str], timeout: int = 30) -> Dict[str, Any]:
137
+ body = urllib.parse.urlencode(data).encode("utf-8")
138
+ req = urllib.request.Request(
139
+ url, data=body, method="POST",
140
+ headers={"Content-Type": "application/x-www-form-urlencoded","Accept": "application/json"},
141
+ )
142
+ try:
143
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
144
+ raw = resp.read()
145
+ if resp.status != 200:
146
+ raise RuntimeError(f"token exchange failed: {resp.status}: {raw.decode('utf-8','replace')}")
147
+ return json.loads(raw.decode("utf-8"))
148
+ except urllib.error.HTTPError as exc:
149
+ raw = exc.read()
150
+ raise RuntimeError(f"token exchange failed: {exc.code}: {raw.decode('utf-8','replace')}") from exc
151
+
152
+ @dataclass(frozen=True)
153
+ class OAuthStart:
154
+ auth_url: str
155
+ state: str
156
+ code_verifier: str
157
+ redirect_uri: str
158
+
159
+ def generate_oauth_url(*, redirect_uri: str = DEFAULT_REDIRECT_URI, scope: str = DEFAULT_SCOPE) -> OAuthStart:
160
+ state = _random_state()
161
+ code_verifier = _pkce_verifier()
162
+ code_challenge = _sha256_b64url_no_pad(code_verifier)
163
+ params = {
164
+ "client_id": CLIENT_ID,
165
+ "response_type": "code",
166
+ "redirect_uri": redirect_uri,
167
+ "scope": scope,
168
+ "state": state,
169
+ "code_challenge": code_challenge,
170
+ "code_challenge_method": "S256",
171
+ "prompt": "login",
172
+ "id_token_add_organizations": "true",
173
+ "codex_cli_simplified_flow": "true",
174
+ }
175
+ auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}"
176
+ return OAuthStart(auth_url=auth_url, state=state, code_verifier=code_verifier, redirect_uri=redirect_uri)
177
+
178
+ def fetch_sentinel_token(*, flow: str, did: str, proxies: Any = None) -> Optional[str]:
179
+ try:
180
+ body = json.dumps({"p": "", "id": did, "flow": flow})
181
+ resp = requests.post(
182
+ "https://sentinel.openai.com/backend-api/sentinel/req",
183
+ headers={
184
+ "origin": "https://sentinel.openai.com",
185
+ "referer": "https://sentinel.openai.com/backend-api/sentinel/frame.html?sv=20260219f9f6",
186
+ "content-type": "text/plain;charset=UTF-8",
187
+ },
188
+ data=body,
189
+ proxies=proxies,
190
+ impersonate="chrome",
191
+ timeout=15,
192
+ )
193
+ if resp.status_code != 200:
194
+ print(f"[Error] Sentinel flow={flow} 状态码: {resp.status_code}")
195
+ try:
196
+ print(resp.text)
197
+ except Exception:
198
+ pass
199
+ return None
200
+ return resp.json().get("token")
201
+ except Exception as e:
202
+ print(f"[Error] Sentinel flow={flow} 获取失败: {e}")
203
+ return None
204
+
205
+ def submit_callback_url(*, callback_url: str, expected_state: str, code_verifier: str, redirect_uri: str = DEFAULT_REDIRECT_URI) -> str:
206
+ cb = _parse_callback_url(callback_url)
207
+ if cb["error"]:
208
+ desc = cb["error_description"]
209
+ raise RuntimeError(f"oauth error: {cb['error']}: {desc}".strip())
210
+ if not cb["code"]:
211
+ raise ValueError("callback url missing ?code=")
212
+ if not cb["state"]:
213
+ raise ValueError("callback url missing ?state=")
214
+ if cb["state"] != expected_state:
215
+ raise ValueError("state mismatch")
216
+
217
+ token_resp = _post_form(
218
+ TOKEN_URL,
219
+ {
220
+ "grant_type": "authorization_code",
221
+ "client_id": CLIENT_ID,
222
+ "code": cb["code"],
223
+ "redirect_uri": redirect_uri,
224
+ "code_verifier": code_verifier,
225
+ },
226
+ )
227
+ access_token = (token_resp.get("access_token") or "").strip()
228
+ refresh_token = (token_resp.get("refresh_token") or "").strip()
229
+ id_token = (token_resp.get("id_token") or "").strip()
230
+ expires_in = _to_int(token_resp.get("expires_in"))
231
+
232
+ claims = _jwt_claims_no_verify(id_token)
233
+ email = str(claims.get("email") or "").strip()
234
+ auth_claims = claims.get("https://api.openai.com/auth") or {}
235
+ account_id = str(auth_claims.get("chatgpt_account_id") or "").strip()
236
+
237
+ now = int(time.time())
238
+ expired_rfc3339 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now + max(expires_in, 0)))
239
+ now_rfc3339 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now))
240
+
241
+ config = {
242
+ "id_token": id_token,
243
+ "access_token": access_token,
244
+ "refresh_token": refresh_token,
245
+ "account_id": account_id,
246
+ "last_refresh": now_rfc3339,
247
+ "email": email,
248
+ "type": "codex",
249
+ "expired": expired_rfc3339,
250
+ }
251
+ return json.dumps(config, ensure_ascii=False, separators=(",", ":"))
252
+
253
+ # ==========================================
254
+ # Playwright 邮箱功能(含弹窗关闭)
255
+ # ==========================================
256
+ async def generate_email_by_click(mail_page) -> Tuple[str, str]:
257
+ """关闭“我知道了”弹窗,然后点击生成按钮,返回 (email, token)"""
258
+ try:
259
+ close_btn = await mail_page.query_selector('button.announcement-close-btn, button:has-text("我知道了")')
260
+ if close_btn:
261
+ is_visible = await close_btn.is_visible()
262
+ if is_visible:
263
+ await close_btn.click()
264
+ print("[*] 已点击“我知道了”关闭按钮")
265
+ await asyncio.sleep(1)
266
+ except Exception as e:
267
+ print(f"[*] 关闭弹窗异常(可忽略): {e}")
268
+
269
+ button_selectors = [
270
+ 'button.action-btn',
271
+ 'button:has-text("随机生成")',
272
+ 'button:has-text("Generate")',
273
+ 'button:has-text("刷新")',
274
+ 'button:has-text("新邮箱")',
275
+ '.generate-btn',
276
+ '#generate'
277
+ ]
278
+ button = None
279
+ for selector in button_selectors:
280
+ try:
281
+ button = await mail_page.wait_for_selector(selector, timeout=5000)
282
+ print(f"找到按钮: {selector}")
283
+ break
284
+ except:
285
+ continue
286
+
287
+ if not button:
288
+ raise RuntimeError("未找到生成按钮,请检查页面元素")
289
+
290
+ async with mail_page.expect_response(lambda response: "/api/generate-email" in response.url and response.status == 200) as response_info:
291
+ await button.click()
292
+ response = await response_info.value
293
+ data = await response.json()
294
+ if data.get('success'):
295
+ email = data['data']['email']
296
+ token = data['auth']['token']
297
+ return email, token
298
+ else:
299
+ raise RuntimeError(f"生成邮箱失败: {data.get('error')}")
300
+
301
+ async def playwright_fetch_code(mail_page, email: str, token: str, timeout: int = 180) -> str:
302
+ """在邮箱页面轮询获取验证码,返回验证码字符串(增强版)"""
303
+ try:
304
+ await mail_page.evaluate("""
305
+ if (typeof refreshInbox === 'function') {
306
+ refreshInbox();
307
+ }
308
+ """)
309
+ refresh_btn = await mail_page.query_selector('button:has-text("手动刷新")')
310
+ if refresh_btn:
311
+ await refresh_btn.click()
312
+ print("[*] 点击了手动刷新按钮")
313
+ await asyncio.sleep(2)
314
+ except Exception as e:
315
+ print(f"[*] 首次刷新异常: {e}")
316
+
317
+ start_time = time.time()
318
+ attempt = 0
319
+ while time.time() - start_time < timeout:
320
+ attempt += 1
321
+ try:
322
+ await mail_page.evaluate("refreshInbox?.()")
323
+ refresh_btn = await mail_page.query_selector('button:has-text("手动刷新")')
324
+ if refresh_btn:
325
+ await refresh_btn.click()
326
+ except:
327
+ pass
328
+
329
+ result = await mail_page.evaluate("""
330
+ async ([email, token]) => {
331
+ try {
332
+ const response = await fetch(`/api/emails?email=${encodeURIComponent(email)}`, {
333
+ method: 'GET',
334
+ headers: {
335
+ 'Accept': 'application/json',
336
+ 'X-Inbox-Token': token
337
+ }
338
+ });
339
+ if (!response.ok) {
340
+ return { success: false, error: `HTTP ${response.status}` };
341
+ }
342
+ return await response.json();
343
+ } catch (error) {
344
+ return { success: false, error: error.toString() };
345
+ }
346
+ }
347
+ """, [email, token])
348
+
349
+ if not result.get('success'):
350
+ print(f"[*] 第 {attempt} 次获取邮件列表失败: {result.get('error')}")
351
+ await asyncio.sleep(3)
352
+ continue
353
+
354
+ emails = result.get('data', {}).get('emails', [])
355
+ emails.sort(key=lambda x: x.get('timestamp', 0), reverse=True)
356
+ print(f"[*] 第 {attempt} 次获取到 {len(emails)} 封邮件")
357
+
358
+ for idx, mail in enumerate(emails):
359
+ subject = mail.get('subject', '')
360
+ content = mail.get('html_content', '') or mail.get('content', '') or mail.get('text', '')
361
+ print(f" 邮件 {idx+1} 主题: {subject[:50]}")
362
+ print(f" 内容预览: {content[:100].replace(chr(10), ' ')}")
363
+
364
+ patterns = [
365
+ (r'(?<!\d)(\d{6})(?!\d)', 1),
366
+ (r'代码为\s*(\d{6})', 1),
367
+ (r'verification code[:\s]*(\d{6})', 1),
368
+ (r'is[:\s]*(\d{6})', 1),
369
+ (r'<[^>]*>(\d)[^>]*>(\d)[^>]*>(\d)[^>]*>(\d)[^>]*>(\d)[^>]*>(\d)', 6),
370
+ ]
371
+ for pattern, group_count in patterns:
372
+ if group_count == 6:
373
+ match = re.search(pattern, content, re.DOTALL)
374
+ if match:
375
+ code = ''.join(match.groups())
376
+ if len(code) == 6 and code.isdigit():
377
+ print(f"[✓] 从分隔数字中提取验证码: {code}")
378
+ return code
379
+ else:
380
+ match = re.search(pattern, subject + ' ' + content, re.IGNORECASE)
381
+ if match:
382
+ code = match.group(1)
383
+ if len(code) == 6 and code.isdigit():
384
+ print(f"[✓] 提取验证码: {code}")
385
+ return code
386
+
387
+ print("[*] 未找到验证码,等待后重试...")
388
+ await asyncio.sleep(3)
389
+
390
+ return ""
391
+
392
+ # ==========================================
393
+ # 核心注册函数
394
+ # ==========================================
395
+ async def async_run_registration(proxy: Optional[str]) -> Optional[Tuple[str, str, str]]:
396
+ proxies = None
397
+ if proxy:
398
+ proxies = {"http": proxy, "https": proxy}
399
+
400
+ async with async_playwright() as p:
401
+ launch_args = [
402
+ '--disable-blink-features=AutomationControlled',
403
+ '--no-sandbox',
404
+ '--disable-gpu',
405
+ '--disable-dev-shm-usage',
406
+ ]
407
+ browser = await p.chromium.launch(headless=True, args=launch_args)
408
+ context_kwargs = {}
409
+ if proxy:
410
+ context_kwargs["proxy"] = {"server": proxy}
411
+ context = await browser.new_context(**context_kwargs)
412
+ mail_page = await context.new_page()
413
+
414
+ await mail_page.goto("https://mail.chatgpt.org.uk/", timeout=60000)
415
+ await mail_page.wait_for_load_state("networkidle")
416
+
417
+ email, token = await generate_email_by_click(mail_page)
418
+ print(f"[*] 成功获取临时邮箱: {email}")
419
+
420
+ s = requests.Session(proxies=proxies, impersonate="chrome")
421
+ s.headers.update({"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"})
422
+
423
+ try:
424
+ trace = s.get("https://cloudflare.com/cdn-cgi/trace", timeout=10)
425
+ loc_re = re.search(r"^loc=(.+)$", trace.text, re.MULTILINE)
426
+ loc = loc_re.group(1) if loc_re else None
427
+ print(f"[*] 当前 IP 所在地: {loc}")
428
+ if loc in ("CN", "HK"):
429
+ raise RuntimeError("代理所在地不支持 CN/HK")
430
+ except Exception as e:
431
+ print(f"[Error] 网络连接检查失败: {e}")
432
+ await browser.close()
433
+ return None
434
+
435
+ oauth = generate_oauth_url()
436
+ url = oauth.auth_url
437
+
438
+ try:
439
+ resp = s.get(url, timeout=15)
440
+ did = s.cookies.get("oai-did")
441
+ print(f"[*] Device ID: {did}")
442
+
443
+ signup_body = json.dumps({"username": {"value": email, "kind": "email"}, "screen_hint": "signup"})
444
+ sen_req_body = json.dumps({"p": "", "id": did, "flow": "authorize_continue"})
445
+
446
+ sen_resp = requests.post(
447
+ "https://sentinel.openai.com/backend-api/sentinel/req",
448
+ headers={
449
+ "origin": "https://sentinel.openai.com",
450
+ "referer": "https://sentinel.openai.com/backend-api/sentinel/frame.html?sv=20260219f9f6",
451
+ "content-type": "text/plain;charset=UTF-8",
452
+ "user-agent": s.headers["user-agent"],
453
+ },
454
+ data=sen_req_body,
455
+ proxies=proxies,
456
+ impersonate="chrome",
457
+ timeout=15,
458
+ )
459
+ print(f"[*] sentinel authorize_continue 状态: {sen_resp.status_code}")
460
+ if sen_resp.status_code != 200:
461
+ print(f"[Error] Sentinel 异常拦截")
462
+ await browser.close()
463
+ return None
464
+
465
+ sen_token = sen_resp.json().get("token")
466
+ sentinel = json.dumps({"p": "", "t": "", "c": sen_token, "id": did, "flow": "authorize_continue"}) if sen_token else None
467
+
468
+ so_token = fetch_sentinel_token(flow="oauth_create_account", did=did, proxies=proxies)
469
+ print(f"[*] sentinel oauth_create_account token: {bool(so_token)}")
470
+
471
+ signup_headers = {
472
+ "referer": "https://auth.openai.com/create-account",
473
+ "accept": "application/json",
474
+ "content-type": "application/json",
475
+ }
476
+ if sentinel:
477
+ signup_headers["openai-sentinel-token"] = sentinel
478
+ signup_resp = s.post(
479
+ "https://auth.openai.com/api/accounts/authorize/continue",
480
+ headers=signup_headers,
481
+ data=signup_body,
482
+ )
483
+ print(f"[*] authorize/continue 状态: {signup_resp.status_code}")
484
+ if signup_resp.status_code != 200:
485
+ print("[Error] 注册表单提交失败")
486
+ await browser.close()
487
+ return None
488
+
489
+ password = _gen_password()
490
+ register_headers = {
491
+ "referer": "https://auth.openai.com/create-account/password",
492
+ "accept": "application/json",
493
+ "content-type": "application/json",
494
+ }
495
+ if sentinel:
496
+ register_headers["openai-sentinel-token"] = sentinel
497
+ reg_resp = s.post(
498
+ "https://auth.openai.com/api/accounts/user/register",
499
+ headers=register_headers,
500
+ data=json.dumps({"password": password, "username": email}),
501
+ )
502
+ print(f"[*] 设置密码状态: {reg_resp.status_code}")
503
+ if reg_resp.status_code != 200:
504
+ print("[Error] 设置密码失败")
505
+ await browser.close()
506
+ return None
507
+
508
+ for attempt in range(3):
509
+ try:
510
+ send_headers = {
511
+ "referer": "https://auth.openai.com/create-account/password",
512
+ "accept": "application/json",
513
+ }
514
+ send_resp = s.get(
515
+ "https://auth.openai.com/api/accounts/email-otp/send",
516
+ headers=send_headers,
517
+ timeout=30,
518
+ )
519
+ print(f"[*] 触发发送验证码状态: {send_resp.status_code}")
520
+ if send_resp.status_code == 200:
521
+ break
522
+ except Exception as e:
523
+ print(f"[Warn] send 调用异常 (尝试 {attempt+1}/3): {e}")
524
+ await asyncio.sleep(2)
525
+ else:
526
+ print("[Error] 触发验证码发送失败,重试耗尽")
527
+ await browser.close()
528
+ return None
529
+
530
+ code = await playwright_fetch_code(mail_page, email, token, timeout=180)
531
+ if not code:
532
+ print("[Error] 未能收到验证码")
533
+ await browser.close()
534
+ return None
535
+ print(f"[*] 收到验证码: {code}")
536
+
537
+ code_body = json.dumps({"code": code})
538
+ validate_headers = {
539
+ "referer": "https://auth.openai.com/email-verification",
540
+ "accept": "application/json",
541
+ "content-type": "application/json",
542
+ }
543
+ if sentinel:
544
+ validate_headers["openai-sentinel-token"] = sentinel
545
+
546
+ code_resp = s.post(
547
+ "https://auth.openai.com/api/accounts/email-otp/validate",
548
+ headers=validate_headers,
549
+ data=code_body,
550
+ )
551
+ print(f"[*] 验证码校验状态: {code_resp.status_code}")
552
+ if code_resp.status_code != 200:
553
+ print("[Error] 验证码校验失败")
554
+ await browser.close()
555
+ return None
556
+
557
+ create_account_body = json.dumps({"name": _random_name(), "birthdate": _random_birthdate()})
558
+ create_headers = {
559
+ "referer": "https://auth.openai.com/about-you",
560
+ "accept": "application/json",
561
+ "content-type": "application/json",
562
+ }
563
+ if so_token:
564
+ create_headers["openai-sentinel-so-token"] = so_token
565
+
566
+ create_account_resp = s.post(
567
+ "https://auth.openai.com/api/accounts/create_account",
568
+ headers=create_headers,
569
+ data=create_account_body,
570
+ )
571
+ print(f"[*] 账户创建状态: {create_account_resp.status_code}")
572
+ if create_account_resp.status_code != 200:
573
+ print("[Error] 账户创建失败")
574
+ await browser.close()
575
+ return None
576
+
577
+ auth_cookie = s.cookies.get("oai-client-auth-session")
578
+ if not auth_cookie:
579
+ print("[Error] 未能获取到授权 Cookie")
580
+ await browser.close()
581
+ return None
582
+
583
+ auth_json = _decode_jwt_segment(auth_cookie.split(".")[0])
584
+ workspaces = auth_json.get("workspaces") or []
585
+ if not workspaces:
586
+ print("[Error] 授权 Cookie 里没有 workspace 信息")
587
+ await browser.close()
588
+ return None
589
+ workspace_id = str((workspaces[0] or {}).get("id") or "").strip()
590
+ if not workspace_id:
591
+ print("[Error] 无法解析 workspace_id")
592
+ await browser.close()
593
+ return None
594
+
595
+ select_body = json.dumps({"workspace_id": workspace_id})
596
+ select_resp = s.post(
597
+ "https://auth.openai.com/api/accounts/workspace/select",
598
+ headers={
599
+ "referer": "https://auth.openai.com/sign-in-with-chatgpt/codex/consent",
600
+ "content-type": "application/json",
601
+ },
602
+ data=select_body,
603
+ )
604
+ if select_resp.status_code != 200:
605
+ print(f"[Error] 选择 workspace 失败")
606
+ await browser.close()
607
+ return None
608
+
609
+ continue_url = str((select_resp.json() or {}).get("continue_url") or "").strip()
610
+ if not continue_url:
611
+ print("[Error] workspace/select 响应缺少 continue_url")
612
+ await browser.close()
613
+ return None
614
+
615
+ current_url = continue_url
616
+ for _ in range(6):
617
+ final_resp = s.get(current_url, allow_redirects=False, timeout=15)
618
+ location = final_resp.headers.get("Location") or ""
619
+ if final_resp.status_code not in [301, 302, 303, 307, 308]:
620
+ break
621
+ if not location:
622
+ break
623
+ next_url = urllib.parse.urljoin(current_url, location)
624
+ if "code=" in next_url and "state=" in next_url:
625
+ token_json = submit_callback_url(
626
+ callback_url=next_url,
627
+ code_verifier=oauth.code_verifier,
628
+ redirect_uri=oauth.redirect_uri,
629
+ expected_state=oauth.state,
630
+ )
631
+ await browser.close()
632
+ return token_json, email, password
633
+ current_url = next_url
634
+
635
+ print("[Error] 未能在重定向链中捕获到最终 Callback URL")
636
+ await browser.close()
637
+ return None
638
+
639
+ except Exception as e:
640
+ print(f"[Error] 运行时发生错误: {e}")
641
+ await browser.close()
642
+ return None
643
+
644
+ # ==========================================
645
+ # 主函数
646
+ # ==========================================
647
+ async def main():
648
+ parser = argparse.ArgumentParser(description="OpenAI 自动注册脚本(异步最终版)")
649
+ parser.add_argument("--proxy", default=None, help="代理地址,如 http://127.0.0.1:7890")
650
+ parser.add_argument("--once", action="store_true", help="只运行一次")
651
+ parser.add_argument("--sleep-min", type=int, default=5, help="循环模式最短等待秒数")
652
+ parser.add_argument("--sleep-max", type=int, default=30, help="循环模式最长等待秒数")
653
+ args = parser.parse_args()
654
+
655
+ sleep_min = max(1, args.sleep_min)
656
+ sleep_max = max(sleep_min, args.sleep_max)
657
+
658
+ count = 0
659
+ print("[Info] OpenAI 自动注册脚本 (异步最终版) 启动")
660
+ OUT_DIR.mkdir(parents=True, exist_ok=True)
661
+
662
+ while True:
663
+ count += 1
664
+ print(f"\n[{datetime.now().strftime('%H:%M:%S')}] >>> 开始第 {count} 次注册流程 <<<")
665
+ try:
666
+ result = await async_run_registration(args.proxy)
667
+ if result:
668
+ token_json, email, password = result
669
+ try:
670
+ t_data = json.loads(token_json)
671
+ fname_email = t_data.get("email", "unknown").replace("@", "_")
672
+ except Exception:
673
+ fname_email = "unknown"
674
+
675
+ file_path = TOKEN_DIR / f"token_{fname_email}_{int(time.time())}.json"
676
+ try:
677
+ file_path.write_text(token_json, encoding="utf-8")
678
+ print(f"[*] 成功! Token 已保存至: {file_path}")
679
+ except Exception as e:
680
+ print(f"[Error] 保存 token 失败: {e}")
681
+
682
+ acc_file = TOKEN_DIR / "accounts.txt"
683
+ try:
684
+ with open(acc_file, "a", encoding="utf-8") as f:
685
+ f.write(f"{email}----{password}\n")
686
+ except Exception as e:
687
+ print(f"[Error] 保存账号信息失败: {e}")
688
+ else:
689
+ print("[-] 本次注册失败。")
690
+ except Exception as e:
691
+ print(f"[Error] 发生未捕获异常: {e}")
692
+
693
+ if args.once:
694
+ break
695
+ wait_time = random.randint(sleep_min, sleep_max)
696
+ print(f"[*] 休息 {wait_time} 秒...")
697
+ await asyncio.sleep(wait_time)
698
+
699
+ if __name__ == "__main__":
700
+ asyncio.run(main())
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ playwright
3
+ curl_cffi