diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css
index bc02d56..6004558 100644
--- a/sidepanel/sidepanel.css
+++ b/sidepanel/sidepanel.css
@@ -3286,22 +3286,6 @@ header {
white-space: pre-line;
}
-.plus-contribution-prompt-copy {
- display: block;
- margin-bottom: 8px;
-}
-
-.plus-contribution-prompt-image {
- display: block;
- width: min(220px, 100%);
- max-height: 280px;
- object-fit: contain;
- margin: 12px auto 0;
- border: 1px solid var(--border);
- border-radius: 8px;
- background: var(--bg-primary);
-}
-
.modal-message a,
.modal-alert a {
color: var(--blue);
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 6e60951..5e81e45 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -708,8 +708,6 @@ const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
const NEW_USER_GUIDE_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-new-user-guide-prompt-dismissed';
const AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-skip-failures-prompt-dismissed';
const AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-fallback-risk-prompt-dismissed';
-const AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-plus-risk-prompt-dismissed';
-const PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY = 'multipage-plus-contribution-prompt-ledger';
const PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY = 'multipage-phone-verification-section-expanded';
let phoneVerificationSectionExpanded = false;
@@ -794,10 +792,6 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
}
const CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY = 'multipage-contribution-content-prompt-dismissed-version';
const AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS = 3;
-const AUTO_RUN_PLUS_RISK_WARNING_MAX_SAFE_RUNS = 3;
-const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
-const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
-const PLUS_CONTRIBUTION_DONATION_CREDIT = 20;
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
const ICLOUD_PROVIDER = 'icloud';
@@ -1794,192 +1788,10 @@ function setAutoRunFallbackRiskPromptDismissed(dismissed) {
setPromptDismissed(AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY, dismissed);
}
-function isAutoRunPlusRiskPromptDismissed() {
- return isPromptDismissed(AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY);
-}
-
-function setAutoRunPlusRiskPromptDismissed(dismissed) {
- setPromptDismissed(AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY, dismissed);
-}
-
function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) {
return totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS;
}
-function shouldWarnPlusAutoRunRisk(totalRuns, plusModeEnabled) {
- return Boolean(plusModeEnabled)
- && Math.floor(Number(totalRuns) || 0) > AUTO_RUN_PLUS_RISK_WARNING_MAX_SAFE_RUNS;
-}
-
-function normalizePlusContributionPromptNumber(value) {
- const number = Math.floor(Number(value) || 0);
- return Number.isFinite(number) ? number : 0;
-}
-
-function normalizePlusContributionPromptLedger(value = {}) {
- const source = value && typeof value === 'object' ? value : {};
- return {
- promptBaseline: normalizePlusContributionPromptNumber(source.promptBaseline),
- donationCredit: Math.max(0, normalizePlusContributionPromptNumber(source.donationCredit)),
- };
-}
-
-function getPlusContributionPromptLedger() {
- try {
- return normalizePlusContributionPromptLedger(
- JSON.parse(localStorage.getItem(PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY) || '{}')
- );
- } catch {
- return normalizePlusContributionPromptLedger();
- }
-}
-
-function setPlusContributionPromptLedger(ledger) {
- localStorage.setItem(
- PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY,
- JSON.stringify(normalizePlusContributionPromptLedger(ledger))
- );
-}
-
-function isSuccessfulPlusAccountRecord(record = {}) {
- return record?.finalStatus === 'success' && Boolean(record.plusModeEnabled);
-}
-
-function getPlusContributionPromptTotals(records = []) {
- return (Array.isArray(records) ? records : []).reduce((totals, record) => {
- if (!isSuccessfulPlusAccountRecord(record)) {
- return totals;
- }
- if (record.contributionMode) {
- totals.contributionSuccess += 1;
- } else {
- totals.plusSuccess += 1;
- }
- return totals;
- }, {
- plusSuccess: 0,
- contributionSuccess: 0,
- });
-}
-
-function getPlusContributionPromptProgress(records = [], ledger = getPlusContributionPromptLedger()) {
- const totals = getPlusContributionPromptTotals(records);
- const normalizedLedger = normalizePlusContributionPromptLedger(ledger);
- const credit = (totals.contributionSuccess * PLUS_CONTRIBUTION_ACCOUNT_CREDIT)
- + normalizedLedger.donationCredit;
- const netCount = totals.plusSuccess - credit;
- const sinceLastPrompt = netCount - normalizedLedger.promptBaseline;
- return {
- ...totals,
- credit,
- netCount,
- sinceLastPrompt,
- shouldPrompt: sinceLastPrompt >= PLUS_CONTRIBUTION_PROMPT_THRESHOLD,
- };
-}
-
-function shouldShowPlusContributionPrompt(records = [], plusModeEnabled = false, ledger = getPlusContributionPromptLedger()) {
- return Boolean(plusModeEnabled)
- && getPlusContributionPromptProgress(records, ledger).shouldPrompt;
-}
-
-function markPlusContributionPromptShown(records = [], ledger = getPlusContributionPromptLedger()) {
- const progress = getPlusContributionPromptProgress(records, ledger);
- const nextLedger = {
- ...normalizePlusContributionPromptLedger(ledger),
- promptBaseline: progress.netCount,
- };
- setPlusContributionPromptLedger(nextLedger);
- return nextLedger;
-}
-
-function addPlusContributionPromptCredit(credit, ledger = getPlusContributionPromptLedger()) {
- const normalizedLedger = normalizePlusContributionPromptLedger(ledger);
- const nextLedger = {
- ...normalizedLedger,
- donationCredit: normalizedLedger.donationCredit + Math.max(0, normalizePlusContributionPromptNumber(credit)),
- };
- setPlusContributionPromptLedger(nextLedger);
- return nextLedger;
-}
-
-function getPlusContributionSupportImageUrl() {
- if (typeof chrome !== 'undefined' && chrome.runtime?.getURL) {
- return chrome.runtime.getURL('docs/images/微信.png');
- }
- return '../docs/images/微信.png';
-}
-
-function buildPlusContributionSupportPromptHtml() {
- const imageUrl = getPlusContributionSupportImageUrl();
- return [
- '您觉得这个 Plus 功能怎么样?您的账户数量应该已经够个人使用啦。',
- '可以打开贡献给作者贡献几个账号,以便于让作者开发更好的功能出来吗?或者打赏一下作者?',
- `
`,
- ].join('');
-}
-
-function openPlusContributionSupportModal() {
- return openActionModal({
- title: 'Plus 功能使用反馈',
- messageHtml: buildPlusContributionSupportPromptHtml(),
- actions: [
- { id: null, label: '取消', variant: 'btn-ghost' },
- { id: 'contribute', label: '去贡献账号', variant: 'btn-outline' },
- { id: 'donated', label: '已打赏', variant: 'btn-primary' },
- ],
- });
-}
-
-async function enterContributionModeFromPlusPrompt() {
- if (typeof chrome === 'undefined' || !chrome.runtime?.sendMessage) {
- return null;
- }
-
- const response = await chrome.runtime.sendMessage({
- type: 'SET_CONTRIBUTION_MODE',
- source: 'sidepanel',
- payload: { enabled: true },
- });
-
- if (response?.error) {
- throw new Error(response.error);
- }
- if (response?.state && typeof applySettingsState === 'function') {
- applySettingsState(response.state);
- }
- if (typeof renderContributionMode === 'function') {
- renderContributionMode();
- }
- return response?.state || null;
-}
-
-async function maybeShowPlusContributionPromptBeforeAutoRun(plusModeEnabled) {
- const records = Array.isArray(latestState?.accountRunHistory) ? latestState.accountRunHistory : [];
- if (!shouldShowPlusContributionPrompt(records, plusModeEnabled)) {
- return true;
- }
-
- const choice = await openPlusContributionSupportModal();
- const ledger = markPlusContributionPromptShown(records);
- if (choice === 'donated') {
- addPlusContributionPromptCredit(PLUS_CONTRIBUTION_DONATION_CREDIT, ledger);
- showToast('感谢打赏支持,已延后下一次 Plus 提醒。', 'success', 2200);
- return true;
- }
- if (choice === 'contribute') {
- openExternalUrl(getContributionPortalUrl());
- try {
- await enterContributionModeFromPlusPrompt();
- showToast('已进入贡献模式,并打开贡献页面。', 'info', 2200);
- } catch (error) {
- showToast(`贡献模式开启失败:${error.message}`, 'error', 2600);
- }
- return false;
- }
- return true;
-}
-
async function openAutoSkipFailuresConfirmModal() {
const result = await openConfirmModalWithOption({
title: '自动重试说明',
@@ -2006,19 +1818,6 @@ async function openAutoRunFallbackRiskConfirmModal(totalRuns) {
};
}
-async function openPlusAutoRunRiskConfirmModal(totalRuns) {
- const result = await openConfirmModalWithOption({
- title: 'Plus 自动轮数提醒',
- message: `Plus 模式下当前设置为 ${totalRuns} 轮。轮数过多可能造成 PayPal 或账号快速封号。建议够用就好:我注册了几个使用,没多注册,完全足够使用,并且没有封号。这个模式下只要可以注册成功就能使用,所以不要贪杯哦。`,
- confirmLabel: '我知道了,继续',
- });
-
- return {
- confirmed: result.confirmed,
- dismissPrompt: result.optionChecked,
- };
-}
-
function updateConfigMenuControls() {
const disabled = configActionInFlight || settingsSaveInFlight;
const contributionModeEnabled = Boolean(latestState?.contributionMode);
@@ -10877,9 +10676,6 @@ async function startAutoRunFromCurrentSettings() {
if (lockedRunCount > 0) {
inputRunCount.value = String(lockedRunCount);
}
- const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
- ? Boolean(inputPlusModeEnabled.checked)
- : Boolean(currentPlusModeEnabled || latestState?.plusModeEnabled);
let mode = 'restart';
const autoRunSkipFailures = inputAutoSkipFailures.checked;
const contributionNickname = String(inputContributionNickname?.value || '').trim();
@@ -10900,12 +10696,6 @@ async function startAutoRunFromCurrentSettings() {
mode = choice;
}
- const confirmedPlusContributionPrompt = await maybeShowPlusContributionPromptBeforeAutoRun(plusModeEnabled);
- if (!confirmedPlusContributionPrompt) {
- clearPendingAutoRunStartRunCount();
- return false;
- }
-
if (shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures)
&& !isAutoRunFallbackRiskPromptDismissed()) {
const result = await openAutoRunFallbackRiskConfirmModal(totalRuns);
@@ -10918,18 +10708,6 @@ async function startAutoRunFromCurrentSettings() {
}
}
- if (shouldWarnPlusAutoRunRisk(totalRuns, plusModeEnabled)
- && !isAutoRunPlusRiskPromptDismissed()) {
- const result = await openPlusAutoRunRiskConfirmModal(totalRuns);
- if (!result.confirmed) {
- clearPendingAutoRunStartRunCount();
- return false;
- }
- if (result.dismissPrompt) {
- setAutoRunPlusRiskPromptDismissed(true);
- }
- }
-
btnAutoRun.disabled = true;
inputRunCount.disabled = true;
const delayEnabled = inputAutoDelayEnabled.checked;
diff --git a/tests/sidepanel-auto-run-content-refresh.test.js b/tests/sidepanel-auto-run-content-refresh.test.js
index 115be14..3a33047 100644
--- a/tests/sidepanel-auto-run-content-refresh.test.js
+++ b/tests/sidepanel-auto-run-content-refresh.test.js
@@ -51,11 +51,6 @@ function extractFunction(name) {
function createApi({
refreshImpl,
runCount = 3,
- plusModeEnabled = false,
- plusRiskEnabled = false,
- plusRiskConfirmed = true,
- plusRiskDismissPrompt = false,
- plusContributionImpl,
persistImpl,
} = {}) {
const bundle = [
@@ -68,8 +63,6 @@ function createApi({
return new Function(`
const events = [];
const latestState = { contributionMode: false };
-const currentPlusModeEnabled = ${JSON.stringify(Boolean(plusModeEnabled))};
-const inputPlusModeEnabled = { checked: ${JSON.stringify(Boolean(plusModeEnabled))} };
const inputAutoSkipFailures = { checked: false };
const inputContributionNickname = { value: 'tester' };
const inputContributionQq = { value: '123456' };
@@ -115,23 +108,6 @@ function shouldWarnAutoRunFallbackRisk() { return false; }
function isAutoRunFallbackRiskPromptDismissed() { return false; }
async function openAutoRunFallbackRiskConfirmModal() { throw new Error('should not be called'); }
function setAutoRunFallbackRiskPromptDismissed() {}
-function shouldWarnPlusAutoRunRisk(totalRuns, plusModeEnabled) {
- return ${JSON.stringify(Boolean(plusRiskEnabled))} && Boolean(plusModeEnabled) && Number(totalRuns) > 3;
-}
-function isAutoRunPlusRiskPromptDismissed() { return false; }
-async function openPlusAutoRunRiskConfirmModal(totalRuns) {
- events.push({ type: 'plus-risk-modal', totalRuns });
- return {
- confirmed: ${JSON.stringify(Boolean(plusRiskConfirmed))},
- dismissPrompt: ${JSON.stringify(Boolean(plusRiskDismissPrompt))},
- };
-}
-function setAutoRunPlusRiskPromptDismissed(dismissed) {
- events.push({ type: 'plus-risk-dismiss', dismissed });
-}
-async function maybeShowPlusContributionPromptBeforeAutoRun(plusModeEnabled) {
- ${plusContributionImpl ? 'return (' + plusContributionImpl + ')(plusModeEnabled, events);' : 'return true;'}
-}
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
async function refreshContributionContentHint() {
events.push({ type: 'refresh' });
@@ -214,61 +190,6 @@ test('startAutoRunFromCurrentSettings freezes run count before async settings sy
assert.equal(events[3].message.payload.totalRuns, 20);
});
-test('startAutoRunFromCurrentSettings shows Plus risk warning before starting more than 3 runs', async () => {
- const api = createApi({
- runCount: 4,
- plusModeEnabled: true,
- plusRiskEnabled: true,
- });
-
- const result = await api.startAutoRunFromCurrentSettings();
- const events = api.getEvents();
-
- assert.equal(result, true);
- assert.deepEqual(
- events.map((entry) => entry.type),
- ['refresh', 'sync-settings', 'plus-risk-modal', 'send']
- );
- assert.equal(events[2].totalRuns, 4);
- assert.equal(events[3].message.payload.totalRuns, 4);
-});
-
-test('startAutoRunFromCurrentSettings aborts when Plus risk warning is declined', async () => {
- const api = createApi({
- runCount: 4,
- plusModeEnabled: true,
- plusRiskEnabled: true,
- plusRiskConfirmed: false,
- });
-
- const result = await api.startAutoRunFromCurrentSettings();
-
- assert.equal(result, false);
- assert.deepEqual(
- api.getEvents().map((entry) => entry.type),
- ['refresh', 'sync-settings', 'plus-risk-modal']
- );
-});
-
-test('startAutoRunFromCurrentSettings aborts when Plus contribution prompt opens contribution page', async () => {
- const api = createApi({
- plusModeEnabled: true,
- plusContributionImpl: `async (plusModeEnabled, events) => {
- events.push({ type: 'plus-contribution-modal', plusModeEnabled });
- return false;
- }`,
- });
-
- const result = await api.startAutoRunFromCurrentSettings();
-
- assert.equal(result, false);
- assert.deepEqual(
- api.getEvents().map((entry) => entry.type),
- ['refresh', 'sync-settings', 'plus-contribution-modal']
- );
- assert.equal(api.getEvents()[2].plusModeEnabled, true);
-});
-
test('persistCurrentSettingsForAction forces a silent save even when settings are not marked dirty', async () => {
const bundle = [
extractFunction('waitForSettingsSaveIdle'),
diff --git a/tests/sidepanel-auto-run-risk-warning.test.js b/tests/sidepanel-auto-run-risk-warning.test.js
index aa059d3..5a7ba4a 100644
--- a/tests/sidepanel-auto-run-risk-warning.test.js
+++ b/tests/sidepanel-auto-run-risk-warning.test.js
@@ -65,48 +65,6 @@ return { shouldWarnAutoRunFallbackRisk };
assert.equal(api.shouldWarnAutoRunFallbackRisk(10, true), true);
});
-test('Plus auto-run risk warning only starts above 3 runs in Plus mode', () => {
- const bundle = extractFunction('shouldWarnPlusAutoRunRisk');
-
- const api = new Function(`
-const AUTO_RUN_PLUS_RISK_WARNING_MAX_SAFE_RUNS = 3;
-${bundle}
-return { shouldWarnPlusAutoRunRisk };
-`)();
-
- assert.equal(api.shouldWarnPlusAutoRunRisk(3, true), false);
- assert.equal(api.shouldWarnPlusAutoRunRisk(4, true), true);
- assert.equal(api.shouldWarnPlusAutoRunRisk(10, false), false);
-});
-
-test('Plus auto-run risk modal uses PayPal account warning copy', async () => {
- const bundle = extractFunction('openPlusAutoRunRiskConfirmModal');
-
- const api = new Function(`
-let capturedOptions = null;
-async function openConfirmModalWithOption(options) {
- capturedOptions = options;
- return { confirmed: true, optionChecked: true };
-}
-${bundle}
-return {
- openPlusAutoRunRiskConfirmModal,
- getCapturedOptions() {
- return capturedOptions;
- },
-};
-`)();
-
- const result = await api.openPlusAutoRunRiskConfirmModal(4);
- const options = api.getCapturedOptions();
-
- assert.deepStrictEqual(result, { confirmed: true, dismissPrompt: true });
- assert.equal(options.title, 'Plus 自动轮数提醒');
- assert.match(options.message, /轮数过多可能造成 PayPal 或账号快速封号/);
- assert.match(options.message, /不要贪杯哦/);
- assert.equal(options.confirmLabel, '我知道了,继续');
-});
-
test('auto-run fallback risk modal uses the single-node warning copy', async () => {
const bundle = extractFunction('openAutoRunFallbackRiskConfirmModal');
diff --git a/tests/sidepanel-plus-contribution-prompt.test.js b/tests/sidepanel-plus-contribution-prompt.test.js
deleted file mode 100644
index b811d6e..0000000
--- a/tests/sidepanel-plus-contribution-prompt.test.js
+++ /dev/null
@@ -1,312 +0,0 @@
-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;
- }
- }
- 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);
-}
-
-function extractBundle(names) {
- return names.map((name) => extractFunction(name)).join('\n');
-}
-
-function createPlusRecords(count, contributionMode = false) {
- return Array.from({ length: count }, (_, index) => ({
- recordId: `${contributionMode ? 'contribution' : 'plus'}-${index}@example.com`,
- email: `${contributionMode ? 'contribution' : 'plus'}-${index}@example.com`,
- finalStatus: 'success',
- plusModeEnabled: true,
- contributionMode,
- }));
-}
-
-test('Plus contribution prompt starts every five normal Plus successes', () => {
- const bundle = extractBundle([
- 'normalizePlusContributionPromptNumber',
- 'normalizePlusContributionPromptLedger',
- 'isSuccessfulPlusAccountRecord',
- 'getPlusContributionPromptTotals',
- 'getPlusContributionPromptProgress',
- 'shouldShowPlusContributionPrompt',
- ]);
-
- const api = new Function(`
-const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
-const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
-${bundle}
-return {
- getPlusContributionPromptProgress,
- shouldShowPlusContributionPrompt,
-};
-`)();
-
- assert.equal(
- api.shouldShowPlusContributionPrompt(createPlusRecords(4), true, { promptBaseline: 0, donationCredit: 0 }),
- false
- );
- assert.equal(
- api.shouldShowPlusContributionPrompt(createPlusRecords(5), true, { promptBaseline: 0, donationCredit: 0 }),
- true
- );
- assert.equal(
- api.shouldShowPlusContributionPrompt(createPlusRecords(8), false, { promptBaseline: 0, donationCredit: 0 }),
- false
- );
-});
-
-test('Plus contribution success and donated credit delay the next prompt', () => {
- const bundle = extractBundle([
- 'normalizePlusContributionPromptNumber',
- 'normalizePlusContributionPromptLedger',
- 'isSuccessfulPlusAccountRecord',
- 'getPlusContributionPromptTotals',
- 'getPlusContributionPromptProgress',
- 'shouldShowPlusContributionPrompt',
- ]);
-
- const api = new Function(`
-const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
-const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
-${bundle}
-return { shouldShowPlusContributionPrompt };
-`)();
-
- const afterPromptLedger = { promptBaseline: 5, donationCredit: 0 };
- assert.equal(
- api.shouldShowPlusContributionPrompt(
- [...createPlusRecords(14), ...createPlusRecords(1, true)],
- true,
- afterPromptLedger
- ),
- false
- );
- assert.equal(
- api.shouldShowPlusContributionPrompt(
- [...createPlusRecords(15), ...createPlusRecords(1, true)],
- true,
- afterPromptLedger
- ),
- true
- );
-
- const donatedLedger = { promptBaseline: 5, donationCredit: 20 };
- assert.equal(api.shouldShowPlusContributionPrompt(createPlusRecords(29), true, donatedLedger), false);
- assert.equal(api.shouldShowPlusContributionPrompt(createPlusRecords(30), true, donatedLedger), true);
-});
-
-test('Plus contribution support modal includes WeChat image and expected actions', async () => {
- const bundle = extractBundle([
- 'getPlusContributionSupportImageUrl',
- 'buildPlusContributionSupportPromptHtml',
- 'openPlusContributionSupportModal',
- ]);
-
- const api = new Function(`
-let capturedOptions = null;
-const chrome = { runtime: { getURL: (path) => 'chrome-extension://test/' + path } };
-function escapeHtml(value) { return String(value || ''); }
-async function openActionModal(options) {
- capturedOptions = options;
- return 'donated';
-}
-${bundle}
-return {
- openPlusContributionSupportModal,
- getCapturedOptions() {
- return capturedOptions;
- },
-};
-`)();
-
- const choice = await api.openPlusContributionSupportModal();
- const options = api.getCapturedOptions();
-
- assert.equal(choice, 'donated');
- assert.equal(options.title, 'Plus 功能使用反馈');
- assert.match(options.messageHtml, /docs\/images\/微信\.png/);
- assert.deepEqual(options.actions.map((action) => action.label), ['取消', '去贡献账号', '已打赏']);
-});
-
-test('Plus contribution prompt marks shown and donated choice adds twenty credits', async () => {
- const bundle = extractBundle([
- 'normalizePlusContributionPromptNumber',
- 'normalizePlusContributionPromptLedger',
- 'getPlusContributionPromptLedger',
- 'setPlusContributionPromptLedger',
- 'isSuccessfulPlusAccountRecord',
- 'getPlusContributionPromptTotals',
- 'getPlusContributionPromptProgress',
- 'shouldShowPlusContributionPrompt',
- 'markPlusContributionPromptShown',
- 'addPlusContributionPromptCredit',
- 'enterContributionModeFromPlusPrompt',
- 'maybeShowPlusContributionPromptBeforeAutoRun',
- ]);
-
- const api = new Function('records', `
-const PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY = 'multipage-plus-contribution-prompt-ledger';
-const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
-const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
-const PLUS_CONTRIBUTION_DONATION_CREDIT = 20;
-const latestState = { accountRunHistory: records };
-const storage = {};
-const events = [];
-const localStorage = {
- getItem(key) { return Object.prototype.hasOwnProperty.call(storage, key) ? storage[key] : null; },
- setItem(key, value) { storage[key] = String(value); },
-};
-async function openPlusContributionSupportModal() {
- events.push({ type: 'modal' });
- return 'donated';
-}
-function showToast(message, type) {
- events.push({ type: 'toast', message, toastType: type });
-}
-function openExternalUrl(url) {
- events.push({ type: 'open', url });
-}
-function getContributionPortalUrl() {
- return 'https://apikey.qzz.io';
-}
-${bundle}
-return {
- maybeShowPlusContributionPromptBeforeAutoRun,
- getLedger() {
- return JSON.parse(storage[PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY] || '{}');
- },
- getEvents() {
- return events;
- },
-};
-`)(createPlusRecords(5));
-
- const result = await api.maybeShowPlusContributionPromptBeforeAutoRun(true);
-
- assert.equal(result, true);
- assert.deepEqual(api.getEvents().map((event) => event.type), ['modal', 'toast']);
- assert.deepEqual(api.getLedger(), {
- promptBaseline: 5,
- donationCredit: 20,
- });
-});
-
-test('Plus contribution prompt opens portal and aborts normal auto run when contribute is chosen', async () => {
- const bundle = extractBundle([
- 'normalizePlusContributionPromptNumber',
- 'normalizePlusContributionPromptLedger',
- 'getPlusContributionPromptLedger',
- 'setPlusContributionPromptLedger',
- 'isSuccessfulPlusAccountRecord',
- 'getPlusContributionPromptTotals',
- 'getPlusContributionPromptProgress',
- 'shouldShowPlusContributionPrompt',
- 'markPlusContributionPromptShown',
- 'addPlusContributionPromptCredit',
- 'enterContributionModeFromPlusPrompt',
- 'maybeShowPlusContributionPromptBeforeAutoRun',
- ]);
-
- const api = new Function('records', `
-const PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY = 'multipage-plus-contribution-prompt-ledger';
-const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
-const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
-const PLUS_CONTRIBUTION_DONATION_CREDIT = 20;
-const latestState = { accountRunHistory: records };
-const storage = {};
-const events = [];
-const localStorage = {
- getItem(key) { return Object.prototype.hasOwnProperty.call(storage, key) ? storage[key] : null; },
- setItem(key, value) { storage[key] = String(value); },
-};
-async function openPlusContributionSupportModal() {
- events.push({ type: 'modal' });
- return 'contribute';
-}
-function showToast(message, type) {
- events.push({ type: 'toast', message, toastType: type });
-}
-function openExternalUrl(url) {
- events.push({ type: 'open', url });
-}
-function getContributionPortalUrl() {
- return 'https://apikey.qzz.io';
-}
-const chrome = {
- runtime: {
- async sendMessage(message) {
- events.push({ type: 'runtime', message });
- return { state: { contributionMode: true } };
- },
- },
-};
-function applySettingsState(state) {
- events.push({ type: 'apply', state });
-}
-function renderContributionMode() {
- events.push({ type: 'render' });
-}
-${bundle}
-return {
- maybeShowPlusContributionPromptBeforeAutoRun,
- getEvents() {
- return events;
- },
-};
-`)(createPlusRecords(5));
-
- const result = await api.maybeShowPlusContributionPromptBeforeAutoRun(true);
-
- assert.equal(result, false);
- assert.deepEqual(api.getEvents().map((event) => event.type), ['modal', 'open', 'runtime', 'apply', 'render', 'toast']);
- assert.equal(api.getEvents()[1].url, 'https://apikey.qzz.io');
- assert.deepEqual(api.getEvents()[2].message, {
- type: 'SET_CONTRIBUTION_MODE',
- source: 'sidepanel',
- payload: { enabled: true },
- });
-});
diff --git a/项目文件结构说明.md b/项目文件结构说明.md
index e71e66e..1f3bed2 100644
--- a/项目文件结构说明.md
+++ b/项目文件结构说明.md
@@ -110,7 +110,6 @@
- `docs/ip-proxy-module.md`:IP Proxy 模块说明,记录模块定位、后台/侧栏代码结构、当前功能范围、711Proxy 参数联动、操作方式、发布策略与已知限制。
- `docs/使用教程.md`:面向使用者的补充教程文档,当前说明扩展更新流程与 Clash Verge 的“非港轮询”配置步骤。
- `docs/images/交流群.jpg`:README 中展示的官网 / QQ 交流群入口图片。
-- `docs/images/微信.png`:侧边栏 Plus 功能支持弹窗使用的微信打赏二维码图片。
- `docs/refactor/2026-04-16-architecture-refactor-plan.md`:本轮重构阶段的结构设计与迁移计划记录。
- `docs/superpowers/plans/2026-04-10-hotmail-oauth-mail-pool.md`:Hotmail OAuth 邮箱池实现计划文档。
- `docs/superpowers/specs/2026-04-10-hotmail-oauth-design.md`:Hotmail OAuth + Graph Mail 的设计规格文档。