Skip to content
This repository was archived by the owner on Apr 22, 2026. It is now read-only.

Commit 07c1b7f

Browse files
committed
release(v1.0.6): 修复 Graph 接口模式注册链路 issues
统一将 unsupported_country_region_territory 归因为 region_blocked,并补齐已注册账号 remark 回写、验证码登录兜底与 OTP 多次重发,减少无效删号和补位重试。
1 parent 653dcdb commit 07c1b7f

11 files changed

Lines changed: 1784 additions & 161 deletions

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ CodeX Register 是一个本地桌面化控制台,用于统一执行注册流
88
- 后端:Python 本地 HTTP 服务
99
- 运行模式:`window`(pywebview)/ `browser`
1010

11+
## 0. 更新日志
12+
13+
### v1.0.6 (2026-04-06)
14+
15+
- Issue: Graph 接口模式注册链路异常(区域限制误归因、已注册账号处理、邮箱 OTP 重试策略)。
16+
- 新增 Graph 双模式配置:`graph_accounts_mode=file/api`,接口模式支持仅 `graph_api_base_url + graph_api_token`
17+
- 修复 `Country, region, or territory not supported` 归因,统一标记为 `region_blocked` 并停止无效补位重试。
18+
- 遇到 `Failed to register username` 时先尝试登录换 token,并将上游账号 `remark` 更新为 `已注册`
19+
1120
## 1. 核心能力
1221

1322
- 工作台:启动/停止任务、实时日志、成功率、重试原因、SMS 消耗与余额统计。
@@ -98,7 +107,7 @@ python gui.py --mode browser --no-auto-open
98107
- 先选择 `mail_service_provider`
99108
- MailFree / Cloudflare Temp / CloudMail 可配置域名策略(随机域名、白名单、自定义 local-part)。
100109
- Luckyous API 支持按项目编码自动下单邮箱并轮询验证码。
101-
- Graph 模式需提供账号文件,支持启动前预刷新 token。
110+
- Graph 支持文件模式与接口模式:文件模式使用账号文件;接口模式使用项目地址 + token。
102111
- Gmail 模式需使用应用专用密码(非登录密码)。
103112

104113
### 5.3 MailFree 域名配置(Cloudflare DNS)
@@ -225,7 +234,10 @@ Cloudflare Token 要求(用于域名配置页读取 Zone 和管理 CNAME):
225234
| `gmail_imap_port` | int | `993` | IMAP 端口 |
226235
| `gmail_alias_tag_len` | int | `8` | 别名 tag 长度 |
227236
| `gmail_alias_mix_googlemail` | bool | `true` | 混用 gmail/googlemail 域名 |
237+
| `graph_accounts_mode` | string | `file` | Graph 账号来源:`file` / `api` |
228238
| `graph_accounts_file` | string | `""` | Graph 账号文件路径 |
239+
| `graph_api_base_url` | string | `""` | Graph 接口模式地址 |
240+
| `graph_api_token` | string | `""` | Graph 接口模式 token |
229241
| `graph_tenant` | string | `common` | Graph tenant |
230242
| `graph_fetch_mode` | string | `graph_api` | `graph_api` / `imap_xoauth2` |
231243
| `graph_pre_refresh_before_run` | bool | `true` | 启动前预刷新 token |

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.5
1+
1.0.6

codex_register/gui_config_store.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@
5959
"luckyous_variant_mode": "",
6060
"luckyous_specified_email": "",
6161
"mail_service_provider": "mailfree",
62+
"graph_accounts_mode": "file",
6263
"graph_accounts_file": "",
64+
"graph_api_base_url": "",
65+
"graph_api_token": "",
6366
"graph_tenant": "common",
6467
"graph_fetch_mode": "graph_api",
6568
"graph_pre_refresh_before_run": True,
@@ -181,7 +184,16 @@ def load_config() -> dict[str, Any]:
181184
cfg["luckyous_variant_mode"] = env.get("LUCKYOUS_VARIANT_MODE", "")
182185
cfg["luckyous_specified_email"] = env.get("LUCKYOUS_SPECIFIED_EMAIL", "")
183186
cfg["mail_service_provider"] = mode
187+
cfg["graph_accounts_mode"] = env.get("GRAPH_ACCOUNTS_MODE", "file")
184188
cfg["remote_account_provider"] = env.get("REMOTE_ACCOUNT_PROVIDER", cfg.get("remote_account_provider", "sub2api"))
189+
cfg["graph_api_base_url"] = env.get(
190+
"GRAPH_API_BASE_URL",
191+
env.get("GRAPH_API_URL", ""),
192+
)
193+
cfg["graph_api_token"] = env.get(
194+
"GRAPH_API_TOKEN",
195+
env.get("MAIL_API_TOKEN", ""),
196+
)
185197
cfg["cliproxy_api_base"] = env.get("CLIPROXY_API_BASE", env.get("CLIPROXY_MANAGEMENT_API", ""))
186198
cfg["cliproxy_management_key"] = env.get("CLIPROXY_MANAGEMENT_KEY", env.get("MANAGEMENT_KEY", ""))
187199
try:

codex_register/gui_frontend_app_setup.js

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@
120120
luckyous_domain: "",
121121
luckyous_variant_mode: "",
122122
luckyous_specified_email: "",
123+
graph_accounts_mode: "file",
123124
graph_accounts_file: "",
125+
graph_api_base_url: "",
126+
graph_api_token: "",
124127
graph_tenant: "common",
125128
graph_fetch_mode: "graph_api",
126129
graph_pre_refresh_before_run: true,
@@ -364,6 +367,13 @@
364367
mailRows: [],
365368
mailTotal: 0
366369
},
370+
luckyous: {
371+
domains: [],
372+
mailboxRows: [],
373+
selectedMailbox: "",
374+
mailRows: [],
375+
mailTotal: 0
376+
},
367377
graph: {
368378
domains: [],
369379
mailboxRows: [],
@@ -1009,6 +1019,19 @@
10091019
{ label: "IMAP XOAUTH2 取件", value: "imap_xoauth2" }
10101020
];
10111021

1022+
const graphAccountSourceOptions = [
1023+
{ label: "文件模式", value: "file" },
1024+
{ label: "接口模式", value: "api" }
1025+
];
1026+
1027+
function normalizeGraphAccountsMode(raw) {
1028+
const val = String(raw || "file").trim().toLowerCase();
1029+
if (["api", "http", "interface", "openapi", "open_api", "remote"].includes(val)) {
1030+
return "api";
1031+
}
1032+
return "file";
1033+
}
1034+
10121035
const remoteAccountProviderOptions = [
10131036
{ label: "Sub2API", value: "sub2api" },
10141037
{ label: "CLIProxyAPI", value: "cliproxyapi" }
@@ -1536,7 +1559,10 @@
15361559
settingsForm.luckyous_domain = String(cfg.luckyous_domain || "");
15371560
settingsForm.luckyous_variant_mode = String(cfg.luckyous_variant_mode || "");
15381561
settingsForm.luckyous_specified_email = String(cfg.luckyous_specified_email || "");
1562+
settingsForm.graph_accounts_mode = normalizeGraphAccountsMode(cfg.graph_accounts_mode || "file");
15391563
settingsForm.graph_accounts_file = String(cfg.graph_accounts_file || "");
1564+
settingsForm.graph_api_base_url = String(cfg.graph_api_base_url || "");
1565+
settingsForm.graph_api_token = String(cfg.graph_api_token || "");
15401566
settingsForm.graph_tenant = String(cfg.graph_tenant || "common");
15411567
settingsForm.graph_fetch_mode = String(cfg.graph_fetch_mode || "graph_api");
15421568
settingsForm.graph_pre_refresh_before_run = cfg.graph_pre_refresh_before_run !== false;
@@ -1634,7 +1660,10 @@
16341660
luckyous_domain: String(settingsForm.luckyous_domain || "").trim(),
16351661
luckyous_variant_mode: String(settingsForm.luckyous_variant_mode || "").trim(),
16361662
luckyous_specified_email: String(settingsForm.luckyous_specified_email || "").trim(),
1663+
graph_accounts_mode: normalizeGraphAccountsMode(settingsForm.graph_accounts_mode || "file"),
16371664
graph_accounts_file: String(settingsForm.graph_accounts_file || "").trim(),
1665+
graph_api_base_url: String(settingsForm.graph_api_base_url || "").trim(),
1666+
graph_api_token: String(settingsForm.graph_api_token || "").trim(),
16381667
graph_tenant: String(settingsForm.graph_tenant || "common").trim(),
16391668
graph_fetch_mode: String(settingsForm.graph_fetch_mode || "graph_api").trim(),
16401669
graph_pre_refresh_before_run: !!settingsForm.graph_pre_refresh_before_run,
@@ -2766,7 +2795,16 @@
27662795

27672796
function snapshotMailViewToCache() {
27682797
const key = currentMailProviderKey();
2769-
const target = mailViewCache[key];
2798+
const target = mailViewCache[key] || {
2799+
domains: [],
2800+
mailboxRows: [],
2801+
selectedMailbox: "",
2802+
mailRows: [],
2803+
mailTotal: 0
2804+
};
2805+
if (!mailViewCache[key]) {
2806+
mailViewCache[key] = target;
2807+
}
27702808
target.domains = Array.isArray(mailDomains.value) ? [...mailDomains.value] : [];
27712809
target.mailboxRows = Array.isArray(mailboxRows.value)
27722810
? mailboxRows.value.map((x) => Object.assign({}, x))
@@ -2780,7 +2818,16 @@
27802818

27812819
function restoreMailViewFromCache(provider) {
27822820
const key = normalizeMailProvider(provider || "mailfree");
2783-
const source = mailViewCache[key];
2821+
const source = mailViewCache[key] || {
2822+
domains: [],
2823+
mailboxRows: [],
2824+
selectedMailbox: "",
2825+
mailRows: [],
2826+
mailTotal: 0
2827+
};
2828+
if (!mailViewCache[key]) {
2829+
mailViewCache[key] = source;
2830+
}
27842831
mailDomains.value = Array.isArray(source.domains) ? [...source.domains] : [];
27852832
mailboxRows.value = Array.isArray(source.mailboxRows)
27862833
? source.mailboxRows.map((x) => Object.assign({}, x))
@@ -2897,6 +2944,10 @@
28972944
}
28982945

28992946
async function refreshGraphAccountFiles(showError = false) {
2947+
if (normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) !== "file") {
2948+
graphAccountFileOptions.value = [];
2949+
return;
2950+
}
29002951
loading.graph_files = true;
29012952
try {
29022953
const data = await apiRequest("/api/mail/graph-account-files");
@@ -2915,6 +2966,10 @@
29152966
}
29162967

29172968
function pickGraphAccountFile() {
2969+
if (normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) !== "file") {
2970+
message.warning("接口模式无需选择账号文件");
2971+
return;
2972+
}
29182973
const el = graphFileInputRef.value;
29192974
if (!el) {
29202975
message.error("文件选择器不可用");
@@ -2925,6 +2980,9 @@
29252980
}
29262981

29272982
async function onGraphAccountFilePicked(e) {
2983+
if (normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) !== "file") {
2984+
return;
2985+
}
29282986
const files = (e && e.target && e.target.files) ? e.target.files : null;
29292987
if (!files || !files.length) return;
29302988
const file = files[0];
@@ -2954,6 +3012,10 @@
29543012
}
29553013

29563014
async function deleteSelectedGraphAccountFile() {
3015+
if (normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) !== "file") {
3016+
message.warning("接口模式没有本地账号文件可删除");
3017+
return;
3018+
}
29573019
const target = String(settingsForm.graph_accounts_file || "").trim();
29583020
if (!target) {
29593021
message.warning("请先选择要删除的 Graph 账号文件");
@@ -2987,7 +3049,7 @@
29873049
mailfreePanelTab.value = "basic";
29883050
}
29893051
restoreMailViewFromCache(mailProviderTab.value);
2990-
if (mailProviderTab.value === "graph") {
3052+
if (mailProviderTab.value === "graph" && normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) === "file") {
29913053
refreshGraphAccountFiles(false);
29923054
}
29933055
if (mailProviderTab.value === "mailfree" && mailfreePanelTab.value === "domain") {
@@ -3823,7 +3885,10 @@
38233885
return;
38243886
}
38253887
if (tab === "mail") {
3826-
if (normalizeMailProvider(mailProviderTab.value) === "graph") {
3888+
if (
3889+
normalizeMailProvider(mailProviderTab.value) === "graph"
3890+
&& normalizeGraphAccountsMode(settingsForm.graph_accounts_mode) === "file"
3891+
) {
38273892
await refreshGraphAccountFiles(false);
38283893
}
38293894
await loadMailDomainStats();
@@ -3845,6 +3910,22 @@
38453910
}
38463911
});
38473912

3913+
Vue.watch(
3914+
() => normalizeGraphAccountsMode(settingsForm.graph_accounts_mode || "file"),
3915+
async (mode, oldMode) => {
3916+
if (mode === oldMode) return;
3917+
if (mode === "api") {
3918+
settingsForm.graph_fetch_mode = "graph_api";
3919+
settingsForm.graph_pre_refresh_before_run = false;
3920+
graphAccountFileOptions.value = [];
3921+
return;
3922+
}
3923+
if (mailProviderTab.value === "graph") {
3924+
await refreshGraphAccountFiles(false);
3925+
}
3926+
}
3927+
);
3928+
38483929
Vue.watch(
38493930
() => String(settingsForm.hero_sms_service || "").trim(),
38503931
async (_val, _oldVal) => {
@@ -3981,6 +4062,7 @@
39814062
flclashProbeSummary,
39824063
flclashPolicyOptions,
39834064
graphFetchModeOptions,
4065+
graphAccountSourceOptions,
39844066
remoteAccountProviderOptions,
39854067
flclashProbeColumns,
39864068
jsonColumns,

codex_register/gui_frontend_app_template.html

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -597,44 +597,72 @@ <h1>注册任务面板</h1>
597597
<span class="meta">混用 gmail/googlemail 域名</span>
598598
</div>
599599
<div v-else-if="mailProviderTab === 'graph'" class="toolbar" style="margin-top: 10px">
600+
<n-select
601+
class="toolbar-select"
602+
v-model:value="settingsForm.graph_accounts_mode"
603+
:options="graphAccountSourceOptions"
604+
placeholder="Graph 账号来源"
605+
/>
600606
<input
607+
v-if="settingsForm.graph_accounts_mode === 'file'"
601608
ref="graphFileInputRef"
602609
type="file"
603610
accept=".txt,text/plain"
604611
style="display: none"
605612
@change="onGraphAccountFilePicked"
606613
/>
607-
<n-select
608-
class="toolbar-select"
609-
v-model:value="settingsForm.graph_accounts_file"
610-
:options="graphAccountFileOptions"
611-
clearable
612-
filterable
613-
placeholder="当前 Graph 账号文件"
614-
/>
614+
<template v-if="settingsForm.graph_accounts_mode === 'file'">
615+
<n-select
616+
class="toolbar-select"
617+
v-model:value="settingsForm.graph_accounts_file"
618+
:options="graphAccountFileOptions"
619+
clearable
620+
filterable
621+
placeholder="当前 Graph 账号文件"
622+
/>
623+
</template>
624+
<template v-else>
625+
<n-input
626+
class="toolbar-path"
627+
v-model:value="settingsForm.graph_api_base_url"
628+
placeholder="接口地址(如 https://xxx.workers.dev)"
629+
/>
630+
<n-input
631+
class="toolbar-path"
632+
v-model:value="settingsForm.graph_api_token"
633+
type="password"
634+
show-password-on="click"
635+
placeholder="接口 token(Bearer)"
636+
/>
637+
</template>
615638
<n-input
639+
v-if="settingsForm.graph_accounts_mode === 'file'"
616640
class="toolbar-path"
617641
v-model:value="settingsForm.graph_tenant"
618642
placeholder="Tenant(common / organizations / tenant-id)"
619643
/>
620-
<n-select
621-
class="toolbar-select"
622-
v-model:value="settingsForm.graph_fetch_mode"
623-
:options="graphFetchModeOptions"
624-
placeholder="选择取件模式"
625-
/>
626-
<n-switch v-model:value="settingsForm.graph_pre_refresh_before_run" />
627-
<span class="meta">注册前预刷新 token</span>
628-
<n-button class="toolbar-btn" type="primary" :loading="loading.graph_files" @click="pickGraphAccountFile">选择 TXT 文件</n-button>
629-
<n-button
630-
class="toolbar-btn"
631-
type="error"
632-
ghost
633-
:disabled="!settingsForm.graph_accounts_file"
634-
:loading="loading.graph_files"
635-
@click="deleteSelectedGraphAccountFile"
636-
>删除当前文件</n-button>
637-
<n-button class="toolbar-btn" :loading="loading.graph_files" @click="refreshGraphAccountFiles(true)">刷新文件列表</n-button>
644+
<template v-if="settingsForm.graph_accounts_mode === 'file'">
645+
<n-select
646+
class="toolbar-select"
647+
v-model:value="settingsForm.graph_fetch_mode"
648+
:options="graphFetchModeOptions"
649+
placeholder="选择取件模式"
650+
/>
651+
<n-switch v-model:value="settingsForm.graph_pre_refresh_before_run" />
652+
<span class="meta">注册前预刷新 token</span>
653+
</template>
654+
<template v-if="settingsForm.graph_accounts_mode === 'file'">
655+
<n-button class="toolbar-btn" type="primary" :loading="loading.graph_files" @click="pickGraphAccountFile">选择 TXT 文件</n-button>
656+
<n-button
657+
class="toolbar-btn"
658+
type="error"
659+
ghost
660+
:disabled="!settingsForm.graph_accounts_file"
661+
:loading="loading.graph_files"
662+
@click="deleteSelectedGraphAccountFile"
663+
>删除当前文件</n-button>
664+
<n-button class="toolbar-btn" :loading="loading.graph_files" @click="refreshGraphAccountFiles(true)">刷新文件列表</n-button>
665+
</template>
638666
</div>
639667
<div v-else-if="mailProviderTab === 'cf_email_routing'" style="margin-top: 10px">
640668
<div class="meta" style="margin-bottom: 8px; font-weight: 500">Cloudflare Email Routing</div>
@@ -710,15 +738,21 @@ <h1>注册任务面板</h1>
710738
<div v-if="mailProviderTab === 'luckyous'" class="meta" style="margin-top: 6px">
711739
Luckyous API 使用 <code>/api/v1/openapi/order/create</code> 下单并通过 <code>/order/{order_no}/code</code> 轮询验证码。
712740
</div>
713-
<div v-if="mailProviderTab === 'graph'" class="meta" style="margin-top: 6px">
741+
<div v-if="mailProviderTab === 'graph' && settingsForm.graph_accounts_mode === 'file'" class="meta" style="margin-top: 6px">
714742
文件格式要求:每行必须为 <code>邮箱----密码----client_id----令牌</code>(共 4 段,使用四个中划线分隔)。
715743
</div>
716-
<div v-if="mailProviderTab === 'graph'" class="meta" style="margin-top: 4px">
717-
取件模式:Graph API(/me/messages)或 IMAP XOAUTH2(读取 INBOX/Junk)
744+
<div v-if="mailProviderTab === 'graph' && settingsForm.graph_accounts_mode === 'api'" class="meta" style="margin-top: 6px">
745+
接口模式:仅需填写 <code>graph_api_base_url</code><code>graph_api_token</code>,账号与邮件由上游 API 提供
718746
</div>
719-
<div v-if="mailProviderTab === 'graph'" class="meta" style="margin-top: 4px">
747+
<div v-if="mailProviderTab === 'graph' && settingsForm.graph_accounts_mode === 'file'" class="meta" style="margin-top: 4px">
748+
取件模式:Graph API 或 IMAP XOAUTH2(接口模式会透传 mode=graph/imap)。
749+
</div>
750+
<div v-if="mailProviderTab === 'graph' && settingsForm.graph_accounts_mode === 'file'" class="meta" style="margin-top: 4px">
720751
可切换“注册前预刷新 token”:关闭后可加快启动,但失效账号会在运行中再被淘汰。
721752
</div>
753+
<div v-if="mailProviderTab === 'graph' && settingsForm.graph_accounts_mode === 'api'" class="meta" style="margin-top: 4px">
754+
接口模式默认不做 token 预刷新,也不需要选择取件模式。
755+
</div>
722756
<div
723757
v-if="['mailfree','cloudflare_temp_email','cloudmail'].includes(mailProviderTab) && !(mailProviderTab === 'mailfree' && mailfreePanelTab === 'domain')"
724758
class="meta"

0 commit comments

Comments
 (0)