Merge pull request #206 from initiatione/codex/free-phone-reuse-upstream

增加 HeroSMS 免费手机号复用
This commit is contained in:
Q3CC
2026-05-06 19:39:05 +08:00
committed by GitHub
13 changed files with 2170 additions and 15 deletions
+14
View File
@@ -2367,6 +2367,20 @@ header {
min-height: 33px;
}
.hero-sms-free-phone-cell {
flex-wrap: wrap;
}
.hero-sms-manual-phone-input {
flex: 1 1 150px;
min-width: 120px;
}
.hero-sms-free-phone-country {
flex: 0 0 auto;
white-space: nowrap;
}
.signup-phone-runtime-inline,
.signup-phone-runtime-input {
width: 100%;
+30
View File
@@ -1471,6 +1471,28 @@
<span class="data-unit"></span>
</div>
</div>
<div id="row-free-phone-reuse-enabled" class="hero-sms-settings-cell hero-sms-reuse-cell" style="display:none;">
<span class="hero-sms-settings-caption">白嫖复用</span>
<div class="setting-controls hero-sms-toggle-controls">
<label class="toggle-switch" for="input-free-phone-reuse-enabled" title="开启后保存首个成功接码手机号;自动复用关闭时,下次只填号码、不提交,并停止自动流程">
<input type="checkbox" id="input-free-phone-reuse-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
</div>
<div id="row-free-phone-reuse-auto-enabled" class="hero-sms-settings-cell hero-sms-reuse-cell" style="display:none;">
<span class="hero-sms-settings-caption">自动白嫖复用</span>
<div class="setting-controls hero-sms-toggle-controls">
<label class="toggle-switch" for="input-free-phone-reuse-auto-enabled" title="开启后自动将已记录手机号设为等待短信状态,自动提交并轮询验证码;最多复用 3 次">
<input type="checkbox" id="input-free-phone-reuse-auto-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
</div>
</div>
</div>
</div>
@@ -1491,6 +1513,14 @@
<span class="hero-sms-runtime-key">验证码</span>
<span id="display-hero-sms-current-code" class="data-value mono hero-sms-runtime-value">未获取</span>
</div>
<div id="row-free-reusable-phone" class="hero-sms-runtime-cell hero-sms-runtime-cell-span2 hero-sms-free-phone-cell" style="display:none;">
<span class="hero-sms-runtime-key">白嫖号码</span>
<input type="text" id="input-free-reusable-phone" class="data-input mono hero-sms-manual-phone-input" placeholder="复用手机号" title="已记录号码会自动显示;未记录时可手动填写并记录" />
<span id="display-free-reusable-phone-country" class="data-value hero-sms-free-phone-country">地区:未保存</span>
<span id="display-free-reusable-phone" class="data-value mono hero-sms-runtime-value">未保存</span>
<button id="btn-save-free-reusable-phone" class="btn btn-outline btn-xs data-inline-btn hero-sms-runtime-action" type="button">记录</button>
<button id="btn-clear-free-reusable-phone" class="btn btn-ghost btn-xs data-inline-btn hero-sms-runtime-action" type="button">清除</button>
</div>
<div id="row-hero-sms-preferred-activation" class="hero-sms-runtime-cell hero-sms-runtime-cell-span2" style="display:none;">
<span class="hero-sms-runtime-key">优先号码</span>
<select id="select-hero-sms-preferred-activation" class="data-input mono hero-sms-runtime-select">
+191 -1
View File
@@ -408,6 +408,9 @@ const rowPhoneCodeWaitSeconds = document.getElementById('row-phone-code-wait-sec
const rowPhoneCodeTimeoutWindows = document.getElementById('row-phone-code-timeout-windows');
const rowPhoneCodePollIntervalSeconds = document.getElementById('row-phone-code-poll-interval-seconds');
const rowPhoneCodePollMaxRounds = document.getElementById('row-phone-code-poll-max-rounds');
const rowFreePhoneReuseEnabled = document.getElementById('row-free-phone-reuse-enabled');
const rowFreePhoneReuseAutoEnabled = document.getElementById('row-free-phone-reuse-auto-enabled');
const rowFreeReusablePhone = document.getElementById('row-free-reusable-phone');
const inputHeroSmsApiKey = document.getElementById('input-hero-sms-api-key');
const btnToggleHeroSmsApiKey = document.getElementById('btn-toggle-hero-sms-api-key');
const inputFiveSimApiKey = document.getElementById('input-five-sim-api-key');
@@ -425,6 +428,9 @@ const inputPhoneCodeTimeoutWindows = document.getElementById('input-phone-code-t
const inputPhoneCodePollIntervalSeconds = document.getElementById('input-phone-code-poll-interval-seconds');
const inputPhoneCodePollMaxRounds = document.getElementById('input-phone-code-poll-max-rounds');
const inputHeroSmsReuseEnabled = document.getElementById('input-hero-sms-reuse-enabled');
const inputFreePhoneReuseEnabled = document.getElementById('input-free-phone-reuse-enabled');
const inputFreePhoneReuseAutoEnabled = document.getElementById('input-free-phone-reuse-auto-enabled');
const inputFreeReusablePhone = document.getElementById('input-free-reusable-phone');
const selectHeroSmsCountry = document.getElementById('select-hero-sms-country');
const selectHeroSmsCountryFallback = document.getElementById('select-hero-sms-country-fallback');
const selectHeroSmsAcquirePriority = document.getElementById('select-hero-sms-acquire-priority');
@@ -456,10 +462,14 @@ const displayHeroSmsCurrentCountdown = document.getElementById('display-hero-sms
const displayHeroSmsPriceTiers = document.getElementById('display-hero-sms-price-tiers');
const displayPhoneSmsBalance = document.getElementById('display-phone-sms-balance');
const displayHeroSmsCurrentCode = document.getElementById('display-hero-sms-current-code');
const displayFreeReusablePhoneCountry = document.getElementById('display-free-reusable-phone-country');
const displayFreeReusablePhone = document.getElementById('display-free-reusable-phone');
const displayHeroSmsCountryFallbackOrder = document.getElementById('display-hero-sms-country-fallback-order');
const displayFiveSimCountryFallbackOrder = document.getElementById('display-five-sim-country-fallback-order');
const displayNexSmsCountryFallbackOrder = document.getElementById('display-nex-sms-country-fallback-order');
const displayPhoneSmsProviderOrder = document.getElementById('display-phone-sms-provider-order');
const btnSaveFreeReusablePhone = document.getElementById('btn-save-free-reusable-phone');
const btnClearFreeReusablePhone = document.getElementById('btn-clear-free-reusable-phone');
const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
const inputAccountRunHistoryHelperBaseUrl = document.getElementById('input-account-run-history-helper-base-url');
const autoStartModal = document.getElementById('auto-start-modal');
@@ -2989,6 +2999,12 @@ function collectSettingsPayload() {
const heroSmsReuseEnabledValue = typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled
? normalizeHeroSmsReuseEnabledValue(inputHeroSmsReuseEnabled.checked)
: defaultHeroSmsReuseEnabled;
const freePhoneReuseEnabledValue = typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled
? Boolean(inputFreePhoneReuseEnabled.checked)
: Boolean(latestState?.freePhoneReuseEnabled);
const freePhoneReuseAutoEnabledValue = typeof inputFreePhoneReuseAutoEnabled !== 'undefined' && inputFreePhoneReuseAutoEnabled
? Boolean(inputFreePhoneReuseAutoEnabled.checked)
: Boolean(latestState?.freePhoneReuseAutoEnabled);
const defaultHeroSmsAcquirePriority = typeof DEFAULT_HERO_SMS_ACQUIRE_PRIORITY !== 'undefined'
? DEFAULT_HERO_SMS_ACQUIRE_PRIORITY
: (typeof HERO_SMS_ACQUIRE_PRIORITY_COUNTRY !== 'undefined' ? HERO_SMS_ACQUIRE_PRIORITY_COUNTRY : 'country');
@@ -3364,6 +3380,8 @@ function collectSettingsPayload() {
nexSmsCountryOrder: nexSmsCountryOrderValue,
nexSmsServiceCode: nexSmsServiceCodeValue,
heroSmsReuseEnabled: heroSmsReuseEnabledValue,
freePhoneReuseEnabled: freePhoneReuseEnabledValue,
freePhoneReuseAutoEnabled: freePhoneReuseAutoEnabledValue,
heroSmsAcquirePriority: heroSmsAcquirePriorityValue,
heroSmsMaxPrice: heroSmsMaxPriceValue,
heroSmsPreferredPrice: heroSmsPreferredPriceValue,
@@ -5363,10 +5381,79 @@ function updateHeroSmsRuntimeDisplay(state = {}) {
const code = String(state?.currentPhoneVerificationCode ?? latestState?.currentPhoneVerificationCode ?? '').trim();
displayHeroSmsCurrentCode.textContent = code || '未获取';
}
if (displayFreeReusablePhone || displayFreeReusablePhoneCountry || inputFreeReusablePhone) {
const activation = state?.freeReusablePhoneActivation ?? latestState?.freeReusablePhoneActivation ?? null;
const phoneNumber = String(activation?.phoneNumber || '').trim();
const activationId = String(activation?.activationId || '').trim();
const successfulUses = Number.isFinite(Number(activation?.successfulUses))
? Number(activation.successfulUses)
: null;
const maxUses = Number.isFinite(Number(activation?.maxUses))
? Number(activation.maxUses)
: null;
const countryLabel = normalizePhoneSmsCountryLabel(
activation?.countryLabel
|| getHeroSmsCountryLabelById(activation?.countryId || '')
|| state?.heroSmsCountryLabel
|| latestState?.heroSmsCountryLabel
|| getHeroSmsCountryLabelById(state?.heroSmsCountryId || latestState?.heroSmsCountryId || ''),
activation?.provider || getSelectedPhoneSmsProvider()
);
const usesText = successfulUses !== null || maxUses !== null
? ` / ${successfulUses ?? 0}/${maxUses ?? 3}`
: '';
if (displayFreeReusablePhone) {
displayFreeReusablePhone.textContent = phoneNumber
? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${usesText}`
: '未保存';
}
if (displayFreeReusablePhoneCountry) {
displayFreeReusablePhoneCountry.textContent = phoneNumber
? `地区:${countryLabel || '未保存'}`
: '地区:未保存';
}
if (inputFreeReusablePhone) {
inputFreeReusablePhone.value = phoneNumber;
}
}
syncPhoneRuntimeCountdown(state);
renderPhonePreferredActivationOptions(state);
}
function applyFreeReusablePhoneMutationResult(refreshedState = {}, mutationResult = {}, options = {}) {
const { clear = false } = options;
const explicitActivation = clear
? null
: (
mutationResult?.freeReusablePhoneActivation
|| mutationResult?.state?.freeReusablePhoneActivation
|| null
);
const statePatch = {
...(refreshedState || {}),
...(mutationResult?.state || {}),
...(clear || explicitActivation
? { freeReusablePhoneActivation: explicitActivation }
: {}),
};
syncLatestState(statePatch);
updateHeroSmsRuntimeDisplay(latestState || statePatch);
}
async function refreshFreeReusablePhoneStateFallback(mutationResult = {}, options = {}) {
if (mutationResult?.state || mutationResult?.freeReusablePhoneActivation || options.clear) {
applyFreeReusablePhoneMutationResult(latestState, mutationResult, options);
return;
}
try {
const refreshedState = await chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' });
applyFreeReusablePhoneMutationResult(refreshedState || latestState, mutationResult, options);
} catch (error) {
console.warn('Failed to refresh free reusable phone state:', error);
applyFreeReusablePhoneMutationResult(latestState, mutationResult, options);
}
}
async function loadHeroSmsCountries() {
const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback;
if (!countrySelect) {
@@ -6970,6 +7057,8 @@ function updatePhoneVerificationSettingsUI() {
typeof rowPhoneCodeTimeoutWindows !== 'undefined' ? rowPhoneCodeTimeoutWindows : null,
typeof rowPhoneCodePollIntervalSeconds !== 'undefined' ? rowPhoneCodePollIntervalSeconds : null,
typeof rowPhoneCodePollMaxRounds !== 'undefined' ? rowPhoneCodePollMaxRounds : null,
typeof rowFreePhoneReuseEnabled !== 'undefined' ? rowFreePhoneReuseEnabled : null,
typeof rowFreePhoneReuseAutoEnabled !== 'undefined' ? rowFreePhoneReuseAutoEnabled : null,
];
phoneVerificationRows.forEach((row) => {
if (row) {
@@ -6992,12 +7081,33 @@ function updatePhoneVerificationSettingsUI() {
if (rowFiveSimOperator) {
rowFiveSimOperator.style.display = showSettings && fiveSimProvider ? '' : 'none';
}
if (typeof rowFreePhoneReuseEnabled !== 'undefined' && rowFreePhoneReuseEnabled) {
rowFreePhoneReuseEnabled.style.display = showSettings ? '' : 'none';
}
if (typeof rowFreePhoneReuseAutoEnabled !== 'undefined' && rowFreePhoneReuseAutoEnabled) {
rowFreePhoneReuseAutoEnabled.style.display = showSettings ? '' : 'none';
}
const freePhoneReuseEnabled = Boolean(
typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled?.checked
);
const freePhoneReuseAutoAvailable = showSettings && freePhoneReuseEnabled;
if (typeof inputFreePhoneReuseAutoEnabled !== 'undefined' && inputFreePhoneReuseAutoEnabled) {
inputFreePhoneReuseAutoEnabled.disabled = !freePhoneReuseAutoAvailable;
if (!freePhoneReuseAutoAvailable) {
inputFreePhoneReuseAutoEnabled.checked = false;
}
}
setFreePhoneReuseControlsLocked(isAutoRunLockedPhase() || isAutoRunScheduledPhase());
if (typeof rowFreePhoneReuseAutoEnabled !== 'undefined' && rowFreePhoneReuseAutoEnabled) {
rowFreePhoneReuseAutoEnabled.classList.toggle('is-disabled', !freePhoneReuseAutoAvailable);
}
const runtimeVisible = enabled;
[
typeof rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null,
typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null,
typeof rowHeroSmsCurrentCountdown !== 'undefined' ? rowHeroSmsCurrentCountdown : null,
typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : null,
typeof rowFreeReusablePhone !== 'undefined' ? rowFreeReusablePhone : null,
typeof rowHeroSmsPreferredActivation !== 'undefined' ? rowHeroSmsPreferredActivation : null,
].forEach((row) => {
if (row) {
@@ -7119,6 +7229,17 @@ function setSettingsCardLocked(locked) {
});
}
function setFreePhoneReuseControlsLocked(locked) {
if (inputFreePhoneReuseEnabled) {
inputFreePhoneReuseEnabled.disabled = locked;
}
if (inputFreePhoneReuseAutoEnabled) {
inputFreePhoneReuseAutoEnabled.disabled = locked
|| !Boolean(inputFreePhoneReuseEnabled?.checked)
|| !Boolean(inputPhoneVerificationEnabled?.checked && phoneVerificationSectionExpanded);
}
}
async function setRuntimeEmailState(email) {
const normalizedEmail = String(email || '').trim() || null;
const response = await chrome.runtime.sendMessage({
@@ -7558,6 +7679,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
const settingsCardLocked = scheduled || locked;
setSettingsCardLocked(settingsCardLocked);
setFreePhoneReuseControlsLocked(settingsCardLocked);
inputRunCount.disabled = currentAutoRun.autoRunning || (
typeof shouldLockRunCountToEmailPool === 'function'
@@ -8120,6 +8242,12 @@ function applySettingsState(state) {
if (typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled) {
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(state?.heroSmsReuseEnabled);
}
if (typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled) {
inputFreePhoneReuseEnabled.checked = Boolean(state?.freePhoneReuseEnabled);
}
if (typeof inputFreePhoneReuseAutoEnabled !== 'undefined' && inputFreePhoneReuseAutoEnabled) {
inputFreePhoneReuseAutoEnabled.checked = Boolean(state?.freePhoneReuseAutoEnabled);
}
if (typeof selectHeroSmsAcquirePriority !== 'undefined' && selectHeroSmsAcquirePriority) {
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(state?.heroSmsAcquirePriority);
}
@@ -12459,6 +12587,59 @@ inputHeroSmsReuseEnabled?.addEventListener('change', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputFreePhoneReuseEnabled?.addEventListener('change', () => {
if (!inputFreePhoneReuseEnabled.checked && inputFreePhoneReuseAutoEnabled) {
inputFreePhoneReuseAutoEnabled.checked = false;
}
updatePhoneVerificationSettingsUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
inputFreePhoneReuseAutoEnabled?.addEventListener('change', () => {
updatePhoneVerificationSettingsUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
btnSaveFreeReusablePhone?.addEventListener('click', async () => {
const phoneNumber = String(inputFreeReusablePhone?.value || '').trim();
if (!phoneNumber) {
showToast?.('请先填写白嫖复用手机号。', 'warn', 2200);
inputFreeReusablePhone?.focus?.();
return;
}
try {
const response = await chrome.runtime.sendMessage({
type: 'SET_FREE_REUSABLE_PHONE', payload: { phoneNumber },
});
if (response?.error) {
throw new Error(response.error);
}
await refreshFreeReusablePhoneStateFallback(response || {});
showToast?.('已记录白嫖复用手机号。', 'success', 1800);
} catch (error) {
console.error('Failed to save free reusable phone:', error);
showToast?.(`记录白嫖复用手机号失败:${error?.message || error}`, 'error', 4000);
}
});
btnClearFreeReusablePhone?.addEventListener('click', async () => {
try {
const response = await chrome.runtime.sendMessage({
type: 'CLEAR_FREE_REUSABLE_PHONE',
});
if (response?.error) {
throw new Error(response.error);
}
await refreshFreeReusablePhoneStateFallback(response || {}, { clear: true });
showToast?.('已清除白嫖复用手机号。', 'info', 1800);
} catch (error) {
console.error('Failed to clear free reusable phone:', error);
showToast?.(`清除白嫖复用手机号失败:${error?.message || error}`, 'error', 4000);
}
});
selectHeroSmsAcquirePriority?.addEventListener('change', () => {
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(selectHeroSmsAcquirePriority.value);
markSettingsDirty(true);
@@ -13283,6 +13464,14 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.payload.heroSmsReuseEnabled !== undefined && inputHeroSmsReuseEnabled) {
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(message.payload.heroSmsReuseEnabled);
}
if (message.payload.freePhoneReuseEnabled !== undefined && inputFreePhoneReuseEnabled) {
inputFreePhoneReuseEnabled.checked = Boolean(message.payload.freePhoneReuseEnabled);
updatePhoneVerificationSettingsUI();
}
if (message.payload.freePhoneReuseAutoEnabled !== undefined && inputFreePhoneReuseAutoEnabled) {
inputFreePhoneReuseAutoEnabled.checked = Boolean(message.payload.freePhoneReuseAutoEnabled);
updatePhoneVerificationSettingsUI();
}
if (message.payload.heroSmsAcquirePriority !== undefined && selectHeroSmsAcquirePriority) {
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(message.payload.heroSmsAcquirePriority);
}
@@ -13437,7 +13626,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
: latestState?.heroSmsCountryFallback
);
applyHeroSmsFallbackSelection(
[...nextPrimaryCountries, ...nextFallback],
[nextPrimary, ...nextFallback],
{ includePrimary: true }
);
updateHeroSmsPlatformDisplay();
@@ -13451,6 +13640,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|| message.payload.currentPhoneVerificationCountdownEndsAt !== undefined
|| message.payload.currentPhoneVerificationCountdownWindowIndex !== undefined
|| message.payload.currentPhoneVerificationCountdownWindowTotal !== undefined
|| message.payload.freeReusablePhoneActivation !== undefined
|| message.payload.heroSmsLastPriceTiers !== undefined
|| message.payload.heroSmsLastPriceCountryId !== undefined
|| message.payload.heroSmsLastPriceCountryLabel !== undefined