feat: improve Cloudflare Temp Email random subdomain support and sidepanel UX
- 合并 PR #136 的核心改动:补充 Cloudflare Temp Email 随机子域支持、独立侧边栏卡片与使用教程入口 - 本地补充修复:恢复注册诊断测试中的可读中文触发词,并同步 docs/使用教程.md 到结构文档 - 影响范围:generated email helpers、sidepanel Cloudflare Temp Email 配置、signup diagnostics tests、docs
This commit is contained in:
@@ -280,6 +280,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
cloudflareTempEmailAdminAuth: '',
|
cloudflareTempEmailAdminAuth: '',
|
||||||
cloudflareTempEmailCustomAuth: '',
|
cloudflareTempEmailCustomAuth: '',
|
||||||
cloudflareTempEmailReceiveMailbox: '',
|
cloudflareTempEmailReceiveMailbox: '',
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: false,
|
||||||
cloudflareTempEmailDomain: '',
|
cloudflareTempEmailDomain: '',
|
||||||
cloudflareTempEmailDomains: [],
|
cloudflareTempEmailDomains: [],
|
||||||
hotmailAccounts: [],
|
hotmailAccounts: [],
|
||||||
@@ -827,6 +828,7 @@ function getCloudflareTempEmailConfig(state = {}) {
|
|||||||
adminAuth: String(state.cloudflareTempEmailAdminAuth || ''),
|
adminAuth: String(state.cloudflareTempEmailAdminAuth || ''),
|
||||||
customAuth: String(state.cloudflareTempEmailCustomAuth || ''),
|
customAuth: String(state.cloudflareTempEmailCustomAuth || ''),
|
||||||
receiveMailbox: normalizeCloudflareTempEmailReceiveMailbox(state.cloudflareTempEmailReceiveMailbox),
|
receiveMailbox: normalizeCloudflareTempEmailReceiveMailbox(state.cloudflareTempEmailReceiveMailbox),
|
||||||
|
useRandomSubdomain: Boolean(state.cloudflareTempEmailUseRandomSubdomain),
|
||||||
domain: normalizeCloudflareTempEmailDomain(state.cloudflareTempEmailDomain),
|
domain: normalizeCloudflareTempEmailDomain(state.cloudflareTempEmailDomain),
|
||||||
domains: normalizeCloudflareTempEmailDomains(state.cloudflareTempEmailDomains),
|
domains: normalizeCloudflareTempEmailDomains(state.cloudflareTempEmailDomains),
|
||||||
};
|
};
|
||||||
@@ -895,6 +897,7 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeEmailGenerator(value);
|
return normalizeEmailGenerator(value);
|
||||||
case 'autoDeleteUsedIcloudAlias':
|
case 'autoDeleteUsedIcloudAlias':
|
||||||
case 'accountRunHistoryTextEnabled':
|
case 'accountRunHistoryTextEnabled':
|
||||||
|
case 'cloudflareTempEmailUseRandomSubdomain':
|
||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
case 'icloudHostPreference':
|
case 'icloudHostPreference':
|
||||||
return normalizeIcloudHost(value) || 'auto';
|
return normalizeIcloudHost(value) || 'auto';
|
||||||
|
|||||||
@@ -146,6 +146,7 @@
|
|||||||
const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
|
const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
|
||||||
const payload = {
|
const payload = {
|
||||||
enablePrefix: true,
|
enablePrefix: true,
|
||||||
|
enableRandomSubdomain: Boolean(config.useRandomSubdomain),
|
||||||
name: requestedName,
|
name: requestedName,
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
};
|
};
|
||||||
|
|||||||
+220
-10
@@ -385,13 +385,82 @@ function getSignupEntryStateSummary(snapshot = inspectSignupEntryState()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSignupEntryDiagnostics() {
|
function getSignupEntryDiagnostics() {
|
||||||
|
const view = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const safeGetComputedStyle = (el) => {
|
||||||
|
if (!el || typeof view?.getComputedStyle !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return view.getComputedStyle(el);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const buildRectSummary = (el) => {
|
||||||
|
const rect = typeof el?.getBoundingClientRect === 'function'
|
||||||
|
? el.getBoundingClientRect()
|
||||||
|
: null;
|
||||||
|
return rect
|
||||||
|
? {
|
||||||
|
width: Math.round(rect.width || 0),
|
||||||
|
height: Math.round(rect.height || 0),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
const buildVisibilityMeta = (el) => {
|
||||||
|
const style = safeGetComputedStyle(el);
|
||||||
|
return {
|
||||||
|
className: String(el?.className || '').slice(0, 200),
|
||||||
|
hidden: Boolean(el?.hidden),
|
||||||
|
ariaHidden: el?.getAttribute?.('aria-hidden') || '',
|
||||||
|
inert: typeof el?.hasAttribute === 'function' ? el.hasAttribute('inert') : false,
|
||||||
|
display: style?.display || '',
|
||||||
|
visibility: style?.visibility || '',
|
||||||
|
opacity: style?.opacity || '',
|
||||||
|
pointerEvents: style?.pointerEvents || '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const findBlockingAncestor = (el) => {
|
||||||
|
let current = el?.parentElement || null;
|
||||||
|
while (current) {
|
||||||
|
const style = safeGetComputedStyle(current);
|
||||||
|
const rect = buildRectSummary(current);
|
||||||
|
const hidden = Boolean(current.hidden);
|
||||||
|
const ariaHidden = current.getAttribute?.('aria-hidden') || '';
|
||||||
|
const inert = typeof current.hasAttribute === 'function' ? current.hasAttribute('inert') : false;
|
||||||
|
const blockedByStyle = Boolean(
|
||||||
|
style
|
||||||
|
&& (
|
||||||
|
style.display === 'none'
|
||||||
|
|| style.visibility === 'hidden'
|
||||||
|
|| style.opacity === '0'
|
||||||
|
|| style.pointerEvents === 'none'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const blockedByRect = Boolean(rect && (rect.width === 0 || rect.height === 0));
|
||||||
|
if (hidden || ariaHidden === 'true' || inert || blockedByStyle || blockedByRect) {
|
||||||
|
return {
|
||||||
|
tag: (current.tagName || '').toLowerCase(),
|
||||||
|
id: current.id || '',
|
||||||
|
className: String(current.className || '').slice(0, 200),
|
||||||
|
hidden,
|
||||||
|
ariaHidden,
|
||||||
|
inert,
|
||||||
|
display: style?.display || '',
|
||||||
|
visibility: style?.visibility || '',
|
||||||
|
opacity: style?.opacity || '',
|
||||||
|
pointerEvents: style?.pointerEvents || '',
|
||||||
|
rect,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
current = current.parentElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
const actionCandidates = document.querySelectorAll(
|
const actionCandidates = document.querySelectorAll(
|
||||||
'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]'
|
'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]'
|
||||||
);
|
);
|
||||||
const allActions = Array.from(actionCandidates).map((el) => {
|
const allActions = Array.from(actionCandidates).map((el) => {
|
||||||
const rect = typeof el?.getBoundingClientRect === 'function'
|
|
||||||
? el.getBoundingClientRect()
|
|
||||||
: null;
|
|
||||||
const text = getActionText(el);
|
const text = getActionText(el);
|
||||||
return {
|
return {
|
||||||
tag: (el.tagName || '').toLowerCase(),
|
tag: (el.tagName || '').toLowerCase(),
|
||||||
@@ -399,12 +468,7 @@ function getSignupEntryDiagnostics() {
|
|||||||
text: text.slice(0, 80),
|
text: text.slice(0, 80),
|
||||||
visible: isVisibleElement(el),
|
visible: isVisibleElement(el),
|
||||||
enabled: isActionEnabled(el),
|
enabled: isActionEnabled(el),
|
||||||
rect: rect
|
rect: buildRectSummary(el),
|
||||||
? {
|
|
||||||
width: Math.round(rect.width || 0),
|
|
||||||
height: Math.round(rect.height || 0),
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const visibleActions = Array.from(actionCandidates)
|
const visibleActions = Array.from(actionCandidates)
|
||||||
@@ -417,7 +481,20 @@ function getSignupEntryDiagnostics() {
|
|||||||
enabled: isActionEnabled(el),
|
enabled: isActionEnabled(el),
|
||||||
}))
|
}))
|
||||||
.filter((item) => item.text);
|
.filter((item) => item.text);
|
||||||
const signupLikeActions = allActions
|
const signupLikeActions = Array.from(actionCandidates)
|
||||||
|
.map((el) => {
|
||||||
|
const text = getActionText(el);
|
||||||
|
return {
|
||||||
|
tag: (el.tagName || '').toLowerCase(),
|
||||||
|
type: el.getAttribute?.('type') || '',
|
||||||
|
text: text.slice(0, 80),
|
||||||
|
visible: isVisibleElement(el),
|
||||||
|
enabled: isActionEnabled(el),
|
||||||
|
rect: buildRectSummary(el),
|
||||||
|
...buildVisibilityMeta(el),
|
||||||
|
blockingAncestor: findBlockingAncestor(el),
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter((item) => item.text && SIGNUP_ENTRY_TRIGGER_PATTERN.test(item.text))
|
.filter((item) => item.text && SIGNUP_ENTRY_TRIGGER_PATTERN.test(item.text))
|
||||||
.slice(0, 12);
|
.slice(0, 12);
|
||||||
|
|
||||||
@@ -425,15 +502,133 @@ function getSignupEntryDiagnostics() {
|
|||||||
url: location.href,
|
url: location.href,
|
||||||
title: document.title || '',
|
title: document.title || '',
|
||||||
readyState: document.readyState || '',
|
readyState: document.readyState || '',
|
||||||
|
viewport: {
|
||||||
|
innerWidth: Math.round(Number(view?.innerWidth) || 0),
|
||||||
|
innerHeight: Math.round(Number(view?.innerHeight) || 0),
|
||||||
|
outerWidth: Math.round(Number(view?.outerWidth) || 0),
|
||||||
|
outerHeight: Math.round(Number(view?.outerHeight) || 0),
|
||||||
|
devicePixelRatio: Number(view?.devicePixelRatio) || 0,
|
||||||
|
},
|
||||||
hasEmailInput: Boolean(getSignupEmailInput()),
|
hasEmailInput: Boolean(getSignupEmailInput()),
|
||||||
hasPasswordInput: Boolean(getSignupPasswordInput()),
|
hasPasswordInput: Boolean(getSignupPasswordInput()),
|
||||||
bodyContainsSignupText: SIGNUP_ENTRY_TRIGGER_PATTERN.test(getPageTextSnapshot()),
|
bodyContainsSignupText: SIGNUP_ENTRY_TRIGGER_PATTERN.test(getPageTextSnapshot()),
|
||||||
|
signupLikeActionCounts: {
|
||||||
|
total: signupLikeActions.length,
|
||||||
|
visible: signupLikeActions.filter((item) => item.visible).length,
|
||||||
|
hidden: signupLikeActions.filter((item) => !item.visible).length,
|
||||||
|
},
|
||||||
signupLikeActions,
|
signupLikeActions,
|
||||||
visibleActions,
|
visibleActions,
|
||||||
bodyTextPreview: getPageTextSnapshot().slice(0, 240),
|
bodyTextPreview: getPageTextSnapshot().slice(0, 240),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordDiagnostics() {
|
||||||
|
const view = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const safeGetComputedStyle = (el) => {
|
||||||
|
if (!el || typeof view?.getComputedStyle !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return view.getComputedStyle(el);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const buildRectSummary = (el) => {
|
||||||
|
const rect = typeof el?.getBoundingClientRect === 'function'
|
||||||
|
? el.getBoundingClientRect()
|
||||||
|
: null;
|
||||||
|
return rect
|
||||||
|
? {
|
||||||
|
width: Math.round(rect.width || 0),
|
||||||
|
height: Math.round(rect.height || 0),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
const buildInputSummary = (el) => {
|
||||||
|
const style = safeGetComputedStyle(el);
|
||||||
|
return {
|
||||||
|
tag: (el?.tagName || '').toLowerCase(),
|
||||||
|
type: el?.getAttribute?.('type') || el?.type || '',
|
||||||
|
name: el?.getAttribute?.('name') || el?.name || '',
|
||||||
|
id: el?.id || '',
|
||||||
|
autocomplete: el?.getAttribute?.('autocomplete') || '',
|
||||||
|
placeholder: String(el?.getAttribute?.('placeholder') || '').slice(0, 80),
|
||||||
|
visible: isVisibleElement(el),
|
||||||
|
enabled: isActionEnabled(el),
|
||||||
|
valueLength: String(el?.value || '').length,
|
||||||
|
rect: buildRectSummary(el),
|
||||||
|
className: String(el?.className || '').slice(0, 200),
|
||||||
|
display: style?.display || '',
|
||||||
|
visibility: style?.visibility || '',
|
||||||
|
opacity: style?.opacity || '',
|
||||||
|
pointerEvents: style?.pointerEvents || '',
|
||||||
|
formAction: el?.form?.action || '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const buildActionSummary = (el) => {
|
||||||
|
const style = safeGetComputedStyle(el);
|
||||||
|
return {
|
||||||
|
tag: (el?.tagName || '').toLowerCase(),
|
||||||
|
type: el?.getAttribute?.('type') || el?.type || '',
|
||||||
|
role: el?.getAttribute?.('role') || '',
|
||||||
|
text: getActionText(el).slice(0, 120),
|
||||||
|
visible: isVisibleElement(el),
|
||||||
|
enabled: isActionEnabled(el),
|
||||||
|
rect: buildRectSummary(el),
|
||||||
|
className: String(el?.className || '').slice(0, 200),
|
||||||
|
display: style?.display || '',
|
||||||
|
visibility: style?.visibility || '',
|
||||||
|
opacity: style?.opacity || '',
|
||||||
|
pointerEvents: style?.pointerEvents || '',
|
||||||
|
dataDdActionName: el?.getAttribute?.('data-dd-action-name') || '',
|
||||||
|
formAction: el?.form?.action || '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const passwordInputs = Array.from(document.querySelectorAll(
|
||||||
|
'input[type="password"], input[name*="password" i], input[autocomplete="new-password"], input[autocomplete="current-password"]'
|
||||||
|
))
|
||||||
|
.map(buildInputSummary)
|
||||||
|
.slice(0, 8);
|
||||||
|
const actionCandidates = Array.from(document.querySelectorAll(
|
||||||
|
'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]'
|
||||||
|
))
|
||||||
|
.map(buildActionSummary)
|
||||||
|
.filter((item) => item.text)
|
||||||
|
.slice(0, 16);
|
||||||
|
const visibleActions = actionCandidates.filter((item) => item.visible).slice(0, 12);
|
||||||
|
const submitButton = getSignupPasswordSubmitButton({ allowDisabled: true });
|
||||||
|
const oneTimeCodeTrigger = findOneTimeCodeLoginTrigger();
|
||||||
|
const retryState = getSignupPasswordTimeoutErrorPageState();
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: location.href,
|
||||||
|
title: document.title || '',
|
||||||
|
readyState: document.readyState || '',
|
||||||
|
displayedEmail: getSignupPasswordDisplayedEmail(),
|
||||||
|
hasVisiblePasswordInput: Boolean(getSignupPasswordInput()),
|
||||||
|
passwordInputCount: passwordInputs.length,
|
||||||
|
visiblePasswordInputCount: passwordInputs.filter((item) => item.visible).length,
|
||||||
|
passwordInputs,
|
||||||
|
submitButton: submitButton ? buildActionSummary(submitButton) : null,
|
||||||
|
oneTimeCodeTrigger: oneTimeCodeTrigger ? buildActionSummary(oneTimeCodeTrigger) : null,
|
||||||
|
retryPage: Boolean(retryState),
|
||||||
|
retryEnabled: Boolean(retryState?.retryEnabled),
|
||||||
|
userAlreadyExistsBlocked: Boolean(retryState?.userAlreadyExistsBlocked),
|
||||||
|
visibleActions,
|
||||||
|
bodyTextPreview: getPageTextSnapshot().slice(0, 240),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSignupPasswordDiagnostics(context, level = 'warn') {
|
||||||
|
try {
|
||||||
|
log(`${context}:密码页诊断快照:${JSON.stringify(getSignupPasswordDiagnostics())}`, level);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[MultiPage:signup-page] failed to build signup password diagnostics:', error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForSignupEntryState(options = {}) {
|
async function waitForSignupEntryState(options = {}) {
|
||||||
const {
|
const {
|
||||||
timeout = 15000,
|
timeout = 15000,
|
||||||
@@ -620,6 +815,10 @@ async function step3_fillEmailPassword(payload) {
|
|||||||
snapshot = inspectSignupEntryState();
|
snapshot = inspectSignupEntryState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (snapshot.state !== 'password_page' || !snapshot.passwordInput) {
|
||||||
|
logSignupPasswordDiagnostics('步骤 3:未能识别可填写的密码输入框');
|
||||||
|
}
|
||||||
|
|
||||||
if (snapshot.state !== 'password_page' || !snapshot.passwordInput) {
|
if (snapshot.state !== 'password_page' || !snapshot.passwordInput) {
|
||||||
throw new Error('在密码页未找到密码输入框。URL: ' + location.href);
|
throw new Error('在密码页未找到密码输入框。URL: ' + location.href);
|
||||||
}
|
}
|
||||||
@@ -635,6 +834,12 @@ async function step3_fillEmailPassword(payload) {
|
|||||||
|| getSignupPasswordSubmitButton({ allowDisabled: true })
|
|| getSignupPasswordSubmitButton({ allowDisabled: true })
|
||||||
|| await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|create/i, 5000).catch(() => null);
|
|| await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|create/i, 5000).catch(() => null);
|
||||||
|
|
||||||
|
if (!submitBtn) {
|
||||||
|
logSignupPasswordDiagnostics('步骤 3:未找到可提交的密码页按钮');
|
||||||
|
} else if (typeof findOneTimeCodeLoginTrigger === 'function' && findOneTimeCodeLoginTrigger()) {
|
||||||
|
logSignupPasswordDiagnostics('步骤 3:当前密码页同时存在一次性验证码入口', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
// Report complete BEFORE submit, because submit causes page navigation
|
// Report complete BEFORE submit, because submit causes page navigation
|
||||||
// which kills the content script connection
|
// which kills the content script connection
|
||||||
const signupVerificationRequestedAt = submitBtn ? Date.now() : null;
|
const signupVerificationRequestedAt = submitBtn ? Date.now() : null;
|
||||||
@@ -1640,6 +1845,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
let recoveryRound = 0;
|
let recoveryRound = 0;
|
||||||
const maxRecoveryRounds = 3;
|
const maxRecoveryRounds = 3;
|
||||||
|
let passwordPageDiagnosticsLogged = false;
|
||||||
|
|
||||||
while (Date.now() - start < timeout && recoveryRound < maxRecoveryRounds) {
|
while (Date.now() - start < timeout && recoveryRound < maxRecoveryRounds) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
@@ -1678,6 +1884,10 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.state === 'password') {
|
if (snapshot.state === 'password') {
|
||||||
|
if (!passwordPageDiagnosticsLogged) {
|
||||||
|
passwordPageDiagnosticsLogged = true;
|
||||||
|
logSignupPasswordDiagnostics(`${prepareLogLabel}:页面仍停留在密码页`);
|
||||||
|
}
|
||||||
if (!password) {
|
if (!password) {
|
||||||
throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。');
|
throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。');
|
||||||
}
|
}
|
||||||
|
|||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
# Codex 注册扩展更新与 Clash Verge 非港轮询配置教程
|
||||||
|
|
||||||
|
本教程用于说明如何更新 `Codex 注册扩展`,以及如何在 `Clash Verge` 中配置 `🔁 非港轮询`。
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 已经安装本扩展,想更新到最新版本
|
||||||
|
- 想用更方便的方式长期同步扩展更新
|
||||||
|
- 需要在 `Clash Verge` 中启用 `🔁 非港轮询`
|
||||||
|
|
||||||
|
## 准备内容
|
||||||
|
|
||||||
|
- 已安装好的扩展文件夹
|
||||||
|
- 可以打开浏览器的 `扩展程序管理` 页面
|
||||||
|
- 如需使用 `git pull`,本地已安装 `git`
|
||||||
|
- 如需使用 `GitHub Desktop`,本地已安装 `GitHub Desktop`
|
||||||
|
- 已安装 `Clash Verge`,并已导入可用订阅
|
||||||
|
|
||||||
|
## 操作步骤
|
||||||
|
|
||||||
|
### 第一部分:更新扩展
|
||||||
|
|
||||||
|
1. 使用 `GitHub Desktop` 更新
|
||||||
|
先安装 `GitHub Desktop`。
|
||||||
|
把当前扩展仓库交给 `GitHub Desktop` 管理后,之后就可以随时更新。
|
||||||
|
`GitHub Desktop` 的具体使用方法本教程不展开,需要时可直接询问豆包。
|
||||||
|
|
||||||
|
2. 使用 `git pull` 更新(最方便,最推荐)
|
||||||
|
先在本地安装 `git`。
|
||||||
|
打开终端后执行 `cd 扩展文件夹路径` 进入当前扩展目录。
|
||||||
|
接着执行 `git pull` 拉取最新更新。
|
||||||
|
这是最方便、最推荐的更新方式。
|
||||||
|
|
||||||
|
3. 手动下载最新版本覆盖更新
|
||||||
|
点击左上角的版本号或 `更新` 按钮。
|
||||||
|
下载最新版本到本地后,直接覆盖现有扩展文件夹即可。
|
||||||
|
|
||||||
|
4. 更新完成后重新加载扩展
|
||||||
|
不论使用哪种更新方式,更新完成后都必须打开浏览器的 `扩展程序管理` 页面。
|
||||||
|
找到该扩展后,手动点击一次 `重新加载`。
|
||||||
|
这一步一定要做,否则浏览器可能仍在使用旧版本。
|
||||||
|
|
||||||
|
### 第二部分:配置 `Clash Verge` 的 `🔁 非港轮询`
|
||||||
|
|
||||||
|
#### 第一步:添加扩展脚本
|
||||||
|
|
||||||
|
1. 打开 `Clash Verge`,进入左侧的 `订阅`(`Profiles`)界面。
|
||||||
|
2. 在右上角或对应位置找到并双击打开 `全局扩展脚本`(`Merge/Script`)。
|
||||||
|
3. 将里面的内容全部清空,替换为下方格式化好的代码。
|
||||||
|
4. 点击保存,使用右上角保存按钮或按 `Ctrl+S`。
|
||||||
|
|
||||||
|
💻 格式化后的代码(如果遇到格式错误,则让豆包修复一下)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function uniqPrepend(arr, items) {
|
||||||
|
if (!Array.isArray(arr)) arr = [];
|
||||||
|
for (var i = items.length - 1; i >= 0; i--) {
|
||||||
|
var item = items[i];
|
||||||
|
var exists = false;
|
||||||
|
for (var j = 0; j < arr.length; j++) {
|
||||||
|
if (arr[j] === item) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) arr.unshift(item);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function upsertGroup(groups, group) {
|
||||||
|
for (var i = 0; i < groups.length; i++) {
|
||||||
|
if (groups[i] && groups[i].name === group.name) {
|
||||||
|
groups[i] = group;
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups.unshift(group);
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(config, profileName) {
|
||||||
|
if (!config) return config;
|
||||||
|
|
||||||
|
if (!Array.isArray(config["proxy-groups"])) {
|
||||||
|
config["proxy-groups"] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups = config["proxy-groups"];
|
||||||
|
var LB_NAME = "🔁 非港轮询";
|
||||||
|
|
||||||
|
var excludeRegex =
|
||||||
|
"(?i)(" +
|
||||||
|
"香港|hong[ -]?kong|\\bhk\\b|\\bhkg\\b|🇭🇰" +
|
||||||
|
"|剩余流量|套餐到期|下次重置剩余|重置剩余|到期时间|流量重置" +
|
||||||
|
"|traffic|expire|expiration|subscription|subscribe|reset|plan" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
groups = upsertGroup(groups, {
|
||||||
|
name: LB_NAME,
|
||||||
|
type: "load-balance",
|
||||||
|
strategy: "round-robin",
|
||||||
|
"include-all-proxies": true,
|
||||||
|
"exclude-filter": excludeRegex,
|
||||||
|
url: "https://www.gstatic.com/generate_204",
|
||||||
|
interval: 300,
|
||||||
|
lazy: true,
|
||||||
|
"expected-status": 204
|
||||||
|
});
|
||||||
|
|
||||||
|
var injected = false;
|
||||||
|
var entryNameRegex = /节点选择|代理|Proxy|PROXY|默认|GLOBAL|全局|选择/i;
|
||||||
|
|
||||||
|
for (var i = 0; i < groups.length; i++) {
|
||||||
|
var g = groups[i];
|
||||||
|
if (!g || g.type !== "select") continue;
|
||||||
|
|
||||||
|
if (entryNameRegex.test(g.name || "")) {
|
||||||
|
if (!Array.isArray(g.proxies)) g.proxies = [];
|
||||||
|
g.proxies = uniqPrepend(g.proxies, [LB_NAME]);
|
||||||
|
injected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!injected) {
|
||||||
|
for (var k = 0; k < groups.length; k++) {
|
||||||
|
var g2 = groups[k];
|
||||||
|
if (g2 && g2.type === "select") {
|
||||||
|
if (!Array.isArray(g2.proxies)) g2.proxies = [];
|
||||||
|
g2.proxies = uniqPrepend(g2.proxies, [LB_NAME]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config["proxy-groups"] = groups;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第二步:应用设置
|
||||||
|
|
||||||
|
1. 切换到左侧的 `代理`(`Proxies`)界面,也就是首页。
|
||||||
|
2. 在顶部的分类中,通常会看到 `节点选择`、`Proxy` 或 `当前节点` 之类的分组。
|
||||||
|
3. 在对应下拉框中选择 `🔁 非港轮询`。
|
||||||
|
4. 确认右上角的 `代理模式`(`Mode`)已经设置为 `规则模式`(`Rule`)。
|
||||||
|
5. 确认左侧 `设置` 中的 `系统代理`(`System Proxy`)已经开启。
|
||||||
|
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 不论使用哪种方式更新扩展,更新完成后都必须在浏览器的 `扩展程序管理` 页面重新加载一次该扩展。
|
||||||
|
- 如果你长期更新扩展,优先使用 `git pull`,这是最方便、最推荐的方式。
|
||||||
|
- 使用手动覆盖更新时,请直接覆盖当前扩展文件夹,避免同时保留多份不同版本的目录。
|
||||||
|
- 在 `Clash Verge` 中粘贴脚本时,请先清空旧内容,再完整粘贴新代码并保存。
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "多页面自动化",
|
"name": "codex-oauth-automation-extension",
|
||||||
"version": "5.8",
|
"version": "5.8",
|
||||||
"version_name": "Pro5.8",
|
"version_name": "Pro5.8",
|
||||||
"description": "用于自动执行多步骤 OAuth 注册流程",
|
"description": "用于自动执行多步骤 OAuth 注册流程",
|
||||||
|
|||||||
@@ -2393,6 +2393,19 @@ header {
|
|||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-message a,
|
||||||
|
.modal-alert a {
|
||||||
|
color: var(--blue);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-message a:hover,
|
||||||
|
.modal-alert a:hover {
|
||||||
|
color: var(--cyan);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-alert {
|
.modal-alert {
|
||||||
|
|||||||
+51
-26
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>多页面自动化面板</title>
|
<title>codex-oauth-automation-extension</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link
|
<link
|
||||||
@@ -235,31 +235,6 @@
|
|||||||
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
|
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-temp-email-base-url" style="display:none;">
|
|
||||||
<span class="data-label">Temp API</span>
|
|
||||||
<input type="text" id="input-temp-email-base-url" class="data-input" placeholder="https://your-worker-domain" />
|
|
||||||
</div>
|
|
||||||
<div class="data-row" id="row-temp-email-admin-auth" style="display:none;">
|
|
||||||
<span class="data-label">Admin Auth</span>
|
|
||||||
<input type="password" id="input-temp-email-admin-auth" class="data-input data-input-with-icon" placeholder="Cloudflare Temp Email admin password" />
|
|
||||||
</div>
|
|
||||||
<div class="data-row" id="row-temp-email-custom-auth" style="display:none;">
|
|
||||||
<span class="data-label">Custom Auth</span>
|
|
||||||
<input type="password" id="input-temp-email-custom-auth" class="data-input data-input-with-icon" placeholder="仅当站点启用了访问密码时再填写;这是额外鉴权,不替代 Admin Auth。" />
|
|
||||||
</div>
|
|
||||||
<div class="data-row" id="row-temp-email-receive-mailbox" style="display:none;">
|
|
||||||
<span class="data-label">邮件接收</span>
|
|
||||||
<input type="text" id="input-temp-email-receive-mailbox" class="data-input" placeholder="用于接收转发邮件的邮箱,例如 1@email.example.com" />
|
|
||||||
</div>
|
|
||||||
<div class="data-row" id="row-temp-email-domain" style="display:none;">
|
|
||||||
<span class="data-label">Temp 域名</span>
|
|
||||||
<div class="data-inline">
|
|
||||||
<select id="select-temp-email-domain" class="data-select"></select>
|
|
||||||
<input type="text" id="input-temp-email-domain" class="data-input" placeholder="例如 mail.example.com"
|
|
||||||
style="display:none;" />
|
|
||||||
<button id="btn-temp-email-domain-mode" class="btn btn-outline btn-sm" type="button">添加</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-row" id="row-cf-domain" style="display:none;">
|
<div class="data-row" id="row-cf-domain" style="display:none;">
|
||||||
<span class="data-label">CF 域名</span>
|
<span class="data-label">CF 域名</span>
|
||||||
<div class="data-inline">
|
<div class="data-inline">
|
||||||
@@ -389,6 +364,56 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="cloudflare-temp-email-section" class="data-card hotmail-card" style="display:none;">
|
||||||
|
<div class="section-mini-header">
|
||||||
|
<div class="section-mini-copy">
|
||||||
|
<span class="section-label">Cloudflare Temp Email</span>
|
||||||
|
<span class="data-value">用于生成邮箱或接收转发邮件</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-mini-actions">
|
||||||
|
<button id="btn-cloudflare-temp-email-usage-guide" class="btn btn-ghost btn-xs" type="button">使用教程</button>
|
||||||
|
<button id="btn-cloudflare-temp-email-github" class="btn btn-ghost btn-xs" type="button">GitHub</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-base-url" style="display:none;">
|
||||||
|
<span class="data-label">Temp API</span>
|
||||||
|
<input type="text" id="input-temp-email-base-url" class="data-input" placeholder="https://your-worker-domain" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-admin-auth" style="display:none;">
|
||||||
|
<span class="data-label">Admin Auth</span>
|
||||||
|
<input type="password" id="input-temp-email-admin-auth" class="data-input data-input-with-icon" placeholder="Cloudflare Temp Email admin password" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-custom-auth" style="display:none;">
|
||||||
|
<span class="data-label">Custom Auth</span>
|
||||||
|
<input type="password" id="input-temp-email-custom-auth" class="data-input data-input-with-icon" placeholder="仅当站点启用了访问密码时再填写;这是额外鉴权,不替代 Admin Auth。" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-receive-mailbox" style="display:none;">
|
||||||
|
<span class="data-label">邮件接收</span>
|
||||||
|
<input type="text" id="input-temp-email-receive-mailbox" class="data-input" placeholder="用于接收转发邮件的邮箱,例如 1@email.example.com" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-random-subdomain-toggle" style="display:none;">
|
||||||
|
<span class="data-label">随机子域</span>
|
||||||
|
<div class="data-inline">
|
||||||
|
<label class="toggle-switch" for="input-temp-email-use-random-subdomain" title="依赖后端 RANDOM_SUBDOMAIN_DOMAINS 配置">
|
||||||
|
<input type="checkbox" id="input-temp-email-use-random-subdomain" />
|
||||||
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
|
<span class="toggle-switch-thumb"></span>
|
||||||
|
</span>
|
||||||
|
<span>启用</span>
|
||||||
|
</label>
|
||||||
|
<span class="setting-caption">依赖后端 RANDOM_SUBDOMAIN_DOMAINS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-temp-email-domain" style="display:none;">
|
||||||
|
<span class="data-label">Temp 域名</span>
|
||||||
|
<div class="data-inline">
|
||||||
|
<select id="select-temp-email-domain" class="data-select"></select>
|
||||||
|
<input type="text" id="input-temp-email-domain" class="data-input" placeholder="例如 mail.example.com"
|
||||||
|
style="display:none;" />
|
||||||
|
<button id="btn-temp-email-domain-mode" class="btn btn-outline btn-sm" type="button">添加</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="hotmail-section" class="data-card hotmail-card" style="display:none;">
|
<div id="hotmail-section" class="data-card hotmail-card" style="display:none;">
|
||||||
<div class="section-mini-header">
|
<div class="section-mini-header">
|
||||||
<div class="section-mini-copy">
|
<div class="section-mini-copy">
|
||||||
|
|||||||
+118
-8
@@ -110,10 +110,15 @@ const rowTempEmailCustomAuth = document.getElementById('row-temp-email-custom-au
|
|||||||
const inputTempEmailCustomAuth = document.getElementById('input-temp-email-custom-auth');
|
const inputTempEmailCustomAuth = document.getElementById('input-temp-email-custom-auth');
|
||||||
const rowTempEmailReceiveMailbox = document.getElementById('row-temp-email-receive-mailbox');
|
const rowTempEmailReceiveMailbox = document.getElementById('row-temp-email-receive-mailbox');
|
||||||
const inputTempEmailReceiveMailbox = document.getElementById('input-temp-email-receive-mailbox');
|
const inputTempEmailReceiveMailbox = document.getElementById('input-temp-email-receive-mailbox');
|
||||||
|
const rowTempEmailRandomSubdomainToggle = document.getElementById('row-temp-email-random-subdomain-toggle');
|
||||||
|
const inputTempEmailUseRandomSubdomain = document.getElementById('input-temp-email-use-random-subdomain');
|
||||||
const rowTempEmailDomain = document.getElementById('row-temp-email-domain');
|
const rowTempEmailDomain = document.getElementById('row-temp-email-domain');
|
||||||
const selectTempEmailDomain = document.getElementById('select-temp-email-domain');
|
const selectTempEmailDomain = document.getElementById('select-temp-email-domain');
|
||||||
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
||||||
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
||||||
|
const cloudflareTempEmailSection = document.getElementById('cloudflare-temp-email-section');
|
||||||
|
const btnCloudflareTempEmailUsageGuide = document.getElementById('btn-cloudflare-temp-email-usage-guide');
|
||||||
|
const btnCloudflareTempEmailGithub = document.getElementById('btn-cloudflare-temp-email-github');
|
||||||
const hotmailSection = document.getElementById('hotmail-section');
|
const hotmailSection = document.getElementById('hotmail-section');
|
||||||
const mail2925Section = document.getElementById('mail2925-section');
|
const mail2925Section = document.getElementById('mail2925-section');
|
||||||
const luckmailSection = document.getElementById('luckmail-section');
|
const luckmailSection = document.getElementById('luckmail-section');
|
||||||
@@ -606,6 +611,10 @@ const LOG_LEVEL_LABELS = {
|
|||||||
error: '错误',
|
error: '错误',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email';
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_BUILD_TUTORIAL_URL = 'https://linux.do/t/topic/316819';
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_RANDOM_SUBDOMAIN_ISSUE_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email/issues/942';
|
||||||
|
|
||||||
function usesGeneratedAliasMailProvider(provider, mail2925Mode = getSelectedMail2925Mode()) {
|
function usesGeneratedAliasMailProvider(provider, mail2925Mode = getSelectedMail2925Mode()) {
|
||||||
return isManagedAliasProvider(provider, mail2925Mode);
|
return isManagedAliasProvider(provider, mail2925Mode);
|
||||||
}
|
}
|
||||||
@@ -670,6 +679,19 @@ function resetActionModalAlert() {
|
|||||||
autoStartAlert.className = 'modal-alert';
|
autoStartAlert.className = 'modal-alert';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setActionModalMessageContent({ text = '', html = '' } = {}) {
|
||||||
|
if (!autoStartMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html) {
|
||||||
|
autoStartMessage.innerHTML = html;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoStartMessage.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
function resetActionModalButtons() {
|
function resetActionModalButtons() {
|
||||||
const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
|
const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
@@ -745,7 +767,7 @@ function resolveModalChoice(choice) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openActionModal({ title, message, actions, option, alert, buildResult }) {
|
function openActionModal({ title, message, messageHtml, actions, option, alert, buildResult }) {
|
||||||
if (!autoStartModal) {
|
if (!autoStartModal) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
@@ -756,7 +778,7 @@ function openActionModal({ title, message, actions, option, alert, buildResult }
|
|||||||
|
|
||||||
resetActionModalButtons();
|
resetActionModalButtons();
|
||||||
autoStartTitle.textContent = title;
|
autoStartTitle.textContent = title;
|
||||||
autoStartMessage.textContent = message;
|
setActionModalMessageContent({ text: message, html: messageHtml });
|
||||||
currentModalActions = actions || [];
|
currentModalActions = actions || [];
|
||||||
modalResultBuilder = typeof buildResult === 'function' ? buildResult : null;
|
modalResultBuilder = typeof buildResult === 'function' ? buildResult : null;
|
||||||
const buttonSlots = currentModalActions.length <= 2
|
const buttonSlots = currentModalActions.length <= 2
|
||||||
@@ -1456,6 +1478,18 @@ function setCloudflareTempEmailDomainEditMode(editing, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyCloudflareTempEmailSettingsState(state = {}) {
|
||||||
|
inputTempEmailBaseUrl.value = state?.cloudflareTempEmailBaseUrl || '';
|
||||||
|
inputTempEmailAdminAuth.value = state?.cloudflareTempEmailAdminAuth || '';
|
||||||
|
inputTempEmailCustomAuth.value = state?.cloudflareTempEmailCustomAuth || '';
|
||||||
|
inputTempEmailReceiveMailbox.value = state?.cloudflareTempEmailReceiveMailbox || '';
|
||||||
|
if (inputTempEmailUseRandomSubdomain) {
|
||||||
|
inputTempEmailUseRandomSubdomain.checked = Boolean(state?.cloudflareTempEmailUseRandomSubdomain);
|
||||||
|
}
|
||||||
|
renderCloudflareTempEmailDomainOptions(state?.cloudflareTempEmailDomain || '');
|
||||||
|
setCloudflareTempEmailDomainEditMode(false, { clearInput: true });
|
||||||
|
}
|
||||||
|
|
||||||
function collectSettingsPayload() {
|
function collectSettingsPayload() {
|
||||||
const { domains, activeDomain } = getCloudflareDomainsFromState();
|
const { domains, activeDomain } = getCloudflareDomainsFromState();
|
||||||
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
|
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
|
||||||
@@ -1508,6 +1542,7 @@ function collectSettingsPayload() {
|
|||||||
cloudflareTempEmailAdminAuth: inputTempEmailAdminAuth.value,
|
cloudflareTempEmailAdminAuth: inputTempEmailAdminAuth.value,
|
||||||
cloudflareTempEmailCustomAuth: inputTempEmailCustomAuth.value,
|
cloudflareTempEmailCustomAuth: inputTempEmailCustomAuth.value,
|
||||||
cloudflareTempEmailReceiveMailbox: normalizeCloudflareTempEmailReceiveMailboxValue(inputTempEmailReceiveMailbox.value),
|
cloudflareTempEmailReceiveMailbox: normalizeCloudflareTempEmailReceiveMailboxValue(inputTempEmailReceiveMailbox.value),
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: Boolean(inputTempEmailUseRandomSubdomain?.checked),
|
||||||
cloudflareTempEmailDomain: selectedCloudflareTempEmailDomain,
|
cloudflareTempEmailDomain: selectedCloudflareTempEmailDomain,
|
||||||
cloudflareTempEmailDomains: tempEmailDomains,
|
cloudflareTempEmailDomains: tempEmailDomains,
|
||||||
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
||||||
@@ -1912,14 +1947,9 @@ function applySettingsState(state) {
|
|||||||
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(state?.luckmailBaseUrl);
|
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(state?.luckmailBaseUrl);
|
||||||
selectLuckmailEmailType.value = normalizeLuckmailEmailType(state?.luckmailEmailType);
|
selectLuckmailEmailType.value = normalizeLuckmailEmailType(state?.luckmailEmailType);
|
||||||
inputLuckmailDomain.value = state?.luckmailDomain || '';
|
inputLuckmailDomain.value = state?.luckmailDomain || '';
|
||||||
inputTempEmailBaseUrl.value = state?.cloudflareTempEmailBaseUrl || '';
|
applyCloudflareTempEmailSettingsState(state);
|
||||||
inputTempEmailAdminAuth.value = state?.cloudflareTempEmailAdminAuth || '';
|
|
||||||
inputTempEmailCustomAuth.value = state?.cloudflareTempEmailCustomAuth || '';
|
|
||||||
inputTempEmailReceiveMailbox.value = state?.cloudflareTempEmailReceiveMailbox || '';
|
|
||||||
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
||||||
setCloudflareDomainEditMode(false, { clearInput: true });
|
setCloudflareDomainEditMode(false, { clearInput: true });
|
||||||
renderCloudflareTempEmailDomainOptions(state?.cloudflareTempEmailDomain || '');
|
|
||||||
setCloudflareTempEmailDomainEditMode(false, { clearInput: true });
|
|
||||||
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
||||||
inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
|
inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
|
||||||
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
||||||
@@ -2038,6 +2068,51 @@ function openReleaseListPage() {
|
|||||||
openExternalUrl(getReleaseListUrl());
|
openExternalUrl(getReleaseListUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCloudflareTempEmailUsageGuideModalConfig() {
|
||||||
|
const useCloudflareTempEmailProvider = String(selectMailProvider?.value || '').trim().toLowerCase() === 'cloudflare-temp-email';
|
||||||
|
const useCloudflareTempEmailGenerator = getSelectedEmailGenerator() === 'cloudflare-temp-email';
|
||||||
|
let alertText = '当前还没有把 Cloudflare Temp Email 选为邮箱服务或邮箱生成器。下面说明同时覆盖“生成邮箱”和“接收转发邮件”两种用法。';
|
||||||
|
if (useCloudflareTempEmailProvider && useCloudflareTempEmailGenerator) {
|
||||||
|
alertText = '当前同时把 Cloudflare Temp Email 用作“邮箱服务”和“邮箱生成器”。请同时配置生成邮箱和接收转发两套必填项。';
|
||||||
|
} else if (useCloudflareTempEmailProvider) {
|
||||||
|
alertText = '当前把 Cloudflare Temp Email 用作“邮箱服务”。重点填写 Temp API、Custom Auth 和 邮件接收。';
|
||||||
|
} else if (useCloudflareTempEmailGenerator) {
|
||||||
|
alertText = '当前把 Cloudflare Temp Email 用作“邮箱生成器”。重点填写 Temp API、Admin Auth 和 Temp 域名;随机子域按需开启。';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Cloudflare Temp Email 使用教程',
|
||||||
|
alert: { text: alertText },
|
||||||
|
messageHtml: `Temp API:填写 Cloudflare Temp Email 后端地址。<br><br>
|
||||||
|
Admin Auth:填写后端 admin auth。<br>
|
||||||
|
仅在“邮箱生成 = Cloudflare Temp Email”时必填。<br><br>
|
||||||
|
Custom Auth:仅当站点额外开启访问密码时填写。没有开启就留空。<br><br>
|
||||||
|
Temp 域名:填写允许创建的基础域名。启用随机子域时,这里仍然填写基础域名。<br><br>
|
||||||
|
随机子域:仅在“邮箱生成 = Cloudflare Temp Email”时使用。<br>
|
||||||
|
后端需要已配置 RANDOM_SUBDOMAIN_DOMAINS。<br>
|
||||||
|
CF DNS 需要设置 MX *,参考 <a href="${CLOUDFLARE_TEMP_EMAIL_RANDOM_SUBDOMAIN_ISSUE_URL}" target="_blank" rel="noopener noreferrer">Issue #942</a>。<br><br>
|
||||||
|
邮件接收:仅在“邮箱服务 = Cloudflare Temp Email”时填写。<br>
|
||||||
|
这里填写真正接收转发邮件的目标邮箱。<br><br>
|
||||||
|
搭建教程:<a href="${CLOUDFLARE_TEMP_EMAIL_BUILD_TUTORIAL_URL}" target="_blank" rel="noopener noreferrer">LINUX DO 教程</a>。`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCloudflareTempEmailUsageGuide() {
|
||||||
|
const modalConfig = buildCloudflareTempEmailUsageGuideModalConfig();
|
||||||
|
return openActionModal({
|
||||||
|
title: modalConfig.title,
|
||||||
|
messageHtml: modalConfig.messageHtml,
|
||||||
|
alert: modalConfig.alert,
|
||||||
|
actions: [
|
||||||
|
{ id: 'ok', label: '知道了', variant: 'btn-primary' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCloudflareTempEmailRepositoryPage() {
|
||||||
|
openExternalUrl(CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL);
|
||||||
|
}
|
||||||
|
|
||||||
function createUpdateNoteList(notes = []) {
|
function createUpdateNoteList(notes = []) {
|
||||||
if (!Array.isArray(notes) || notes.length === 0) {
|
if (!Array.isArray(notes) || notes.length === 0) {
|
||||||
const empty = document.createElement('p');
|
const empty = document.createElement('p');
|
||||||
@@ -2672,10 +2747,14 @@ function updateMailProviderUI() {
|
|||||||
const showCloudflareDomain = useEmailGenerator && useCloudflare;
|
const showCloudflareDomain = useEmailGenerator && useCloudflare;
|
||||||
const showCloudflareTempEmailSettings = useCloudflareTempEmailProvider || (useEmailGenerator && useCloudflareTempEmailGenerator);
|
const showCloudflareTempEmailSettings = useCloudflareTempEmailProvider || (useEmailGenerator && useCloudflareTempEmailGenerator);
|
||||||
const showCloudflareTempEmailReceiveMailbox = useCloudflareTempEmailProvider && !useCloudflareTempEmailGenerator;
|
const showCloudflareTempEmailReceiveMailbox = useCloudflareTempEmailProvider && !useCloudflareTempEmailGenerator;
|
||||||
|
const showCloudflareTempEmailRandomSubdomainToggle = useEmailGenerator && useCloudflareTempEmailGenerator;
|
||||||
const showCloudflareTempEmailDomain = useEmailGenerator && useCloudflareTempEmailGenerator;
|
const showCloudflareTempEmailDomain = useEmailGenerator && useCloudflareTempEmailGenerator;
|
||||||
if (rowEmailGenerator) {
|
if (rowEmailGenerator) {
|
||||||
rowEmailGenerator.style.display = useEmailGenerator ? '' : 'none';
|
rowEmailGenerator.style.display = useEmailGenerator ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
if (cloudflareTempEmailSection) {
|
||||||
|
cloudflareTempEmailSection.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||||||
|
}
|
||||||
if (icloudSection) {
|
if (icloudSection) {
|
||||||
const showIcloudSection = (useEmailGenerator && useIcloud) || useIcloudProvider;
|
const showIcloudSection = (useEmailGenerator && useIcloud) || useIcloudProvider;
|
||||||
icloudSection.style.display = showIcloudSection ? '' : 'none';
|
icloudSection.style.display = showIcloudSection ? '' : 'none';
|
||||||
@@ -2697,6 +2776,9 @@ function updateMailProviderUI() {
|
|||||||
rowTempEmailAdminAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
rowTempEmailAdminAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||||||
rowTempEmailCustomAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
rowTempEmailCustomAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||||||
rowTempEmailReceiveMailbox.style.display = showCloudflareTempEmailReceiveMailbox ? '' : 'none';
|
rowTempEmailReceiveMailbox.style.display = showCloudflareTempEmailReceiveMailbox ? '' : 'none';
|
||||||
|
if (rowTempEmailRandomSubdomainToggle) {
|
||||||
|
rowTempEmailRandomSubdomainToggle.style.display = showCloudflareTempEmailRandomSubdomainToggle ? '' : 'none';
|
||||||
|
}
|
||||||
rowTempEmailDomain.style.display = showCloudflareTempEmailDomain ? '' : 'none';
|
rowTempEmailDomain.style.display = showCloudflareTempEmailDomain ? '' : 'none';
|
||||||
const { domains: tempEmailDomains } = getCloudflareTempEmailDomainsFromState();
|
const { domains: tempEmailDomains } = getCloudflareTempEmailDomainsFromState();
|
||||||
if (showCloudflareTempEmailDomain) {
|
if (showCloudflareTempEmailDomain) {
|
||||||
@@ -2784,6 +2866,9 @@ function updateMailProviderUI() {
|
|||||||
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
|
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
|
||||||
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
|
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
|
||||||
}
|
}
|
||||||
|
if (autoHintText && showCloudflareTempEmailRandomSubdomainToggle && inputTempEmailUseRandomSubdomain?.checked) {
|
||||||
|
autoHintText.textContent = '已启用随机子域名:扩展会按当前选中的 Temp 域名提交,并额外携带 enableRandomSubdomain;是否生效取决于后端 RANDOM_SUBDOMAIN_DOMAINS 配置。';
|
||||||
|
}
|
||||||
if (useHotmail) {
|
if (useHotmail) {
|
||||||
inputEmail.value = getCurrentHotmailEmail();
|
inputEmail.value = getCurrentHotmailEmail();
|
||||||
} else if (useLuckmail) {
|
} else if (useLuckmail) {
|
||||||
@@ -3776,6 +3861,14 @@ btnRepoHome?.addEventListener('click', () => {
|
|||||||
openRepositoryHomePage();
|
openRepositoryHomePage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnCloudflareTempEmailUsageGuide?.addEventListener('click', () => {
|
||||||
|
showCloudflareTempEmailUsageGuide();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCloudflareTempEmailGithub?.addEventListener('click', () => {
|
||||||
|
openCloudflareTempEmailRepositoryPage();
|
||||||
|
});
|
||||||
|
|
||||||
extensionUpdateStatus?.addEventListener('click', () => {
|
extensionUpdateStatus?.addEventListener('click', () => {
|
||||||
openReleaseListPage();
|
openReleaseListPage();
|
||||||
});
|
});
|
||||||
@@ -4387,6 +4480,13 @@ inputTempEmailReceiveMailbox.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inputTempEmailUseRandomSubdomain?.addEventListener('change', () => {
|
||||||
|
updateMailProviderUI();
|
||||||
|
clearRegistrationEmail({ silent: true }).catch(() => { });
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('input', () => {
|
inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
scheduleSettingsAutoSave();
|
scheduleSettingsAutoSave();
|
||||||
@@ -4609,9 +4709,19 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.cloudflareTempEmailReceiveMailbox !== undefined) {
|
if (message.payload.cloudflareTempEmailReceiveMailbox !== undefined) {
|
||||||
inputTempEmailReceiveMailbox.value = message.payload.cloudflareTempEmailReceiveMailbox || '';
|
inputTempEmailReceiveMailbox.value = message.payload.cloudflareTempEmailReceiveMailbox || '';
|
||||||
}
|
}
|
||||||
|
if (message.payload.cloudflareTempEmailUseRandomSubdomain !== undefined && inputTempEmailUseRandomSubdomain) {
|
||||||
|
inputTempEmailUseRandomSubdomain.checked = Boolean(message.payload.cloudflareTempEmailUseRandomSubdomain);
|
||||||
|
}
|
||||||
if (message.payload.cloudflareTempEmailDomain !== undefined || message.payload.cloudflareTempEmailDomains !== undefined) {
|
if (message.payload.cloudflareTempEmailDomain !== undefined || message.payload.cloudflareTempEmailDomains !== undefined) {
|
||||||
renderCloudflareTempEmailDomainOptions(message.payload.cloudflareTempEmailDomain || latestState?.cloudflareTempEmailDomain || '');
|
renderCloudflareTempEmailDomainOptions(message.payload.cloudflareTempEmailDomain || latestState?.cloudflareTempEmailDomain || '');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
message.payload.cloudflareTempEmailUseRandomSubdomain !== undefined
|
||||||
|
|| message.payload.cloudflareTempEmailDomain !== undefined
|
||||||
|
|| message.payload.cloudflareTempEmailDomains !== undefined
|
||||||
|
) {
|
||||||
|
updateMailProviderUI();
|
||||||
|
}
|
||||||
if (message.payload.currentHotmailAccountId !== undefined || message.payload.hotmailAccounts !== undefined) {
|
if (message.payload.currentHotmailAccountId !== undefined || message.payload.hotmailAccounts !== undefined) {
|
||||||
renderHotmailAccounts();
|
renderHotmailAccounts();
|
||||||
if (selectMailProvider.value === 'hotmail-api') {
|
if (selectMailProvider.value === 'hotmail-api') {
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||||
|
const start = markers
|
||||||
|
.map((marker) => source.indexOf(marker))
|
||||||
|
.find((index) => index >= 0);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parenDepth = 0;
|
||||||
|
let signatureEnded = false;
|
||||||
|
let braceStart = -1;
|
||||||
|
for (let i = start; i < source.length; i += 1) {
|
||||||
|
const ch = source[i];
|
||||||
|
if (ch === '(') {
|
||||||
|
parenDepth += 1;
|
||||||
|
} else if (ch === ')') {
|
||||||
|
parenDepth -= 1;
|
||||||
|
if (parenDepth === 0) {
|
||||||
|
signatureEnded = true;
|
||||||
|
}
|
||||||
|
} else if (ch === '{' && signatureEnded) {
|
||||||
|
braceStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < source.length; end += 1) {
|
||||||
|
const ch = source[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('cloudflare temp email settings normalize and expose the random-subdomain toggle', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizeCloudflareTempEmailReceiveMailbox'),
|
||||||
|
extractFunction('getCloudflareTempEmailConfig'),
|
||||||
|
extractFunction('normalizePersistentSettingValue'),
|
||||||
|
extractFunction('buildPersistentSettingsPayload'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||||
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
|
panelMode: 'cpa',
|
||||||
|
autoStepDelaySeconds: null,
|
||||||
|
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||||
|
mailProvider: '163',
|
||||||
|
mail2925Mode: 'provide',
|
||||||
|
emailGenerator: 'duck',
|
||||||
|
autoDeleteUsedIcloudAlias: false,
|
||||||
|
accountRunHistoryTextEnabled: false,
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: false,
|
||||||
|
cloudflareTempEmailDomain: '',
|
||||||
|
cloudflareTempEmailDomains: [],
|
||||||
|
};
|
||||||
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
|
function normalizePanelMode(value) { return value === 'sub2api' ? 'sub2api' : 'cpa'; }
|
||||||
|
function normalizeLocalCpaStep9Mode(value) { return value === 'bypass' ? 'bypass' : 'submit'; }
|
||||||
|
function normalizeAutoRunFallbackThreadIntervalMinutes(value) { return Number(value) || 0; }
|
||||||
|
function normalizeAutoRunDelayMinutes(value) { return Number(value) || 30; }
|
||||||
|
function normalizeAutoStepDelaySeconds(value, fallback = null) { return value == null || value === '' ? fallback : Number(value); }
|
||||||
|
function normalizeVerificationResendCount(value, fallback) { return Number.isFinite(Number(value)) ? Number(value) : fallback; }
|
||||||
|
function normalizeMailProvider(value) { return String(value || '').trim().toLowerCase() || '163'; }
|
||||||
|
function normalizeMail2925Mode(value) { return String(value || '').trim().toLowerCase() === 'receive' ? 'receive' : 'provide'; }
|
||||||
|
function normalizeEmailGenerator(value) { return String(value || '').trim().toLowerCase() || 'duck'; }
|
||||||
|
function normalizeIcloudHost(value) { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : ''; }
|
||||||
|
function normalizeAccountRunHistoryHelperBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeHotmailServiceMode(value) { return String(value || '').trim().toLowerCase() === 'remote' ? 'remote' : 'local'; }
|
||||||
|
function normalizeHotmailRemoteBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeHotmailLocalBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeCloudflareDomain(value) { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareDomains(value) { return Array.isArray(value) ? value.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean) : []; }
|
||||||
|
function normalizeCloudflareTempEmailBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeCloudflareTempEmailAddress(value = '') { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareTempEmailDomain(value) { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareTempEmailDomains(value) {
|
||||||
|
const seen = new Set();
|
||||||
|
const domains = [];
|
||||||
|
for (const item of Array.isArray(value) ? value : []) {
|
||||||
|
const normalized = normalizeCloudflareTempEmailDomain(item);
|
||||||
|
if (!normalized || seen.has(normalized)) continue;
|
||||||
|
seen.add(normalized);
|
||||||
|
domains.push(normalized);
|
||||||
|
}
|
||||||
|
return domains;
|
||||||
|
}
|
||||||
|
function normalizeHotmailAccounts(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeMail2925Accounts(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function resolveLegacyAutoStepDelaySeconds() { return undefined; }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
buildPersistentSettingsPayload,
|
||||||
|
getCloudflareTempEmailConfig,
|
||||||
|
normalizePersistentSettingValue,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('cloudflareTempEmailUseRandomSubdomain', 1), true);
|
||||||
|
|
||||||
|
const payload = api.buildPersistentSettingsPayload({
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: true,
|
||||||
|
cloudflareTempEmailDomain: 'mail.example.com',
|
||||||
|
cloudflareTempEmailDomains: ['mail.example.com', 'alt.example.com'],
|
||||||
|
});
|
||||||
|
assert.equal(payload.cloudflareTempEmailUseRandomSubdomain, true);
|
||||||
|
assert.equal(payload.cloudflareTempEmailDomain, 'mail.example.com');
|
||||||
|
assert.deepEqual(payload.cloudflareTempEmailDomains, ['mail.example.com', 'alt.example.com']);
|
||||||
|
|
||||||
|
const config = api.getCloudflareTempEmailConfig({
|
||||||
|
cloudflareTempEmailBaseUrl: 'https://temp.example.com',
|
||||||
|
cloudflareTempEmailAdminAuth: 'admin-secret',
|
||||||
|
cloudflareTempEmailCustomAuth: 'custom-secret',
|
||||||
|
cloudflareTempEmailReceiveMailbox: 'Forward@Example.com',
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: true,
|
||||||
|
cloudflareTempEmailDomain: 'mail.example.com',
|
||||||
|
cloudflareTempEmailDomains: ['mail.example.com'],
|
||||||
|
});
|
||||||
|
assert.deepEqual(config, {
|
||||||
|
baseUrl: 'https://temp.example.com',
|
||||||
|
adminAuth: 'admin-secret',
|
||||||
|
customAuth: 'custom-secret',
|
||||||
|
receiveMailbox: 'forward@example.com',
|
||||||
|
useRandomSubdomain: true,
|
||||||
|
domain: 'mail.example.com',
|
||||||
|
domains: ['mail.example.com'],
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,24 +2,25 @@ const test = require('node:test');
|
|||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
function loadGeneratedEmailHelpersApi() {
|
||||||
|
const source = fs.readFileSync('background/generated-email-helpers.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
return new Function('self', `${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope);
|
||||||
|
}
|
||||||
|
|
||||||
test('background imports generated email helper module', () => {
|
test('background imports generated email helper module', () => {
|
||||||
const source = fs.readFileSync('background.js', 'utf8');
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
assert.match(source, /importScripts\([\s\S]*'background\/generated-email-helpers\.js'/);
|
assert.match(source, /importScripts\([\s\S]*'background\/generated-email-helpers\.js'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generated email helper module exposes a factory', () => {
|
test('generated email helper module exposes a factory', () => {
|
||||||
const source = fs.readFileSync('background/generated-email-helpers.js', 'utf8');
|
const api = loadGeneratedEmailHelpersApi();
|
||||||
const globalScope = {};
|
|
||||||
|
|
||||||
const api = new Function('self', `${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope);
|
|
||||||
|
|
||||||
assert.equal(typeof api?.createGeneratedEmailHelpers, 'function');
|
assert.equal(typeof api?.createGeneratedEmailHelpers, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generated email helper falls back to normal generator when 2925 is in receive mode', async () => {
|
test('generated email helper falls back to normal generator when 2925 is in receive mode', async () => {
|
||||||
const source = fs.readFileSync('background/generated-email-helpers.js', 'utf8');
|
const api = loadGeneratedEmailHelpersApi();
|
||||||
const globalScope = {};
|
|
||||||
const api = new Function('self', `${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope);
|
|
||||||
const events = [];
|
const events = [];
|
||||||
|
|
||||||
const helpers = api.createGeneratedEmailHelpers({
|
const helpers = api.createGeneratedEmailHelpers({
|
||||||
@@ -76,3 +77,157 @@ test('generated email helper falls back to normal generator when 2925 is in rece
|
|||||||
['email', 'duck@example.com'],
|
['email', 'duck@example.com'],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('generated email helper uses the regular temp email domain when random subdomain mode is disabled', async () => {
|
||||||
|
const api = loadGeneratedEmailHelpersApi();
|
||||||
|
const requests = [];
|
||||||
|
const savedEmails = [];
|
||||||
|
|
||||||
|
const helpers = api.createGeneratedEmailHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
buildGeneratedAliasEmail: () => {
|
||||||
|
throw new Error('should not build managed alias');
|
||||||
|
},
|
||||||
|
buildCloudflareTempEmailHeaders: () => ({ 'x-admin-auth': 'admin-secret' }),
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email',
|
||||||
|
DUCK_AUTOFILL_URL: 'https://duckduckgo.com/email',
|
||||||
|
fetch: async (url, options = {}) => {
|
||||||
|
requests.push({
|
||||||
|
url,
|
||||||
|
method: options.method,
|
||||||
|
body: options.body ? JSON.parse(options.body) : null,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => JSON.stringify({ address: 'user@mail.example.com' }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
fetchIcloudHideMyEmail: async () => {
|
||||||
|
throw new Error('should not use icloud generator');
|
||||||
|
},
|
||||||
|
getCloudflareTempEmailAddressFromResponse: (payload) => payload.address,
|
||||||
|
getCloudflareTempEmailConfig: () => ({
|
||||||
|
baseUrl: 'https://temp.example.com',
|
||||||
|
adminAuth: 'admin-secret',
|
||||||
|
customAuth: '',
|
||||||
|
useRandomSubdomain: false,
|
||||||
|
domain: 'mail.example.com',
|
||||||
|
}),
|
||||||
|
getState: async () => ({
|
||||||
|
mailProvider: '163',
|
||||||
|
emailGenerator: 'cloudflare-temp-email',
|
||||||
|
}),
|
||||||
|
ensureMail2925AccountForFlow: async () => {
|
||||||
|
throw new Error('should not allocate mail2925 account');
|
||||||
|
},
|
||||||
|
joinCloudflareTempEmailUrl: (baseUrl, path) => `${baseUrl}${path}`,
|
||||||
|
normalizeCloudflareDomain: () => '',
|
||||||
|
normalizeCloudflareTempEmailAddress: (value) => String(value || '').trim().toLowerCase(),
|
||||||
|
normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(),
|
||||||
|
isGeneratedAliasProvider: () => false,
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
sendToContentScript: async () => {
|
||||||
|
throw new Error('should not use duck generator');
|
||||||
|
},
|
||||||
|
setEmailState: async (email) => {
|
||||||
|
savedEmails.push(email);
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await helpers.fetchGeneratedEmail({
|
||||||
|
emailGenerator: 'cloudflare-temp-email',
|
||||||
|
}, {
|
||||||
|
generator: 'cloudflare-temp-email',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(email, 'user@mail.example.com');
|
||||||
|
assert.deepEqual(savedEmails, ['user@mail.example.com']);
|
||||||
|
assert.equal(requests.length, 1);
|
||||||
|
assert.equal(requests[0].url, 'https://temp.example.com/admin/new_address');
|
||||||
|
assert.equal(requests[0].method, 'POST');
|
||||||
|
assert.deepEqual(requests[0].body, {
|
||||||
|
enablePrefix: true,
|
||||||
|
enableRandomSubdomain: false,
|
||||||
|
name: requests[0].body.name,
|
||||||
|
domain: 'mail.example.com',
|
||||||
|
});
|
||||||
|
assert.match(requests[0].body.name, /^[a-z0-9]+$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generated email helper requests random subdomain creation while preserving the returned address', async () => {
|
||||||
|
const api = loadGeneratedEmailHelpersApi();
|
||||||
|
const requests = [];
|
||||||
|
const savedEmails = [];
|
||||||
|
|
||||||
|
const helpers = api.createGeneratedEmailHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
buildGeneratedAliasEmail: () => {
|
||||||
|
throw new Error('should not build managed alias');
|
||||||
|
},
|
||||||
|
buildCloudflareTempEmailHeaders: () => ({ 'x-admin-auth': 'admin-secret' }),
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email',
|
||||||
|
DUCK_AUTOFILL_URL: 'https://duckduckgo.com/email',
|
||||||
|
fetch: async (url, options = {}) => {
|
||||||
|
requests.push({
|
||||||
|
url,
|
||||||
|
method: options.method,
|
||||||
|
body: options.body ? JSON.parse(options.body) : null,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => JSON.stringify({ address: 'user@a1b2c3d4.example.com' }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
fetchIcloudHideMyEmail: async () => {
|
||||||
|
throw new Error('should not use icloud generator');
|
||||||
|
},
|
||||||
|
getCloudflareTempEmailAddressFromResponse: (payload) => payload.address,
|
||||||
|
getCloudflareTempEmailConfig: () => ({
|
||||||
|
baseUrl: 'https://temp.example.com',
|
||||||
|
adminAuth: 'admin-secret',
|
||||||
|
customAuth: '',
|
||||||
|
useRandomSubdomain: true,
|
||||||
|
domain: 'mail.example.com',
|
||||||
|
}),
|
||||||
|
getState: async () => ({
|
||||||
|
mailProvider: '163',
|
||||||
|
emailGenerator: 'cloudflare-temp-email',
|
||||||
|
}),
|
||||||
|
ensureMail2925AccountForFlow: async () => {
|
||||||
|
throw new Error('should not allocate mail2925 account');
|
||||||
|
},
|
||||||
|
joinCloudflareTempEmailUrl: (baseUrl, path) => `${baseUrl}${path}`,
|
||||||
|
normalizeCloudflareDomain: () => '',
|
||||||
|
normalizeCloudflareTempEmailAddress: (value) => String(value || '').trim().toLowerCase(),
|
||||||
|
normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(),
|
||||||
|
isGeneratedAliasProvider: () => false,
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
sendToContentScript: async () => {
|
||||||
|
throw new Error('should not use duck generator');
|
||||||
|
},
|
||||||
|
setEmailState: async (email) => {
|
||||||
|
savedEmails.push(email);
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await helpers.fetchGeneratedEmail({
|
||||||
|
emailGenerator: 'cloudflare-temp-email',
|
||||||
|
}, {
|
||||||
|
generator: 'cloudflare-temp-email',
|
||||||
|
localPart: 'user',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(email, 'user@a1b2c3d4.example.com');
|
||||||
|
assert.deepEqual(savedEmails, ['user@a1b2c3d4.example.com']);
|
||||||
|
assert.equal(requests.length, 1);
|
||||||
|
assert.equal(requests[0].url, 'https://temp.example.com/admin/new_address');
|
||||||
|
assert.equal(requests[0].method, 'POST');
|
||||||
|
assert.deepEqual(requests[0].body, {
|
||||||
|
enablePrefix: true,
|
||||||
|
enableRandomSubdomain: true,
|
||||||
|
name: 'user',
|
||||||
|
domain: 'mail.example.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||||
|
const start = markers
|
||||||
|
.map((marker) => source.indexOf(marker))
|
||||||
|
.find((index) => index >= 0);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parenDepth = 0;
|
||||||
|
let signatureEnded = false;
|
||||||
|
let braceStart = -1;
|
||||||
|
for (let i = start; i < source.length; i += 1) {
|
||||||
|
const ch = source[i];
|
||||||
|
if (ch === '(') {
|
||||||
|
parenDepth += 1;
|
||||||
|
} else if (ch === ')') {
|
||||||
|
parenDepth -= 1;
|
||||||
|
if (parenDepth === 0) {
|
||||||
|
signatureEnded = true;
|
||||||
|
}
|
||||||
|
} else if (ch === '{' && signatureEnded) {
|
||||||
|
braceStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < source.length; end += 1) {
|
||||||
|
const ch = source[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRow(initialDisplay = 'none') {
|
||||||
|
return {
|
||||||
|
style: { display: initialDisplay },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sidepanel html places cloudflare temp email controls in a standalone section', () => {
|
||||||
|
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
|
assert.match(html, /id="cloudflare-temp-email-section"/);
|
||||||
|
assert.match(html, /id="btn-cloudflare-temp-email-usage-guide"/);
|
||||||
|
assert.match(html, /id="btn-cloudflare-temp-email-github"/);
|
||||||
|
assert.match(html, /id="row-temp-email-random-subdomain-toggle"/);
|
||||||
|
assert.match(html, /id="input-temp-email-use-random-subdomain"/);
|
||||||
|
assert.doesNotMatch(html, /id="row-temp-email-random-subdomain-domain"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sidepanel modal message preserves line breaks and supports inline links', () => {
|
||||||
|
const css = fs.readFileSync('sidepanel/sidepanel.css', 'utf8');
|
||||||
|
assert.match(css, /\.modal-message\s*\{[\s\S]*white-space:\s*pre-line;/);
|
||||||
|
assert.match(css, /\.modal-message a,\s*[\s\S]*\.modal-alert a/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildCloudflareTempEmailUsageGuideModalConfig returns a modal payload with inline links for generator mode', () => {
|
||||||
|
const bundle = extractFunction('buildCloudflareTempEmailUsageGuideModalConfig');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const selectMailProvider = { value: '163' };
|
||||||
|
const selectEmailGenerator = { value: 'cloudflare-temp-email' };
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_BUILD_TUTORIAL_URL = 'https://linux.do/t/topic/316819';
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_RANDOM_SUBDOMAIN_ISSUE_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email/issues/942';
|
||||||
|
function getSelectedEmailGenerator() { return String(selectEmailGenerator.value || '').trim().toLowerCase(); }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
buildCloudflareTempEmailUsageGuideModalConfig,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const modalConfig = api.buildCloudflareTempEmailUsageGuideModalConfig();
|
||||||
|
assert.equal(typeof modalConfig.title, 'string');
|
||||||
|
assert.equal(typeof modalConfig.messageHtml, 'string');
|
||||||
|
assert.equal(typeof modalConfig.alert?.text, 'string');
|
||||||
|
assert.equal(modalConfig.title.length > 0, true);
|
||||||
|
assert.equal(modalConfig.messageHtml.length > 0, true);
|
||||||
|
assert.equal(modalConfig.alert.text.length > 0, true);
|
||||||
|
assert.equal(modalConfig.messageHtml.includes('<a '), true);
|
||||||
|
assert.equal(modalConfig.messageHtml.includes('Issue #942'), true);
|
||||||
|
assert.equal(modalConfig.messageHtml.includes('LINUX DO 教程'), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildCloudflareTempEmailUsageGuideModalConfig returns a distinct alert for provider mode', () => {
|
||||||
|
const bundle = extractFunction('buildCloudflareTempEmailUsageGuideModalConfig');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const selectMailProvider = { value: 'cloudflare-temp-email' };
|
||||||
|
const selectEmailGenerator = { value: 'duck' };
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_BUILD_TUTORIAL_URL = 'https://linux.do/t/topic/316819';
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_RANDOM_SUBDOMAIN_ISSUE_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email/issues/942';
|
||||||
|
function getSelectedEmailGenerator() { return String(selectEmailGenerator.value || '').trim().toLowerCase(); }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
buildCloudflareTempEmailUsageGuideModalConfig,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const providerConfig = api.buildCloudflareTempEmailUsageGuideModalConfig();
|
||||||
|
|
||||||
|
const generatorApi = new Function(`
|
||||||
|
const selectMailProvider = { value: '163' };
|
||||||
|
const selectEmailGenerator = { value: 'cloudflare-temp-email' };
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_BUILD_TUTORIAL_URL = 'https://linux.do/t/topic/316819';
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_RANDOM_SUBDOMAIN_ISSUE_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email/issues/942';
|
||||||
|
function getSelectedEmailGenerator() { return String(selectEmailGenerator.value || '').trim().toLowerCase(); }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
buildCloudflareTempEmailUsageGuideModalConfig,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const generatorConfig = generatorApi.buildCloudflareTempEmailUsageGuideModalConfig();
|
||||||
|
assert.equal(typeof providerConfig.alert?.text, 'string');
|
||||||
|
assert.equal(typeof providerConfig.messageHtml, 'string');
|
||||||
|
assert.equal(providerConfig.alert.text.length > 0, true);
|
||||||
|
assert.equal(providerConfig.messageHtml.length > 0, true);
|
||||||
|
assert.notEqual(providerConfig.alert.text, generatorConfig.alert.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('openCloudflareTempEmailRepositoryPage opens the upstream repository', () => {
|
||||||
|
const bundle = extractFunction('openCloudflareTempEmailRepositoryPage');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const calls = [];
|
||||||
|
const CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email';
|
||||||
|
function openExternalUrl(url) { calls.push(url); }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
calls,
|
||||||
|
openCloudflareTempEmailRepositoryPage,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.openCloudflareTempEmailRepositoryPage();
|
||||||
|
assert.deepEqual(api.calls, ['https://github.com/dreamhunter2333/cloudflare_temp_email']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('applyCloudflareTempEmailSettingsState restores the random subdomain toggle and temp domain list', () => {
|
||||||
|
const bundle = extractFunction('applyCloudflareTempEmailSettingsState');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const inputTempEmailBaseUrl = { value: '' };
|
||||||
|
const inputTempEmailAdminAuth = { value: '' };
|
||||||
|
const inputTempEmailCustomAuth = { value: '' };
|
||||||
|
const inputTempEmailReceiveMailbox = { value: '' };
|
||||||
|
const inputTempEmailUseRandomSubdomain = { checked: false };
|
||||||
|
const calls = {
|
||||||
|
domainOptions: [],
|
||||||
|
domainEditMode: [],
|
||||||
|
};
|
||||||
|
function renderCloudflareTempEmailDomainOptions(value) { calls.domainOptions.push(value); }
|
||||||
|
function setCloudflareTempEmailDomainEditMode(editing, options) { calls.domainEditMode.push({ editing, options }); }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
applyCloudflareTempEmailSettingsState,
|
||||||
|
calls,
|
||||||
|
inputTempEmailBaseUrl,
|
||||||
|
inputTempEmailAdminAuth,
|
||||||
|
inputTempEmailCustomAuth,
|
||||||
|
inputTempEmailReceiveMailbox,
|
||||||
|
inputTempEmailUseRandomSubdomain,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.applyCloudflareTempEmailSettingsState({
|
||||||
|
cloudflareTempEmailBaseUrl: 'https://temp.example.com',
|
||||||
|
cloudflareTempEmailAdminAuth: 'admin-secret',
|
||||||
|
cloudflareTempEmailCustomAuth: 'custom-secret',
|
||||||
|
cloudflareTempEmailReceiveMailbox: 'relay@example.com',
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: true,
|
||||||
|
cloudflareTempEmailDomain: 'mail.example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(api.inputTempEmailBaseUrl.value, 'https://temp.example.com');
|
||||||
|
assert.equal(api.inputTempEmailAdminAuth.value, 'admin-secret');
|
||||||
|
assert.equal(api.inputTempEmailCustomAuth.value, 'custom-secret');
|
||||||
|
assert.equal(api.inputTempEmailReceiveMailbox.value, 'relay@example.com');
|
||||||
|
assert.equal(api.inputTempEmailUseRandomSubdomain.checked, true);
|
||||||
|
assert.deepEqual(api.calls.domainOptions, ['mail.example.com']);
|
||||||
|
assert.deepEqual(api.calls.domainEditMode, [{ editing: false, options: { clearInput: true } }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateMailProviderUI keeps the temp domain selector visible and updates the hint when random subdomain is enabled', () => {
|
||||||
|
const bundle = extractFunction('updateMailProviderUI');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let latestState = {
|
||||||
|
cloudflareTempEmailDomains: ['mail.example.com'],
|
||||||
|
};
|
||||||
|
let cloudflareTempEmailDomainEditMode = false;
|
||||||
|
const ICLOUD_PROVIDER = 'icloud';
|
||||||
|
const GMAIL_PROVIDER = 'gmail';
|
||||||
|
const LUCKMAIL_PROVIDER = 'luckmail-api';
|
||||||
|
const rowMail2925Mode = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowMail2925PoolSettings = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowEmailPrefix = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowInbucketHost = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowInbucketMailbox = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowEmailGenerator = ${JSON.stringify(createRow(''))};
|
||||||
|
const rowCfDomain = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailBaseUrl = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailAdminAuth = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailCustomAuth = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailReceiveMailbox = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailRandomSubdomainToggle = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowTempEmailDomain = ${JSON.stringify(createRow('none'))};
|
||||||
|
const cloudflareTempEmailSection = ${JSON.stringify(createRow('none'))};
|
||||||
|
const hotmailSection = ${JSON.stringify(createRow('none'))};
|
||||||
|
const mail2925Section = ${JSON.stringify(createRow('none'))};
|
||||||
|
const luckmailSection = ${JSON.stringify(createRow('none'))};
|
||||||
|
const icloudSection = ${JSON.stringify(createRow('none'))};
|
||||||
|
const labelEmailPrefix = { textContent: '' };
|
||||||
|
const inputEmailPrefix = { placeholder: '', style: { display: '' }, readOnly: false };
|
||||||
|
const labelMail2925UseAccountPool = ${JSON.stringify(createRow('none'))};
|
||||||
|
const selectMail2925PoolAccount = { style: { display: 'none' }, disabled: false };
|
||||||
|
const btnFetchEmail = { hidden: false, disabled: false, textContent: '' };
|
||||||
|
const btnMailLogin = { disabled: false, textContent: '', title: '' };
|
||||||
|
const inputEmail = { readOnly: false, placeholder: '', value: '' };
|
||||||
|
const autoHintText = { textContent: '' };
|
||||||
|
const rowHotmailServiceMode = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowHotmailRemoteBaseUrl = ${JSON.stringify(createRow('none'))};
|
||||||
|
const rowHotmailLocalBaseUrl = ${JSON.stringify(createRow('none'))};
|
||||||
|
const inputMail2925UseAccountPool = { checked: false };
|
||||||
|
const selectMailProvider = { value: '163' };
|
||||||
|
const selectEmailGenerator = { value: 'cloudflare-temp-email', disabled: false };
|
||||||
|
const inputTempEmailUseRandomSubdomain = { checked: false };
|
||||||
|
const calls = {
|
||||||
|
tempDomainEditMode: [],
|
||||||
|
};
|
||||||
|
function isLuckmailProvider() { return false; }
|
||||||
|
function isCustomMailProvider() { return false; }
|
||||||
|
function isIcloudMailProvider() { return false; }
|
||||||
|
function usesGeneratedAliasMailProvider() { return false; }
|
||||||
|
function getSelectedMail2925Mode() { return 'provide'; }
|
||||||
|
function getManagedAliasProviderUiCopy() { return null; }
|
||||||
|
function getCurrentRegistrationEmailUiCopy() {
|
||||||
|
return {
|
||||||
|
buttonLabel: '生成 Temp',
|
||||||
|
placeholder: '点击生成 Cloudflare Temp Email,或手动粘贴邮箱',
|
||||||
|
label: 'Cloudflare Temp Email',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function updateMailLoginButtonState() {}
|
||||||
|
function getSelectedHotmailServiceMode() { return 'local'; }
|
||||||
|
function getCloudflareDomainsFromState() { return { domains: [], activeDomain: '' }; }
|
||||||
|
function setCloudflareDomainEditMode() {}
|
||||||
|
function getCloudflareTempEmailDomainsFromState() { return { domains: ['mail.example.com'], activeDomain: 'mail.example.com' }; }
|
||||||
|
function setCloudflareTempEmailDomainEditMode(editing) { calls.tempDomainEditMode.push(editing); }
|
||||||
|
function queueIcloudAliasRefresh() {}
|
||||||
|
function hideIcloudLoginHelp() {}
|
||||||
|
function syncMail2925PoolAccountOptions() {}
|
||||||
|
function getMail2925Accounts() { return []; }
|
||||||
|
function renderHotmailAccounts() {}
|
||||||
|
function renderMail2925Accounts() {}
|
||||||
|
function renderLuckmailPurchases() {}
|
||||||
|
function getSelectedEmailGenerator() { return String(selectEmailGenerator.value || '').trim().toLowerCase(); }
|
||||||
|
function isAutoRunLockedPhase() { return false; }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
updateMailProviderUI,
|
||||||
|
cloudflareTempEmailSection,
|
||||||
|
rowTempEmailRandomSubdomainToggle,
|
||||||
|
rowTempEmailDomain,
|
||||||
|
inputTempEmailUseRandomSubdomain,
|
||||||
|
autoHintText,
|
||||||
|
calls,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.updateMailProviderUI();
|
||||||
|
assert.equal(api.cloudflareTempEmailSection.style.display, '');
|
||||||
|
assert.equal(api.rowTempEmailRandomSubdomainToggle.style.display, '');
|
||||||
|
assert.equal(api.rowTempEmailDomain.style.display, '');
|
||||||
|
|
||||||
|
api.inputTempEmailUseRandomSubdomain.checked = true;
|
||||||
|
api.updateMailProviderUI();
|
||||||
|
assert.equal(api.cloudflareTempEmailSection.style.display, '');
|
||||||
|
assert.equal(api.rowTempEmailDomain.style.display, '');
|
||||||
|
assert.match(api.autoHintText.textContent, /RANDOM_SUBDOMAIN_DOMAINS/);
|
||||||
|
});
|
||||||
@@ -154,6 +154,7 @@ const inputTempEmailBaseUrl = { value: 'https://temp.example.com' };
|
|||||||
const inputTempEmailAdminAuth = { value: 'admin-secret' };
|
const inputTempEmailAdminAuth = { value: 'admin-secret' };
|
||||||
const inputTempEmailCustomAuth = { value: 'custom-secret' };
|
const inputTempEmailCustomAuth = { value: 'custom-secret' };
|
||||||
const inputTempEmailReceiveMailbox = { value: 'relay@example.com' };
|
const inputTempEmailReceiveMailbox = { value: 'relay@example.com' };
|
||||||
|
const inputTempEmailUseRandomSubdomain = { checked: true };
|
||||||
const inputAutoSkipFailures = { checked: false };
|
const inputAutoSkipFailures = { checked: false };
|
||||||
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
|
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
|
||||||
const inputAutoDelayEnabled = { checked: true };
|
const inputAutoDelayEnabled = { checked: true };
|
||||||
@@ -190,12 +191,14 @@ return {
|
|||||||
assert.equal('customPassword' in contributionPayload, false);
|
assert.equal('customPassword' in contributionPayload, false);
|
||||||
assert.equal('accountRunHistoryTextEnabled' in contributionPayload, false);
|
assert.equal('accountRunHistoryTextEnabled' in contributionPayload, false);
|
||||||
assert.equal('accountRunHistoryHelperBaseUrl' in contributionPayload, false);
|
assert.equal('accountRunHistoryHelperBaseUrl' in contributionPayload, false);
|
||||||
|
assert.equal(contributionPayload.cloudflareTempEmailUseRandomSubdomain, true);
|
||||||
|
|
||||||
api.setLatestState({ contributionMode: false });
|
api.setLatestState({ contributionMode: false });
|
||||||
const normalPayload = api.collectSettingsPayload();
|
const normalPayload = api.collectSettingsPayload();
|
||||||
assert.equal(normalPayload.customPassword, 'Secret123!');
|
assert.equal(normalPayload.customPassword, 'Secret123!');
|
||||||
assert.equal(normalPayload.accountRunHistoryTextEnabled, true);
|
assert.equal(normalPayload.accountRunHistoryTextEnabled, true);
|
||||||
assert.equal(normalPayload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
assert.equal(normalPayload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
||||||
|
assert.equal(normalPayload.cloudflareTempEmailUseRandomSubdomain, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('contribution mode manager enters mode, starts main auto flow, polls contribution status, and exits cleanly', async () => {
|
test('contribution mode manager enters mode, starts main auto flow, polls contribution status, and exits cleanly', async () => {
|
||||||
|
|||||||
@@ -144,3 +144,317 @@ return {
|
|||||||
]);
|
]);
|
||||||
assert.match(result.bodyTextPreview, /Welcome to ChatGPT/);
|
assert.match(result.bodyTextPreview, /Welcome to ChatGPT/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('signup entry diagnostics captures hidden signup button style and blocking ancestor details', () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const SIGNUP_ENTRY_TRIGGER_PATTERN = /免费注册|立即注册|注册|sign\\s*up|register|create\\s*account|create\\s+account/i;
|
||||||
|
const location = { href: 'https://chatgpt.com/' };
|
||||||
|
const hiddenSection = {
|
||||||
|
tagName: 'DIV',
|
||||||
|
id: 'mobile-cta',
|
||||||
|
className: 'max-xs:hidden',
|
||||||
|
hidden: false,
|
||||||
|
parentElement: null,
|
||||||
|
hasAttribute() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'aria-hidden') return '';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
},
|
||||||
|
_style: {
|
||||||
|
display: 'none',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hiddenSignupButton = {
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
textContent: 'Sign up for free',
|
||||||
|
disabled: false,
|
||||||
|
className: 'signup-button',
|
||||||
|
hidden: false,
|
||||||
|
parentElement: hiddenSection,
|
||||||
|
hasAttribute() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return '';
|
||||||
|
if (name === 'aria-hidden') return '';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
_style: {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const document = {
|
||||||
|
title: 'ChatGPT',
|
||||||
|
readyState: 'complete',
|
||||||
|
querySelector() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
||||||
|
return [hiddenSignupButton];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const window = {
|
||||||
|
innerWidth: 390,
|
||||||
|
innerHeight: 844,
|
||||||
|
outerWidth: 390,
|
||||||
|
outerHeight: 844,
|
||||||
|
devicePixelRatio: 3,
|
||||||
|
getComputedStyle(el) {
|
||||||
|
return el?._style || {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function isVisibleElement(el) {
|
||||||
|
const style = window.getComputedStyle(el);
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
return style.display !== 'none'
|
||||||
|
&& style.visibility !== 'hidden'
|
||||||
|
&& rect.width > 0
|
||||||
|
&& rect.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionText(el) {
|
||||||
|
return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.replace(/\\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionEnabled(el) {
|
||||||
|
return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupEmailInput() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordInput() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageTextSnapshot() {
|
||||||
|
return 'ChatGPT 登录';
|
||||||
|
}
|
||||||
|
|
||||||
|
${extractFunction('getSignupEntryDiagnostics')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return getSignupEntryDiagnostics();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = api.run();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result.viewport, {
|
||||||
|
innerWidth: 390,
|
||||||
|
innerHeight: 844,
|
||||||
|
outerWidth: 390,
|
||||||
|
outerHeight: 844,
|
||||||
|
devicePixelRatio: 3,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(result.signupLikeActionCounts, {
|
||||||
|
total: 1,
|
||||||
|
visible: 0,
|
||||||
|
hidden: 1,
|
||||||
|
});
|
||||||
|
assert.equal(result.signupLikeActions[0]?.text, 'Sign up for free');
|
||||||
|
assert.equal(result.signupLikeActions[0]?.className, 'signup-button');
|
||||||
|
assert.equal(result.signupLikeActions[0]?.display, 'block');
|
||||||
|
assert.equal(result.signupLikeActions[0]?.blockingAncestor?.className, 'max-xs:hidden');
|
||||||
|
assert.equal(result.signupLikeActions[0]?.blockingAncestor?.display, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('signup password diagnostics summarizes password inputs, submit button, and alternate code entry', () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const location = { href: 'https://auth.openai.com/create-account/password' };
|
||||||
|
const form = { action: 'https://auth.openai.com/u/signup/password' };
|
||||||
|
const passwordInput = {
|
||||||
|
tagName: 'INPUT',
|
||||||
|
type: 'password',
|
||||||
|
name: 'new-password',
|
||||||
|
id: 'password-field',
|
||||||
|
value: 'SecretLength14',
|
||||||
|
className: 'password-input',
|
||||||
|
disabled: false,
|
||||||
|
form,
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 320, height: 44 };
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'password';
|
||||||
|
if (name === 'name') return 'new-password';
|
||||||
|
if (name === 'autocomplete') return 'new-password';
|
||||||
|
if (name === 'placeholder') return 'Password';
|
||||||
|
if (name === 'aria-disabled') return 'false';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
_style: {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const submitButton = {
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
textContent: 'Continue',
|
||||||
|
className: 'submit-btn',
|
||||||
|
disabled: false,
|
||||||
|
form,
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 160, height: 40 };
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'submit';
|
||||||
|
if (name === 'aria-disabled') return 'false';
|
||||||
|
if (name === 'data-dd-action-name') return 'Continue';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
_style: {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const oneTimeCodeButton = {
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
textContent: 'Use a one-time code instead',
|
||||||
|
className: 'switch-btn',
|
||||||
|
disabled: false,
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 220, height: 36 };
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'button';
|
||||||
|
if (name === 'aria-disabled') return 'false';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
_style: {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const document = {
|
||||||
|
title: 'Create your account',
|
||||||
|
readyState: 'complete',
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'input[type="password"], input[name*="password" i], input[autocomplete="new-password"], input[autocomplete="current-password"]') {
|
||||||
|
return [passwordInput];
|
||||||
|
}
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
||||||
|
return [submitButton, oneTimeCodeButton];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const window = {
|
||||||
|
getComputedStyle(el) {
|
||||||
|
return el?._style || {
|
||||||
|
display: 'block',
|
||||||
|
visibility: 'visible',
|
||||||
|
opacity: '1',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function isVisibleElement(el) {
|
||||||
|
const style = window.getComputedStyle(el);
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
return style.display !== 'none'
|
||||||
|
&& style.visibility !== 'hidden'
|
||||||
|
&& rect.width > 0
|
||||||
|
&& rect.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionEnabled(el) {
|
||||||
|
return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionText(el) {
|
||||||
|
return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.replace(/\\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageTextSnapshot() {
|
||||||
|
return 'Create your account Use a one-time code instead';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordInput() {
|
||||||
|
return passwordInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordSubmitButton() {
|
||||||
|
return submitButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordDisplayedEmail() {
|
||||||
|
return 'user@example.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOneTimeCodeLoginTrigger() {
|
||||||
|
return oneTimeCodeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordTimeoutErrorPageState() {
|
||||||
|
return {
|
||||||
|
retryEnabled: true,
|
||||||
|
userAlreadyExistsBlocked: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
${extractFunction('getSignupPasswordDiagnostics')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return getSignupPasswordDiagnostics();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = api.run();
|
||||||
|
|
||||||
|
assert.equal(result.url, 'https://auth.openai.com/create-account/password');
|
||||||
|
assert.equal(result.displayedEmail, 'user@example.com');
|
||||||
|
assert.equal(result.hasVisiblePasswordInput, true);
|
||||||
|
assert.equal(result.passwordInputCount, 1);
|
||||||
|
assert.equal(result.visiblePasswordInputCount, 1);
|
||||||
|
assert.equal(result.passwordInputs[0]?.name, 'new-password');
|
||||||
|
assert.equal(result.passwordInputs[0]?.valueLength, 14);
|
||||||
|
assert.equal(result.submitButton?.text, 'Continue');
|
||||||
|
assert.equal(result.oneTimeCodeTrigger?.text, 'Use a one-time code instead');
|
||||||
|
assert.equal(result.retryPage, true);
|
||||||
|
assert.equal(result.retryEnabled, true);
|
||||||
|
assert.match(result.bodyTextPreview, /one-time code/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
## `docs/`
|
## `docs/`
|
||||||
|
|
||||||
- `docs/仓库协作者AI分析PR与合并标准流程.md`:仓库协作者进行 AI 分析 PR 与合并时的流程说明。
|
- `docs/仓库协作者AI分析PR与合并标准流程.md`:仓库协作者进行 AI 分析 PR 与合并时的流程说明。
|
||||||
|
- `docs/使用教程.md`:面向使用者的补充教程文档,当前说明扩展更新流程与 Clash Verge 的“非港轮询”配置步骤。
|
||||||
- `docs/images/交流群.jpg`:README 中展示的交流群图片资源。
|
- `docs/images/交流群.jpg`:README 中展示的交流群图片资源。
|
||||||
- `docs/images/十轮自动.png`:README 中展示的自动运行效果图。
|
- `docs/images/十轮自动.png`:README 中展示的自动运行效果图。
|
||||||
- `docs/images/微信.png`:README 中展示的微信收款码图片。
|
- `docs/images/微信.png`:README 中展示的微信收款码图片。
|
||||||
|
|||||||
Reference in New Issue
Block a user