feat: 增加邮箱重试逻辑,优化步骤4失败后的处理方式
This commit is contained in:
@@ -488,6 +488,8 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
|
|
||||||
进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / 操作超时(Operation timed out)` 并带有 `重试` 按钮,会先通过共享恢复逻辑自动点击 `重试`、回到密码页重新提交,再继续等待验证码页面。
|
进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / 操作超时(Operation timed out)` 并带有 `重试` 按钮,会先通过共享恢复逻辑自动点击 `重试`、回到密码页重新提交,再继续等待验证码页面。
|
||||||
|
|
||||||
|
在 `Auto` 模式下,如果 Step 4 当前轮失败,后台不会立刻丢弃这轮邮箱;而是沿用当前邮箱回到 Step 1 重新开始当前轮,避免刚拿到的邮箱被直接换掉。
|
||||||
|
|
||||||
支持:
|
支持:
|
||||||
|
|
||||||
- `Hotmail`(远程服务 / 本地助手)
|
- `Hotmail`(远程服务 / 本地助手)
|
||||||
|
|||||||
+70
-5
@@ -151,6 +151,10 @@ const AUTO_RUN_RETRY_DELAY_MS = 3000;
|
|||||||
const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
|
const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
|
||||||
const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
|
const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
|
||||||
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
|
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MIN = 0;
|
||||||
|
const VERIFICATION_RESEND_COUNT_MAX = 20;
|
||||||
|
const DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT = 5;
|
||||||
|
const DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT = 4;
|
||||||
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
|
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
const DEFAULT_CPA_CALLBACK_MODE = 'step9';
|
||||||
@@ -218,6 +222,8 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
autoRunDelayEnabled: false,
|
autoRunDelayEnabled: false,
|
||||||
autoRunDelayMinutes: 30,
|
autoRunDelayMinutes: 30,
|
||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
|
signupVerificationResendCount: DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT,
|
||||||
|
loginVerificationResendCount: DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT,
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
||||||
emailGenerator: 'duck',
|
emailGenerator: 'duck',
|
||||||
@@ -359,6 +365,23 @@ function normalizeAutoStepDelaySeconds(value, fallback = null) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeVerificationResendCount(value, fallback) {
|
||||||
|
const rawValue = String(value ?? '').trim();
|
||||||
|
if (!rawValue) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numeric = Number(rawValue);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(
|
||||||
|
VERIFICATION_RESEND_COUNT_MAX,
|
||||||
|
Math.max(VERIFICATION_RESEND_COUNT_MIN, Math.floor(numeric))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveLegacyAutoStepDelaySeconds(input = {}) {
|
function resolveLegacyAutoStepDelaySeconds(input = {}) {
|
||||||
const hasLegacyMin = input.autoStepRandomDelayMinSeconds !== undefined;
|
const hasLegacyMin = input.autoStepRandomDelayMinSeconds !== undefined;
|
||||||
const hasLegacyMax = input.autoStepRandomDelayMaxSeconds !== undefined;
|
const hasLegacyMax = input.autoStepRandomDelayMaxSeconds !== undefined;
|
||||||
@@ -781,6 +804,10 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeAutoRunDelayMinutes(value);
|
return normalizeAutoRunDelayMinutes(value);
|
||||||
case 'autoStepDelaySeconds':
|
case 'autoStepDelaySeconds':
|
||||||
return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
|
return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
|
||||||
|
case 'signupVerificationResendCount':
|
||||||
|
return normalizeVerificationResendCount(value, DEFAULT_SIGNUP_VERIFICATION_RESEND_COUNT);
|
||||||
|
case 'loginVerificationResendCount':
|
||||||
|
return normalizeVerificationResendCount(value, DEFAULT_LOGIN_VERIFICATION_RESEND_COUNT);
|
||||||
case 'mailProvider':
|
case 'mailProvider':
|
||||||
return normalizeMailProvider(value);
|
return normalizeMailProvider(value);
|
||||||
case 'mail2925Mode':
|
case 'mail2925Mode':
|
||||||
@@ -5286,23 +5313,28 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
|
|||||||
async function runAutoSequenceFromStep(startStep, context = {}) {
|
async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||||
const { targetRun, totalRuns, attemptRuns, continued = false } = context;
|
const { targetRun, totalRuns, attemptRuns, continued = false } = context;
|
||||||
let postStep7RestartCount = 0;
|
let postStep7RestartCount = 0;
|
||||||
|
let step4RestartCount = 0;
|
||||||
|
let currentStartStep = startStep;
|
||||||
|
let continueCurrentAttempt = continued;
|
||||||
|
|
||||||
if (continued) {
|
while (true) {
|
||||||
|
|
||||||
|
if (continueCurrentAttempt) {
|
||||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从步骤 ${startStep} 开始(第 ${attemptRuns} 次尝试)===`, 'info');
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从步骤 ${startStep} 开始(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||||
} else {
|
} else {
|
||||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:第 ${attemptRuns} 次尝试,阶段 1,打开官网并进入密码页 ===`, 'info');
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:第 ${attemptRuns} 次尝试,阶段 1,打开官网并进入密码页 ===`, 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startStep <= 1) {
|
if (currentStartStep <= 1) {
|
||||||
await executeStepAndWait(1, AUTO_STEP_DELAYS[1]);
|
await executeStepAndWait(1, AUTO_STEP_DELAYS[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startStep <= 2) {
|
if (currentStartStep <= 2) {
|
||||||
await ensureAutoEmailReady(targetRun, totalRuns, attemptRuns);
|
await ensureAutoEmailReady(targetRun, totalRuns, attemptRuns);
|
||||||
await executeStepAndWait(2, AUTO_STEP_DELAYS[2]);
|
await executeStepAndWait(2, AUTO_STEP_DELAYS[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startStep <= 3) {
|
if (currentStartStep <= 3) {
|
||||||
const latestState = await getState();
|
const latestState = await getState();
|
||||||
const step3Status = latestState.stepStatuses?.[3] || 'pending';
|
const step3Status = latestState.stepStatuses?.[3] || 'pending';
|
||||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:阶段 2,填写密码、验证、登录并完成授权(第 ${attemptRuns} 次尝试)===`, 'info');
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:阶段 2,填写密码、验证、登录并完成授权(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||||
@@ -5325,7 +5357,8 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
await chrome.tabs.update(signupTabId, { active: true });
|
await chrome.tabs.update(signupTabId, { active: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
let step = Math.max(startStep, 4);
|
let restartFromStep1WithCurrentEmail = false;
|
||||||
|
let step = Math.max(currentStartStep, 4);
|
||||||
while (step <= LAST_STEP_ID) {
|
while (step <= LAST_STEP_ID) {
|
||||||
try {
|
try {
|
||||||
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
||||||
@@ -5340,6 +5373,31 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (step === 4) {
|
||||||
|
step4RestartCount += 1;
|
||||||
|
const preservedState = await getState();
|
||||||
|
const preservedEmail = String(preservedState.email || '').trim();
|
||||||
|
const preservedPassword = String(preservedState.password || '').trim();
|
||||||
|
const emailSuffix = preservedEmail ? `当前邮箱:${preservedEmail};` : '';
|
||||||
|
await addLog(
|
||||||
|
`步骤 4:执行失败,准备沿用当前邮箱回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${emailSuffix}原因:${getErrorMessage(err)}`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
await invalidateDownstreamAfterStepRestart(1, {
|
||||||
|
logLabel: `步骤 4 报错后准备回到步骤 1 沿用当前邮箱重试(第 ${step4RestartCount} 次重开)`,
|
||||||
|
});
|
||||||
|
const restorePayload = {};
|
||||||
|
if (preservedEmail) restorePayload.email = preservedEmail;
|
||||||
|
if (preservedPassword) restorePayload.password = preservedPassword;
|
||||||
|
if (Object.keys(restorePayload).length) {
|
||||||
|
await setState(restorePayload);
|
||||||
|
}
|
||||||
|
currentStartStep = 1;
|
||||||
|
continueCurrentAttempt = true;
|
||||||
|
restartFromStep1WithCurrentEmail = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const restartDecision = await getPostStep6AutoRestartDecision(step, err);
|
const restartDecision = await getPostStep6AutoRestartDecision(step, err);
|
||||||
if (restartDecision.shouldRestart) {
|
if (restartDecision.shouldRestart) {
|
||||||
postStep7RestartCount += 1;
|
postStep7RestartCount += 1;
|
||||||
@@ -5368,6 +5426,13 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (restartFromStep1WithCurrentEmail) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForResume() {
|
async function waitForResume() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
throw new Error('认证页面标签页已关闭,请先重新完成步骤 2。');
|
throw new Error('认证页面标签页已关闭,请先重新完成步骤 2。');
|
||||||
}
|
}
|
||||||
|
|
||||||
const password = state.customPassword || generatePassword();
|
const password = state.customPassword || state.password || generatePassword();
|
||||||
await setPasswordState(password);
|
await setPasswordState(password);
|
||||||
|
|
||||||
const accounts = state.accounts || [];
|
const accounts = state.accounts || [];
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (braceStart < 0) {
|
||||||
|
throw new Error(`missing body for function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('isAddPhoneAuthUrl'),
|
||||||
|
extractFunction('isAddPhoneAuthState'),
|
||||||
|
extractFunction('getPostStep6AutoRestartDecision'),
|
||||||
|
extractFunction('runAutoSequenceFromStep'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
test('auto-run restarts from step 1 with the same email after step 4 failure', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||||
|
const LAST_STEP_ID = 10;
|
||||||
|
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||||
|
const chrome = {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let remainingFailures = 1;
|
||||||
|
let currentState = {
|
||||||
|
email: 'keep@example.com',
|
||||||
|
password: 'Secret123!',
|
||||||
|
mailProvider: '163',
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'pending',
|
||||||
|
2: 'pending',
|
||||||
|
3: 'pending',
|
||||||
|
4: 'pending',
|
||||||
|
5: 'pending',
|
||||||
|
6: 'pending',
|
||||||
|
7: 'pending',
|
||||||
|
8: 'pending',
|
||||||
|
9: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const events = {
|
||||||
|
steps: [],
|
||||||
|
emails: [],
|
||||||
|
invalidations: [],
|
||||||
|
logs: [],
|
||||||
|
setStateCalls: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addLog(message, level = 'info') {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureAutoEmailReady() {
|
||||||
|
events.emails.push(currentState.email);
|
||||||
|
return currentState.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function broadcastAutoRunStatus() {}
|
||||||
|
|
||||||
|
async function getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setState(updates) {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...updates,
|
||||||
|
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||||
|
};
|
||||||
|
events.setStateCalls.push(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStopError(error) {
|
||||||
|
return (error?.message || String(error || '')) === '流程已被用户停止。';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStepDoneStatus(status) {
|
||||||
|
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeStepAndWait(step) {
|
||||||
|
events.steps.push(step);
|
||||||
|
if (step === 4 && remainingFailures > 0) {
|
||||||
|
remainingFailures -= 1;
|
||||||
|
throw new Error('步骤 4 提交验证码前页面异常。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipLoginVerificationForCpaCallback() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||||
|
events.invalidations.push({ step, options });
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
password: null,
|
||||||
|
stepStatuses: {
|
||||||
|
1: currentState.stepStatuses[1] || 'completed',
|
||||||
|
2: 'pending',
|
||||||
|
3: 'pending',
|
||||||
|
4: 'pending',
|
||||||
|
5: 'pending',
|
||||||
|
6: 'pending',
|
||||||
|
7: 'pending',
|
||||||
|
8: 'pending',
|
||||||
|
9: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoginAuthStateLabel(state) {
|
||||||
|
return state || 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return error?.message || String(error || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLoginAuthStateFromContent() {
|
||||||
|
return { state: 'password_page', url: 'https://auth.openai.com/log-in' };
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await runAutoSequenceFromStep(1, {
|
||||||
|
targetRun: 1,
|
||||||
|
totalRuns: 1,
|
||||||
|
attemptRuns: 1,
|
||||||
|
continued: false,
|
||||||
|
});
|
||||||
|
return { events, currentState };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const { events, currentState } = await api.run();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.invalidations, [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
options: {
|
||||||
|
logLabel: '步骤 4 报错后准备回到步骤 1 沿用当前邮箱重试(第 1 次重开)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(events.emails, ['keep@example.com', 'keep@example.com']);
|
||||||
|
assert.deepStrictEqual(events.steps, [1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||||
|
assert.equal(currentState.email, 'keep@example.com');
|
||||||
|
assert.equal(currentState.password, 'Secret123!');
|
||||||
|
assert.equal(events.logs.some(({ message }) => /沿用当前邮箱回到步骤 1 重新开始/.test(message)), true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background/steps/fill-password.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep3;`)(globalScope);
|
||||||
|
|
||||||
|
test('step 3 reuses existing generated password when rerunning the same email flow', async () => {
|
||||||
|
const events = {
|
||||||
|
passwordStates: [],
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep3Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
generatePassword: () => 'Generated-Should-Not-Be-Used',
|
||||||
|
getTabId: async () => 88,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
events.messages.push(message);
|
||||||
|
},
|
||||||
|
setPasswordState: async (password) => {
|
||||||
|
events.passwordStates.push(password);
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep3({
|
||||||
|
email: 'keep@example.com',
|
||||||
|
password: 'Secret123!',
|
||||||
|
customPassword: '',
|
||||||
|
accounts: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.passwordStates, ['Secret123!']);
|
||||||
|
assert.deepStrictEqual(events.messages, [
|
||||||
|
{
|
||||||
|
type: 'EXECUTE_STEP',
|
||||||
|
step: 3,
|
||||||
|
source: 'background',
|
||||||
|
payload: {
|
||||||
|
email: 'keep@example.com',
|
||||||
|
password: 'Secret123!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -249,6 +249,8 @@
|
|||||||
- Hotmail 在 Step 4 / 8 首轮轮询时会优先使用最近一次验证码请求时间作为时间窗口;如果中途触发重发,轮询窗口也会同步前移,避免旧邮件反复命中。
|
- Hotmail 在 Step 4 / 8 首轮轮询时会优先使用最近一次验证码请求时间作为时间窗口;如果中途触发重发,轮询窗口也会同步前移,避免旧邮件反复命中。
|
||||||
- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。
|
- CPA 模式下,Step 8 在真正回填登录验证码前,会先刷新最新 OAuth 地址并快速重走一次 Step 7,降低验证码等待过久导致 OAuth 过期的概率。
|
||||||
|
|
||||||
|
- Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
|
||||||
|
|
||||||
### Step 5
|
### Step 5
|
||||||
|
|
||||||
文件:
|
文件:
|
||||||
|
|||||||
Reference in New Issue
Block a user