// phone-sms/providers/hero-sms.js — HeroSMS 接码平台适配层 (function attachHeroSmsProvider(root, factory) { root.PhoneSmsHeroSmsProvider = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createHeroSmsProviderModule() { const PROVIDER_ID = 'hero-sms'; const DEFAULT_BASE_URL = 'https://hero-sms.com/stubs/handler_api.php'; const DEFAULT_SERVICE_CODE = 'dr'; const DEFAULT_SERVICE_LABEL = 'OpenAI'; const DEFAULT_COUNTRY_ID = 52; const DEFAULT_COUNTRY_LABEL = 'Thailand'; const DEFAULT_REQUEST_TIMEOUT_MS = 20000; const DEFAULT_OPERATOR = 'any'; const DEFAULT_MAX_USES = 3; const DEFAULT_PRICE_LOOKUP_ATTEMPTS = 3; const DEFAULT_ACQUIRE_RETRY_ROUNDS = 3; const DEFAULT_ACQUIRE_RETRY_DELAY_MS = 2000; const DEFAULT_POLL_TIMEOUT_MS = 180000; const DEFAULT_POLL_INTERVAL_MS = 5000; const MAX_PRICE_CANDIDATES = 8; const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::'; const HERO_SMS_ACQUIRE_PRIORITY_COUNTRY = 'country'; const HERO_SMS_ACQUIRE_PRIORITY_PRICE = 'price'; const HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH = 'price_high'; const COUNTRY_BY_PHONE_PREFIX = Object.freeze([ { prefix: '84', id: 10, iso: 'VN', label: 'Vietnam' }, { prefix: '66', id: 52, iso: 'TH', label: 'Thailand' }, { prefix: '62', id: 6, iso: 'ID', label: 'Indonesia' }, { prefix: '44', id: 16, iso: 'GB', label: 'United Kingdom' }, { prefix: '81', id: 151, iso: 'JP', label: 'Japan' }, { prefix: '49', id: 43, iso: 'DE', label: 'Germany' }, { prefix: '33', id: 73, iso: 'FR', label: 'France' }, { prefix: '1', id: 187, iso: 'US', label: 'USA' }, ]); function normalizeHeroSmsCountryId(value, fallback = DEFAULT_COUNTRY_ID) { const parsed = Math.floor(Number(value)); if (Number.isFinite(parsed) && parsed > 0) return parsed; const fallbackParsed = Math.floor(Number(fallback)); return Number.isFinite(fallbackParsed) && fallbackParsed > 0 ? fallbackParsed : DEFAULT_COUNTRY_ID; } function normalizeOptionalHeroSmsCountryId(value) { const parsed = Math.floor(Number(value)); return Number.isFinite(parsed) && parsed > 0 ? parsed : 0; } function normalizeHeroSmsCountryLabel(value = '', fallback = DEFAULT_COUNTRY_LABEL) { return String(value || '').trim() || fallback; } function normalizeCountryKey(value) { const countryId = normalizeHeroSmsCountryId(value, 0); return countryId > 0 ? String(countryId) : ''; } function inferCountryFromPhoneNumber(phoneNumber = '') { const digits = String(phoneNumber || '').replace(/\D+/g, ''); if (!digits) { return null; } const match = COUNTRY_BY_PHONE_PREFIX.find((entry) => digits.startsWith(entry.prefix)); if (!match) { return null; } return { id: normalizeHeroSmsCountryId(match.id, 0), iso: match.iso, label: normalizeHeroSmsCountryLabel(match.label, `Country #${match.id}`), }; } function normalizeHeroSmsMaxPrice(value = '') { const rawValue = String(value ?? '').trim(); if (!rawValue) return ''; const numeric = Number(rawValue); if (!Number.isFinite(numeric) || numeric <= 0) return ''; return String(Math.round(numeric * 10000) / 10000); } function normalizeHeroSmsPrice(value) { const direct = Number(value); if (Number.isFinite(direct) && direct >= 0) { return direct; } const text = String(value ?? '').trim(); if (!text) { return null; } const matched = text.match(/-?\d+(?:[.,]\d+)?/); if (!matched) { return null; } const parsed = Number(String(matched[0] || '').replace(',', '.')); return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; } function normalizeHeroSmsPriceLimit(value) { if (value === undefined || value === null || String(value).trim() === '') { return null; } const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.round(parsed * 10000) / 10000; } function normalizeHeroSmsOperator(value = '', fallback = DEFAULT_OPERATOR) { const normalized = String(value || '') .trim() .toLowerCase() .replace(/[^a-z0-9_-]+/g, ''); if (normalized) { return normalized; } const fallbackNormalized = String(fallback || '') .trim() .toLowerCase() .replace(/[^a-z0-9_-]+/g, ''); return fallbackNormalized || DEFAULT_OPERATOR; } function normalizeHeroSmsAcquirePriority(value = '') { const normalized = String(value || '').trim().toLowerCase(); if (normalized === HERO_SMS_ACQUIRE_PRIORITY_PRICE) { return HERO_SMS_ACQUIRE_PRIORITY_PRICE; } if (normalized === HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH) { return HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH; } return HERO_SMS_ACQUIRE_PRIORITY_COUNTRY; } function normalizeActivationRetryRounds(value) { const parsed = Math.floor(Number(value)); if (!Number.isFinite(parsed) || parsed <= 0) { return DEFAULT_ACQUIRE_RETRY_ROUNDS; } return Math.max(1, Math.min(10, parsed)); } function normalizeActivationRetryDelayMs(value) { const parsed = Math.floor(Number(value)); if (!Number.isFinite(parsed) || parsed <= 0) { return DEFAULT_ACQUIRE_RETRY_DELAY_MS; } return Math.max(500, Math.min(30000, parsed)); } function normalizeHeroSmsCountryFallback(value = []) { const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,,;;]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const seen = new Set(); const normalized = []; for (const entry of source) { let id = 0; let label = ''; if (entry && typeof entry === 'object' && !Array.isArray(entry)) { id = normalizeHeroSmsCountryId(entry.id ?? entry.countryId, 0); label = String((entry.label ?? entry.countryLabel) || '').trim(); } else { const text = String(entry || '').trim(); const structured = text.match(/^(\d+)\s*(?:[:|/-]\s*(.+))?$/); id = normalizeHeroSmsCountryId(structured?.[1] || text, 0); label = String(structured?.[2] || '').trim(); } if (!id || seen.has(id)) continue; seen.add(id); normalized.push({ id, label: label || `Country #${id}` }); if (normalized.length >= 20) break; } return normalized; } function normalizeBaseUrl(value = '') { const trimmed = String(value || '').trim() || DEFAULT_BASE_URL; try { return new URL(trimmed).toString(); } catch { return DEFAULT_BASE_URL; } } function buildUrl(config = {}, query = {}) { const url = new URL(normalizeBaseUrl(config.baseUrl)); Object.entries(query || {}).forEach(([key, value]) => { if (value === undefined || value === null || value === '') return; url.searchParams.set(key, String(value)); }); return url.toString(); } function parsePayload(text) { const trimmed = String(text || '').trim(); if (!trimmed) return ''; if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) { try { return JSON.parse(trimmed); } catch { return trimmed; } } return trimmed; } function describePayload(raw) { if (typeof raw === 'string') return raw.trim(); if (raw && typeof raw === 'object') { const direct = String(raw.message || raw.msg || raw.error || raw.title || raw.status || '').trim(); if (direct) return direct; try { return JSON.stringify(raw); } catch { return String(raw); } } return String(raw || '').trim(); } function resolveConfig(state = {}, deps = {}) { return { apiKey: String(state.heroSmsApiKey || '').trim(), baseUrl: state.heroSmsBaseUrl || DEFAULT_BASE_URL, operator: normalizeHeroSmsOperator(state.heroSmsOperator, DEFAULT_OPERATOR), fetchImpl: deps.fetchImpl || (typeof fetch === 'function' ? fetch.bind(globalThis) : null), requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS, }; } function assertApiKey(config) { if (!config.apiKey) { throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); } } async function fetchPayload(config, query, actionLabel = 'HeroSMS request') { assertApiKey(config); if (query.api_key === undefined && config.apiKey) { query = { api_key: config.apiKey, ...query }; } if (!config.fetchImpl) { throw new Error('HeroSMS 网络请求实现不可用。'); } const controller = typeof AbortController === 'function' ? new AbortController() : null; const timeoutId = controller ? setTimeout(() => controller.abort(), Number(config.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS) : null; try { const response = await config.fetchImpl(buildUrl(config, query), { method: 'GET', signal: controller?.signal, }); const text = await response.text(); const payload = parsePayload(text); if (!response.ok) { const error = new Error(`${actionLabel}失败:${describePayload(payload) || response.status}`); error.payload = payload; error.status = response.status; throw error; } return payload; } catch (error) { if (error?.name === 'AbortError') { throw new Error(`${actionLabel}超时。`); } throw error; } finally { if (timeoutId) clearTimeout(timeoutId); } } function resolveCountryConfig(state = {}) { const hasExplicitPrimaryCountry = Object.prototype.hasOwnProperty.call(state || {}, 'heroSmsCountryId'); const fallbackList = normalizeHeroSmsCountryFallback(state.heroSmsCountryFallback); const primaryCountryId = normalizeOptionalHeroSmsCountryId(state.heroSmsCountryId); if (primaryCountryId > 0) { return { id: primaryCountryId, label: normalizeHeroSmsCountryLabel(state.heroSmsCountryLabel), }; } if (hasExplicitPrimaryCountry) { if (fallbackList.length) { const firstFallback = fallbackList[0]; return { id: normalizeOptionalHeroSmsCountryId(firstFallback.id), label: normalizeHeroSmsCountryLabel(firstFallback.label, `Country #${firstFallback.id}`), }; } return null; } return { id: normalizeHeroSmsCountryId(DEFAULT_COUNTRY_ID, DEFAULT_COUNTRY_ID), label: normalizeHeroSmsCountryLabel(state.heroSmsCountryLabel), }; } function resolveCountryCandidates(state = {}) { const primary = resolveCountryConfig(state); const fallbackList = normalizeHeroSmsCountryFallback(state.heroSmsCountryFallback); if (!primary || !Number.isFinite(primary.id) || primary.id <= 0) { return fallbackList .map((entry) => ({ id: normalizeOptionalHeroSmsCountryId(entry.id), label: normalizeHeroSmsCountryLabel(entry.label, `Country #${entry.id}`), })) .filter((entry) => entry.id > 0); } const seen = new Set([primary.id]); const candidates = [primary]; fallbackList.forEach((entry) => { const id = normalizeOptionalHeroSmsCountryId(entry.id); if (!id || seen.has(id)) return; seen.add(id); candidates.push({ id, label: normalizeHeroSmsCountryLabel(entry.label, `Country #${id}`) }); }); return candidates; } async function fetchBalance(state = {}, deps = {}) { const config = resolveConfig(state, deps); if (!config.apiKey) { throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。'); } const payload = await fetchPayload(config, { action: 'getBalance' }, 'HeroSMS getBalance'); const balance = Number(String(describePayload(payload)).replace(/^ACCESS_BALANCE:/i, '').trim()); return { balance, raw: payload }; } async function fetchPrices(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) { const config = resolveConfig(state, deps); return fetchPayload(config, { action: 'getPrices', service: DEFAULT_SERVICE_CODE, country: normalizeHeroSmsCountryId(countryConfig?.id), }, 'HeroSMS getPrices'); } function shouldUseExpandedPriceLookup(state = {}) { if (typeof state?.heroSmsUseExpandedPriceLookup === 'boolean') { return state.heroSmsUseExpandedPriceLookup; } const runningInNode = ( typeof process !== 'undefined' && process && process.versions && process.versions.node ); return !runningInNode; } function resolveStockState(payload = {}) { const physicalCount = Number(payload.physicalCount); if (Number.isFinite(physicalCount)) { return { hasStockField: true, stockCount: physicalCount }; } const stockCandidates = [ payload.count, payload.stock, payload.available, payload.quantity, payload.qty, payload.left, payload.free, ] .map((value) => Number(value)) .filter((value) => Number.isFinite(value)); if (!stockCandidates.length) { return { hasStockField: false, stockCount: 0 }; } return { hasStockField: true, stockCount: Math.max(...stockCandidates) }; } function collectPriceCandidates(payload, candidates = []) { if (Array.isArray(payload)) { payload.forEach((entry) => collectPriceCandidates(entry, candidates)); return candidates; } if (!payload || typeof payload !== 'object') { return candidates; } const cost = normalizeHeroSmsPrice(payload.cost); if (cost !== null) { const stockState = resolveStockState(payload); if (!stockState.hasStockField || stockState.stockCount > 0) { candidates.push(cost); } } Object.entries(payload).forEach(([key, value]) => { const keyedPrice = normalizeHeroSmsPrice(key); if (keyedPrice === null) { return; } if (value && typeof value === 'object') { const stockState = resolveStockState(value); if (stockState.hasStockField && stockState.stockCount > 0) { candidates.push(keyedPrice); } return; } const numericCount = Number(value); if (Number.isFinite(numericCount) && numericCount > 0) { candidates.push(keyedPrice); } }); Object.values(payload).forEach((value) => collectPriceCandidates(value, candidates)); return candidates; } function collectPriceCandidatesIncludingZeroStock(payload, candidates = []) { if (Array.isArray(payload)) { payload.forEach((entry) => collectPriceCandidatesIncludingZeroStock(entry, candidates)); return candidates; } if (!payload || typeof payload !== 'object') { return candidates; } const cost = normalizeHeroSmsPrice(payload.cost); if (cost !== null) { candidates.push(cost); } Object.entries(payload).forEach(([key, value]) => { const keyedPrice = normalizeHeroSmsPrice(key); if (keyedPrice === null) { return; } if (value && typeof value === 'object') { if (resolveStockState(value).hasStockField) { candidates.push(keyedPrice); } return; } if (Number.isFinite(Number(value))) { candidates.push(keyedPrice); } }); Object.values(payload).forEach((value) => collectPriceCandidatesIncludingZeroStock(value, candidates)); return candidates; } function buildSortedUniquePriceCandidates(values = []) { return Array.from( new Set( values .map((value) => normalizeHeroSmsPrice(value)) .filter((value) => value !== null) .map((value) => Math.round(value * 10000) / 10000) ) ) .sort((left, right) => left - right) .slice(0, MAX_PRICE_CANDIDATES); } function isPriceWithinRange(price, minPriceLimit = null, maxPriceLimit = null) { const numeric = Number(price); if (!Number.isFinite(numeric) || numeric <= 0) { return false; } const normalized = Math.round(numeric * 10000) / 10000; if (minPriceLimit !== null && normalized < minPriceLimit) { return false; } if (maxPriceLimit !== null && normalized > maxPriceLimit) { return false; } return true; } function filterPriceCandidatesWithinRange(prices = [], minPriceLimit = null, maxPriceLimit = null) { return (Array.isArray(prices) ? prices : []).filter((price) => ( isPriceWithinRange(price, minPriceLimit, maxPriceLimit) )); } function filterPriceCandidatesAboveFloor(prices = [], minExclusivePrice = null) { const floor = normalizeHeroSmsPrice(minExclusivePrice); if (floor === null || floor <= 0) { return Array.isArray(prices) ? [...prices] : []; } return (Array.isArray(prices) ? prices : []).filter((value) => { const numeric = Number(value); if (!Number.isFinite(numeric) || numeric <= 0) { return false; } return Math.round(numeric * 10000) / 10000 > floor; }); } function reorderPriceCandidates(prices = [], acquirePriority = HERO_SMS_ACQUIRE_PRIORITY_COUNTRY, preferredPrice = null) { const hasNullTier = Array.isArray(prices) && prices.some((value) => value === null || value === undefined || String(value).trim() === ''); const normalized = buildSortedUniquePriceCandidates( (Array.isArray(prices) ? prices : []).filter((value) => ( !(value === null || value === undefined || String(value).trim() === '') )) ); const ordered = acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH ? normalized.reverse() : normalized; const preferred = Number(preferredPrice); if (!Number.isFinite(preferred) || preferred <= 0) { if (ordered.length) { return ordered; } return hasNullTier ? [null] : []; } const normalizedPreferred = Math.round(preferred * 10000) / 10000; const withoutPreferred = ordered.filter((value) => value !== normalizedPreferred); return [normalizedPreferred, ...withoutPreferred]; } function normalizePriceFloorMap(rawMap = {}, normalizeCountryKey) { const normalizedMap = new Map(); if (!rawMap || typeof rawMap !== 'object') { return normalizedMap; } Object.entries(rawMap).forEach(([rawCountryKey, rawPrice]) => { const countryKey = String( typeof normalizeCountryKey === 'function' ? normalizeCountryKey(rawCountryKey) : rawCountryKey ).trim(); if (!countryKey) { return; } const normalizedPrice = normalizeHeroSmsPrice(rawPrice); if (normalizedPrice === null || normalizedPrice <= 0) { return; } normalizedMap.set(countryKey, Math.round(normalizedPrice * 10000) / 10000); }); return normalizedMap; } function formatPriceRangeText(minPriceLimit = null, maxPriceLimit = null) { const minPrice = normalizeHeroSmsPriceLimit(minPriceLimit); const maxPrice = normalizeHeroSmsPriceLimit(maxPriceLimit); if (minPrice !== null && maxPrice !== null) { return `${minPrice}~${maxPrice}`; } if (minPrice !== null) { return `${minPrice}~`; } if (maxPrice !== null) { return `~${maxPrice}`; } return 'unbounded'; } function resolvePriceRange(state = {}) { const minPriceLimit = normalizeHeroSmsPriceLimit(state?.heroSmsMinPrice); const maxPriceLimit = normalizeHeroSmsPriceLimit(state?.heroSmsMaxPrice); return { minPriceLimit, maxPriceLimit, hasMinPriceLimit: minPriceLimit !== null, hasMaxPriceLimit: maxPriceLimit !== null, invalidRange: minPriceLimit !== null && maxPriceLimit !== null && minPriceLimit > maxPriceLimit, }; } async function fetchPricePayloads(config, countryConfig, state = {}) { const payloads = []; const actions = shouldUseExpandedPriceLookup(state) ? ['getPricesExtended', 'getPrices'] : ['getPrices']; for (const action of actions) { try { const query = { action, service: DEFAULT_SERVICE_CODE, country: normalizeHeroSmsCountryId(countryConfig?.id), }; if (action === 'getPricesExtended') { query.freePrice = 'true'; } payloads.push(await fetchPayload(config, query, `HeroSMS ${action}`)); } catch (_) { // Price lookup is best-effort; acquisition may still probe a tier. } } return payloads; } async function persistPricePlanSnapshot(countryConfig, pricePlan, deps = {}) { if (typeof deps.setState !== 'function') { return; } const prices = Array.isArray(pricePlan?.prices) ? pricePlan.prices.filter((price) => Number.isFinite(Number(price))) : []; const userLimit = pricePlan?.userLimit === null || pricePlan?.userLimit === undefined ? '' : String(pricePlan.userLimit); await deps.setState({ heroSmsLastPriceTiers: prices, heroSmsLastPriceCountryId: normalizeHeroSmsCountryId(countryConfig?.id, 0), heroSmsLastPriceCountryLabel: normalizeHeroSmsCountryLabel(countryConfig?.label, DEFAULT_COUNTRY_LABEL), heroSmsLastPriceUserLimit: userLimit, heroSmsLastPriceAt: Date.now(), }); } async function resolvePricePlan(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) { const config = resolveConfig(state, deps); for (let attempt = 1; attempt <= DEFAULT_PRICE_LOOKUP_ATTEMPTS; attempt += 1) { try { const payloads = await fetchPricePayloads(config, countryConfig, state); const userLimit = normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice); const inStockCandidates = buildSortedUniquePriceCandidates( payloads.flatMap((payload) => collectPriceCandidates(payload, [])) ); const allCatalogCandidates = buildSortedUniquePriceCandidates( payloads.flatMap((payload) => collectPriceCandidatesIncludingZeroStock(payload, [])) ); const mergedCandidates = inStockCandidates.length ? buildSortedUniquePriceCandidates([...inStockCandidates, ...allCatalogCandidates]) : []; const minCatalogPrice = allCatalogCandidates.length ? allCatalogCandidates[0] : (mergedCandidates.length ? mergedCandidates[0] : null); let plan = null; if (userLimit !== null) { const bounded = mergedCandidates.filter((price) => price <= userLimit); plan = { prices: bounded.length > 0 ? bounded : [userLimit], userLimit, minCatalogPrice, syntheticUserLimitProbe: bounded.length <= 0, }; } else if (mergedCandidates.length > 0) { plan = { prices: mergedCandidates, userLimit: null, minCatalogPrice, syntheticUserLimitProbe: false, }; } else { plan = { prices: [null], userLimit: null, minCatalogPrice: null, syntheticUserLimitProbe: false, }; } await persistPricePlanSnapshot(countryConfig, plan, deps); if ( Array.isArray(plan.prices) && plan.prices.length > 0 && ( plan.prices.some((price) => Number.isFinite(Number(price)) && Number(price) > 0) || plan.syntheticUserLimitProbe ) ) { return plan; } } catch (_) { // Retry price lookup before falling back to automatic tier. } } const fallbackPlan = { prices: [null], userLimit: null, minCatalogPrice: null, syntheticUserLimitProbe: false, }; await persistPricePlanSnapshot(countryConfig, fallbackPlan, deps); return fallbackPlan; } function extractWrongMaxPrice(payload) { if (payload && typeof payload === 'object') { const title = String(payload.title || '').trim(); const minPrice = normalizeHeroSmsPrice(payload.info?.min); if (/^WRONG_MAX_PRICE$/i.test(title) && minPrice !== null) { return minPrice; } } const text = describePayload(payload); const match = text.match(/\bWRONG_MAX_PRICE:(\d+(?:\.\d+)?)\b/i); return match ? normalizeHeroSmsPrice(match[1]) : null; } function isNetworkFetchFailure(error) { return /failed to fetch|networkerror|load failed/i.test(String(error?.message || '').trim()); } function isNoNumbersPayload(payload) { return /\bNO_NUMBERS\b/i.test(describePayload(payload)); } function isTerminalError(payloadOrMessage) { const text = describePayload(payloadOrMessage); return /\bNO_BALANCE\b|\bNOT_ENOUGH_BALANCE\b|\bBAD_KEY\b|\bINVALID_KEY\b|\bBANNED\b|\bACCOUNT_BANNED\b|\bWRONG_KEY\b/i.test(text); } function formatActionName(action = '') { const normalized = String(action || '').trim().toLowerCase(); if (normalized === 'getnumber' || normalized === 'getnumberv2') { return '获取手机号'; } if (normalized === 'getstatus' || normalized === 'getstatusv2') { return '查询短信状态'; } if (normalized === 'setstatus') { return '更新订单状态'; } if (normalized === 'getprices' || normalized === 'getpricesextended') { return '查询价格'; } return action ? `${action} 请求` : '请求'; } function createActionFailureError(action, reason = '') { const rawReason = describePayload(reason) || '未知错误'; let normalizedReason = rawReason; if (/\bBAD_KEY\b|\bWRONG_KEY\b|\bINVALID_KEY\b/i.test(rawReason)) { normalizedReason = 'API Key 无效(BAD_KEY)'; } else if (/\bNO_BALANCE\b|\bNOT_ENOUGH_BALANCE\b/i.test(rawReason)) { normalizedReason = '余额不足'; } else if (/\bBANNED\b|\bACCOUNT_BANNED\b/i.test(rawReason)) { normalizedReason = '账号已被封禁'; } const error = new Error(`HeroSMS ${formatActionName(action)}失败:${normalizedReason}`); error.localizedPhoneSmsFailure = true; return error; } async function fetchActivationPayload(config, countryConfig, action, options = {}) { const query = { action, service: DEFAULT_SERVICE_CODE, country: normalizeHeroSmsCountryId(countryConfig?.id), }; const operator = normalizeHeroSmsOperator(config?.operator, DEFAULT_OPERATOR); if (operator && operator !== DEFAULT_OPERATOR) { query.operator = operator; } if (options.maxPrice !== null && options.maxPrice !== undefined) { query.maxPrice = options.maxPrice; if (options.fixedPrice !== false) { query.fixedPrice = 'true'; } } return fetchPayload(config, query, `HeroSMS ${action}`); } async function requestActivationWithPrice(config, countryConfig, action, maxPrice, options = {}) { let nextMaxPrice = maxPrice; let retriedWithUpdatedPrice = false; let retriedWithoutPrice = false; const userLimit = normalizeHeroSmsPriceLimit(options.userLimit); const userMinLimit = normalizeHeroSmsPriceLimit(options.userMinLimit); while (true) { try { return await fetchActivationPayload(config, countryConfig, action, { maxPrice: nextMaxPrice, fixedPrice: options.fixedPrice, }); } catch (error) { const updatedMaxPrice = extractWrongMaxPrice(error?.payload || error?.message); if ( nextMaxPrice !== null && nextMaxPrice !== undefined && !retriedWithUpdatedPrice && updatedMaxPrice !== null ) { if (userLimit !== null && updatedMaxPrice > userLimit) { throw new Error( `HeroSMS ${formatActionName(action)}失败:价格上限过低,平台要求至少 ${updatedMaxPrice},已超过当前配置的价格上限 ${userLimit}。` ); } if (userMinLimit !== null && updatedMaxPrice < userMinLimit) { throw new Error( `HeroSMS ${formatActionName(action)}失败:平台要求价格 ${updatedMaxPrice} 低于当前配置的最低购买价 ${userMinLimit}。` ); } nextMaxPrice = updatedMaxPrice; retriedWithUpdatedPrice = true; continue; } if ( nextMaxPrice !== null && nextMaxPrice !== undefined && !retriedWithoutPrice && isNetworkFetchFailure(error) ) { nextMaxPrice = null; retriedWithoutPrice = true; continue; } throw error; } } } function normalizeActivation(record, fallback = {}) { if (!record || typeof record !== 'object' || Array.isArray(record)) { return null; } const activationId = String(record.activationId ?? record.id ?? record.activation ?? '').trim(); const phoneNumber = String(record.phoneNumber ?? record.number ?? record.phone ?? '').trim(); if (!activationId || !phoneNumber) { return null; } const statusAction = String(record.statusAction || fallback.statusAction || '').trim(); return { activationId, phoneNumber, provider: PROVIDER_ID, serviceCode: String(record.serviceCode || fallback.serviceCode || DEFAULT_SERVICE_CODE).trim() || DEFAULT_SERVICE_CODE, countryId: normalizeHeroSmsCountryId(record.countryId ?? record.country ?? fallback.countryId), ...(record.countryLabel ? { countryLabel: normalizeHeroSmsCountryLabel(record.countryLabel, fallback.countryLabel) } : {}), successfulUses: Math.max(0, Math.floor(Number(record.successfulUses ?? fallback.successfulUses) || 0)), maxUses: Math.max(1, Math.floor(Number(record.maxUses ?? fallback.maxUses) || DEFAULT_MAX_USES)), ...(statusAction ? { statusAction } : {}), ...(record.source ? { source: String(record.source || '').trim() } : {}), }; } function resolveCountryLabel(state = {}, countryId = DEFAULT_COUNTRY_ID) { const countryKey = normalizeCountryKey(countryId); const matched = resolveCountryCandidates(state) .find((entry) => normalizeCountryKey(entry.id) === countryKey); return matched?.label || normalizeHeroSmsCountryLabel('', countryKey ? `Country #${countryKey}` : DEFAULT_COUNTRY_LABEL); } function resolveActivationCountry(activation = {}, state = {}) { const normalizedActivation = normalizeActivation(activation) || (activation && typeof activation === 'object' ? activation : {}); const inferred = inferCountryFromPhoneNumber(normalizedActivation.phoneNumber); const rawCountryId = normalizeHeroSmsCountryId(normalizedActivation.countryId, 0); const hasExplicitCountry = ( rawCountryId > 0 && !( normalizedActivation.manualOnly && rawCountryId === DEFAULT_COUNTRY_ID && inferred?.id && inferred.id !== rawCountryId ) ); const countryId = hasExplicitCountry ? rawCountryId : normalizeHeroSmsCountryId(inferred?.id, rawCountryId || DEFAULT_COUNTRY_ID); const matched = resolveCountryCandidates(state) .find((entry) => normalizeHeroSmsCountryId(entry.id, 0) === countryId); if (matched) { return matched; } return { id: countryId, label: normalizeHeroSmsCountryLabel( hasExplicitCountry ? normalizedActivation.countryLabel : (inferred?.label || normalizedActivation.countryLabel), `Country #${countryId}` ), }; } function getActivationCountryKey(activation = {}) { return normalizeCountryKey(activation?.countryId ?? activation?.country); } function getActivationPrice(activation = {}) { return normalizeHeroSmsPrice( activation?.selectedPrice ?? activation?.price ?? activation?.maxPrice ); } function parseActivationPayload(payload, fallback = {}) { const normalizedFallback = normalizeActivation(fallback) || (fallback && typeof fallback === 'object' ? fallback : {}); const direct = normalizeActivation(payload, normalizedFallback); if (direct) { return direct; } const text = describePayload(payload); const accessNumberMatch = text.match(/^ACCESS_NUMBER:([^:]+):(.+)$/i); if (accessNumberMatch) { return normalizeActivation({ activationId: String(accessNumberMatch[1] || '').trim(), phoneNumber: String(accessNumberMatch[2] || '').trim(), provider: PROVIDER_ID, serviceCode: normalizedFallback.serviceCode || DEFAULT_SERVICE_CODE, countryId: normalizedFallback.countryId || DEFAULT_COUNTRY_ID, successfulUses: normalizedFallback.successfulUses ?? 0, maxUses: normalizedFallback.maxUses ?? DEFAULT_MAX_USES, statusAction: normalizedFallback.statusAction, }); } if (/^ACCESS_READY$/i.test(text) && normalizedFallback?.activationId) { return normalizeActivation(normalizedFallback); } return null; } function resolveActivationStatusAction(activation = {}) { return activation?.statusAction === 'getStatusV2' ? 'getStatusV2' : 'getStatus'; } function extractVerificationCode(rawCode) { const trimmed = String(rawCode || '').trim(); if (!trimmed) { return ''; } return trimmed.match(/\b(\d{4,8})\b/)?.[1] || ''; } async function setActivationStatus(state = {}, activation, status, deps = {}, actionLabel = 'HeroSMS setStatus') { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { return ''; } const config = resolveConfig(state, deps); const payload = await fetchPayload(config, { action: 'setStatus', id: normalizedActivation.activationId, status: Math.floor(Number(status) || 0), }, actionLabel); return describePayload(payload); } async function requestActivation(state = {}, options = {}, deps = {}) { const config = resolveConfig(state, deps); const allCountryCandidates = resolveCountryCandidates(state); if (!allCountryCandidates.length) { throw new Error('步骤 9:HeroSMS 未选择国家,请先在接码设置中至少选择 1 个国家。'); } const blockedCountryIds = new Set( (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : []) .map((value) => normalizeHeroSmsCountryId(value, 0)) .filter((id) => id > 0) ); let countryCandidates = allCountryCandidates.filter( (entry) => !blockedCountryIds.has(normalizeHeroSmsCountryId(entry.id, 0)) ); if (!countryCandidates.length) { countryCandidates = allCountryCandidates; if (blockedCountryIds.size && typeof deps.addLog === 'function') { await deps.addLog('步骤 9:已选国家均达到临时收码失败跳过阈值,本轮解除跳过并重新尝试。', 'warn'); } } const acquirePriority = normalizeHeroSmsAcquirePriority(state?.heroSmsAcquirePriority); const priceRange = resolvePriceRange(state); if (priceRange.invalidRange) { throw new Error(`HeroSMS 价格区间无效:最低购买价 ${priceRange.minPriceLimit} 高于价格上限 ${priceRange.maxPriceLimit}。`); } const preferredPriceTier = normalizeHeroSmsPriceLimit(state?.heroSmsPreferredPrice); const countryPriceFloorByCountryId = normalizePriceFloorMap( options?.countryPriceFloorByCountryId, (value) => String(normalizeHeroSmsCountryId(value, 0)) ); const requestActions = ['getNumber', 'getNumberV2']; const maxAcquireRounds = Math.max(2, normalizeActivationRetryRounds(state?.heroSmsActivationRetryRounds)); const retryDelayMs = normalizeActivationRetryDelayMs(state?.heroSmsActivationRetryDelayMs); let finalNoNumbersByCountry = []; let finalLastError = null; let finalLastFailureText = ''; for (let round = 1; round <= maxAcquireRounds; round += 1) { if (maxAcquireRounds > 1 && typeof deps.addLog === 'function') { await deps.addLog(`步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`, 'info'); } const countryAttempts = countryCandidates.map((countryConfig, index) => ({ index, countryConfig, pricePlan: null, orderingPrice: Number.POSITIVE_INFINITY, })); if ( acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE || acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH ) { for (const attempt of countryAttempts) { const pricePlan = await resolvePricePlan(state, attempt.countryConfig, deps); const orderedPrices = reorderPriceCandidates(pricePlan?.prices, acquirePriority, preferredPriceTier); const rangeFilteredForRanking = filterPriceCandidatesWithinRange( orderedPrices, priceRange.minPriceLimit, priceRange.maxPriceLimit ); const rankingPrices = rangeFilteredForRanking.length ? rangeFilteredForRanking : ((priceRange.hasMinPriceLimit || priceRange.hasMaxPriceLimit) ? [] : orderedPrices); const numericPrices = rankingPrices .map((value) => Number(value)) .filter((value) => Number.isFinite(value) && value > 0); const candidateOrderingPrice = numericPrices.length ? numericPrices[0] : null; const cappedByUserLimit = ( pricePlan?.userLimit !== null && pricePlan?.userLimit !== undefined && pricePlan?.minCatalogPrice !== null && pricePlan?.minCatalogPrice !== undefined && Number(pricePlan.minCatalogPrice) > Number(pricePlan.userLimit) ); attempt.pricePlan = pricePlan; attempt.orderingPrice = cappedByUserLimit ? Number.POSITIVE_INFINITY : (candidateOrderingPrice !== null ? candidateOrderingPrice : Number.POSITIVE_INFINITY); } } if ( (acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE || acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH) && countryAttempts.length > 1 ) { countryAttempts.sort((left, right) => { if (left.orderingPrice !== right.orderingPrice) { return acquirePriority === HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH ? (right.orderingPrice - left.orderingPrice) : (left.orderingPrice - right.orderingPrice); } return left.index - right.index; }); } const noNumbersByCountry = []; const retryableNoNumberCountries = []; let lastError = null; let lastFailureText = ''; for (const attempt of countryAttempts) { const countryConfig = attempt.countryConfig; const countryIdKey = String(normalizeHeroSmsCountryId(countryConfig?.id, 0)); const countryPriceFloor = countryPriceFloorByCountryId.get(countryIdKey) ?? null; const buildFallbackActivation = (requestAction) => ({ countryId: countryConfig.id, ...(requestAction === 'getNumberV2' ? { statusAction: 'getStatusV2' } : {}), }); const pricePlan = attempt.pricePlan || await resolvePricePlan(state, countryConfig, deps); let noNumbersObservedInCountry = false; const orderedPrices = reorderPriceCandidates(pricePlan.prices, acquirePriority, preferredPriceTier); const rangeFilteredPrices = filterPriceCandidatesWithinRange( orderedPrices, priceRange.minPriceLimit, priceRange.maxPriceLimit ); const candidatePrices = rangeFilteredPrices.length ? rangeFilteredPrices : ((priceRange.hasMinPriceLimit || priceRange.hasMaxPriceLimit) ? [] : orderedPrices); const floorFilteredPrices = filterPriceCandidatesAboveFloor(candidatePrices, countryPriceFloor); const hasCountryPriceFloor = ( countryPriceFloor !== null && Number.isFinite(Number(countryPriceFloor)) && Number(countryPriceFloor) > 0 ); const hasAlternativeCountries = countryAttempts.some((entry) => ( String(normalizeHeroSmsCountryId(entry?.countryConfig?.id, 0)) !== String(normalizeHeroSmsCountryId(countryConfig?.id, 0)) )); const pricesToTry = hasCountryPriceFloor ? (floorFilteredPrices.length ? floorFilteredPrices : (hasAlternativeCountries ? [] : candidatePrices.slice(0, 1))) : (floorFilteredPrices.length ? floorFilteredPrices : candidatePrices); if (typeof deps.addLog === 'function') { const rawTierText = Array.isArray(pricePlan?.prices) && pricePlan.prices.length ? pricePlan.prices.map((value) => (value === null || value === undefined ? '自动' : String(value))).join(', ') : '无'; await deps.addLog( `步骤 9:HeroSMS ${countryConfig.label} 价格方案:档位=[${rawTierText}],用户上限=${pricePlan?.userLimit ?? '未设置'},目录最低价=${pricePlan?.minCatalogPrice ?? '未知'}。`, 'info' ); } if (!pricesToTry.length) { if (priceRange.hasMinPriceLimit && !rangeFilteredPrices.length) { noNumbersByCountry.push(`${countryConfig.label}: 价格区间 ${formatPriceRangeText(priceRange.minPriceLimit, priceRange.maxPriceLimit)} 内暂无可用号码`); continue; } if ( countryPriceFloor !== null && Array.isArray(pricePlan.prices) && pricePlan.prices.length > 0 ) { noNumbersByCountry.push(`${countryConfig.label}: 当前回退尝试没有高于 ${countryPriceFloor} 的价格档位`); continue; } if ( pricePlan.userLimit !== null && pricePlan.minCatalogPrice !== null && pricePlan.minCatalogPrice > pricePlan.userLimit ) { noNumbersByCountry.push(`${countryConfig.label}: 价格上限 ${pricePlan.userLimit} 内暂无可用号码;平台最低价=${pricePlan.minCatalogPrice}`); } else { noNumbersByCountry.push(`${countryConfig.label}: ${lastFailureText || 'NO_NUMBERS'}`); retryableNoNumberCountries.push(countryConfig.label); } continue; } for (const maxPrice of pricesToTry) { for (const requestAction of requestActions) { try { const fixedPrice = !Boolean(pricePlan.syntheticUserLimitProbe); if (typeof deps.addLog === 'function') { await deps.addLog( `步骤 9:HeroSMS ${countryConfig.label} 正在尝试${formatActionName(requestAction)},价格档位 ${maxPrice === null || maxPrice === undefined ? '自动' : maxPrice}。`, 'info' ); } const payload = await requestActivationWithPrice(config, countryConfig, requestAction, maxPrice, { userLimit: pricePlan.userLimit, userMinLimit: priceRange.minPriceLimit, fixedPrice, }); const activation = parseActivationPayload(payload, buildFallbackActivation(requestAction)); if (activation) { return { ...activation, countryId: normalizeHeroSmsCountryId(countryConfig.id), ...(Number.isFinite(Number(maxPrice)) && Number(maxPrice) > 0 ? { selectedPrice: Math.round(Number(maxPrice) * 10000) / 10000 } : {}), }; } const payloadText = describePayload(payload); if (isNoNumbersPayload(payload)) { noNumbersObservedInCountry = true; lastFailureText = payloadText || lastFailureText; continue; } if (isTerminalError(payload)) { throw createActionFailureError(requestAction, payloadText || 'empty response'); } lastFailureText = payloadText || lastFailureText; lastError = createActionFailureError(requestAction, payloadText || 'empty response'); } catch (error) { if (error?.localizedPhoneSmsFailure) { throw error; } const payloadOrMessage = error?.payload || error?.message; if (isTerminalError(payloadOrMessage)) { throw createActionFailureError(requestAction, payloadOrMessage || 'empty response'); } if (isNoNumbersPayload(payloadOrMessage)) { noNumbersObservedInCountry = true; lastFailureText = describePayload(payloadOrMessage) || lastFailureText; continue; } lastFailureText = describePayload(payloadOrMessage) || lastFailureText; lastError = error; } } } if (noNumbersObservedInCountry) { const tiersTriedText = pricesToTry .map((value) => (value === null || value === undefined ? '自动' : String(value))) .join(', '); noNumbersByCountry.push( `${countryConfig.label}: ${lastFailureText || 'NO_NUMBERS'}${tiersTriedText ? `(已尝试档位:${tiersTriedText})` : ''}` ); retryableNoNumberCountries.push(countryConfig.label); } } finalNoNumbersByCountry = noNumbersByCountry; finalLastError = lastError; finalLastFailureText = lastFailureText; if ( noNumbersByCountry.length && round < maxAcquireRounds && retryableNoNumberCountries.length > 0 ) { if (typeof deps.addLog === 'function') { await deps.addLog( `步骤 9:HeroSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮);${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`, 'warn' ); } await deps.sleepWithStop?.(retryDelayMs); continue; } break; } if (finalNoNumbersByCountry.length) { throw new Error(`HeroSMS 已尝试 ${countryCandidates.length} 个候选国家,均无可用号码:${finalNoNumbersByCountry.join(' | ')}。`); } if (finalLastError) { throw finalLastError; } throw new Error(`HeroSMS 获取手机号失败,最后状态:${finalLastFailureText || '未知'}。`); } async function reuseActivation(state = {}, activation, deps = {}) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { throw new Error('缺少可复用的 HeroSMS 手机号订单。'); } const config = resolveConfig(state, deps); const payload = await fetchPayload(config, { action: 'reactivate', id: normalizedActivation.activationId, }, 'HeroSMS reactivate'); const nextActivation = parseActivationPayload(payload, normalizedActivation); if (!nextActivation) { throw new Error(`HeroSMS 复用手机号失败:${describePayload(payload) || '空响应'}`); } return nextActivation; } async function pollActivationCode(state = {}, activation, options = {}, deps = {}) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { throw new Error('缺少手机号接码订单。'); } const config = resolveConfig(state, deps); const statusAction = resolveActivationStatusAction(normalizedActivation); const timeoutMs = Math.max(1000, Number(options.timeoutMs) || DEFAULT_POLL_TIMEOUT_MS); const intervalMs = Math.max(1000, Number(options.intervalMs) || DEFAULT_POLL_INTERVAL_MS); const maxRoundsRaw = Math.floor(Number(options.maxRounds)); const maxRounds = Number.isFinite(maxRoundsRaw) && maxRoundsRaw > 0 ? maxRoundsRaw : 0; const start = Date.now(); let lastResponse = ''; let pollCount = 0; const emitWaitingForCode = async (statusText) => { if (typeof options.onWaitingForCode === 'function') { await options.onWaitingForCode({ activation: normalizedActivation, elapsedMs: Date.now() - start, pollCount, statusText, timeoutMs, }); } }; while (Date.now() - start < timeoutMs) { if (maxRounds > 0 && pollCount >= maxRounds) { break; } deps.throwIfStopped?.(); const payload = await fetchPayload(config, { action: statusAction, id: normalizedActivation.activationId, }, `HeroSMS ${statusAction}`); const text = describePayload(payload); lastResponse = text; pollCount += 1; if (typeof options.onStatus === 'function') { await options.onStatus({ activation: normalizedActivation, elapsedMs: Date.now() - start, pollCount, statusText: text, timeoutMs, }); } const v2Code = ( payload && typeof payload === 'object' && !Array.isArray(payload) && ( extractVerificationCode(payload.sms?.code) || extractVerificationCode(payload.call?.code) ) ); if (v2Code) { return v2Code; } const okMatch = text.match(/^STATUS_OK:(.+)$/i); if (okMatch) { const code = extractVerificationCode(okMatch[1] || ''); if (code) { return code; } await emitWaitingForCode(text || 'STATUS_OK'); await deps.sleepWithStop?.(intervalMs); continue; } if (/^STATUS_(WAIT_CODE|WAIT_RETRY|WAIT_RESEND)(?::.+)?$/i.test(text)) { await emitWaitingForCode(text); await deps.sleepWithStop?.(intervalMs); continue; } if (statusAction === 'getStatusV2' && payload && typeof payload === 'object' && !Array.isArray(payload)) { await emitWaitingForCode(text || 'PENDING'); await deps.sleepWithStop?.(intervalMs); continue; } if (/^STATUS_CANCEL$/i.test(text)) { throw new Error('HeroSMS 订单在短信到达前已被取消。'); } throw createActionFailureError(statusAction, text || 'empty response'); } throw new Error(`${PHONE_CODE_TIMEOUT_ERROR_PREFIX}等待手机验证码超时。${lastResponse ? ` HeroSMS 最后状态:${lastResponse}` : ''}`); } async function finishActivation(state = {}, activation, deps = {}) { return setActivationStatus(state, activation, 6, deps, 'HeroSMS setStatus(6)'); } async function cancelActivation(state = {}, activation, deps = {}) { return setActivationStatus(state, activation, 8, deps, 'HeroSMS setStatus(8)'); } async function banActivation(state = {}, activation, deps = {}) { return setActivationStatus(state, activation, 8, deps, 'HeroSMS setStatus(8)'); } async function requestAdditionalSms(state = {}, activation, deps = {}) { return setActivationStatus(state, activation, 3, deps, 'HeroSMS setStatus(3)'); } async function rotateActivation(state = {}, activation, options = {}, deps = {}) { const releaseAction = String(options?.releaseAction || '').trim().toLowerCase() === 'ban' ? 'ban' : 'cancel'; if (releaseAction === 'ban') { await banActivation(state, activation, deps); } else { await cancelActivation(state, activation, deps); } return { currentTicketId: String(activation?.activationId || ''), nextActivation: null, }; } function isWaitingStatusText(text) { return /^STATUS_(WAIT_CODE|WAIT_RETRY|WAIT_RESEND)(?::.+)?$/i.test(String(text || '').trim()); } function isReadyForFreshSmsText(text) { return /^STATUS_WAIT_CODE(?::.+)?$/i.test(String(text || '').trim()); } async function prepareActivationForReuse(state = {}, activation, options = {}, deps = {}) { const normalizedActivation = normalizeActivation(activation); if (!normalizedActivation) { return { ok: false, reason: 'missing_activation', message: '已保存的免费复用手机号记录无效。', }; } const config = resolveConfig(state, deps); const statusAction = resolveActivationStatusAction(normalizedActivation); const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 20000); const intervalMs = Math.max(1000, Number(options.intervalMs) || 2000); const maxRounds = Math.max(1, Math.floor(Number(options.maxRounds) || 10)); const start = Date.now(); let lastStatus = ''; let prepareRound = 0; while (Date.now() - start < timeoutMs && prepareRound < maxRounds) { deps.throwIfStopped?.(); prepareRound += 1; try { await requestAdditionalSms(state, normalizedActivation, deps); } catch (error) { return { ok: false, reason: 'set_status_failed', message: error.message || 'HeroSMS 更新订单状态失败。', lastStatus, prepareRound, }; } if (typeof deps.addLog === 'function') { await deps.addLog( `步骤 9:自动白嫖复用已刷新 ${normalizedActivation.phoneNumber},${Math.ceil(intervalMs / 1000)} 秒后检查等待状态(${prepareRound}/${maxRounds})。`, 'info' ); } await deps.sleepWithStop?.(intervalMs); try { const payload = await fetchPayload(config, { action: statusAction, id: normalizedActivation.activationId, }, `HeroSMS 自动复用${statusAction}`); const statusText = describePayload(payload); lastStatus = statusText; if (typeof deps.addLog === 'function') { await deps.addLog( `步骤 9:自动白嫖复用号码 ${normalizedActivation.phoneNumber} 状态:${statusText || '空响应'}(${prepareRound}/${maxRounds})。`, 'info' ); } const v2Waiting = statusAction === 'getStatusV2' && payload && typeof payload === 'object' && !Array.isArray(payload) && !payload.sms?.code && !payload.call?.code; if (isReadyForFreshSmsText(statusText) || isWaitingStatusText(statusText) || v2Waiting) { return { ok: true, activation: normalizedActivation, }; } if (/^STATUS_OK:/i.test(statusText)) { if (typeof deps.addLog === 'function') { await deps.addLog('步骤 9:自动白嫖复用仍看到旧验证码,将再次刷新等待短信状态。', 'warn'); } continue; } if (/^STATUS_CANCEL$/i.test(statusText)) { return { ok: false, reason: 'activation_cancelled', message: 'HeroSMS 订单在自动白嫖复用前已被取消。', lastStatus, prepareRound, }; } } catch (error) { return { ok: false, reason: 'get_status_failed', message: error.message || 'HeroSMS 查询短信状态失败。', lastStatus, prepareRound, }; } } return { ok: false, reason: 'prepare_timeout', message: `等待已保存手机号进入短信等待状态超时。最后状态:${lastStatus || '未知'}。`, lastStatus, prepareRound, }; } function createProvider(deps = {}) { const providerDeps = { fetchImpl: deps.fetchImpl, sleepWithStop: deps.sleepWithStop, throwIfStopped: deps.throwIfStopped, addLog: deps.addLog, setState: deps.setState, requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS, }; const capabilities = Object.freeze({ supportsReusableActivation: true, supportsAutomaticFreeReuse: true, supportsFreeReusePreservation: true, supportsPageResend: true, supportsPageResendProbe: true, requiresCountrySelection: true, }); return { id: PROVIDER_ID, label: 'HeroSMS', capabilities, defaultCountryId: DEFAULT_COUNTRY_ID, defaultCountryLabel: DEFAULT_COUNTRY_LABEL, defaultProduct: DEFAULT_SERVICE_LABEL, normalizeCountryId: normalizeHeroSmsCountryId, normalizeCountryLabel: normalizeHeroSmsCountryLabel, normalizeCountryFallback: normalizeHeroSmsCountryFallback, normalizeCountryKey, normalizeMaxPrice: normalizeHeroSmsMaxPrice, normalizeActivation, resolveCountryCandidates, resolveCountryLabel, resolveActivationCountry, getActivationCountryKey, getActivationPrice, requestActivation: (state, options) => requestActivation(state, options, providerDeps), reuseActivation: (state, activation) => reuseActivation(state, activation, providerDeps), finishActivation: (state, activation) => finishActivation(state, activation, providerDeps), cancelActivation: (state, activation) => cancelActivation(state, activation, providerDeps), banActivation: (state, activation) => banActivation(state, activation, providerDeps), requestAdditionalSms: (state, activation) => requestAdditionalSms(state, activation, providerDeps), rotateActivation: (state, activation, options) => rotateActivation(state, activation, options, providerDeps), pollActivationCode: (state, activation, options) => pollActivationCode(state, activation, options, providerDeps), prepareActivationForReuse: (state, activation, options) => prepareActivationForReuse(state, activation, options, providerDeps), canPersistReusableActivation: () => true, canPreserveActivationForFreeReuse: (_state, activation) => { const normalizedActivation = normalizeActivation(activation); return Boolean( normalizedActivation && activation && typeof activation === 'object' && activation.phoneCodeReceived ); }, shouldUsePageResend: () => true, shouldProbePageResend: () => true, fetchBalance: (state) => fetchBalance(state, providerDeps), fetchPrices: (state, countryConfig) => fetchPrices(state, countryConfig, providerDeps), resolvePricePlan: (state, countryConfig) => resolvePricePlan(state, countryConfig, providerDeps), resolvePriceRange, formatPriceRangeText, describePayload, }; } return { PROVIDER_ID, DEFAULT_BASE_URL, DEFAULT_COUNTRY_ID, DEFAULT_COUNTRY_LABEL, DEFAULT_SERVICE_CODE, DEFAULT_SERVICE_LABEL, createProvider, describePayload, normalizeHeroSmsCountryFallback, normalizeHeroSmsCountryId, normalizeHeroSmsCountryLabel, normalizeCountryKey, normalizeHeroSmsMaxPrice, normalizeHeroSmsOperator, normalizeActivation, resolveActivationCountry, }; });