541 lines
24 KiB
JavaScript
541 lines
24 KiB
JavaScript
(function smsBowerFloatingTool() {
|
||
if (window.__smsBowerFloatingToolInstalled) {
|
||
return;
|
||
}
|
||
window.__smsBowerFloatingToolInstalled = true;
|
||
|
||
const PANEL_ID = 'smsbower-floating-phone-tool';
|
||
const FALLBACK_COUNTRIES = [
|
||
{ id: 0, label: 'Russia' }, { id: 1, label: 'Ukraine' }, { id: 2, label: 'Kazakhstan' },
|
||
{ id: 3, label: 'China' }, { id: 4, label: 'Philippines' }, { id: 5, label: 'Myanmar' },
|
||
{ id: 6, label: 'Indonesia' }, { id: 7, label: 'Malaysia' }, { id: 10, label: 'Vietnam' },
|
||
{ id: 12, label: 'USA virtual' }, { id: 16, label: 'United Kingdom' }, { id: 22, label: 'India' },
|
||
{ id: 32, label: 'Romania' }, { id: 36, label: 'Canada' }, { id: 43, label: 'Germany' },
|
||
{ id: 52, label: 'Thailand' }, { id: 73, label: 'France' }, { id: 78, label: 'Turkey' },
|
||
{ id: 151, label: 'Japan' }, { id: 187, label: 'USA' }
|
||
];
|
||
const COUNTRY_ALIASES = {
|
||
0: '俄罗斯 俄国 Russia RU', 1: '乌克兰 Ukraine UA', 2: '哈萨克斯坦 Kazakhstan KZ',
|
||
3: '中国 China CN', 4: '菲律宾 Philippines PH', 5: '缅甸 Myanmar Burma MM',
|
||
6: '印度尼西亚 印尼 Indonesia ID', 7: '马来西亚 Malaysia MY', 10: '越南 Vietnam VN',
|
||
12: '美国虚拟 USA virtual United States US', 16: '英国 United Kingdom Britain GB UK',
|
||
22: '印度 India IN', 32: '罗马尼亚 Romania RO', 36: '加拿大 Canada CA',
|
||
43: '德国 Germany DE', 52: '泰国 Thailand TH', 73: '法国 France FR',
|
||
78: '土耳其 Turkey TR', 151: '日本 Japan JP', 187: '美国 USA United States US'
|
||
};
|
||
let allCountries = FALLBACK_COUNTRIES.slice();
|
||
let selectedCountries = [{ id: 6, label: 'Indonesia' }];
|
||
let latestSettings = null;
|
||
let panel = null;
|
||
let dragState = null;
|
||
let codePollToken = 0;
|
||
let autoRotateObserver = null;
|
||
let autoRotateTimer = null;
|
||
let autoRotateInFlight = false;
|
||
let autoRotateEnabled = true;
|
||
let lastAutoRotateSignature = '';
|
||
let lastAutoRotateAt = 0;
|
||
const AUTO_ROTATE_DEBOUNCE_MS = 800;
|
||
const AUTO_ROTATE_COOLDOWN_MS = 8000;
|
||
|
||
function stopCodePolling() {
|
||
codePollToken += 1;
|
||
}
|
||
|
||
function isVisible(el) {
|
||
if (!el) return false;
|
||
const style = window.getComputedStyle(el);
|
||
const rect = el.getBoundingClientRect();
|
||
return style.visibility !== 'hidden' && style.display !== 'none' && rect.width > 0 && rect.height > 0;
|
||
}
|
||
|
||
function scoreInput(el) {
|
||
const text = [el.type, el.name, el.id, el.placeholder, el.autocomplete, el.getAttribute('aria-label')].join(' ').toLowerCase();
|
||
let score = 0;
|
||
if (el === document.activeElement) score += 100;
|
||
if (/phone|mobile|tel|手机号|電話|电话|号码|number/.test(text)) score += 50;
|
||
if (el.type === 'tel') score += 40;
|
||
if (el.type === 'text' || !el.type) score += 5;
|
||
if (el.readOnly || el.disabled) score -= 1000;
|
||
if (!isVisible(el)) score -= 1000;
|
||
return score;
|
||
}
|
||
|
||
function findBestInput() {
|
||
const inputs = Array.from(document.querySelectorAll('input, textarea'))
|
||
.filter((el) => !panel || !panel.contains(el));
|
||
const ranked = inputs.map((el) => ({ el, score: scoreInput(el) })).sort((a, b) => b.score - a.score);
|
||
return ranked[0]?.score > -100 ? ranked[0].el : null;
|
||
}
|
||
|
||
function setValue(el, value) {
|
||
el.focus();
|
||
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
||
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
||
if (desc?.set) desc.set.call(el, value); else el.value = value;
|
||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||
}
|
||
|
||
function fillPhone(phoneNumber) {
|
||
const input = findBestInput();
|
||
if (!input) throw new Error('当前页面没有找到可填写的输入框。请先点一下目标输入框再获取。');
|
||
setValue(input, phoneNumber);
|
||
return { phoneNumber, tag: input.tagName, name: input.name || '', id: input.id || '' };
|
||
}
|
||
|
||
function send(type, payload = {}) {
|
||
return chrome.runtime.sendMessage({ type, payload }).then((res) => {
|
||
if (!res?.ok) throw new Error(res?.error || '操作失败');
|
||
return res;
|
||
});
|
||
}
|
||
|
||
function normalizeCountry(country) {
|
||
const id = Math.floor(Number(country?.id));
|
||
return { id, label: String(country?.label || `Country #${id}`).trim() };
|
||
}
|
||
|
||
function sameCountry(a, b) {
|
||
return Number(a?.id) === Number(b?.id);
|
||
}
|
||
|
||
function getCountrySearchText(country = {}) {
|
||
const id = Math.floor(Number(country?.id));
|
||
return [country?.label, id, COUNTRY_ALIASES[id] || ''].join(' ').toLowerCase();
|
||
}
|
||
|
||
function sleep(ms) {
|
||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
}
|
||
|
||
function $(selector) {
|
||
return panel?.querySelector(selector) || null;
|
||
}
|
||
|
||
function setStatus(text, isError = false) {
|
||
const node = $('.sb-status');
|
||
if (!node) return;
|
||
node.textContent = text;
|
||
node.style.color = isError ? '#fecaca' : '#bbf7d0';
|
||
}
|
||
|
||
async function copyText(text) {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
} catch {}
|
||
}
|
||
|
||
function collectSettings() {
|
||
return {
|
||
apiKey: $('.sb-api-key')?.value.trim() || '',
|
||
baseUrl: $('.sb-base-url')?.value.trim() || '',
|
||
serviceCode: $('.sb-service-code')?.value.trim() || 'dr',
|
||
minPrice: $('.sb-min-price')?.value.trim() || '',
|
||
maxPrice: $('.sb-max-price')?.value.trim() || '',
|
||
countries: selectedCountries,
|
||
};
|
||
}
|
||
|
||
async function saveSettings() {
|
||
stopCodePolling();
|
||
const res = await send('SAVE_SETTINGS', collectSettings());
|
||
latestSettings = res.settings;
|
||
selectedCountries = (res.settings.countries || []).map(normalizeCountry);
|
||
renderCountries();
|
||
setStatus('设置已保存');
|
||
}
|
||
|
||
async function refreshCountries() {
|
||
stopCodePolling();
|
||
await saveSettings();
|
||
setStatus('正在刷新国家列表...');
|
||
const res = await send('FETCH_COUNTRIES');
|
||
allCountries = (res.countries || []).map(normalizeCountry).filter((c) => c.id >= 0);
|
||
renderCountries();
|
||
setStatus('国家列表已刷新');
|
||
}
|
||
|
||
async function fetchAndFill(options = {}) {
|
||
stopCodePolling();
|
||
const btn = $('.sb-fetch');
|
||
if (btn) btn.disabled = true;
|
||
try {
|
||
if (!options.skipSave) await saveSettings();
|
||
const reason = options.reason || 'manual_replace';
|
||
const releaseAction = options.releaseAction || (reason === 'phone_number_used' ? 'ban' : 'cancel');
|
||
setStatus(releaseAction === 'ban' ? '正在释放被拒号码并获取新号...' : '正在取消上一个激活并获取新号...');
|
||
const res = await send('FETCH_NEXT_PHONE', { reason, releaseAction });
|
||
const phone = res.activation.phoneNumber;
|
||
await copyText(phone);
|
||
fillPhone(phone);
|
||
$('.sb-current-phone').textContent = phone;
|
||
$('.sb-current-id').textContent = res.activation.activationId;
|
||
const codeNode = $('.sb-current-code');
|
||
if (codeNode) codeNode.textContent = '无';
|
||
setStatus(`已切换、填入并复制:${phone}`);
|
||
} catch (error) {
|
||
setStatus(error.message || String(error), true);
|
||
} finally {
|
||
if (btn) btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function getPageTextForPhoneError() {
|
||
const clone = document.body?.cloneNode(true);
|
||
if (!clone) return '';
|
||
clone.querySelector(`#${PANEL_ID}`)?.remove();
|
||
return String(clone.innerText || clone.textContent || '').slice(-12000);
|
||
}
|
||
|
||
function updateAutoRotateToggle() {
|
||
const btn = $('.sb-auto-toggle');
|
||
if (!btn) return;
|
||
btn.textContent = autoRotateEnabled ? '自动切号:开' : '自动切号:关';
|
||
btn.classList.toggle('sb-auto-on', autoRotateEnabled);
|
||
}
|
||
|
||
function buildAutoRotateSignature(reason, text) {
|
||
const compact = String(text || '').replace(/\s+/g, ' ').trim();
|
||
return `${reason}:${compact.slice(-220)}`;
|
||
}
|
||
|
||
async function rotateFromClassifiedError(classified, source = 'manual') {
|
||
const text = getPageTextForPhoneError();
|
||
const signature = buildAutoRotateSignature(classified.reason, text);
|
||
const now = Date.now();
|
||
if (autoRotateInFlight) return false;
|
||
if (source === 'auto' && signature === lastAutoRotateSignature && now - lastAutoRotateAt < AUTO_ROTATE_COOLDOWN_MS) {
|
||
return false;
|
||
}
|
||
autoRotateInFlight = true;
|
||
lastAutoRotateSignature = signature;
|
||
lastAutoRotateAt = now;
|
||
try {
|
||
setStatus(source === 'auto'
|
||
? `检测到${classified.reason === 'phone_number_used' ? '号码已使用' : '验证码发送失败'},正在自动切号...`
|
||
: '检测到手机号错误,正在切号...');
|
||
await fetchAndFill({ reason: classified.reason, releaseAction: classified.releaseAction, skipSave: true });
|
||
return true;
|
||
} finally {
|
||
autoRotateInFlight = false;
|
||
}
|
||
}
|
||
|
||
async function detectAndRotate(source = 'manual') {
|
||
const text = getPageTextForPhoneError();
|
||
const classified = await send('CLASSIFY_PHONE_ERROR', { text });
|
||
if (!classified.matched) {
|
||
if (source !== 'auto') setStatus('未检测到明确的手机号错误。', true);
|
||
return false;
|
||
}
|
||
return rotateFromClassifiedError(classified, source);
|
||
}
|
||
|
||
function scheduleAutoRotateCheck() {
|
||
if (!autoRotateEnabled || autoRotateInFlight) return;
|
||
if (autoRotateTimer) clearTimeout(autoRotateTimer);
|
||
autoRotateTimer = setTimeout(() => {
|
||
autoRotateTimer = null;
|
||
detectAndRotate('auto').catch((error) => setStatus(error.message || String(error), true));
|
||
}, AUTO_ROTATE_DEBOUNCE_MS);
|
||
}
|
||
|
||
function startAutoRotateObserver() {
|
||
if (autoRotateObserver) return;
|
||
autoRotateObserver = new MutationObserver((mutations) => {
|
||
if (!autoRotateEnabled) return;
|
||
if (mutations.some((m) => m.target && panel && panel.contains(m.target))) return;
|
||
scheduleAutoRotateCheck();
|
||
});
|
||
if (document.body) {
|
||
autoRotateObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
|
||
}
|
||
window.addEventListener('focus', scheduleAutoRotateCheck);
|
||
}
|
||
|
||
async function cancelActive() {
|
||
stopCodePolling();
|
||
try {
|
||
setStatus('正在取消当前激活...');
|
||
await send('CANCEL_ACTIVE');
|
||
$('.sb-current-phone').textContent = '无';
|
||
$('.sb-current-id').textContent = '无';
|
||
const codeNode = $('.sb-current-code');
|
||
if (codeNode) codeNode.textContent = '无';
|
||
setStatus('当前激活已取消');
|
||
} catch (error) {
|
||
setStatus(error.message || String(error), true);
|
||
}
|
||
}
|
||
|
||
async function fetchAndCopyCode() {
|
||
stopCodePolling();
|
||
const token = codePollToken;
|
||
const btn = $('.sb-code');
|
||
if (btn) {
|
||
btn.disabled = true;
|
||
btn.textContent = '获取中...';
|
||
}
|
||
try {
|
||
let round = 0;
|
||
while (token === codePollToken) {
|
||
round += 1;
|
||
setStatus(`正在查询验证码,第 ${round} 次...`);
|
||
try {
|
||
const res = await send('FETCH_ACTIVE_CODE');
|
||
if (token !== codePollToken) return;
|
||
const codeNode = $('.sb-current-code');
|
||
if (codeNode) codeNode.textContent = res.code || '无';
|
||
setStatus(`已获取验证码:${res.code}`);
|
||
return;
|
||
} catch (error) {
|
||
if (token !== codePollToken) return;
|
||
const message = error.message || String(error);
|
||
if (/未知消息\s*[::]\s*FETCH_ACTIVE_CODE/i.test(message)) {
|
||
throw new Error('后台脚本还是旧版,没加载验证码接口。请到扩展管理页点“重新加载”,然后刷新当前网页并重新打开悬浮窗。');
|
||
}
|
||
if (/验证码还没到|STATUS_WAIT_CODE|NO_CODE|WAIT/i.test(message)) {
|
||
setStatus(`验证码还没到,1 秒后继续查询...(第 ${round} 次)`);
|
||
await sleep(1000);
|
||
continue;
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
if (token === codePollToken) setStatus(error.message || String(error), true);
|
||
} finally {
|
||
if (token === codePollToken && btn) {
|
||
btn.disabled = false;
|
||
btn.textContent = '获取验证码';
|
||
}
|
||
}
|
||
}
|
||
|
||
function renderCountries() {
|
||
const selectedBox = $('.sb-selected-countries');
|
||
const list = $('.sb-country-list');
|
||
const keyword = ($('.sb-country-search')?.value || '').trim().toLowerCase();
|
||
if (!selectedBox || !list) return;
|
||
selectedBox.innerHTML = '';
|
||
selectedCountries.forEach((country, index) => {
|
||
const chip = document.createElement('span');
|
||
chip.className = 'sb-chip';
|
||
chip.textContent = `${index + 1}. ${country.label} #${country.id}`;
|
||
const close = document.createElement('button');
|
||
close.type = 'button';
|
||
close.textContent = '×';
|
||
close.addEventListener('click', () => {
|
||
selectedCountries = selectedCountries.filter((x) => !sameCountry(x, country));
|
||
renderCountries();
|
||
});
|
||
chip.appendChild(close);
|
||
selectedBox.appendChild(chip);
|
||
});
|
||
list.innerHTML = '';
|
||
allCountries
|
||
.filter((c) => !keyword || getCountrySearchText(c).includes(keyword))
|
||
.slice(0, 120)
|
||
.forEach((country) => {
|
||
const btn = document.createElement('button');
|
||
btn.type = 'button';
|
||
btn.className = 'sb-country-item' + (selectedCountries.some((x) => sameCountry(x, country)) ? ' selected' : '');
|
||
btn.innerHTML = `<span>${escapeHtml(country.label)}</span><span>#${country.id}</span>`;
|
||
btn.addEventListener('click', () => {
|
||
if (selectedCountries.some((x) => sameCountry(x, country))) {
|
||
selectedCountries = selectedCountries.filter((x) => !sameCountry(x, country));
|
||
} else {
|
||
selectedCountries.push(country);
|
||
}
|
||
renderCountries();
|
||
});
|
||
list.appendChild(btn);
|
||
});
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return String(value || '').replace(/[&<>'"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[ch]));
|
||
}
|
||
|
||
function injectStyles() {
|
||
if (document.getElementById('smsbower-floating-phone-tool-style')) return;
|
||
const style = document.createElement('style');
|
||
style.id = 'smsbower-floating-phone-tool-style';
|
||
style.textContent = `
|
||
#${PANEL_ID} { position: fixed; z-index: 2147483647; right: 18px; top: 80px; width: 390px; max-height: calc(100vh - 110px); overflow: auto; box-sizing: border-box; padding: 12px; border: 1px solid #334155; border-radius: 14px; background: #0f172a; color: #e5e7eb; box-shadow: 0 18px 60px rgba(0,0,0,.45); font: 13px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
|
||
#${PANEL_ID} * { box-sizing: border-box; }
|
||
#${PANEL_ID} .sb-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; cursor: move; user-select: none; }
|
||
#${PANEL_ID} .sb-title { font-size: 16px; font-weight: 700; }
|
||
#${PANEL_ID} .sb-close { width: 28px; height: 28px; padding: 0; border: 1px solid #334155; border-radius: 8px; color: #e5e7eb; background: #1f2937; cursor: pointer; }
|
||
#${PANEL_ID} label { display: grid; gap: 4px; margin: 7px 0; color: #cbd5e1; }
|
||
#${PANEL_ID} input { width: 100%; padding: 7px 9px; border: 1px solid #334155; border-radius: 8px; color: #e5e7eb; background: #111827; outline: none; }
|
||
#${PANEL_ID} input:focus { border-color: #38bdf8; box-shadow: 0 0 0 2px rgba(56,189,248,.18); }
|
||
#${PANEL_ID} .sb-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
|
||
#${PANEL_ID} .sb-country-box { margin-top: 9px; padding: 9px; border: 1px solid #253244; border-radius: 10px; background: #111827; }
|
||
#${PANEL_ID} .sb-country-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
|
||
#${PANEL_ID} button { padding: 7px 10px; border: 1px solid #334155; border-radius: 8px; color: #e5e7eb; background: #1f2937; cursor: pointer; font: inherit; }
|
||
#${PANEL_ID} button:hover { background: #273449; }
|
||
#${PANEL_ID} button:disabled { opacity: .55; cursor: not-allowed; }
|
||
#${PANEL_ID} .sb-primary { background: #2563eb; border-color: #3b82f6; }
|
||
#${PANEL_ID} .sb-primary:hover { background: #1d4ed8; }
|
||
#${PANEL_ID} .sb-selected-countries { display: flex; flex-wrap: wrap; gap: 6px; min-height: 24px; margin: 7px 0; }
|
||
#${PANEL_ID} .sb-chip { display: inline-flex; gap: 5px; align-items: center; padding: 4px 7px; border-radius: 999px; background: #0e7490; color: #ecfeff; }
|
||
#${PANEL_ID} .sb-chip button { padding: 0 3px; border: 0; background: transparent; }
|
||
#${PANEL_ID} .sb-country-list { max-height: 150px; overflow: auto; border: 1px solid #253244; border-radius: 8px; }
|
||
#${PANEL_ID} .sb-country-item { display: flex; justify-content: space-between; gap: 8px; width: 100%; padding: 7px 9px; border: 0; border-bottom: 1px solid #1f2937; border-radius: 0; background: #0b1220; text-align: left; }
|
||
#${PANEL_ID} .sb-country-item.selected { background: #123044; color: #bae6fd; }
|
||
#${PANEL_ID} .sb-top-fetch { width: 100%; margin: 2px 0 10px; font-weight: 700; }
|
||
#${PANEL_ID} .sb-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 11px; }
|
||
#${PANEL_ID} .sb-code-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
|
||
#${PANEL_ID} .sb-current { margin-top: 10px; padding: 9px; border-radius: 10px; background: #020617; color: #cbd5e1; }
|
||
#${PANEL_ID} code { color: #67e8f9; user-select: all; }
|
||
#${PANEL_ID} .sb-hint { margin: 7px 0 0; color: #94a3b8; font-size: 12px; }
|
||
#${PANEL_ID} .sb-status { color: #bbf7d0; }
|
||
#${PANEL_ID} .sb-auto-on { border-color: #22c55e; color: #bbf7d0; }
|
||
`;
|
||
document.documentElement.appendChild(style);
|
||
}
|
||
|
||
async function createPanel() {
|
||
injectStyles();
|
||
panel = document.createElement('div');
|
||
panel.id = PANEL_ID;
|
||
panel.innerHTML = `
|
||
<div class="sb-head">
|
||
<span class="sb-title">SMSBower 填号</span>
|
||
<button class="sb-close" type="button" title="隐藏">×</button>
|
||
</div>
|
||
<button class="sb-fetch sb-primary sb-top-fetch" type="button">获取并填入</button>
|
||
<button class="sb-auto-toggle sb-auto-on" type="button">自动切号:开</button>
|
||
<label>API Key <input class="sb-api-key" type="password" placeholder="SMSBower API Key" /></label>
|
||
<label>API 地址 <input class="sb-base-url" type="url" /></label>
|
||
<div class="sb-row">
|
||
<label>项目 <input class="sb-service-code" type="text" value="dr" /></label>
|
||
<label>最低价 <input class="sb-min-price" type="number" min="0" step="0.0001" placeholder="可空" /></label>
|
||
<label>最高价 <input class="sb-max-price" type="number" min="0" step="0.0001" placeholder="可空" /></label>
|
||
</div>
|
||
<section class="sb-country-box">
|
||
<div class="sb-country-head"><strong>国家顺序</strong><button class="sb-refresh" type="button">刷新国家</button></div>
|
||
<input class="sb-country-search" type="search" placeholder="搜索国家 / ID" />
|
||
<div class="sb-selected-countries"></div>
|
||
<div class="sb-country-list"></div>
|
||
<p class="sb-hint">优先级:价低优先;同价按国家选择顺序。</p>
|
||
</section>
|
||
<div class="sb-actions">
|
||
<button class="sb-save" type="button">保存</button>
|
||
<button class="sb-rotate-detect" type="button">检测报错换号</button>
|
||
<button class="sb-rotate-used" type="button">已使用换号</button>
|
||
<button class="sb-cancel" type="button">取消当前</button>
|
||
</div>
|
||
<div class="sb-code-row">
|
||
<button class="sb-code sb-primary" type="button">获取验证码</button>
|
||
<button class="sb-copy-code" type="button">复制验证码</button>
|
||
</div>
|
||
<section class="sb-current">
|
||
<div>当前号码:<code class="sb-current-phone">无</code></div>
|
||
<div>激活ID:<code class="sb-current-id">无</code></div>
|
||
<div>验证码:<code class="sb-current-code">无</code></div>
|
||
<div>状态:<span class="sb-status">就绪</span></div>
|
||
</section>
|
||
`;
|
||
document.documentElement.appendChild(panel);
|
||
bindPanelEvents();
|
||
await loadSettingsIntoPanel();
|
||
updateAutoRotateToggle();
|
||
startAutoRotateObserver();
|
||
scheduleAutoRotateCheck();
|
||
}
|
||
|
||
function bindPanelEvents() {
|
||
$('.sb-close').addEventListener('click', () => { panel.style.display = 'none'; });
|
||
$('.sb-save').addEventListener('click', saveSettings);
|
||
$('.sb-fetch').addEventListener('click', () => fetchAndFill({ reason: 'manual_replace', releaseAction: 'cancel' }));
|
||
$('.sb-auto-toggle').addEventListener('click', () => {
|
||
autoRotateEnabled = !autoRotateEnabled;
|
||
updateAutoRotateToggle();
|
||
setStatus(autoRotateEnabled ? '自动切号已开启' : '自动切号已关闭');
|
||
if (autoRotateEnabled) scheduleAutoRotateCheck();
|
||
});
|
||
$('.sb-rotate-detect').addEventListener('click', () => detectAndRotate('manual'));
|
||
$('.sb-rotate-used').addEventListener('click', () => fetchAndFill({ reason: 'phone_number_used', releaseAction: 'ban' }));
|
||
$('.sb-cancel').addEventListener('click', cancelActive);
|
||
$('.sb-code').addEventListener('click', fetchAndCopyCode);
|
||
$('.sb-copy-code').addEventListener('click', async () => {
|
||
const code = $('.sb-current-code')?.textContent || '';
|
||
if (code && code !== '无') {
|
||
await copyText(code);
|
||
setStatus(`验证码已复制:${code}`);
|
||
} else {
|
||
setStatus('当前没有验证码可复制,请先点“获取验证码”。', true);
|
||
}
|
||
});
|
||
$('.sb-refresh').addEventListener('click', refreshCountries);
|
||
$('.sb-country-search').addEventListener('input', renderCountries);
|
||
const head = $('.sb-head');
|
||
head.addEventListener('mousedown', (event) => {
|
||
if (event.target.closest('button')) return;
|
||
const rect = panel.getBoundingClientRect();
|
||
dragState = { x: event.clientX, y: event.clientY, left: rect.left, top: rect.top };
|
||
event.preventDefault();
|
||
});
|
||
window.addEventListener('mousemove', (event) => {
|
||
if (!dragState) return;
|
||
panel.style.left = `${Math.max(0, dragState.left + event.clientX - dragState.x)}px`;
|
||
panel.style.top = `${Math.max(0, dragState.top + event.clientY - dragState.y)}px`;
|
||
panel.style.right = 'auto';
|
||
});
|
||
window.addEventListener('mouseup', () => { dragState = null; });
|
||
}
|
||
|
||
async function loadSettingsIntoPanel() {
|
||
const res = await send('GET_SETTINGS');
|
||
latestSettings = res.settings;
|
||
allCountries = (res.fallbackCountries || FALLBACK_COUNTRIES).map(normalizeCountry);
|
||
selectedCountries = (latestSettings.countries || []).map(normalizeCountry);
|
||
$('.sb-api-key').value = latestSettings.apiKey || '';
|
||
$('.sb-base-url').value = latestSettings.baseUrl || '';
|
||
$('.sb-service-code').value = latestSettings.serviceCode || 'dr';
|
||
$('.sb-min-price').value = latestSettings.minPrice || '';
|
||
$('.sb-max-price').value = latestSettings.maxPrice || '';
|
||
if (latestSettings.activeActivation) {
|
||
$('.sb-current-phone').textContent = latestSettings.activeActivation.phoneNumber || '无';
|
||
$('.sb-current-id').textContent = latestSettings.activeActivation.activationId || '无';
|
||
const codeNode = $('.sb-current-code');
|
||
if (codeNode) codeNode.textContent = latestSettings.activeActivation.lastCode || '无';
|
||
}
|
||
renderCountries();
|
||
}
|
||
|
||
async function togglePanel() {
|
||
try {
|
||
if (!panel || !document.documentElement.contains(panel)) {
|
||
await createPanel();
|
||
} else {
|
||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||
if (panel.style.display !== 'none') await loadSettingsIntoPanel();
|
||
}
|
||
} catch (error) {
|
||
alert(`SMSBower 悬浮窗打开失败:${error.message || error}`);
|
||
}
|
||
}
|
||
|
||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||
(async () => {
|
||
if (message?.type === 'FILL_PHONE') {
|
||
const phoneNumber = String(message.phoneNumber || '').trim();
|
||
if (!phoneNumber) throw new Error('缺少手机号。');
|
||
return { ok: true, ...fillPhone(phoneNumber) };
|
||
}
|
||
if (message?.type === 'TOGGLE_FLOAT_PANEL') {
|
||
await togglePanel();
|
||
return { ok: true };
|
||
}
|
||
return undefined;
|
||
})().then((result) => {
|
||
if (result !== undefined) sendResponse(result);
|
||
}).catch((error) => sendResponse({ ok: false, error: error.message || String(error) }));
|
||
return true;
|
||
});
|
||
})();
|