const DEFAULT_SETTINGS = Object.freeze({ provider: 'sms-bower', apiKey: '', baseUrl: 'https://smsbower.app/stubs/handler_api.php', lubanBaseUrl: 'https://lubansms.com/v2/api', fiveSimBaseUrl: 'https://5sim.net', serviceCode: 'dr', lubanServiceId: '', fiveSimCountry: 'vietnam', fiveSimOperator: 'any', fiveSimProduct: 'openai', minPrice: '', maxPrice: '', countries: [{ id: 6, label: 'Indonesia' }], activeActivation: null, }); const FALLBACK_COUNTRIES = Object.freeze([ { 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 PROVIDERS = Object.freeze({ 'sms-bower': { id: 'sms-bower', name: 'SMSBower', apiKeyParam: 'api_key', baseUrlKey: 'baseUrl', defaultBaseUrl: 'https://smsbower.app/stubs/handler_api.php', }, 'luban-sms': { id: 'luban-sms', name: 'Luban SMS', apiKeyParam: 'apikey', baseUrlKey: 'lubanBaseUrl', defaultBaseUrl: 'https://lubansms.com/v2/api', }, '5sim': { id: '5sim', name: '5SIM', apiKeyParam: '', baseUrlKey: 'fiveSimBaseUrl', defaultBaseUrl: 'https://5sim.net', }, }); function asText(value = '', fallback = '') { return String(value ?? '').trim() || fallback; } function normalizeProvider(value = '') { return PROVIDERS[value] ? value : DEFAULT_SETTINGS.provider; } function getProvider(settings = {}) { return PROVIDERS[normalizeProvider(settings.provider)] || PROVIDERS[DEFAULT_SETTINGS.provider]; } function normalizeCountryId(value, fallback = 6) { const parsed = Math.floor(Number(value)); return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback; } function normalizeFiveSimSlug(value, fallback = '') { const normalized = String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); return normalized || fallback; } function normalizePrice(value) { if (value === undefined || value === null || String(value).trim() === '') return null; const n = Number(value); return Number.isFinite(n) && n > 0 ? Math.round(n * 10000) / 10000 : null; } function normalizeFailureReason(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '_').replace(/^_+|_+$/g, '') || 'manual_replace'; } function isPhoneNumberUsedError(value) { const text = String(value || '').trim(); if (!text) return false; return /phone_max_usage_exceeded|phone_number_in_use|already\s+linked\s+to\s+the\s+maximum\s+number\s+of\s+accounts|phone\s+number\s+is\s+already\s+(?:in\s+use|linked|registered)|phone\s+number\s+has\s+already\s+been\s+used|already\s+associated\s+with\s+another\s+account|not\s+eligible\s+to\s+be\s+used|cannot\s+be\s+used\s+for\s+verification|号码.*(?:已|被).*(?:使用|占用|绑定|注册)|手机号.*(?:已|被).*(?:使用|占用|绑定|注册)|电话.*(?:已|被).*(?:使用|占用|绑定|注册)|该手机号.*(?:已|被).*(?:使用|占用|绑定|注册)|该电话.*(?:已|被).*(?:使用|占用|绑定|注册)/i.test(text); } function isPhoneNumberDeliveryRefusedError(value) { const text = String(value || '').trim(); if (!text) return false; return /无法向此电话号码发送验证码|无法向.*(?:电话号码|手机号|号码).*发送(?:验证码|短信)|(?:不能|无法).*发送.*(?:验证码|短信).*(?:电话号码|手机号|号码)|(?:cannot|can't|could\s*not|couldn't|unable\s+to)\s+(?:send|deliver).{0,80}(?:verification\s+code|code|sms|text(?:\s+message)?).{0,80}(?:phone|number)|(?:verification\s+code|sms|text(?:\s+message)?).{0,80}(?:cannot|can't|could\s*not|couldn't|unable\s+to).{0,80}(?:send|deliver)/i.test(text); } function getPhoneReplacementReleaseAction(reason = '') { const normalized = normalizeFailureReason(reason); if (normalized === 'code_rejected' || normalized === 'phone_number_used' || normalized === 'phone_number_in_use' || normalized === 'phone_max_usage_exceeded' || isPhoneNumberUsedError(reason)) return 'ban'; return 'cancel'; } function normalizeSettings(input = {}) { const countries = Array.isArray(input.countries) && input.countries.length ? input.countries.map((c) => ({ id: normalizeCountryId(c.id ?? c.country, 6), label: asText(c.label, `Country #${normalizeCountryId(c.id ?? c.country, 6)}`) })) : DEFAULT_SETTINGS.countries; const provider = normalizeProvider(input.provider); return { ...DEFAULT_SETTINGS, ...input, provider, apiKey: asText(input.apiKey), baseUrl: asText(input.baseUrl, DEFAULT_SETTINGS.baseUrl), lubanBaseUrl: asText(input.lubanBaseUrl, DEFAULT_SETTINGS.lubanBaseUrl), fiveSimBaseUrl: asText(input.fiveSimBaseUrl, DEFAULT_SETTINGS.fiveSimBaseUrl), serviceCode: asText(input.serviceCode, DEFAULT_SETTINGS.serviceCode).toLowerCase().replace(/[^a-z0-9_-]+/g, '') || DEFAULT_SETTINGS.serviceCode, lubanServiceId: asText(input.lubanServiceId ?? input.serviceId ?? input.luban_service_id, DEFAULT_SETTINGS.lubanServiceId).replace(/[^0-9a-zA-Z_-]+/g, ''), fiveSimCountry: normalizeFiveSimSlug(input.fiveSimCountry, DEFAULT_SETTINGS.fiveSimCountry), fiveSimOperator: normalizeFiveSimSlug(input.fiveSimOperator, DEFAULT_SETTINGS.fiveSimOperator), fiveSimProduct: normalizeFiveSimSlug(input.fiveSimProduct, DEFAULT_SETTINGS.fiveSimProduct), minPrice: normalizePrice(input.minPrice) === null ? '' : String(normalizePrice(input.minPrice)), maxPrice: normalizePrice(input.maxPrice) === null ? '' : String(normalizePrice(input.maxPrice)), countries, activeActivation: input.activeActivation && typeof input.activeActivation === 'object' ? input.activeActivation : null, }; } async function getSettings() { const stored = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS)); return normalizeSettings(stored); } async function saveSettings(update = {}) { const next = normalizeSettings({ ...(await getSettings()), ...update }); await chrome.storage.local.set(next); return next; } function providerBaseUrl(settings) { const provider = getProvider(settings); return asText(settings[provider.baseUrlKey], provider.defaultBaseUrl); } function buildUrl(settings, query = {}) { const provider = getProvider(settings); let base = providerBaseUrl(settings); const queryEntries = { ...query }; if (provider.id === 'luban-sms') { const endpoint = String(queryEntries.__endpoint || '').replace(/^\/+/, ''); delete queryEntries.__endpoint; if (endpoint) { const root = base.replace(/\/+(getBalance|getNumber|getSms|setStatus|getAgainNmuber|countries|List|smsHistory|getKeywordNumber|getKeywordSms|delKeywordNumber|keywordSmsHistory|bulksms)\/?$/i, ''); base = `${root.replace(/\/+$/, '')}/${endpoint}`; } } else if (provider.id === '5sim') { const path = String(queryEntries.__path || '').replace(/^\/+/, ''); delete queryEntries.__path; if (path) base = `${base.replace(/\/+$/, '')}/${path}`; } const url = new URL(base); if (settings.apiKey && provider.apiKeyParam) url.searchParams.set(provider.apiKeyParam, settings.apiKey); Object.entries(queryEntries).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v)); }); return url.toString(); } function parsePayload(text) { const t = String(text || '').trim(); if (!t) return ''; if ((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']'))) { try { return JSON.parse(t); } catch {} } return t; } function describePayload(payload) { if (typeof payload === 'string') return payload.trim(); if (payload && typeof payload === 'object') { const direct = payload.message || payload.msg || payload.error || payload.title || payload.status; if (direct) return String(direct).trim(); try { return JSON.stringify(payload); } catch {} } return String(payload || '').trim(); } async function apiRequest(settings, query, label = '接码平台请求') { const provider = getProvider(settings); if (!settings.apiKey) throw new Error(`请先填写 ${provider.name} API Key。`); const headers = { Accept: 'application/json, text/plain, */*' }; if (provider.id === '5sim') headers.Authorization = `Bearer ${settings.apiKey}`; const response = await fetch(buildUrl(settings, query), { method: 'GET', headers }); const payload = parsePayload(await response.text()); const message = describePayload(payload); const apiStatus = payload && typeof payload === 'object' && !Array.isArray(payload) ? Number(payload.status) : null; const code = payload && typeof payload === 'object' && !Array.isArray(payload) ? Number(payload.code) : null; if (!response.ok || apiStatus === 0 || (Number.isFinite(code) && code !== 0) || /^(BAD_KEY|BAD_ACTION|BAD_SERVICE|BAD_STATUS|NO_ACTIVATION|EARLY_CANCEL_DENIED|BAD_COUNTRY)\b/i.test(message)) { throw new Error(`${label}失败:${message || response.status}`); } return payload; } function normalizeActivation(payload, fallback = {}) { if (typeof payload === 'string') { const m = payload.match(/^ACCESS_NUMBER\s*:\s*([^:]+)\s*:\s*(.+)$/i); if (m) payload = { activationId: m[1], phoneNumber: m[2] }; } const src = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {}; const activationId = asText(src.activationId ?? src.activation_id ?? src.request_id ?? src.id ?? fallback.activationId); const phoneNumber = asText(src.phoneNumber ?? src.phone_number ?? src.phone ?? src.number ?? fallback.phoneNumber); if (!activationId || !phoneNumber) return null; const provider = fallback.provider || 'sms-bower'; const rawCountry = src.countryId ?? src.country_id ?? src.country ?? fallback.countryId; const countryId = provider === '5sim' ? normalizeFiveSimSlug(rawCountry, fallback.countryId || DEFAULT_SETTINGS.fiveSimCountry) : normalizeCountryId(rawCountry, fallback.countryId ?? 6); return { activationId, phoneNumber, provider, serviceCode: asText(src.serviceCode ?? src.service_id ?? src.product ?? fallback.serviceCode, fallback.serviceCode || DEFAULT_SETTINGS.serviceCode), countryId, countryLabel: asText(src.countryLabel ?? src.country_name_zh ?? src.country_name_en ?? src.country ?? fallback.countryLabel, provider === '5sim' ? countryId : `Country #${countryId}`), price: Number.isFinite(Number(src.price ?? src.cost ?? fallback.price)) ? Number(src.price ?? src.cost ?? fallback.price) : null, acquiredAt: Date.now(), }; } function collectPriceEntries(payload, entries = []) { if (Array.isArray(payload)) { payload.forEach((x) => collectPriceEntries(x, entries)); return entries; } if (!payload || typeof payload !== 'object') return entries; const count = Number(payload.count ?? payload.qty ?? payload.Qty); const price = Number(payload.cost ?? payload.price ?? payload.Price); if (Number.isFinite(price) && price >= 0) entries.push({ cost: price, count: Number.isFinite(count) ? count : 0, inStock: !Number.isFinite(count) || count > 0 }); Object.entries(payload).forEach(([key, value]) => { if (value && typeof value === 'object' && !Array.isArray(value)) { const keyedPrice = Number(key); const keyedCount = Number(value.count ?? value.qty ?? value.Qty); if (/^\d+(?:\.\d+)?$/.test(String(key)) && String(key).includes('.') && Number.isFinite(keyedPrice) && keyedPrice >= 0 && !Number.isFinite(Number(value.cost ?? value.price ?? value.Price))) { entries.push({ cost: keyedPrice, count: Number.isFinite(keyedCount) ? keyedCount : 0, inStock: !Number.isFinite(keyedCount) || keyedCount > 0 }); } } collectPriceEntries(value, entries); }); return entries; } function filterPriceEntries(entries, settings) { const min = normalizePrice(settings.minPrice); const max = normalizePrice(settings.maxPrice); if (min !== null && max !== null && min > max) throw new Error(`价格区间无效:最低价 ${min} 高于最高价 ${max}。`); return entries.filter((e) => e.inStock && (min === null || e.cost >= min) && (max === null || e.cost <= max)); } async function fetchSmsBowerCandidates(settings) { const out = []; for (let index = 0; index < settings.countries.length; index += 1) { const country = settings.countries[index]; try { const payload = await apiRequest(settings, { action: 'getPricesV3', service: settings.serviceCode, country: country.id }, `查询价格 ${country.label}`); const prices = filterPriceEntries(collectPriceEntries(payload), settings); prices.forEach((entry) => out.push({ country, countryIndex: index, price: entry.cost, count: entry.count })); } catch (error) { out.push({ country, countryIndex: index, price: Number.POSITIVE_INFINITY, count: 0, error: error.message || String(error) }); } } return out.filter((x) => Number.isFinite(x.price)).sort((a, b) => (a.price - b.price) || (a.countryIndex - b.countryIndex)); } async function fetchLubanCandidates(settings) { if (!settings.lubanServiceId) throw new Error('请先填写 Luban SMS service_id。可在 Luban 控制台或服务列表接口查询。'); let price = null; try { const payload = await apiRequest(settings, { __endpoint: 'List', country: '', service: '', language: 'en', page: 1 }, '查询 Luban 服务列表'); const list = Array.isArray(payload?.msg) ? payload.msg : []; const found = list.find((x) => String(x.service_id) === String(settings.lubanServiceId)); if (found && Number.isFinite(Number(found.cost))) price = Number(found.cost); } catch {} if (normalizePrice(settings.minPrice) !== null && price !== null && price < normalizePrice(settings.minPrice)) return []; if (normalizePrice(settings.maxPrice) !== null && price !== null && price > normalizePrice(settings.maxPrice)) return []; return [{ country: { id: -1, label: 'Luban service' }, countryIndex: 0, price: price ?? 0, count: 1, serviceId: settings.lubanServiceId }]; } async function fetchFiveSimCandidates(settings) { const country = normalizeFiveSimSlug(settings.fiveSimCountry, DEFAULT_SETTINGS.fiveSimCountry); const operator = normalizeFiveSimSlug(settings.fiveSimOperator, DEFAULT_SETTINGS.fiveSimOperator); const product = normalizeFiveSimSlug(settings.fiveSimProduct, DEFAULT_SETTINGS.fiveSimProduct); if (!country || !operator || !product) throw new Error('请先填写 5SIM 国家、运营商和产品。'); const max = normalizePrice(settings.maxPrice); return [{ country: { id: country, label: country }, countryIndex: 0, price: max ?? 0, count: 1, operator, product }]; } async function fetchPriceCandidates(settings) { const providerId = getProvider(settings).id; if (providerId === 'luban-sms') return fetchLubanCandidates(settings); if (providerId === '5sim') return fetchFiveSimCandidates(settings); return fetchSmsBowerCandidates(settings); } async function releaseActivation(settings, activation, releaseAction = 'cancel', quiet = false) { if (!activation?.activationId) return { ok: false, skipped: true, releaseAction }; const provider = getProvider(settings); const normalizedAction = String(releaseAction || '').trim().toLowerCase() === 'ban' ? 'ban' : 'cancel'; try { let payload; if (provider.id === 'luban-sms') payload = await apiRequest(settings, { __endpoint: 'setStatus', request_id: activation.activationId, status: 'reject' }, '释放 Luban 号码'); else if (provider.id === '5sim') payload = await apiRequest(settings, { __path: `v1/user/${normalizedAction === 'ban' ? 'ban' : 'cancel'}/${encodeURIComponent(activation.activationId)}` }, normalizedAction === 'ban' ? '5SIM 拉黑号码' : '5SIM 取消订单'); else payload = await apiRequest(settings, { action: 'setStatus', status: 8, id: activation.activationId }, normalizedAction === 'ban' ? '释放被拒号码' : '取消上一个号码激活'); return { ok: true, payload: describePayload(payload), releaseAction: normalizedAction, activationId: activation.activationId }; } catch (error) { if (!quiet) throw error; return { ok: false, error: error.message || String(error), releaseAction: normalizedAction, activationId: activation.activationId }; } } async function cancelActivation(settings, activation, quiet = false) { return releaseActivation(settings, activation, 'cancel', quiet); } async function banActivation(settings, activation, quiet = false) { return releaseActivation(settings, activation, 'ban', quiet); } async function requestActivation(settings, candidate) { const provider = getProvider(settings); if (provider.id === 'luban-sms') { const payload = await apiRequest(settings, { __endpoint: 'getNumber', service_id: candidate.serviceId || settings.lubanServiceId }, '购买 Luban 手机号'); const activation = normalizeActivation(payload, { provider: provider.id, serviceCode: settings.lubanServiceId, price: candidate.price, countryId: -1, countryLabel: 'Luban service' }); if (!activation) throw new Error(`购买手机号返回不可用响应:${describePayload(payload)}`); return activation; } if (provider.id === '5sim') { if (normalizePrice(settings.minPrice) !== null) throw new Error('5SIM 官方购买接口只支持 maxPrice,不支持最低价;请清空最低价。'); const query = {}; const maxPrice = normalizePrice(settings.maxPrice); if (maxPrice !== null) query.maxPrice = maxPrice; const payload = await apiRequest(settings, { __path: `v1/user/buy/activation/${encodeURIComponent(candidate.country.id)}/${encodeURIComponent(candidate.operator)}/${encodeURIComponent(candidate.product)}`, ...query, }, `购买 5SIM 手机号 ${candidate.country.label}`); const activation = normalizeActivation(payload, { provider: provider.id, serviceCode: candidate.product, price: payload?.price ?? candidate.price, countryId: candidate.country.id, countryLabel: candidate.country.label }); if (!activation) throw new Error(`购买手机号返回不可用响应:${describePayload(payload)}`); return activation; } const payload = await apiRequest(settings, { action: 'getNumberV2', service: settings.serviceCode, country: candidate.country.id, minPrice: normalizePrice(settings.minPrice), maxPrice: Number.isFinite(candidate.price) ? candidate.price : normalizePrice(settings.maxPrice) }, `购买手机号 ${candidate.country.label}`); const activation = normalizeActivation(payload, { provider: provider.id, countryId: candidate.country.id, countryLabel: candidate.country.label, serviceCode: settings.serviceCode, price: candidate.price }); if (!activation) throw new Error(`购买手机号返回不可用响应:${describePayload(payload)}`); return activation; } async function fetchNextPhone(options = {}) { const settings = await getSettings(); const previous = settings.activeActivation; const reason = normalizeFailureReason(options.reason || 'manual_replace'); const releaseAction = options.releaseAction || getPhoneReplacementReleaseAction(reason); const releasePrevious = previous ? await releaseActivation(settings, previous, releaseAction, true) : { ok: false, skipped: true, releaseAction }; const candidates = await fetchPriceCandidates(settings); if (!candidates.length) throw new Error('当前服务/价格范围内没有可用库存。'); const errors = []; for (const candidate of candidates) { try { const activation = await requestActivation(settings, candidate); await saveSettings({ activeActivation: { ...activation, replacedReason: reason, previousActivationId: previous?.activationId || '' } }); return { activation, releasePrevious, cancelPrevious: releasePrevious, tried: candidates.length, reason, releaseAction }; } catch (error) { errors.push(`${candidate.country.label}: ${error.message || error}`); } } throw new Error(`获取手机号失败:${errors.join(' | ')}`); } function extractVerificationCode(raw = '') { const text = describePayload(raw).replace(/^STATUS_OK\s*:\s*/i, '').trim(); const matched = text.match(/\b(\d{4,8})\b/); return matched?.[1] || text; } async function fetchActiveCode() { const settings = await getSettings(); const activation = settings.activeActivation; if (!activation?.activationId) throw new Error('当前没有激活号码,请先获取手机号。'); const provider = getProvider(settings); const payload = provider.id === 'luban-sms' ? await apiRequest(settings, { __endpoint: 'getSms', request_id: activation.activationId }, '查询 Luban 验证码') : (provider.id === '5sim' ? await apiRequest(settings, { __path: `v1/user/check/${encodeURIComponent(activation.activationId)}` }, '查询 5SIM 验证码') : await apiRequest(settings, { action: 'getStatus', id: activation.activationId }, '查询当前验证码')); const message = describePayload(payload); if (provider.id === 'luban-sms') { if (payload?.msg === 'success' && payload.sms_code) { const code = extractVerificationCode(payload.sms_code); await saveSettings({ activeActivation: { ...activation, lastCode: code, lastCodeRaw: message, lastCodeAt: Date.now() } }); return { code, raw: message, activationId: activation.activationId, phoneNumber: activation.phoneNumber }; } if (payload?.msg === 'wait') throw new Error('验证码还没到,稍后再点“复制验证码”。'); throw new Error(`暂未获取到验证码:${message || '空响应'}`); } if (provider.id === '5sim') { const smsList = Array.isArray(payload?.sms) ? payload.sms : []; for (let index = smsList.length - 1; index >= 0; index -= 1) { const item = smsList[index] || {}; const code = extractVerificationCode(item.code || item.text || ''); if (code) { await saveSettings({ activeActivation: { ...activation, lastCode: code, lastCodeRaw: message, lastCodeAt: Date.now() } }); return { code, raw: message, activationId: activation.activationId, phoneNumber: activation.phoneNumber }; } } const status = String(payload?.status || '').toUpperCase(); if (['CANCELED', 'BANNED', 'FINISHED', 'TIMEOUT'].includes(status)) throw new Error(`5SIM 订单状态异常:${status}`); throw new Error('验证码还没到,稍后再点“复制验证码”。'); } if (/^STATUS_OK\s*:/i.test(message)) { const code = extractVerificationCode(message); await saveSettings({ activeActivation: { ...activation, lastCode: code, lastCodeRaw: message, lastCodeAt: Date.now() } }); return { code, raw: message, activationId: activation.activationId, phoneNumber: activation.phoneNumber }; } if (/^STATUS_WAIT_CODE\b/i.test(message)) throw new Error('验证码还没到,稍后再点“复制验证码”。'); if (/^STATUS_CANCEL\b/i.test(message)) throw new Error('当前激活已取消。'); throw new Error(`暂未获取到验证码:${message || '空响应'}`); } async function fetchCountries(settings) { if (getProvider(settings).id === 'luban-sms') return [{ id: -1, label: 'Luban service_id 模式' }]; if (getProvider(settings).id === '5sim') return [{ id: settings.fiveSimCountry || DEFAULT_SETTINGS.fiveSimCountry, label: '5SIM country slug 模式' }]; try { const payload = await apiRequest(settings, { action: 'getCountries' }, '获取国家列表'); const list = []; if (Array.isArray(payload)) payload.forEach((x) => list.push({ id: normalizeCountryId(x.id ?? x.country ?? x.code, -1), label: asText(x.name ?? x.label ?? x.title, '') })); else if (payload && typeof payload === 'object') Object.entries(payload).forEach(([id, value]) => { if (value && typeof value === 'object') list.push({ id: normalizeCountryId(value.id ?? id, -1), label: asText(value.name ?? value.label ?? value.title, `Country #${id}`) }); else list.push({ id: normalizeCountryId(id, -1), label: asText(value, `Country #${id}`) }); }); const seen = new Set(); const normalized = list.filter((x) => x.id >= 0 && x.label && !seen.has(x.id) && seen.add(x.id)).sort((a, b) => a.id - b.id); return normalized.length ? normalized : FALLBACK_COUNTRIES.slice(); } catch { return FALLBACK_COUNTRIES.slice(); } } chrome.action.onClicked.addListener(async (tab) => { if (!tab?.id) return; try { await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] }); await chrome.tabs.sendMessage(tab.id, { type: 'TOGGLE_FLOAT_PANEL' }); } catch (error) { console.warn('SMSBower 悬浮窗打开失败:', error); } }); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { (async () => { const type = String(message?.type || ''); if (type === 'GET_SETTINGS') return { ok: true, settings: await getSettings(), fallbackCountries: FALLBACK_COUNTRIES }; if (type === 'SAVE_SETTINGS') return { ok: true, settings: await saveSettings(message.payload || {}) }; if (type === 'FETCH_COUNTRIES') return { ok: true, countries: await fetchCountries(await getSettings()) }; if (type === 'FETCH_NEXT_PHONE') return { ok: true, ...(await fetchNextPhone(message.payload || {})) }; if (type === 'ROTATE_PHONE') return { ok: true, ...(await fetchNextPhone(message.payload || {})) }; if (type === 'CLASSIFY_PHONE_ERROR') { const text = String(message.payload?.text || ''); const reason = isPhoneNumberUsedError(text) ? 'phone_number_used' : (isPhoneNumberDeliveryRefusedError(text) ? 'phone_delivery_refused' : 'unknown'); return { ok: true, matched: reason !== 'unknown', reason, releaseAction: getPhoneReplacementReleaseAction(reason || text) }; } if (type === 'FETCH_ACTIVE_CODE') return { ok: true, ...(await fetchActiveCode()) }; if (type === 'CANCEL_ACTIVE') { const settings = await getSettings(); const result = await cancelActivation(settings, settings.activeActivation, false); await saveSettings({ activeActivation: null }); return { ok: true, result }; } throw new Error(`未知消息:${type}`); })().then(sendResponse).catch((error) => sendResponse({ ok: false, error: error.message || String(error) })); return true; });