diff --git a/phone-sms/providers/madao.js b/phone-sms/providers/madao.js index c3a0320..559d74b 100644 --- a/phone-sms/providers/madao.js +++ b/phone-sms/providers/madao.js @@ -42,6 +42,15 @@ return normalizeText(value).toLowerCase().replace(/[^a-z0-9_-]+/g, ''); } + function normalizeOperator(value = '') { + const rawValue = normalizeText(value); + const compactValue = rawValue.toLowerCase().replace(/[^a-z0-9]+/g, ''); + if (!rawValue || compactValue === 'any' || compactValue === 'anyoperator') { + return ''; + } + return rawValue.toLowerCase().replace(/[^a-z0-9_-]+/g, ''); + } + function normalizeCountry(value = '') { const trimmed = normalizeText(value); if (!trimmed) { @@ -255,7 +264,7 @@ service: normalizeText(options?.service || state?.madaoServiceName, DEFAULT_SERVICE), }; const country = normalizeCountry(options?.country || state?.madaoCountry); - const operator = normalizeProviderId(options?.operator || state?.madaoOperator); + const operator = normalizeOperator(options?.operator || state?.madaoOperator); const minPrice = normalizePrice(options?.minPrice ?? state?.madaoMinPrice); const maxPrice = normalizePrice(options?.maxPrice ?? state?.madaoMaxPrice); @@ -708,6 +717,7 @@ normalizeCountry, normalizeCountryKey, normalizeCountryLabel, + normalizeOperator, resolveActivationCountry, pollActivation, pollActivationCode, diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index c2b30b2..f41f174 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -5705,7 +5705,12 @@ function normalizeMaDaoProviderIdValue(value = '') { } function normalizeMaDaoOperatorValue(value = '') { - return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); + const rawValue = String(value || '').trim(); + const compactValue = rawValue.toLowerCase().replace(/[^a-z0-9]+/g, ''); + if (!rawValue || compactValue === 'any' || compactValue === 'anyoperator') { + return ''; + } + return rawValue.toLowerCase().replace(/[^a-z0-9_-]+/g, ''); } function normalizeMaDaoCountry(value = '') { @@ -5723,6 +5728,22 @@ function normalizeMaDaoCountry(value = '') { return lowered.replace(/[^a-z0-9_-]+/g, ''); } +function formatMaDaoCountryDisplayLabel(value = '', label = '', labelZh = '') { + const country = normalizeMaDaoCountry(value); + const sourceLabelZh = String(labelZh || '').trim(); + const sourceLabel = String(label || '').trim(); + if (!country) { + return sourceLabelZh || sourceLabel; + } + if (country === 'local') { + return sourceLabelZh || '本地'; + } + if (country === 'any') { + return sourceLabelZh || '任意国家'; + } + return sourceLabelZh || sourceLabel || country; +} + function normalizeMaDaoPriceValue(value = '') { const rawValue = String(value ?? '').trim(); if (!rawValue) { @@ -5860,6 +5881,7 @@ function normalizeMaDaoOptionListItems(items = [], selectedValue = '', normalize normalizedItems.push({ value, label: String(item?.label || item?.name || item?.display_name || item?.displayName || value).trim() || value, + labelZh: String(item?.label_zh || item?.labelZh || item?.display_name_zh || item?.displayNameZh || '').trim(), hint: String(item?.hint || item?.description || item?.provider_value || item?.providerValue || '').trim(), enabled: item?.enabled !== false, }); @@ -5875,8 +5897,54 @@ function normalizeMaDaoOptionListItems(items = [], selectedValue = '', normalize return normalizedItems.filter((item) => item.enabled !== false); } +function resolveMaDaoOptionSelectedValue(items = [], selectedValue = '', normalizeValue = normalizeMaDaoIdentifierValue) { + const normalizedSelected = normalizeValue(selectedValue); + if (!normalizedSelected) { + return ''; + } + const sourceItems = Array.isArray(items) ? items : []; + for (const item of sourceItems) { + const value = normalizeValue( + item?.value + || item?.id + || item?.provider + || item?.provider_value + || item?.providerValue + || item?.country + || item?.operator + || '' + ); + if (!value) { + continue; + } + const aliases = [ + item?.value, + item?.id, + item?.provider, + item?.provider_value, + item?.providerValue, + item?.country, + item?.operator, + item?.label, + item?.label_zh, + item?.labelZh, + item?.name, + item?.display_name, + item?.displayName, + ]; + if (aliases.some((alias) => normalizeValue(alias || '') === normalizedSelected)) { + return value; + } + } + return normalizedSelected; +} + function setMaDaoProviderSelectOptions(selectedValue = latestState?.madaoProviderId || '') { - const normalizedSelected = normalizeMaDaoProviderIdValue(selectedValue); + const normalizedSelected = resolveMaDaoOptionSelectedValue( + maDaoProviderOptions, + selectedValue, + normalizeMaDaoProviderIdValue + ); const options = normalizeMaDaoOptionListItems( maDaoProviderOptions, normalizedSelected, @@ -5890,13 +5958,20 @@ function setMaDaoProviderSelectOptions(selectedValue = latestState?.madaoProvide } function setMaDaoCountrySelectOptions(selectedValue = latestState?.madaoCountry || '') { - const normalizedSelected = normalizeMaDaoCountry(selectedValue); + const normalizedSelected = resolveMaDaoOptionSelectedValue( + maDaoCountryOptions, + selectedValue, + normalizeMaDaoCountry + ); const options = normalizeMaDaoOptionListItems( maDaoCountryOptions, normalizedSelected, normalizeMaDaoCountry, '已保存的国家' - ); + ).map((item) => ({ + ...item, + label: formatMaDaoCountryDisplayLabel(item.value, item.label, item.labelZh), + })); setSelectOptions(selectMaDaoCountry, options, { placeholder: '请先选择服务商', value: normalizedSelected, @@ -5904,7 +5979,11 @@ function setMaDaoCountrySelectOptions(selectedValue = latestState?.madaoCountry } function setMaDaoOperatorSelectOptions(selectedValue = latestState?.madaoOperator || '') { - const normalizedSelected = normalizeMaDaoOperatorValue(selectedValue); + const normalizedSelected = resolveMaDaoOptionSelectedValue( + maDaoOperatorOptions, + selectedValue, + normalizeMaDaoOperatorValue + ); const options = normalizeMaDaoOptionListItems( maDaoOperatorOptions, normalizedSelected, @@ -17653,8 +17732,12 @@ function buildPhoneSmsProviderStatePatch(provider = getSelectedPhoneSmsProvider( madaoOperator: shouldReadMaDaoDirectControls ? normalizeMaDaoOperatorValue(selectMaDaoOperator?.value || '') : normalizeMaDaoOperatorValue(latestState?.madaoOperator || ''), - madaoAutoPickCountry: Boolean(inputMaDaoAutoPickCountry?.checked), - madaoReusePhone: Boolean(inputMaDaoReusePhone?.checked), + madaoAutoPickCountry: typeof inputMaDaoAutoPickCountry !== 'undefined' && inputMaDaoAutoPickCountry + ? Boolean(inputMaDaoAutoPickCountry.checked) + : (latestState?.madaoAutoPickCountry !== undefined ? Boolean(latestState.madaoAutoPickCountry) : true), + madaoReusePhone: typeof inputMaDaoReusePhone !== 'undefined' && inputMaDaoReusePhone + ? Boolean(inputMaDaoReusePhone.checked) + : (latestState?.madaoReusePhone !== undefined ? Boolean(latestState.madaoReusePhone) : true), madaoMinPrice: normalizeMaDaoPriceValue(inputMaDaoMinPrice?.value || ''), madaoMaxPrice: normalizeMaDaoPriceValue(inputMaDaoMaxPrice?.value || ''), }; diff --git a/tests/madao-provider.test.js b/tests/madao-provider.test.js index d55d700..b66f2d4 100644 --- a/tests/madao-provider.test.js +++ b/tests/madao-provider.test.js @@ -104,6 +104,37 @@ test('MaDao routing acquire sends routing plan only', async () => { assert.equal(activation.madaoRoutingItemId, 'route-1'); }); +test('MaDao direct acquire treats any operator as default route', async () => { + const requests = []; + const provider = api.createProvider({ + fetchImpl: async (_url, options = {}) => { + requests.push({ body: JSON.parse(options.body) }); + return createJsonResponse({ + ticket_id: 'ticket-any', + phone_number: '+66111111111', + country: 'TH', + provider: 'fivesim', + }); + }, + }); + + await provider.acquireActivation({ + madaoMode: 'direct', + madaoProviderId: 'fivesim', + madaoCountry: 'TH', + madaoOperator: 'Any operator', + }); + + assert.equal(requests.length, 1); + assert.deepEqual(requests[0].body, { + provider: 'fivesim', + service: 'openai', + auto_pick_country: true, + reuse_phone: true, + country: 'TH', + }); +}); + test('MaDao poll extracts codes from nested messages and reports pending status', async () => { const statusEvents = []; const provider = api.createProvider({ diff --git a/tests/sidepanel-phone-verification-settings.test.js b/tests/sidepanel-phone-verification-settings.test.js index 97f5ad3..f20e23c 100644 --- a/tests/sidepanel-phone-verification-settings.test.js +++ b/tests/sidepanel-phone-verification-settings.test.js @@ -315,8 +315,8 @@ test('MaDao direct selects load provider country and operator options from daemo return JSON.stringify({ provider: 'stored-provider', items: [ - { value: 'GB', label: 'United Kingdom' }, - { value: 'TH', label: 'Thailand' }, + { value: 'GB', label: 'United Kingdom', label_zh: '英国', provider_value: 'england' }, + { value: 'TH', label: 'Thailand', label_zh: '泰国', provider_value: '52' }, ], }); }, @@ -331,6 +331,7 @@ test('MaDao direct selects load provider country and operator options from daemo return JSON.stringify({ provider: 'stored-provider', items: [ + { value: 'any', label: 'Any operator' }, { value: 'operator-a', label: 'Operator A' }, { value: 'operator-b', label: 'Operator B' }, ], @@ -347,7 +348,7 @@ let latestState = { madaoBaseUrl: 'http://madao.local/api/acquire', madaoHttpSecret: 'madao-secret', madaoProviderId: 'stored-provider', - madaoCountry: 'GB', + madaoCountry: 'england', madaoOperator: 'operator-a', }; let maDaoProviderOptions = []; @@ -358,7 +359,7 @@ let toastText = ''; const inputMaDaoBaseUrl = { value: 'http://madao.local/api/acquire' }; const inputMaDaoHttpSecret = { value: 'madao-secret' }; const selectMaDaoProviderId = { value: 'stored-provider', options: [], replaceChildren(...children) { this.options = children; } }; -const selectMaDaoCountry = { value: 'GB', options: [], replaceChildren(...children) { this.options = children; } }; +const selectMaDaoCountry = { value: 'england', options: [], replaceChildren(...children) { this.options = children; } }; const selectMaDaoOperator = { value: 'operator-a', options: [], replaceChildren(...children) { this.options = children; } }; function updateHeroSmsPlatformDisplay() { displayText = [selectMaDaoProviderId.value, selectMaDaoCountry.value, selectMaDaoOperator.value].filter(Boolean).join('/'); @@ -371,9 +372,11 @@ ${extractFunction('normalizeMaDaoIdentifierValue')} ${extractFunction('normalizeMaDaoProviderIdValue')} ${extractFunction('normalizeMaDaoOperatorValue')} ${extractFunction('normalizeMaDaoCountry')} +${extractFunction('formatMaDaoCountryDisplayLabel')} ${extractFunction('createSelectOptionElement')} ${extractFunction('setSelectOptions')} ${extractFunction('normalizeMaDaoOptionListItems')} +${extractFunction('resolveMaDaoOptionSelectedValue')} ${extractFunction('setMaDaoProviderSelectOptions')} ${extractFunction('setMaDaoCountrySelectOptions')} ${extractFunction('setMaDaoOperatorSelectOptions')} @@ -408,7 +411,9 @@ return { assert.equal(api.selectMaDaoOperator.value, 'operator-a'); assert.deepStrictEqual(api.providerOptions.map((option) => option.value), ['', 'stored-provider']); assert.deepStrictEqual(api.countryOptions.map((option) => option.value), ['', 'GB', 'TH']); + assert.deepStrictEqual(api.countryOptions.map((option) => option.label), ['请先选择服务商', '英国', '泰国']); assert.deepStrictEqual(api.operatorOptions.map((option) => option.value), ['', 'operator-a', 'operator-b']); + assert.deepStrictEqual(api.operatorOptions.map((option) => option.label), ['任意线路', 'Operator A', 'Operator B']); assert.equal(api.displayText, 'stored-provider/GB/operator-a'); assert.equal(api.toastText, '已刷新 MaDao 服务商。'); assert.deepStrictEqual(requests.map((request) => request.pathname), [ @@ -1503,6 +1508,7 @@ ${extractFunction('normalizeMaDaoIdentifierValue')} ${extractFunction('normalizeMaDaoProviderIdValue')} ${extractFunction('normalizeMaDaoOperatorValue')} ${extractFunction('normalizeMaDaoCountry')} +${extractFunction('formatMaDaoCountryDisplayLabel')} ${extractFunction('normalizeMaDaoPriceValue')} ${extractFunction('normalizeFiveSimCountryCode')} ${extractFunction('normalizeFiveSimCountryOrderValue')} @@ -1755,9 +1761,11 @@ ${extractFunction('normalizeMaDaoProviderIdValue')} ${extractFunction('normalizeMaDaoOperatorValue')} ${extractFunction('normalizeMaDaoCountry')} ${extractFunction('normalizeMaDaoPriceValue')} +${extractFunction('formatMaDaoCountryDisplayLabel')} ${extractFunction('createSelectOptionElement')} ${extractFunction('setSelectOptions')} ${extractFunction('normalizeMaDaoOptionListItems')} +${extractFunction('resolveMaDaoOptionSelectedValue')} ${extractFunction('buildMaDaoRoutingPlanOptions')} ${extractFunction('setMaDaoRoutingPlanSelectOptions')} ${extractFunction('setMaDaoProviderSelectOptions')} @@ -1842,6 +1850,71 @@ return { assert.equal(api.savedPayload.fiveSimMinPrice, '0.88'); }); +test('buildPhoneSmsProviderStatePatch preserves MaDao hidden reuse defaults when controls are absent', () => { + const api = new Function(` +let latestState = { + phoneSmsProvider: 'madao', + madaoBaseUrl: 'http://127.0.0.1:7822', + madaoHttpSecret: 'secret-old', + madaoMode: 'direct', + madaoRoutingPlanId: 'plan-old', + madaoProviderId: 'provider-old', + madaoCountry: 'TH', + madaoOperator: 'any', + madaoAutoPickCountry: true, + madaoReusePhone: true, + madaoMinPrice: '0.01', + madaoMaxPrice: '0.09', +}; +const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms'; +const PHONE_SMS_PROVIDER_HERO = PHONE_SMS_PROVIDER_HERO_SMS; +const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim'; +const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms'; +const PHONE_SMS_PROVIDER_MADAO = 'madao'; +const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS; +const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam'; +const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)'; +const DEFAULT_FIVE_SIM_OPERATOR = 'any'; +const DEFAULT_FIVE_SIM_PRODUCT = 'openai'; +const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot'; +const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822'; +const MADAO_MODE_ROUTING_PLAN = 'routing_plan'; +const MADAO_MODE_DIRECT = 'direct'; +const DEFAULT_MADAO_MODE = MADAO_MODE_ROUTING_PLAN; +const selectMaDaoMode = { value: 'direct' }; +const selectMaDaoRoutingPlanId = { value: 'plan-live' }; +const selectMaDaoProviderId = { value: 'provider-live' }; +const selectMaDaoCountry = { value: 'GB' }; +const selectMaDaoOperator = { value: 'Any operator' }; +const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' }; +const inputMaDaoHttpSecret = { value: 'secret-live' }; +const inputMaDaoMinPrice = { value: '0.02' }; +const inputMaDaoMaxPrice = { value: '0.20' }; + +${extractFunction('normalizePhoneSmsProvider')} +${extractFunction('normalizeMaDaoBaseUrlValue')} +${extractFunction('normalizeMaDaoModeValue')} +${extractFunction('normalizeMaDaoIdentifierValue')} +${extractFunction('normalizeMaDaoRoutingPlanIdValue')} +${extractFunction('normalizeMaDaoProviderIdValue')} +${extractFunction('normalizeMaDaoOperatorValue')} +${extractFunction('normalizeMaDaoCountry')} +${extractFunction('normalizeMaDaoPriceValue')} +${extractFunction('normalizePhoneSmsMinPriceValue')} +${extractFunction('normalizePhoneSmsMaxPriceValue')} +${extractFunction('buildPhoneSmsProviderStatePatch')} +return { buildPhoneSmsProviderStatePatch }; +`)(); + + const patch = api.buildPhoneSmsProviderStatePatch('madao'); + + assert.equal(patch.madaoAutoPickCountry, true); + assert.equal(patch.madaoReusePhone, true); + assert.equal(patch.madaoOperator, ''); + assert.equal(patch.madaoProviderId, 'provider-live'); + assert.equal(patch.madaoCountry, 'GB'); +}); + test('HeroSMS operator helpers normalize keyed operator payloads', () => { const api = new Function(` const DEFAULT_HERO_SMS_OPERATOR = 'any';