merge: integrate hotmail邮箱支持 into dev

This commit is contained in:
QLHazyCoder
2026-04-12 20:10:17 +08:00
16 changed files with 3312 additions and 90 deletions
+53
View File
@@ -0,0 +1,53 @@
(function activationUtilsModule(root, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory();
return;
}
root.MultiPageActivationUtils = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createActivationUtils() {
function normalizeTagName(tagName) {
return String(tagName || '').trim().toLowerCase();
}
function normalizeType(type) {
return String(type || '').trim().toLowerCase();
}
function normalizePathname(pathname) {
return String(pathname || '').trim().toLowerCase();
}
function getActivationStrategy(target = {}) {
const tagName = normalizeTagName(target.tagName);
const type = normalizeType(target.type);
const pathname = normalizePathname(target.pathname);
const hasForm = Boolean(target.hasForm);
const isEmailVerificationRoute = /\/email-verification(?:[/?#]|$)/i.test(pathname);
const isSubmitButton = hasForm
&& (
(tagName === 'button' && (!type || type === 'submit'))
|| (tagName === 'input' && type === 'submit')
);
if (isSubmitButton && isEmailVerificationRoute) {
return { method: 'requestSubmit' };
}
return { method: 'click' };
}
function isRecoverableStep9AuthFailure(statusText) {
const text = String(statusText || '').trim();
if (!/认证失败:\s*/i.test(text)) {
return false;
}
return /timeout waiting for oauth callback|status code 5\d{2}|bad gateway|gateway timeout|temporarily unavailable/i.test(text);
}
return {
getActivationStrategy,
isRecoverableStep9AuthFailure,
};
});
+9 -7
View File
@@ -233,9 +233,10 @@ async function prepareLoginCodeFlow(timeout = 15000) {
loggedPasswordPage = false;
log('步骤 7:检测到密码页,正在切换到一次性验证码登录...');
await humanPause(350, 900);
const verificationRequestedAt = Date.now();
simulateClick(switchTrigger);
await sleep(1200);
continue;
return { ready: true, mode: 'verification_switch', verificationRequestedAt };
}
if (passwordInput && !loggedPasswordPage) {
@@ -372,15 +373,16 @@ async function step3_fillEmailPassword(payload) {
fillInput(passwordInput, payload.password);
log('步骤 3:密码已填写');
// Report complete BEFORE submit, because submit causes page navigation
// which kills the content script connection
reportComplete(3, { email });
// Submit the form (page will navigate away after this)
await sleep(500);
const submitBtn = document.querySelector('button[type="submit"]')
|| await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|create/i, 5000).catch(() => null);
// Report complete BEFORE submit, because submit causes page navigation
// which kills the content script connection
const signupVerificationRequestedAt = submitBtn ? Date.now() : null;
reportComplete(3, { email, signupVerificationRequestedAt });
// Submit the form (page will navigate away after this)
await sleep(500);
if (submitBtn) {
await humanPause(500, 1300);
simulateClick(submitBtn);
+30 -3
View File
@@ -1,5 +1,7 @@
// content/utils.js — Shared utilities for all content scripts
const getActivationStrategy = self.MultiPageActivationUtils?.getActivationStrategy;
const SCRIPT_SOURCE = (() => {
if (window.__MULTIPAGE_SOURCE) return window.__MULTIPAGE_SOURCE;
const url = location.href;
@@ -340,9 +342,34 @@ function reportError(step, errorMessage) {
*/
function simulateClick(el) {
throwIfStopped();
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
console.log(LOG_PREFIX, `已点击: ${el.tagName} ${el.textContent?.slice(0, 30) || ''}`);
log(`已点击 [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`);
if (!el) {
throw new Error('无法点击空元素。');
}
const form = el.form || el.closest?.('form') || null;
const strategy = typeof getActivationStrategy === 'function'
? getActivationStrategy({
tagName: el.tagName,
type: el.getAttribute?.('type') || el.type || '',
hasForm: Boolean(form),
pathname: location.pathname || '',
})
: { method: 'click' };
let method = strategy.method || 'click';
if (method === 'requestSubmit' && form && typeof form.requestSubmit === 'function') {
form.requestSubmit(el);
} else if (typeof el.click === 'function') {
method = 'click';
el.click();
} else {
method = 'dispatchEvent';
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
}
console.log(LOG_PREFIX, `已点击(${method}): ${el.tagName} ${el.textContent?.slice(0, 30) || ''}`);
log(`已点击(${method}) [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`);
}
/**
+12
View File
@@ -26,6 +26,9 @@
console.log('[MultiPage:vps-panel] Content script loaded on', location.href);
const VPS_PANEL_LISTENER_SENTINEL = 'data-multipage-vps-panel-listener';
const {
isRecoverableStep9AuthFailure,
} = self.MultiPageActivationUtils || {};
if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(VPS_PANEL_LISTENER_SENTINEL, '1');
@@ -208,6 +211,12 @@ async function waitForExactSuccessBadge(timeout = 30000) {
while (Date.now() - start < timeout) {
throwIfStopped();
const statusText = getStatusBadgeText();
if (isOAuthCallbackTimeoutFailure(statusText)) {
throw new Error(`STEP9_OAUTH_TIMEOUT::${statusText}`);
}
if (typeof isRecoverableStep9AuthFailure === 'function' && isRecoverableStep9AuthFailure(statusText)) {
throw new Error(`STEP9_OAUTH_RETRY::${statusText}`);
}
if (statusText === '认证成功!') {
return statusText;
}
@@ -218,6 +227,9 @@ async function waitForExactSuccessBadge(timeout = 30000) {
if (isOAuthCallbackTimeoutFailure(finalText)) {
throw new Error(`STEP9_OAUTH_TIMEOUT::${finalText}`);
}
if (typeof isRecoverableStep9AuthFailure === 'function' && isRecoverableStep9AuthFailure(finalText)) {
throw new Error(`STEP9_OAUTH_RETRY::${finalText}`);
}
throw new Error(finalText
? `CPA 面板状态不是“认证成功!”,当前为“${finalText}”。`
: 'CPA 面板长时间未出现“认证成功!”状态徽标。');