Initial project setup

This commit is contained in:
AaronL725
2026-05-11 02:40:03 +08:00
commit 48fd6925f8
7 changed files with 3387 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
*.py text eol=lf
*.md text eol=lf
*.json text eol=lf
*.txt text eol=lf
Executable
+23
View File
@@ -0,0 +1,23 @@
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
.venv/
venv/
env/
accounts_*.txt
mail_credentials.txt
*.txt
!requirements.txt
config.json
config.local.json
*.log
.DS_Store
.idea/
.vscode/
DrissionPage/
autoPortData/
+151
View File
@@ -0,0 +1,151 @@
# Grok Register
Grok Register 是一个 Python 自动化注册工具,支持 GUI 和 CLI 两种运行方式。项目可接入临时邮箱服务完成注册流程,并将成功账号保存到本地文件或写入 [grok2api](https://github.com/chenyme/grok2api) 令牌池。
> 本项目仅用于自动化流程研究、测试环境验证和个人学习。请遵守目标网站服务条款、当地法律法规和第三方服务限制。
## 功能
- 支持 GUI 图形界面运行。
- 支持 CLI 终端运行,不启动 Tk GUI。
- 注册流程使用 Chromium/Chrome 浏览器页面完成。
- 支持 DuckMail、YYDS、Cloudflare 临时邮箱接口。
- 支持验证码邮件轮询和解析。
- 支持成功账号实时写入 `accounts_*.txt`
- 支持将 SSO token 写入 grok2api 本地或远端池。
- 支持注册后尝试开启 NSFW。
- 支持页面卡住检测、当前账号重试、浏览器重启和内存清理。
## 环境要求
- Python 3.9+
- Google Chrome 或 Chromium
- 可访问注册页面和临时邮箱 API 的网络环境
## 安装
下载项目到电脑:
```bash
git clone https://github.com/AaronL725/grok-register.git
cd grok-register
```
安装依赖:
```bash
pip install -r requirements.txt
```
复制配置文件:
```bash
cp config.example.json config.json
```
然后按需编辑 `config.json`
## 配置
常用配置项:
| 配置项 | 说明 |
| --- | --- |
| `email_provider` | 邮箱服务商:`duckmail``yyds``cloudflare` |
| `register_count` | 本次目标注册数量 |
| `proxy` | 代理地址,可留空 |
| `enable_nsfw` | 注册后是否尝试开启 NSFW |
| `cloudflare_api_base` | Cloudflare 临时邮箱 API 地址 |
| `cloudflare_auth_mode` | Cloudflare API 鉴权模式:`none``bearer``x-api-key``query-key` |
| `defaultDomains` | Cloudflare 临时邮箱默认域名 |
| `grok2api_auto_add_local` | 是否写入本地 grok2api token 池 |
| `grok2api_local_token_file` | 本地 grok2api token 文件路径 |
| `grok2api_auto_add_remote` | 是否写入远端 grok2api |
| `grok2api_remote_base` | 远端 grok2api 管理 API 地址 |
| `grok2api_remote_app_key` | 远端 grok2api app key |
`config.json` 包含个人配置和密钥,不要提交到 Git。
## 运行
### CLI 模式
CLI 模式不会启动 Tk GUI,但注册流程仍会打开 Chromium/Chrome 浏览器页面。
```bash
python grok_register_ttk.py cli
```
看到提示后输入:
```text
start
```
停止任务:
```text
Ctrl+C
```
CLI 模式适合长时间批量运行。程序每成功注册 5 个账号会关闭浏览器、清理运行时对象并重新启动浏览器,降低长任务内存占用。
### GUI 模式
```bash
python grok_register_ttk.py
```
GUI 模式会打开 Tkinter 窗口,适合手动调整配置和观察日志。
## 输出文件
运行过程中会生成:
- `accounts_*.txt`:成功账号、密码和 SSO token。
- `mail_credentials.txt`:临时邮箱凭证。
- `*.log`:可选日志文件。
这些文件包含敏感信息,已被 `.gitignore` 忽略。
## 稳定性机制
- 每个账号结束后重启浏览器。
- 每成功 5 个账号执行一次内存清理。
- CLI 模式支持 `Ctrl+C` 中断并清理浏览器。
- 最终页长时间无变化时自动重试当前账号。
- 验证码未收到时自动更换邮箱重试。
## 常见问题
### CLI 模式为什么还会打开浏览器?
CLI 模式只是不启动 Tk GUI。注册页、Turnstile、验证码提交和 SSO cookie 获取仍依赖真实浏览器环境。
### NSFW 开启失败怎么办?
如果日志显示 `Cloudflare 防护拦截,HTTP 403`,说明请求被目标站点防护拦截。程序会继续保存账号和写入 grok2api。
### GUI 显示的数量和配置不同?
GUI 数量控件可能有上限。CLI 模式直接读取 `config.json` 中的 `register_count`
## Git 忽略规则
以下内容不会提交:
- `config.json`
- `*.txt`
- `.venv/`
- 日志、缓存和本地 IDE 文件
## 目录结构
```text
.
├── grok_register_ttk.py # 主程序
├── cf_mail_debug.py # Cloudflare 邮箱调试工具
├── config.example.json # 配置示例
├── requirements.txt # Python 依赖
└── README.md
```
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import re
import time
from typing import Any, Dict, List, Optional, Tuple
from curl_cffi import requests
def extract_code(text: str, subject: str = "") -> Optional[str]:
if subject:
m = re.search(r"\b([A-Z0-9]{3}-[A-Z0-9]{3})\b", subject, re.IGNORECASE)
if m:
return m.group(1)
m = re.search(r"\b([A-Z0-9]{3}-[A-Z0-9]{3})\b", text, re.IGNORECASE)
if m:
return m.group(1)
for p in [
r"verification\s+code[:\s]+(\d{4,8})",
r"your\s+code[:\s]+(\d{4,8})",
r"confirm(?:ation)?\s+code[:\s]+(\d{4,8})",
]:
m = re.search(p, text, re.IGNORECASE)
if m:
return m.group(1)
return None
def json_or_text(resp: requests.Response) -> Tuple[Optional[Dict[str, Any]], str]:
try:
data = resp.json()
return data, ""
except Exception:
return None, (resp.text or "")[:400]
def create_address(api_base: str) -> Tuple[str, str]:
resp = requests.post(
f"{api_base.rstrip('/')}/api/new_address",
json={},
headers={"Content-Type": "application/json"},
timeout=20,
)
resp.raise_for_status()
data, raw = json_or_text(resp)
if not data:
raise RuntimeError(f"/api/new_address 非JSON: {raw}")
address = str(data.get("address", "")).strip()
jwt = str(data.get("jwt", "")).strip()
if not address or not jwt:
raise RuntimeError(f"/api/new_address 缺少 address/jwt: {data}")
return address, jwt
def fetch_box(api_base: str, jwt: str, path: str, params: Dict[str, Any]) -> List[Dict[str, Any]]:
resp = requests.get(
f"{api_base.rstrip('/')}{path}",
params=params,
headers={"Authorization": f"Bearer {jwt}"},
timeout=20,
)
if resp.status_code >= 400:
return []
data, _ = json_or_text(resp)
if not isinstance(data, dict):
return []
if isinstance(data.get("results"), list):
return data["results"]
if isinstance(data.get("data"), list):
return data["data"]
if isinstance(data.get("messages"), list):
return data["messages"]
return []
def probe_all_boxes(api_base: str, jwt: str) -> List[Tuple[str, List[Dict[str, Any]]]]:
probes = [
("/api/mails", {"limit": 20, "offset": 0}),
("/api/sendbox", {"limit": 20, "offset": 0}),
("/api/mails", {"limit": 20, "offset": 0, "box": "trash"}),
("/api/mails", {"limit": 20, "offset": 0, "folder": "trash"}),
("/api/mails", {"limit": 20, "offset": 0, "deleted": "1"}),
("/api/mails", {"limit": 20, "offset": 0, "status": "deleted"}),
]
out: List[Tuple[str, List[Dict[str, Any]]]] = []
for path, params in probes:
mails = fetch_box(api_base, jwt, path, params)
out.append((f"{path}?{params}", mails))
return out
def get_detail(api_base: str, jwt: str, mail_id: Any) -> Dict[str, Any]:
for url in [
f"{api_base.rstrip('/')}/api/mail/{mail_id}",
f"{api_base.rstrip('/')}/api/mails/{mail_id}",
]:
try:
resp = requests.get(url, headers={"Authorization": f"Bearer {jwt}"}, timeout=20)
if resp.status_code >= 400:
continue
data, _ = json_or_text(resp)
if isinstance(data, dict):
return data
except Exception:
continue
return {}
def flatten_mail_text(item: Dict[str, Any], detail: Dict[str, Any]) -> Tuple[str, str]:
subject = str(item.get("subject") or detail.get("subject") or "")
parts: List[str] = []
for src in (item, detail):
for k in ("text", "raw", "content", "intro", "body", "snippet"):
v = src.get(k)
if isinstance(v, str) and v.strip():
parts.append(v)
html_val = src.get("html")
if isinstance(html_val, str):
html_val = [html_val]
if isinstance(html_val, list):
for h in html_val:
if isinstance(h, str):
parts.append(re.sub(r"<[^>]+>", " ", h))
return subject, "\n".join(parts)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--api-base", required=True)
ap.add_argument("--address", default="")
ap.add_argument("--credential", default="")
ap.add_argument("--timeout", type=int, default=180)
ap.add_argument("--interval", type=int, default=3)
args = ap.parse_args()
address = args.address.strip()
credential = args.credential.strip()
if not credential:
address, credential = create_address(args.api_base)
print(f"[NEW] address={address}")
print(f"[NEW] credential(jwt)={credential}")
else:
print(f"[USE] address={address or '(unknown, from credential)'}")
deadline = time.time() + max(args.timeout, 1)
seen_ids = set()
while time.time() < deadline:
boxes = probe_all_boxes(args.api_base, credential)
total = 0
for name, mails in boxes:
if mails:
print(f"[BOX] {name} -> {len(mails)}")
total += len(mails)
for m in mails:
mail_id = m.get("id") or m.get("mail_id")
if not mail_id or mail_id in seen_ids:
continue
seen_ids.add(mail_id)
detail = get_detail(args.api_base, credential, mail_id)
subj, text = flatten_mail_text(m, detail)
code = extract_code(text, subj)
print(f"[MAIL] id={mail_id} subject={subj!r} code={code!r}")
if code:
print(f"[FOUND] {code}")
return
if total == 0:
print("[INFO] no mails yet")
time.sleep(max(args.interval, 1))
print("[TIMEOUT] no code found")
if __name__ == "__main__":
main()
+24
View File
@@ -0,0 +1,24 @@
{
"duckmail_api_key": "",
"cloudflare_api_base": "https://cloudflare.pages.dev",
"cloudflare_api_key": "",
"cloudflare_auth_mode": "none",
"cloudflare_path_domains": "/api/domains",
"cloudflare_path_accounts": "/api/new_address",
"cloudflare_path_token": "/api/token",
"cloudflare_path_messages": "/api/mails",
"proxy": "",
"enable_nsfw": true,
"register_count": 1,
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"grok2api_auto_add_local": false,
"grok2api_local_token_file": "",
"grok2api_pool_name": "ssoBasic",
"grok2api_auto_add_remote": false,
"grok2api_remote_base": "",
"grok2api_remote_app_key": "",
"defaultDomains": "example.com",
"email_provider": "cloudflare",
"yyds_api_key": "",
"yyds_jwt": ""
}
+2992
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
certifi==2026.4.22
cffi==2.0.0
charset-normalizer==3.4.7
cssselect==1.3.0
curl_cffi==0.13.0
DataRecorder==3.6.2
DownloadKit==2.0.7
DrissionPage==4.1.1.2
filelock==3.19.1
idna==3.13
lxml==6.1.0
psutil==7.2.2
pycparser==2.23
requests==2.32.5
requests-file==3.0.1
tldextract==5.3.0
urllib3==2.6.3
websocket-client==1.9.0