feat(mail): add optional Cloud Mail no-recipient provider
This commit is contained in:
@@ -1,37 +0,0 @@
|
|||||||
name: Temporary Cloud Mail issue trigger
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
- edited
|
|
||||||
schedule:
|
|
||||||
- cron: "*/5 * * * *"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
apply-upgrade:
|
|
||||||
if: github.event_name == 'schedule' || (github.event_name == 'issues' && github.event.issue.number == 7)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout main
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Apply Cloud Mail upgrade
|
|
||||||
run: python tools/apply_cloudmail_upgrade.py
|
|
||||||
|
|
||||||
- name: Commit upgrade and clean temporary files
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -f tools/apply_cloudmail_upgrade.py
|
|
||||||
rm -f .github/workflows/temp-cloudmail-upgrade.yml
|
|
||||||
rm -f .github/workflows/temp-cloudmail-issue-trigger.yml
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add grok_register_ttk.py config.example.json README.md tools/apply_cloudmail_upgrade.py .github/workflows/temp-cloudmail-upgrade.yml .github/workflows/temp-cloudmail-issue-trigger.yml
|
|
||||||
git commit -m "feat(mail): add optional Cloud Mail no-recipient provider"
|
|
||||||
git push origin HEAD:main
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# One-shot workflow to finish the Cloud Mail upgrade and remove temporary files.
|
|
||||||
name: Temporary Cloud Mail upgrade
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- .github/workflows/temp-cloudmail-upgrade.yml
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
apply-upgrade:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout main
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Fix Cloud Mail patch generator escaping
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python <<'PY'
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
path = Path("tools/apply_cloudmail_upgrade.py")
|
|
||||||
text = path.read_text(encoding="utf-8")
|
|
||||||
old = ' combined = "\\n".join(parts)'
|
|
||||||
new = ' combined = "\\\\n".join(parts)'
|
|
||||||
count = text.count(old)
|
|
||||||
if count != 1:
|
|
||||||
raise RuntimeError(f"newline escape fix: expected exactly one match, found {count}")
|
|
||||||
path.write_text(text.replace(old, new, 1), encoding="utf-8")
|
|
||||||
PY
|
|
||||||
|
|
||||||
- name: Apply Cloud Mail provider patch
|
|
||||||
run: python tools/apply_cloudmail_upgrade.py
|
|
||||||
|
|
||||||
- name: Commit upgrade and clean temporary files
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -f tools/apply_cloudmail_upgrade.py
|
|
||||||
rm -f .github/workflows/temp-cloudmail-upgrade.yml
|
|
||||||
rm -f .github/workflows/temp-cloudmail-issue-trigger.yml
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add grok_register_ttk.py config.example.json README.md tools/apply_cloudmail_upgrade.py .github/workflows/temp-cloudmail-upgrade.yml .github/workflows/temp-cloudmail-issue-trigger.yml
|
|
||||||
git commit -m "feat(mail): add optional Cloud Mail no-recipient provider"
|
|
||||||
git push origin HEAD:main
|
|
||||||
@@ -49,7 +49,7 @@ Grok Register 是一个面向自动化流程研究、测试环境验证和个人
|
|||||||
- 支持 GUI 图形界面运行。
|
- 支持 GUI 图形界面运行。
|
||||||
- 支持 CLI 终端运行,不启动 Tk GUI。
|
- 支持 CLI 终端运行,不启动 Tk GUI。
|
||||||
- 注册流程使用 Chromium/Chrome 浏览器页面完成。
|
- 注册流程使用 Chromium/Chrome 浏览器页面完成。
|
||||||
- 支持 DuckMail、YYDS、Cloudflare 临时邮箱接口。
|
- 支持 DuckMail、YYDS、Cloudflare,以及 Cloud Mail 无人收件模式。
|
||||||
- 支持验证码邮件轮询和解析。
|
- 支持验证码邮件轮询和解析。
|
||||||
- 支持成功账号实时写入 `accounts_*.txt`。
|
- 支持成功账号实时写入 `accounts_*.txt`。
|
||||||
- 支持将 SSO token 写入 grok2api 本地或远端池。
|
- 支持将 SSO token 写入 grok2api 本地或远端池。
|
||||||
@@ -91,7 +91,7 @@ cp config.example.json config.json
|
|||||||
|
|
||||||
| 配置项 | 说明 |
|
| 配置项 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `email_provider` | 邮箱服务商:`duckmail`、`yyds`、`cloudflare` |
|
| `email_provider` | 邮箱服务商:`duckmail`、`yyds`、`cloudflare`、`cloudmail` |
|
||||||
| `register_count` | 本次目标注册数量 |
|
| `register_count` | 本次目标注册数量 |
|
||||||
| `proxy` | 代理地址,可留空 |
|
| `proxy` | 代理地址,可留空 |
|
||||||
| `enable_nsfw` | 注册后是否尝试开启 NSFW |
|
| `enable_nsfw` | 注册后是否尝试开启 NSFW |
|
||||||
@@ -103,6 +103,10 @@ cp config.example.json config.json
|
|||||||
| `cloudflare_path_token` | Cloudflare token 路径;默认 `/api/token` |
|
| `cloudflare_path_token` | Cloudflare token 路径;默认 `/api/token` |
|
||||||
| `cloudflare_path_messages` | Cloudflare 收件列表路径;默认 `/api/mails` |
|
| `cloudflare_path_messages` | Cloudflare 收件列表路径;默认 `/api/mails` |
|
||||||
| `defaultDomains` | Cloudflare 临时邮箱默认域名 |
|
| `defaultDomains` | Cloudflare 临时邮箱默认域名 |
|
||||||
|
| `cloudmail_api_base` | Cloud Mail 站点地址 |
|
||||||
|
| `cloudmail_public_token` | Cloud Mail 公共 API Token,不会写入邮箱凭证文件 |
|
||||||
|
| `cloudmail_domains` | Cloud Mail 无人收件域名,多个域名用英文逗号分隔 |
|
||||||
|
| `cloudmail_path_messages` | Cloud Mail 公共收件接口路径,默认 `/api/public/emailList` |
|
||||||
| `grok2api_auto_add_local` | 是否写入本地 grok2api token 池 |
|
| `grok2api_auto_add_local` | 是否写入本地 grok2api token 池 |
|
||||||
| `grok2api_local_token_file` | 本地 grok2api token 文件路径 |
|
| `grok2api_local_token_file` | 本地 grok2api token 文件路径 |
|
||||||
| `grok2api_auto_add_remote` | 是否写入远端 grok2api |
|
| `grok2api_auto_add_remote` | 是否写入远端 grok2api |
|
||||||
@@ -158,6 +162,35 @@ cp config.example.json config.json
|
|||||||
python cf_mail_debug.py --api-base "https://你的-worker-api-域名" --auth-mode x-admin-auth --api-key "你的 ADMIN_PASSWORD" --create-path /admin/new_address --domain "你的收信域名.com"
|
python cf_mail_debug.py --api-base "https://你的-worker-api-域名" --auth-mode x-admin-auth --api-key "你的 ADMIN_PASSWORD" --create-path /admin/new_address --domain "你的收信域名.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Cloud Mail 无人收件模式(可选)
|
||||||
|
|
||||||
|
该模式对接 `maillab/cloud-mail`,使用其“无人收件”功能:程序直接生成随机邮箱地址,不需要先在 Cloud Mail 中创建邮箱账号。
|
||||||
|
|
||||||
|
使用前需要:
|
||||||
|
|
||||||
|
1. 在 Cloud Mail 系统设置中开启“无人收件”。
|
||||||
|
2. 使用管理员账号生成公共 API Token:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://你的-Cloud-Mail-域名/api/public/genToken" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"管理员邮箱","password":"管理员密码"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 配置本项目:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email_provider": "cloudmail",
|
||||||
|
"cloudmail_api_base": "https://你的-Cloud-Mail-域名",
|
||||||
|
"cloudmail_public_token": "返回结果 data 中的 token",
|
||||||
|
"cloudmail_domains": "你的收信域名.com",
|
||||||
|
"cloudmail_path_messages": "/api/public/emailList"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
程序会通过 `POST /api/public/emailList` 按目标地址查询邮件。公共 Token 只从 `config.json` 读取,不会作为邮箱 credential 输出到日志或 `mail_credentials.txt`。
|
||||||
|
|
||||||
### grok2api 远端入池配置
|
### grok2api 远端入池配置
|
||||||
|
|
||||||
如果开启 `grok2api_auto_add_remote`,`grok2api_remote_base` 可以填写站点根地址,也可以直接填写管理 API 地址:
|
如果开启 `grok2api_auto_add_remote`,`grok2api_remote_base` 可以填写站点根地址,也可以直接填写管理 API 地址:
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
"cloudflare_path_accounts": "/api/new_address",
|
"cloudflare_path_accounts": "/api/new_address",
|
||||||
"cloudflare_path_token": "/api/token",
|
"cloudflare_path_token": "/api/token",
|
||||||
"cloudflare_path_messages": "/api/mails",
|
"cloudflare_path_messages": "/api/mails",
|
||||||
|
"cloudmail_api_base": "https://mail.example.com",
|
||||||
|
"cloudmail_public_token": "",
|
||||||
|
"cloudmail_domains": "example.com",
|
||||||
|
"cloudmail_path_messages": "/api/public/emailList",
|
||||||
"proxy": "",
|
"proxy": "",
|
||||||
"enable_nsfw": true,
|
"enable_nsfw": true,
|
||||||
"register_count": 1,
|
"register_count": 1,
|
||||||
|
|||||||
+221
-13
@@ -54,6 +54,10 @@ DEFAULT_CONFIG = {
|
|||||||
"cloudflare_path_accounts": "/api/new_address",
|
"cloudflare_path_accounts": "/api/new_address",
|
||||||
"cloudflare_path_token": "/api/token",
|
"cloudflare_path_token": "/api/token",
|
||||||
"cloudflare_path_messages": "/api/mails",
|
"cloudflare_path_messages": "/api/mails",
|
||||||
|
"cloudmail_api_base": "",
|
||||||
|
"cloudmail_public_token": "",
|
||||||
|
"cloudmail_domains": "",
|
||||||
|
"cloudmail_path_messages": "/api/public/emailList",
|
||||||
"proxy": "http://127.0.0.1:7890",
|
"proxy": "http://127.0.0.1:7890",
|
||||||
"enable_nsfw": True,
|
"enable_nsfw": True,
|
||||||
"register_count": 1,
|
"register_count": 1,
|
||||||
@@ -68,6 +72,7 @@ DEFAULT_CONFIG = {
|
|||||||
|
|
||||||
config = DEFAULT_CONFIG.copy()
|
config = DEFAULT_CONFIG.copy()
|
||||||
_cf_domain_index = 0
|
_cf_domain_index = 0
|
||||||
|
_cloudmail_domain_index = 0
|
||||||
|
|
||||||
|
|
||||||
class RegistrationCancelled(Exception):
|
class RegistrationCancelled(Exception):
|
||||||
@@ -519,6 +524,93 @@ def cloudflare_create_temp_address(api_base):
|
|||||||
return address, jwt
|
return address, jwt
|
||||||
|
|
||||||
|
|
||||||
|
def get_cloudmail_api_base():
|
||||||
|
return str(config.get("cloudmail_api_base", "") or "").strip().rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def get_cloudmail_public_token():
|
||||||
|
return str(config.get("cloudmail_public_token", "") or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_cloudmail_path():
|
||||||
|
raw = str(
|
||||||
|
config.get("cloudmail_path_messages", "/api/public/emailList")
|
||||||
|
or "/api/public/emailList"
|
||||||
|
).strip()
|
||||||
|
return raw if raw.startswith("/") else "/" + raw
|
||||||
|
|
||||||
|
|
||||||
|
def cloudmail_next_domain():
|
||||||
|
"""按配置轮换选择 Cloud Mail 无人收件域名。"""
|
||||||
|
global _cloudmail_domain_index
|
||||||
|
domains = [
|
||||||
|
item.strip().lstrip("@")
|
||||||
|
for item in str(config.get("cloudmail_domains", "") or "").split(",")
|
||||||
|
if item.strip().lstrip("@")
|
||||||
|
]
|
||||||
|
if not domains:
|
||||||
|
return ""
|
||||||
|
domain = domains[_cloudmail_domain_index % len(domains)]
|
||||||
|
_cloudmail_domain_index += 1
|
||||||
|
return domain
|
||||||
|
|
||||||
|
|
||||||
|
def cloudmail_get_email_and_token():
|
||||||
|
"""生成无需预创建账号的 Cloud Mail 收件地址。"""
|
||||||
|
if not get_cloudmail_api_base():
|
||||||
|
raise Exception("Cloud Mail API Base 未配置")
|
||||||
|
if not get_cloudmail_public_token():
|
||||||
|
raise Exception("Cloud Mail Public Token 未配置")
|
||||||
|
domain = cloudmail_next_domain()
|
||||||
|
if not domain:
|
||||||
|
raise Exception("Cloud Mail 收件域名未配置")
|
||||||
|
address = f"{generate_username(12)}@{domain}"
|
||||||
|
# 仅返回非敏感占位凭证;公共 Token 始终只从 config.json 读取。
|
||||||
|
return address, f"cloudmail:{address}"
|
||||||
|
|
||||||
|
|
||||||
|
def cloudmail_get_messages(address):
|
||||||
|
api_base = get_cloudmail_api_base()
|
||||||
|
public_token = get_cloudmail_public_token()
|
||||||
|
if not api_base:
|
||||||
|
raise Exception("Cloud Mail API Base 未配置")
|
||||||
|
if not public_token:
|
||||||
|
raise Exception("Cloud Mail Public Token 未配置")
|
||||||
|
payload = {
|
||||||
|
"toEmail": address,
|
||||||
|
"type": 0,
|
||||||
|
"isDel": 0,
|
||||||
|
"timeSort": "desc",
|
||||||
|
"num": 1,
|
||||||
|
"size": 20,
|
||||||
|
}
|
||||||
|
resp = http_post(
|
||||||
|
f"{api_base}{get_cloudmail_path()}",
|
||||||
|
headers={
|
||||||
|
"Authorization": public_token,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
json=payload,
|
||||||
|
timeout=20,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
raise Exception(f"Cloud Mail 邮件接口返回非JSON: {resp.text[:300]}")
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise Exception(f"Cloud Mail 邮件接口返回格式错误: {data}")
|
||||||
|
result_code = data.get("code")
|
||||||
|
if result_code not in (None, 200, "200"):
|
||||||
|
raise Exception(
|
||||||
|
f"Cloud Mail 邮件接口失败: code={result_code}, message={data.get('message', '')}"
|
||||||
|
)
|
||||||
|
messages = data.get("data")
|
||||||
|
if isinstance(messages, list):
|
||||||
|
return messages
|
||||||
|
return _pick_list_payload(data)
|
||||||
|
|
||||||
|
|
||||||
def get_user_agent():
|
def get_user_agent():
|
||||||
return config.get(
|
return config.get(
|
||||||
"user_agent",
|
"user_agent",
|
||||||
@@ -1139,6 +1231,8 @@ def get_email_and_token(api_key=None):
|
|||||||
provider = get_email_provider()
|
provider = get_email_provider()
|
||||||
if provider == "yyds":
|
if provider == "yyds":
|
||||||
return yyds_get_email_and_token(api_key=api_key, jwt=get_yyds_jwt())
|
return yyds_get_email_and_token(api_key=api_key, jwt=get_yyds_jwt())
|
||||||
|
if provider == "cloudmail":
|
||||||
|
return cloudmail_get_email_and_token()
|
||||||
if provider == "cloudflare":
|
if provider == "cloudflare":
|
||||||
api_base = get_cloudflare_api_base()
|
api_base = get_cloudflare_api_base()
|
||||||
if not api_base:
|
if not api_base:
|
||||||
@@ -1199,6 +1293,16 @@ def get_oai_code(
|
|||||||
jwt=get_yyds_jwt(),
|
jwt=get_yyds_jwt(),
|
||||||
cancel_callback=cancel_callback,
|
cancel_callback=cancel_callback,
|
||||||
)
|
)
|
||||||
|
if provider == "cloudmail":
|
||||||
|
return cloudmail_get_oai_code(
|
||||||
|
dev_token,
|
||||||
|
email,
|
||||||
|
timeout=timeout,
|
||||||
|
poll_interval=poll_interval,
|
||||||
|
log_callback=log_callback,
|
||||||
|
cancel_callback=cancel_callback,
|
||||||
|
resend_callback=resend_callback,
|
||||||
|
)
|
||||||
if provider == "cloudflare":
|
if provider == "cloudflare":
|
||||||
return cloudflare_get_oai_code(
|
return cloudflare_get_oai_code(
|
||||||
dev_token,
|
dev_token,
|
||||||
@@ -1239,6 +1343,81 @@ def extract_verification_code(text, subject=""):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def cloudmail_get_oai_code(
|
||||||
|
dev_token,
|
||||||
|
email,
|
||||||
|
timeout=180,
|
||||||
|
poll_interval=3,
|
||||||
|
log_callback=None,
|
||||||
|
cancel_callback=None,
|
||||||
|
resend_callback=None,
|
||||||
|
):
|
||||||
|
# dev_token 是为了保持现有邮箱 Provider 调用契约;Cloud Mail 使用配置中的公共 Token。
|
||||||
|
_ = dev_token
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
seen_attempts = {}
|
||||||
|
next_resend_at = time.time() + 35
|
||||||
|
while time.time() < deadline:
|
||||||
|
raise_if_cancelled(cancel_callback)
|
||||||
|
if resend_callback and time.time() >= next_resend_at:
|
||||||
|
try:
|
||||||
|
resend_callback()
|
||||||
|
if log_callback:
|
||||||
|
log_callback("[*] 已触发重新发送验证码")
|
||||||
|
except Exception as exc:
|
||||||
|
if log_callback:
|
||||||
|
log_callback(f"[Debug] 触发重发验证码失败: {exc}")
|
||||||
|
next_resend_at = time.time() + 35
|
||||||
|
try:
|
||||||
|
messages = cloudmail_get_messages(email)
|
||||||
|
except Exception as exc:
|
||||||
|
if log_callback:
|
||||||
|
log_callback(f"[Debug] Cloud Mail 拉取邮件列表失败: {exc}")
|
||||||
|
sleep_with_cancel(poll_interval, cancel_callback)
|
||||||
|
continue
|
||||||
|
if log_callback:
|
||||||
|
log_callback(f"[Debug] Cloud Mail 本轮邮件数量: {len(messages)}")
|
||||||
|
for msg in messages:
|
||||||
|
msg_id = msg.get("emailId") or msg.get("email_id") or msg.get("id")
|
||||||
|
if not msg_id:
|
||||||
|
continue
|
||||||
|
attempt = int(seen_attempts.get(msg_id, 0))
|
||||||
|
if attempt >= 5:
|
||||||
|
continue
|
||||||
|
seen_attempts[msg_id] = attempt + 1
|
||||||
|
target_address = str(
|
||||||
|
msg.get("toEmail") or msg.get("to_email") or ""
|
||||||
|
).strip().lower()
|
||||||
|
if target_address and target_address != email.lower():
|
||||||
|
continue
|
||||||
|
parts = []
|
||||||
|
code_value = str(msg.get("code", "") or "").strip()
|
||||||
|
if code_value:
|
||||||
|
parts.append(f"verification code: {code_value}")
|
||||||
|
for field in ("text", "content", "html", "body", "snippet"):
|
||||||
|
value = msg.get(field)
|
||||||
|
values = value if isinstance(value, list) else [value]
|
||||||
|
for item in values:
|
||||||
|
if isinstance(item, str) and item.strip():
|
||||||
|
parts.append(re.sub(r"<[^>]+>", " ", item))
|
||||||
|
subject = str(msg.get("subject", "") or "")
|
||||||
|
combined = "\n".join(parts)
|
||||||
|
if log_callback:
|
||||||
|
log_callback(f"[Debug] Cloud Mail 收到邮件: {subject}")
|
||||||
|
code = extract_verification_code(combined, subject)
|
||||||
|
if code:
|
||||||
|
if log_callback:
|
||||||
|
log_callback(f"[*] Cloud Mail 从邮件中提取到验证码: {code}")
|
||||||
|
return code
|
||||||
|
if log_callback:
|
||||||
|
log_callback(
|
||||||
|
f"[Debug] Cloud Mail 邮件已解析但未提取到验证码 "
|
||||||
|
f"id={msg_id} attempt={seen_attempts[msg_id]}"
|
||||||
|
)
|
||||||
|
sleep_with_cancel(poll_interval, cancel_callback)
|
||||||
|
raise Exception(f"Cloud Mail 在 {timeout}s 内未收到验证码邮件")
|
||||||
|
|
||||||
|
|
||||||
def duckmail_get_oai_code(
|
def duckmail_get_oai_code(
|
||||||
dev_token,
|
dev_token,
|
||||||
email,
|
email,
|
||||||
@@ -2826,7 +3005,7 @@ class GrokRegisterGUI:
|
|||||||
|
|
||||||
add_label(0, 0, "邮箱服务商:")
|
add_label(0, 0, "邮箱服务商:")
|
||||||
self.email_provider_var = tk.StringVar(value=config.get("email_provider", "duckmail"))
|
self.email_provider_var = tk.StringVar(value=config.get("email_provider", "duckmail"))
|
||||||
self.email_provider_combo = tk_option_menu(config_frame, self.email_provider_var, ["duckmail", "yyds", "cloudflare"], width=12)
|
self.email_provider_combo = tk_option_menu(config_frame, self.email_provider_var, ["duckmail", "yyds", "cloudflare", "cloudmail"], width=12)
|
||||||
add_field(self.email_provider_combo, 0, 1, sticky=tk.W)
|
add_field(self.email_provider_combo, 0, 1, sticky=tk.W)
|
||||||
|
|
||||||
add_label(0, 2, "注册数量:")
|
add_label(0, 2, "注册数量:")
|
||||||
@@ -2893,37 +3072,52 @@ class GrokRegisterGUI:
|
|||||||
self.cloudflare_paths_entry = tk_entry(config_frame, textvariable=self.cloudflare_paths_var, width=34)
|
self.cloudflare_paths_entry = tk_entry(config_frame, textvariable=self.cloudflare_paths_var, width=34)
|
||||||
add_field(self.cloudflare_paths_entry, 4, 3)
|
add_field(self.cloudflare_paths_entry, 4, 3)
|
||||||
|
|
||||||
add_label(5, 0, "grok2api 本地入池:")
|
add_label(5, 0, "Cloud Mail API Base:")
|
||||||
|
self.cloudmail_api_base_var = tk.StringVar(value=config.get("cloudmail_api_base", ""))
|
||||||
|
self.cloudmail_api_base_entry = tk_entry(config_frame, textvariable=self.cloudmail_api_base_var, width=34)
|
||||||
|
add_field(self.cloudmail_api_base_entry, 5, 1)
|
||||||
|
|
||||||
|
add_label(5, 2, "Cloud Mail 域名:")
|
||||||
|
self.cloudmail_domains_var = tk.StringVar(value=config.get("cloudmail_domains", ""))
|
||||||
|
self.cloudmail_domains_entry = tk_entry(config_frame, textvariable=self.cloudmail_domains_var, width=34)
|
||||||
|
add_field(self.cloudmail_domains_entry, 5, 3)
|
||||||
|
|
||||||
|
add_label(6, 0, "Cloud Mail Public Token:")
|
||||||
|
self.cloudmail_public_token_var = tk.StringVar(value=config.get("cloudmail_public_token", ""))
|
||||||
|
self.cloudmail_public_token_entry = tk_entry(config_frame, textvariable=self.cloudmail_public_token_var, width=72)
|
||||||
|
add_field(self.cloudmail_public_token_entry, 6, 1, columnspan=3)
|
||||||
|
|
||||||
|
add_label(7, 0, "grok2api 本地入池:")
|
||||||
self.grok2api_local_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_local", True)))
|
self.grok2api_local_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_local", True)))
|
||||||
self.grok2api_local_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_local_auto_var)
|
self.grok2api_local_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_local_auto_var)
|
||||||
add_field(self.grok2api_local_auto_check, 5, 1, sticky=tk.W)
|
add_field(self.grok2api_local_auto_check, 7, 1, sticky=tk.W)
|
||||||
|
|
||||||
add_label(5, 2, "grok2api 池名:")
|
add_label(7, 2, "grok2api 池名:")
|
||||||
self.grok2api_pool_name_var = tk.StringVar(value=str(config.get("grok2api_pool_name", "ssoBasic")))
|
self.grok2api_pool_name_var = tk.StringVar(value=str(config.get("grok2api_pool_name", "ssoBasic")))
|
||||||
self.grok2api_pool_name_combo = tk_option_menu(
|
self.grok2api_pool_name_combo = tk_option_menu(
|
||||||
config_frame, self.grok2api_pool_name_var, ["ssoBasic", "ssoSuper"], width=12
|
config_frame, self.grok2api_pool_name_var, ["ssoBasic", "ssoSuper"], width=12
|
||||||
)
|
)
|
||||||
add_field(self.grok2api_pool_name_combo, 5, 3, sticky=tk.W)
|
add_field(self.grok2api_pool_name_combo, 7, 3, sticky=tk.W)
|
||||||
|
|
||||||
add_label(6, 0, "本地 token.json:")
|
add_label(8, 0, "本地 token.json:")
|
||||||
self.grok2api_local_file_var = tk.StringVar(value=str(config.get("grok2api_local_token_file", "")))
|
self.grok2api_local_file_var = tk.StringVar(value=str(config.get("grok2api_local_token_file", "")))
|
||||||
self.grok2api_local_file_entry = tk_entry(config_frame, textvariable=self.grok2api_local_file_var, width=72)
|
self.grok2api_local_file_entry = tk_entry(config_frame, textvariable=self.grok2api_local_file_var, width=72)
|
||||||
add_field(self.grok2api_local_file_entry, 6, 1, columnspan=3)
|
add_field(self.grok2api_local_file_entry, 8, 1, columnspan=3)
|
||||||
|
|
||||||
add_label(7, 0, "grok2api 远端入池:")
|
add_label(9, 0, "grok2api 远端入池:")
|
||||||
self.grok2api_remote_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_remote", False)))
|
self.grok2api_remote_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_remote", False)))
|
||||||
self.grok2api_remote_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_remote_auto_var)
|
self.grok2api_remote_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_remote_auto_var)
|
||||||
add_field(self.grok2api_remote_auto_check, 7, 1, sticky=tk.W)
|
add_field(self.grok2api_remote_auto_check, 9, 1, sticky=tk.W)
|
||||||
|
|
||||||
add_label(8, 0, "grok2api 远端 Base:")
|
add_label(10, 0, "grok2api 远端 Base:")
|
||||||
self.grok2api_remote_base_var = tk.StringVar(value=str(config.get("grok2api_remote_base", "")))
|
self.grok2api_remote_base_var = tk.StringVar(value=str(config.get("grok2api_remote_base", "")))
|
||||||
self.grok2api_remote_base_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_base_var, width=72)
|
self.grok2api_remote_base_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_base_var, width=72)
|
||||||
add_field(self.grok2api_remote_base_entry, 8, 1, columnspan=3)
|
add_field(self.grok2api_remote_base_entry, 10, 1, columnspan=3)
|
||||||
|
|
||||||
add_label(9, 0, "grok2api 远端 app_key:")
|
add_label(11, 0, "grok2api 远端 app_key:")
|
||||||
self.grok2api_remote_key_var = tk.StringVar(value=str(config.get("grok2api_remote_app_key", "")))
|
self.grok2api_remote_key_var = tk.StringVar(value=str(config.get("grok2api_remote_app_key", "")))
|
||||||
self.grok2api_remote_key_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_key_var, width=72)
|
self.grok2api_remote_key_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_key_var, width=72)
|
||||||
add_field(self.grok2api_remote_key_entry, 9, 1, columnspan=3)
|
add_field(self.grok2api_remote_key_entry, 11, 1, columnspan=3)
|
||||||
|
|
||||||
btn_frame = tk.Frame(main_frame, bg=UI_BG)
|
btn_frame = tk.Frame(main_frame, bg=UI_BG)
|
||||||
btn_frame.grid(row=1, column=0, sticky=tk.EW, pady=(0, 6))
|
btn_frame.grid(row=1, column=0, sticky=tk.EW, pady=(0, 6))
|
||||||
@@ -3008,6 +3202,9 @@ class GrokRegisterGUI:
|
|||||||
config["cloudflare_api_base"] = self.cloudflare_api_base_var.get().strip()
|
config["cloudflare_api_base"] = self.cloudflare_api_base_var.get().strip()
|
||||||
config["cloudflare_api_key"] = self.cloudflare_api_key_var.get().strip()
|
config["cloudflare_api_key"] = self.cloudflare_api_key_var.get().strip()
|
||||||
config["cloudflare_auth_mode"] = self.cloudflare_auth_mode_var.get().strip() or "none"
|
config["cloudflare_auth_mode"] = self.cloudflare_auth_mode_var.get().strip() or "none"
|
||||||
|
config["cloudmail_api_base"] = self.cloudmail_api_base_var.get().strip()
|
||||||
|
config["cloudmail_public_token"] = self.cloudmail_public_token_var.get().strip()
|
||||||
|
config["cloudmail_domains"] = self.cloudmail_domains_var.get().strip()
|
||||||
config["grok2api_auto_add_local"] = bool(self.grok2api_local_auto_var.get())
|
config["grok2api_auto_add_local"] = bool(self.grok2api_local_auto_var.get())
|
||||||
config["grok2api_local_token_file"] = self.grok2api_local_file_var.get().strip()
|
config["grok2api_local_token_file"] = self.grok2api_local_file_var.get().strip()
|
||||||
config["grok2api_pool_name"] = self.grok2api_pool_name_var.get().strip() or "ssoBasic"
|
config["grok2api_pool_name"] = self.grok2api_pool_name_var.get().strip() or "ssoBasic"
|
||||||
@@ -3024,6 +3221,17 @@ class GrokRegisterGUI:
|
|||||||
if config["email_provider"] == "cloudflare" and not config["cloudflare_api_base"]:
|
if config["email_provider"] == "cloudflare" and not config["cloudflare_api_base"]:
|
||||||
self.log("[!] Cloudflare 模式需要先填写 Cloudflare API Base")
|
self.log("[!] Cloudflare 模式需要先填写 Cloudflare API Base")
|
||||||
return
|
return
|
||||||
|
if config["email_provider"] == "cloudmail":
|
||||||
|
missing = []
|
||||||
|
if not config["cloudmail_api_base"]:
|
||||||
|
missing.append("API Base")
|
||||||
|
if not config["cloudmail_public_token"]:
|
||||||
|
missing.append("Public Token")
|
||||||
|
if not config["cloudmail_domains"]:
|
||||||
|
missing.append("域名")
|
||||||
|
if missing:
|
||||||
|
self.log(f"[!] Cloud Mail 模式缺少配置: {', '.join(missing)}")
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
count = int(self.count_var.get())
|
count = int(self.count_var.get())
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -1,457 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from pathlib import Path
|
|
||||||
import ast
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def replace_once(text, old, new, label):
|
|
||||||
count = text.count(old)
|
|
||||||
if count != 1:
|
|
||||||
raise RuntimeError(f"{label}: expected exactly one match, found {count}")
|
|
||||||
return text.replace(old, new, 1)
|
|
||||||
|
|
||||||
|
|
||||||
app_path = Path("grok_register_ttk.py")
|
|
||||||
app = app_path.read_text(encoding="utf-8-sig")
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' "cloudflare_path_messages": "/api/mails",\n "proxy": "http://127.0.0.1:7890",',
|
|
||||||
' "cloudflare_path_messages": "/api/mails",\n'
|
|
||||||
' "cloudmail_api_base": "",\n'
|
|
||||||
' "cloudmail_public_token": "",\n'
|
|
||||||
' "cloudmail_domains": "",\n'
|
|
||||||
' "cloudmail_path_messages": "/api/public/emailList",\n'
|
|
||||||
' "proxy": "http://127.0.0.1:7890",',
|
|
||||||
"default Cloud Mail config",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
'config = DEFAULT_CONFIG.copy()\n_cf_domain_index = 0\n',
|
|
||||||
'config = DEFAULT_CONFIG.copy()\n_cf_domain_index = 0\n_cloudmail_domain_index = 0\n',
|
|
||||||
"Cloud Mail domain index",
|
|
||||||
)
|
|
||||||
|
|
||||||
cloudmail_helpers = '''def get_cloudmail_api_base():
|
|
||||||
return str(config.get("cloudmail_api_base", "") or "").strip().rstrip("/")
|
|
||||||
|
|
||||||
|
|
||||||
def get_cloudmail_public_token():
|
|
||||||
return str(config.get("cloudmail_public_token", "") or "").strip()
|
|
||||||
|
|
||||||
|
|
||||||
def get_cloudmail_path():
|
|
||||||
raw = str(
|
|
||||||
config.get("cloudmail_path_messages", "/api/public/emailList")
|
|
||||||
or "/api/public/emailList"
|
|
||||||
).strip()
|
|
||||||
return raw if raw.startswith("/") else "/" + raw
|
|
||||||
|
|
||||||
|
|
||||||
def cloudmail_next_domain():
|
|
||||||
"""按配置轮换选择 Cloud Mail 无人收件域名。"""
|
|
||||||
global _cloudmail_domain_index
|
|
||||||
domains = [
|
|
||||||
item.strip().lstrip("@")
|
|
||||||
for item in str(config.get("cloudmail_domains", "") or "").split(",")
|
|
||||||
if item.strip().lstrip("@")
|
|
||||||
]
|
|
||||||
if not domains:
|
|
||||||
return ""
|
|
||||||
domain = domains[_cloudmail_domain_index % len(domains)]
|
|
||||||
_cloudmail_domain_index += 1
|
|
||||||
return domain
|
|
||||||
|
|
||||||
|
|
||||||
def cloudmail_get_email_and_token():
|
|
||||||
"""生成无需预创建账号的 Cloud Mail 收件地址。"""
|
|
||||||
if not get_cloudmail_api_base():
|
|
||||||
raise Exception("Cloud Mail API Base 未配置")
|
|
||||||
if not get_cloudmail_public_token():
|
|
||||||
raise Exception("Cloud Mail Public Token 未配置")
|
|
||||||
domain = cloudmail_next_domain()
|
|
||||||
if not domain:
|
|
||||||
raise Exception("Cloud Mail 收件域名未配置")
|
|
||||||
address = f"{generate_username(12)}@{domain}"
|
|
||||||
# 仅返回非敏感占位凭证;公共 Token 始终只从 config.json 读取。
|
|
||||||
return address, f"cloudmail:{address}"
|
|
||||||
|
|
||||||
|
|
||||||
def cloudmail_get_messages(address):
|
|
||||||
api_base = get_cloudmail_api_base()
|
|
||||||
public_token = get_cloudmail_public_token()
|
|
||||||
if not api_base:
|
|
||||||
raise Exception("Cloud Mail API Base 未配置")
|
|
||||||
if not public_token:
|
|
||||||
raise Exception("Cloud Mail Public Token 未配置")
|
|
||||||
payload = {
|
|
||||||
"toEmail": address,
|
|
||||||
"type": 0,
|
|
||||||
"isDel": 0,
|
|
||||||
"timeSort": "desc",
|
|
||||||
"num": 1,
|
|
||||||
"size": 20,
|
|
||||||
}
|
|
||||||
resp = http_post(
|
|
||||||
f"{api_base}{get_cloudmail_path()}",
|
|
||||||
headers={
|
|
||||||
"Authorization": public_token,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
json=payload,
|
|
||||||
timeout=20,
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
try:
|
|
||||||
data = resp.json()
|
|
||||||
except Exception:
|
|
||||||
raise Exception(f"Cloud Mail 邮件接口返回非JSON: {resp.text[:300]}")
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
raise Exception(f"Cloud Mail 邮件接口返回格式错误: {data}")
|
|
||||||
result_code = data.get("code")
|
|
||||||
if result_code not in (None, 200, "200"):
|
|
||||||
raise Exception(
|
|
||||||
f"Cloud Mail 邮件接口失败: code={result_code}, message={data.get('message', '')}"
|
|
||||||
)
|
|
||||||
messages = data.get("data")
|
|
||||||
if isinstance(messages, list):
|
|
||||||
return messages
|
|
||||||
return _pick_list_payload(data)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' return address, jwt\n\n\ndef get_user_agent():',
|
|
||||||
' return address, jwt\n\n\n' + cloudmail_helpers + 'def get_user_agent():',
|
|
||||||
"Cloud Mail helper functions",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' if provider == "yyds":\n'
|
|
||||||
' return yyds_get_email_and_token(api_key=api_key, jwt=get_yyds_jwt())\n'
|
|
||||||
' if provider == "cloudflare":',
|
|
||||||
' if provider == "yyds":\n'
|
|
||||||
' return yyds_get_email_and_token(api_key=api_key, jwt=get_yyds_jwt())\n'
|
|
||||||
' if provider == "cloudmail":\n'
|
|
||||||
' return cloudmail_get_email_and_token()\n'
|
|
||||||
' if provider == "cloudflare":',
|
|
||||||
"Cloud Mail address dispatch",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' if provider == "cloudflare":\n'
|
|
||||||
' return cloudflare_get_oai_code(\n',
|
|
||||||
' if provider == "cloudmail":\n'
|
|
||||||
' return cloudmail_get_oai_code(\n'
|
|
||||||
' dev_token,\n'
|
|
||||||
' email,\n'
|
|
||||||
' timeout=timeout,\n'
|
|
||||||
' poll_interval=poll_interval,\n'
|
|
||||||
' log_callback=log_callback,\n'
|
|
||||||
' cancel_callback=cancel_callback,\n'
|
|
||||||
' resend_callback=resend_callback,\n'
|
|
||||||
' )\n'
|
|
||||||
' if provider == "cloudflare":\n'
|
|
||||||
' return cloudflare_get_oai_code(\n',
|
|
||||||
"Cloud Mail verification dispatch",
|
|
||||||
)
|
|
||||||
|
|
||||||
cloudmail_poll = '''def cloudmail_get_oai_code(
|
|
||||||
dev_token,
|
|
||||||
email,
|
|
||||||
timeout=180,
|
|
||||||
poll_interval=3,
|
|
||||||
log_callback=None,
|
|
||||||
cancel_callback=None,
|
|
||||||
resend_callback=None,
|
|
||||||
):
|
|
||||||
# dev_token 是为了保持现有邮箱 Provider 调用契约;Cloud Mail 使用配置中的公共 Token。
|
|
||||||
_ = dev_token
|
|
||||||
deadline = time.time() + timeout
|
|
||||||
seen_attempts = {}
|
|
||||||
next_resend_at = time.time() + 35
|
|
||||||
while time.time() < deadline:
|
|
||||||
raise_if_cancelled(cancel_callback)
|
|
||||||
if resend_callback and time.time() >= next_resend_at:
|
|
||||||
try:
|
|
||||||
resend_callback()
|
|
||||||
if log_callback:
|
|
||||||
log_callback("[*] 已触发重新发送验证码")
|
|
||||||
except Exception as exc:
|
|
||||||
if log_callback:
|
|
||||||
log_callback(f"[Debug] 触发重发验证码失败: {exc}")
|
|
||||||
next_resend_at = time.time() + 35
|
|
||||||
try:
|
|
||||||
messages = cloudmail_get_messages(email)
|
|
||||||
except Exception as exc:
|
|
||||||
if log_callback:
|
|
||||||
log_callback(f"[Debug] Cloud Mail 拉取邮件列表失败: {exc}")
|
|
||||||
sleep_with_cancel(poll_interval, cancel_callback)
|
|
||||||
continue
|
|
||||||
if log_callback:
|
|
||||||
log_callback(f"[Debug] Cloud Mail 本轮邮件数量: {len(messages)}")
|
|
||||||
for msg in messages:
|
|
||||||
msg_id = msg.get("emailId") or msg.get("email_id") or msg.get("id")
|
|
||||||
if not msg_id:
|
|
||||||
continue
|
|
||||||
attempt = int(seen_attempts.get(msg_id, 0))
|
|
||||||
if attempt >= 5:
|
|
||||||
continue
|
|
||||||
seen_attempts[msg_id] = attempt + 1
|
|
||||||
target_address = str(
|
|
||||||
msg.get("toEmail") or msg.get("to_email") or ""
|
|
||||||
).strip().lower()
|
|
||||||
if target_address and target_address != email.lower():
|
|
||||||
continue
|
|
||||||
parts = []
|
|
||||||
code_value = str(msg.get("code", "") or "").strip()
|
|
||||||
if code_value:
|
|
||||||
parts.append(f"verification code: {code_value}")
|
|
||||||
for field in ("text", "content", "html", "body", "snippet"):
|
|
||||||
value = msg.get(field)
|
|
||||||
values = value if isinstance(value, list) else [value]
|
|
||||||
for item in values:
|
|
||||||
if isinstance(item, str) and item.strip():
|
|
||||||
parts.append(re.sub(r"<[^>]+>", " ", item))
|
|
||||||
subject = str(msg.get("subject", "") or "")
|
|
||||||
combined = "\n".join(parts)
|
|
||||||
if log_callback:
|
|
||||||
log_callback(f"[Debug] Cloud Mail 收到邮件: {subject}")
|
|
||||||
code = extract_verification_code(combined, subject)
|
|
||||||
if code:
|
|
||||||
if log_callback:
|
|
||||||
log_callback(f"[*] Cloud Mail 从邮件中提取到验证码: {code}")
|
|
||||||
return code
|
|
||||||
if log_callback:
|
|
||||||
log_callback(
|
|
||||||
f"[Debug] Cloud Mail 邮件已解析但未提取到验证码 "
|
|
||||||
f"id={msg_id} attempt={seen_attempts[msg_id]}"
|
|
||||||
)
|
|
||||||
sleep_with_cancel(poll_interval, cancel_callback)
|
|
||||||
raise Exception(f"Cloud Mail 在 {timeout}s 内未收到验证码邮件")
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' return None\n\n\ndef duckmail_get_oai_code(',
|
|
||||||
' return None\n\n\n' + cloudmail_poll + 'def duckmail_get_oai_code(',
|
|
||||||
"Cloud Mail verification polling",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
'self.email_provider_var, ["duckmail", "yyds", "cloudflare"], width=12',
|
|
||||||
'self.email_provider_var, ["duckmail", "yyds", "cloudflare", "cloudmail"], width=12',
|
|
||||||
"Cloud Mail GUI provider option",
|
|
||||||
)
|
|
||||||
|
|
||||||
old_gui_block = ''' add_label(5, 0, "grok2api 本地入池:")
|
|
||||||
self.grok2api_local_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_local", True)))
|
|
||||||
self.grok2api_local_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_local_auto_var)
|
|
||||||
add_field(self.grok2api_local_auto_check, 5, 1, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(5, 2, "grok2api 池名:")
|
|
||||||
self.grok2api_pool_name_var = tk.StringVar(value=str(config.get("grok2api_pool_name", "ssoBasic")))
|
|
||||||
self.grok2api_pool_name_combo = tk_option_menu(
|
|
||||||
config_frame, self.grok2api_pool_name_var, ["ssoBasic", "ssoSuper"], width=12
|
|
||||||
)
|
|
||||||
add_field(self.grok2api_pool_name_combo, 5, 3, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(6, 0, "本地 token.json:")
|
|
||||||
self.grok2api_local_file_var = tk.StringVar(value=str(config.get("grok2api_local_token_file", "")))
|
|
||||||
self.grok2api_local_file_entry = tk_entry(config_frame, textvariable=self.grok2api_local_file_var, width=72)
|
|
||||||
add_field(self.grok2api_local_file_entry, 6, 1, columnspan=3)
|
|
||||||
|
|
||||||
add_label(7, 0, "grok2api 远端入池:")
|
|
||||||
self.grok2api_remote_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_remote", False)))
|
|
||||||
self.grok2api_remote_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_remote_auto_var)
|
|
||||||
add_field(self.grok2api_remote_auto_check, 7, 1, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(8, 0, "grok2api 远端 Base:")
|
|
||||||
self.grok2api_remote_base_var = tk.StringVar(value=str(config.get("grok2api_remote_base", "")))
|
|
||||||
self.grok2api_remote_base_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_base_var, width=72)
|
|
||||||
add_field(self.grok2api_remote_base_entry, 8, 1, columnspan=3)
|
|
||||||
|
|
||||||
add_label(9, 0, "grok2api 远端 app_key:")
|
|
||||||
self.grok2api_remote_key_var = tk.StringVar(value=str(config.get("grok2api_remote_app_key", "")))
|
|
||||||
self.grok2api_remote_key_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_key_var, width=72)
|
|
||||||
add_field(self.grok2api_remote_key_entry, 9, 1, columnspan=3)
|
|
||||||
'''
|
|
||||||
|
|
||||||
new_gui_block = ''' add_label(5, 0, "Cloud Mail API Base:")
|
|
||||||
self.cloudmail_api_base_var = tk.StringVar(value=config.get("cloudmail_api_base", ""))
|
|
||||||
self.cloudmail_api_base_entry = tk_entry(config_frame, textvariable=self.cloudmail_api_base_var, width=34)
|
|
||||||
add_field(self.cloudmail_api_base_entry, 5, 1)
|
|
||||||
|
|
||||||
add_label(5, 2, "Cloud Mail 域名:")
|
|
||||||
self.cloudmail_domains_var = tk.StringVar(value=config.get("cloudmail_domains", ""))
|
|
||||||
self.cloudmail_domains_entry = tk_entry(config_frame, textvariable=self.cloudmail_domains_var, width=34)
|
|
||||||
add_field(self.cloudmail_domains_entry, 5, 3)
|
|
||||||
|
|
||||||
add_label(6, 0, "Cloud Mail Public Token:")
|
|
||||||
self.cloudmail_public_token_var = tk.StringVar(value=config.get("cloudmail_public_token", ""))
|
|
||||||
self.cloudmail_public_token_entry = tk_entry(config_frame, textvariable=self.cloudmail_public_token_var, width=72)
|
|
||||||
add_field(self.cloudmail_public_token_entry, 6, 1, columnspan=3)
|
|
||||||
|
|
||||||
add_label(7, 0, "grok2api 本地入池:")
|
|
||||||
self.grok2api_local_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_local", True)))
|
|
||||||
self.grok2api_local_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_local_auto_var)
|
|
||||||
add_field(self.grok2api_local_auto_check, 7, 1, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(7, 2, "grok2api 池名:")
|
|
||||||
self.grok2api_pool_name_var = tk.StringVar(value=str(config.get("grok2api_pool_name", "ssoBasic")))
|
|
||||||
self.grok2api_pool_name_combo = tk_option_menu(
|
|
||||||
config_frame, self.grok2api_pool_name_var, ["ssoBasic", "ssoSuper"], width=12
|
|
||||||
)
|
|
||||||
add_field(self.grok2api_pool_name_combo, 7, 3, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(8, 0, "本地 token.json:")
|
|
||||||
self.grok2api_local_file_var = tk.StringVar(value=str(config.get("grok2api_local_token_file", "")))
|
|
||||||
self.grok2api_local_file_entry = tk_entry(config_frame, textvariable=self.grok2api_local_file_var, width=72)
|
|
||||||
add_field(self.grok2api_local_file_entry, 8, 1, columnspan=3)
|
|
||||||
|
|
||||||
add_label(9, 0, "grok2api 远端入池:")
|
|
||||||
self.grok2api_remote_auto_var = tk.BooleanVar(value=bool(config.get("grok2api_auto_add_remote", False)))
|
|
||||||
self.grok2api_remote_auto_check = tk_checkbutton(config_frame, variable=self.grok2api_remote_auto_var)
|
|
||||||
add_field(self.grok2api_remote_auto_check, 9, 1, sticky=tk.W)
|
|
||||||
|
|
||||||
add_label(10, 0, "grok2api 远端 Base:")
|
|
||||||
self.grok2api_remote_base_var = tk.StringVar(value=str(config.get("grok2api_remote_base", "")))
|
|
||||||
self.grok2api_remote_base_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_base_var, width=72)
|
|
||||||
add_field(self.grok2api_remote_base_entry, 10, 1, columnspan=3)
|
|
||||||
|
|
||||||
add_label(11, 0, "grok2api 远端 app_key:")
|
|
||||||
self.grok2api_remote_key_var = tk.StringVar(value=str(config.get("grok2api_remote_app_key", "")))
|
|
||||||
self.grok2api_remote_key_entry = tk_entry(config_frame, textvariable=self.grok2api_remote_key_var, width=72)
|
|
||||||
add_field(self.grok2api_remote_key_entry, 11, 1, columnspan=3)
|
|
||||||
'''
|
|
||||||
|
|
||||||
app = replace_once(app, old_gui_block, new_gui_block, "Cloud Mail GUI fields")
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' config["cloudflare_auth_mode"] = self.cloudflare_auth_mode_var.get().strip() or "none"\n'
|
|
||||||
' config["grok2api_auto_add_local"] = bool(self.grok2api_local_auto_var.get())',
|
|
||||||
' config["cloudflare_auth_mode"] = self.cloudflare_auth_mode_var.get().strip() or "none"\n'
|
|
||||||
' config["cloudmail_api_base"] = self.cloudmail_api_base_var.get().strip()\n'
|
|
||||||
' config["cloudmail_public_token"] = self.cloudmail_public_token_var.get().strip()\n'
|
|
||||||
' config["cloudmail_domains"] = self.cloudmail_domains_var.get().strip()\n'
|
|
||||||
' config["grok2api_auto_add_local"] = bool(self.grok2api_local_auto_var.get())',
|
|
||||||
"save Cloud Mail GUI config",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = replace_once(
|
|
||||||
app,
|
|
||||||
' if config["email_provider"] == "cloudflare" and not config["cloudflare_api_base"]:\n'
|
|
||||||
' self.log("[!] Cloudflare 模式需要先填写 Cloudflare API Base")\n'
|
|
||||||
' return\n'
|
|
||||||
' try:',
|
|
||||||
' if config["email_provider"] == "cloudflare" and not config["cloudflare_api_base"]:\n'
|
|
||||||
' self.log("[!] Cloudflare 模式需要先填写 Cloudflare API Base")\n'
|
|
||||||
' return\n'
|
|
||||||
' if config["email_provider"] == "cloudmail":\n'
|
|
||||||
' missing = []\n'
|
|
||||||
' if not config["cloudmail_api_base"]:\n'
|
|
||||||
' missing.append("API Base")\n'
|
|
||||||
' if not config["cloudmail_public_token"]:\n'
|
|
||||||
' missing.append("Public Token")\n'
|
|
||||||
' if not config["cloudmail_domains"]:\n'
|
|
||||||
' missing.append("域名")\n'
|
|
||||||
' if missing:\n'
|
|
||||||
' self.log(f"[!] Cloud Mail 模式缺少配置: {\', \'.join(missing)}")\n'
|
|
||||||
' return\n'
|
|
||||||
' try:',
|
|
||||||
"validate Cloud Mail GUI config",
|
|
||||||
)
|
|
||||||
|
|
||||||
ast.parse(app)
|
|
||||||
app_path.write_text(app, encoding="utf-8-sig")
|
|
||||||
|
|
||||||
config_path = Path("config.example.json")
|
|
||||||
config_text = config_path.read_text(encoding="utf-8")
|
|
||||||
config_text = replace_once(
|
|
||||||
config_text,
|
|
||||||
' "cloudflare_path_messages": "/api/mails",\n "proxy": "",',
|
|
||||||
' "cloudflare_path_messages": "/api/mails",\n'
|
|
||||||
' "cloudmail_api_base": "https://mail.example.com",\n'
|
|
||||||
' "cloudmail_public_token": "",\n'
|
|
||||||
' "cloudmail_domains": "example.com",\n'
|
|
||||||
' "cloudmail_path_messages": "/api/public/emailList",\n'
|
|
||||||
' "proxy": "",',
|
|
||||||
"config.example Cloud Mail fields",
|
|
||||||
)
|
|
||||||
json.loads(config_text)
|
|
||||||
config_path.write_text(config_text, encoding="utf-8")
|
|
||||||
|
|
||||||
readme_path = Path("README.md")
|
|
||||||
readme = readme_path.read_text(encoding="utf-8")
|
|
||||||
readme = replace_once(
|
|
||||||
readme,
|
|
||||||
'- 支持 DuckMail、YYDS、Cloudflare 临时邮箱接口。',
|
|
||||||
'- 支持 DuckMail、YYDS、Cloudflare,以及 Cloud Mail 无人收件模式。',
|
|
||||||
"README feature list",
|
|
||||||
)
|
|
||||||
readme = replace_once(
|
|
||||||
readme,
|
|
||||||
'| `email_provider` | 邮箱服务商:`duckmail`、`yyds`、`cloudflare` |',
|
|
||||||
'| `email_provider` | 邮箱服务商:`duckmail`、`yyds`、`cloudflare`、`cloudmail` |',
|
|
||||||
"README provider list",
|
|
||||||
)
|
|
||||||
readme = replace_once(
|
|
||||||
readme,
|
|
||||||
'| `defaultDomains` | Cloudflare 临时邮箱默认域名 |\n'
|
|
||||||
'| `grok2api_auto_add_local` | 是否写入本地 grok2api token 池 |',
|
|
||||||
'| `defaultDomains` | Cloudflare 临时邮箱默认域名 |\n'
|
|
||||||
'| `cloudmail_api_base` | Cloud Mail 站点地址 |\n'
|
|
||||||
'| `cloudmail_public_token` | Cloud Mail 公共 API Token,不会写入邮箱凭证文件 |\n'
|
|
||||||
'| `cloudmail_domains` | Cloud Mail 无人收件域名,多个域名用英文逗号分隔 |\n'
|
|
||||||
'| `cloudmail_path_messages` | Cloud Mail 公共收件接口路径,默认 `/api/public/emailList` |\n'
|
|
||||||
'| `grok2api_auto_add_local` | 是否写入本地 grok2api token 池 |',
|
|
||||||
"README Cloud Mail config rows",
|
|
||||||
)
|
|
||||||
|
|
||||||
cloudmail_readme = '''### Cloud Mail 无人收件模式(可选)
|
|
||||||
|
|
||||||
该模式对接 `maillab/cloud-mail`,使用其“无人收件”功能:程序直接生成随机邮箱地址,不需要先在 Cloud Mail 中创建邮箱账号。
|
|
||||||
|
|
||||||
使用前需要:
|
|
||||||
|
|
||||||
1. 在 Cloud Mail 系统设置中开启“无人收件”。
|
|
||||||
2. 使用管理员账号生成公共 API Token:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST "https://你的-Cloud-Mail-域名/api/public/genToken" \\
|
|
||||||
-H "Content-Type: application/json" \\
|
|
||||||
-d '{"email":"管理员邮箱","password":"管理员密码"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 配置本项目:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email_provider": "cloudmail",
|
|
||||||
"cloudmail_api_base": "https://你的-Cloud-Mail-域名",
|
|
||||||
"cloudmail_public_token": "返回结果 data 中的 token",
|
|
||||||
"cloudmail_domains": "你的收信域名.com",
|
|
||||||
"cloudmail_path_messages": "/api/public/emailList"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
程序会通过 `POST /api/public/emailList` 按目标地址查询邮件。公共 Token 只从 `config.json` 读取,不会作为邮箱 credential 输出到日志或 `mail_credentials.txt`。
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
readme = replace_once(
|
|
||||||
readme,
|
|
||||||
'### grok2api 远端入池配置\n',
|
|
||||||
cloudmail_readme + '### grok2api 远端入池配置\n',
|
|
||||||
"README Cloud Mail section",
|
|
||||||
)
|
|
||||||
readme_path.write_text(readme, encoding="utf-8")
|
|
||||||
Reference in New Issue
Block a user