// sidepanel/sidepanel.js — Side Panel logic const STATUS_ICONS = { pending: '', running: '', completed: '\u2713', // ✓ failed: '\u2717', // ✗ stopped: '\u25A0', // ■ manual_completed: '跳', skipped: '跳', }; const logArea = document.getElementById('log-area'); const btnOpenAccountRecords = document.getElementById('btn-open-account-records'); const accountRecordsOverlay = document.getElementById('account-records-overlay'); const accountRecordsMeta = document.getElementById('account-records-meta'); const accountRecordsStats = document.getElementById('account-records-stats'); const accountRecordsList = document.getElementById('account-records-list'); const accountRecordsPageLabel = document.getElementById('account-records-page-label'); const btnAccountRecordsPrev = document.getElementById('btn-account-records-prev'); const btnAccountRecordsNext = document.getElementById('btn-account-records-next'); const btnCloseAccountRecords = document.getElementById('btn-close-account-records'); const btnClearAccountRecords = document.getElementById('btn-clear-account-records'); const btnToggleAccountRecordsSelection = document.getElementById('btn-toggle-account-records-selection'); const btnDeleteSelectedAccountRecords = document.getElementById('btn-delete-selected-account-records'); const updateSection = document.getElementById('update-section'); const btnRepoHome = document.getElementById('btn-repo-home'); const extensionUpdateStatus = document.getElementById('extension-update-status'); const extensionVersionMeta = document.getElementById('extension-version-meta'); const btnReleaseLog = document.getElementById('btn-release-log'); const updateCardVersion = document.getElementById('update-card-version'); const updateCardSummary = document.getElementById('update-card-summary'); const updateReleaseList = document.getElementById('update-release-list'); const btnIgnoreRelease = document.getElementById('btn-ignore-release'); const btnOpenRelease = document.getElementById('btn-open-release'); const settingsCard = document.getElementById('settings-card'); const contributionModePanel = document.getElementById('contribution-mode-panel'); const contributionModeBadge = document.getElementById('contribution-mode-badge'); const contributionModeText = document.getElementById('contribution-mode-text'); const inputContributionNickname = document.getElementById('input-contribution-nickname'); const inputContributionQq = document.getElementById('input-contribution-qq'); const contributionOauthStatus = document.getElementById('contribution-oauth-status'); const contributionCallbackStatus = document.getElementById('contribution-callback-status'); const contributionModeSummary = document.getElementById('contribution-mode-summary'); const btnStartContribution = document.getElementById('btn-start-contribution'); const btnOpenContributionUpload = document.getElementById('btn-open-contribution-upload'); const btnExitContributionMode = document.getElementById('btn-exit-contribution-mode'); const displayOauthUrl = document.getElementById('display-oauth-url'); const displayLocalhostUrl = document.getElementById('display-localhost-url'); const displayStatus = document.getElementById('display-status'); const statusBar = document.getElementById('status-bar'); const inputEmail = document.getElementById('input-email'); const inputSignupPhone = document.getElementById('input-signup-phone'); const inputPassword = document.getElementById('input-password'); const btnToggleVpsUrl = document.getElementById('btn-toggle-vps-url'); const btnToggleVpsPassword = document.getElementById('btn-toggle-vps-password'); const btnFetchEmail = document.getElementById('btn-fetch-email'); const btnTogglePassword = document.getElementById('btn-toggle-password'); const btnSaveSettings = document.getElementById('btn-save-settings'); const btnStop = document.getElementById('btn-stop'); const btnReset = document.getElementById('btn-reset'); const btnContributionMode = document.getElementById('btn-contribution-mode'); const contributionUpdateLayer = document.getElementById('contribution-update-layer'); const contributionUpdateHint = document.getElementById('contribution-update-hint'); const contributionUpdateHintText = document.getElementById('contribution-update-hint-text'); const btnDismissContributionUpdateHint = document.getElementById('btn-dismiss-contribution-update-hint'); const stepsProgress = document.getElementById('steps-progress'); const btnAutoRun = document.getElementById('btn-auto-run'); const btnAutoContinue = document.getElementById('btn-auto-continue'); const autoContinueBar = document.getElementById('auto-continue-bar'); const autoScheduleBar = document.getElementById('auto-schedule-bar'); const autoScheduleTitle = document.getElementById('auto-schedule-title'); const autoScheduleMeta = document.getElementById('auto-schedule-meta'); const btnAutoRunNow = document.getElementById('btn-auto-run-now'); const btnAutoCancelSchedule = document.getElementById('btn-auto-cancel-schedule'); const btnClearLog = document.getElementById('btn-clear-log'); const configMenuShell = document.getElementById('config-menu-shell'); const btnConfigMenu = document.getElementById('btn-config-menu'); const configMenu = document.getElementById('config-menu'); const btnExportSettings = document.getElementById('btn-export-settings'); const btnImportSettings = document.getElementById('btn-import-settings'); const inputImportSettingsFile = document.getElementById('input-import-settings-file'); const selectPanelMode = document.getElementById('select-panel-mode'); const rowVpsUrl = document.getElementById('row-vps-url'); const inputVpsUrl = document.getElementById('input-vps-url'); const rowVpsPassword = document.getElementById('row-vps-password'); const inputVpsPassword = document.getElementById('input-vps-password'); const rowLocalCpaStep9Mode = document.getElementById('row-local-cpa-step9-mode'); const localCpaStep9ModeButtons = Array.from(document.querySelectorAll('[data-local-cpa-step9-mode]')); const rowSub2ApiUrl = document.getElementById('row-sub2api-url'); const inputSub2ApiUrl = document.getElementById('input-sub2api-url'); const rowSub2ApiEmail = document.getElementById('row-sub2api-email'); const inputSub2ApiEmail = document.getElementById('input-sub2api-email'); const rowSub2ApiPassword = document.getElementById('row-sub2api-password'); const inputSub2ApiPassword = document.getElementById('input-sub2api-password'); const rowSub2ApiGroup = document.getElementById('row-sub2api-group'); const inputSub2ApiGroup = document.getElementById('input-sub2api-group'); const sub2ApiGroupPickerRoot = document.getElementById('sub2api-group-picker'); const btnSub2ApiGroupMenu = document.getElementById('btn-sub2api-group-menu'); const sub2ApiGroupCurrent = document.getElementById('sub2api-group-current'); const sub2ApiGroupMenu = document.getElementById('sub2api-group-menu'); const btnAddSub2ApiGroup = document.getElementById('btn-add-sub2api-group'); const rowSub2ApiAccountPriority = document.getElementById('row-sub2api-account-priority'); const inputSub2ApiAccountPriority = document.getElementById('input-sub2api-account-priority'); const rowSub2ApiDefaultProxy = document.getElementById('row-sub2api-default-proxy'); const inputSub2ApiDefaultProxy = document.getElementById('input-sub2api-default-proxy'); const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled'); const inputIpProxyEnabled = document.getElementById('input-ip-proxy-enabled'); const btnToggleIpProxySection = document.getElementById('btn-toggle-ip-proxy-section'); const ipProxyEnabledStatus = document.getElementById('ip-proxy-enabled-status'); const ipProxyEnabledStatusDot = document.getElementById('ip-proxy-enabled-status-dot'); const ipProxyEnabledStatusText = document.getElementById('ip-proxy-enabled-status-text'); const ipProxyEnabledButtons = Array.from(document.querySelectorAll('[data-ip-proxy-enabled]')); const rowIpProxyFold = document.getElementById('row-ip-proxy-fold'); const rowIpProxyService = document.getElementById('row-ip-proxy-service'); const selectIpProxyService = document.getElementById('select-ip-proxy-service'); const btnIpProxyServiceLogin = document.getElementById('btn-ip-proxy-service-login'); const rowIpProxyMode = document.getElementById('row-ip-proxy-mode'); const ipProxyModeButtons = Array.from(document.querySelectorAll('[data-ip-proxy-mode]')); const rowIpProxyLayout = document.getElementById('row-ip-proxy-layout'); const ipProxyLayout = document.getElementById('ip-proxy-layout'); const ipProxyApiPanel = document.getElementById('ip-proxy-api-panel'); const rowIpProxyApiUrl = document.getElementById('row-ip-proxy-api-url'); const inputIpProxyApiUrl = document.getElementById('input-ip-proxy-api-url'); const btnToggleIpProxyApiUrl = document.getElementById('btn-toggle-ip-proxy-api-url'); const rowIpProxyAccountList = document.getElementById('row-ip-proxy-account-list'); const inputIpProxyAccountList = document.getElementById('input-ip-proxy-account-list'); const rowIpProxyAccountSessionPrefix = document.getElementById('row-ip-proxy-account-session-prefix'); const inputIpProxyAccountSessionPrefix = document.getElementById('input-ip-proxy-account-session-prefix'); const rowIpProxyAccountLifeMinutes = document.getElementById('row-ip-proxy-account-life-minutes'); const inputIpProxyAccountLifeMinutes = document.getElementById('input-ip-proxy-account-life-minutes'); const rowIpProxyPoolTargetCount = document.getElementById('row-ip-proxy-pool-target-count'); const inputIpProxyPoolTargetCount = document.getElementById('input-ip-proxy-pool-target-count'); const rowIpProxyAutoSyncEnabled = document.getElementById('row-ip-proxy-auto-sync-enabled'); const inputIpProxyAutoSyncEnabled = document.getElementById('input-ip-proxy-auto-sync-enabled'); const rowIpProxyAutoSyncInterval = document.getElementById('row-ip-proxy-auto-sync-interval'); const inputIpProxyAutoSyncIntervalMinutes = document.getElementById('input-ip-proxy-auto-sync-interval-minutes'); const rowIpProxyHost = document.getElementById('row-ip-proxy-host'); const inputIpProxyHost = document.getElementById('input-ip-proxy-host'); const rowIpProxyPort = document.getElementById('row-ip-proxy-port'); const inputIpProxyPort = document.getElementById('input-ip-proxy-port'); const rowIpProxyProtocol = document.getElementById('row-ip-proxy-protocol'); const selectIpProxyProtocol = document.getElementById('select-ip-proxy-protocol'); const rowIpProxyUsername = document.getElementById('row-ip-proxy-username'); const inputIpProxyUsername = document.getElementById('input-ip-proxy-username'); const btnToggleIpProxyUsername = document.getElementById('btn-toggle-ip-proxy-username'); const rowIpProxyPassword = document.getElementById('row-ip-proxy-password'); const inputIpProxyPassword = document.getElementById('input-ip-proxy-password'); const btnToggleIpProxyPassword = document.getElementById('btn-toggle-ip-proxy-password'); const rowIpProxyRegion = document.getElementById('row-ip-proxy-region'); const inputIpProxyRegion = document.getElementById('input-ip-proxy-region'); const rowIpProxyActions = document.getElementById('row-ip-proxy-actions'); const ipProxyActionButtons = document.getElementById('ip-proxy-action-buttons'); const ipProxyActionHint = document.getElementById('ip-proxy-action-hint'); const btnIpProxyRefresh = document.getElementById('btn-ip-proxy-refresh'); const btnIpProxyNext = document.getElementById('btn-ip-proxy-next'); const btnIpProxyChange = document.getElementById('btn-ip-proxy-change'); const btnIpProxyProbe = document.getElementById('btn-ip-proxy-probe'); const btnIpProxyCheckIp = document.getElementById('btn-ip-proxy-check-ip'); const ipProxyCurrent = document.getElementById('ip-proxy-current'); const rowIpProxyRuntimeStatus = document.getElementById('row-ip-proxy-runtime-status'); const ipProxyRuntimeStatus = document.getElementById('ip-proxy-runtime-status'); const ipProxyRuntimeDot = document.getElementById('ip-proxy-runtime-dot'); const ipProxyRuntimeText = document.getElementById('ip-proxy-runtime-text'); const ipProxyRuntimeDetails = document.getElementById('ip-proxy-runtime-details'); const ipProxyRuntimeDetailsText = document.getElementById('ip-proxy-runtime-details-text'); const rowCodex2ApiUrl = document.getElementById('row-codex2api-url'); const inputCodex2ApiUrl = document.getElementById('input-codex2api-url'); const rowCodex2ApiAdminKey = document.getElementById('row-codex2api-admin-key'); const inputCodex2ApiAdminKey = document.getElementById('input-codex2api-admin-key'); const rowCustomPassword = document.getElementById('row-custom-password'); const rowPlusMode = document.getElementById('row-plus-mode'); const inputPlusModeEnabled = document.getElementById('input-plus-mode-enabled'); const rowPlusPaymentMethod = document.getElementById('row-plus-payment-method'); const selectPlusPaymentMethod = document.getElementById('select-plus-payment-method'); const btnGpcCardKeyPurchase = document.getElementById('btn-gpc-card-key-purchase'); const plusPaymentMethodCaption = document.getElementById('plus-payment-method-caption'); const rowPayPalAccount = document.getElementById('row-paypal-account'); const selectPayPalAccount = document.getElementById('select-paypal-account'); const payPalAccountPickerRoot = document.getElementById('paypal-account-picker'); const btnPayPalAccountMenu = document.getElementById('btn-paypal-account-menu'); const payPalAccountCurrent = document.getElementById('paypal-account-current'); const payPalAccountMenu = document.getElementById('paypal-account-menu'); const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account'); const rowGpcHelperApi = document.getElementById('row-gpc-helper-api'); const inputGpcHelperApi = document.getElementById('input-gpc-helper-api'); const btnGpcHelperConvertApiKey = document.getElementById('btn-gpc-helper-convert-api-key'); const rowGpcHelperCardKey = document.getElementById('row-gpc-helper-card-key'); const inputGpcHelperCardKey = document.getElementById('input-gpc-helper-card-key'); const btnToggleGpcHelperCardKey = document.getElementById('btn-toggle-gpc-helper-card-key'); const btnGpcHelperBalance = document.getElementById('btn-gpc-helper-balance'); const displayGpcHelperBalance = document.getElementById('display-gpc-helper-balance'); const rowGpcHelperPhoneMode = document.getElementById('row-gpc-helper-phone-mode'); const selectGpcHelperPhoneMode = document.getElementById('select-gpc-helper-phone-mode'); const rowGpcHelperCountryCode = document.getElementById('row-gpc-helper-country-code'); const selectGpcHelperCountryCode = document.getElementById('select-gpc-helper-country-code'); const rowGpcHelperPhone = document.getElementById('row-gpc-helper-phone'); const inputGpcHelperPhone = document.getElementById('input-gpc-helper-phone'); const rowGpcHelperOtpChannel = document.getElementById('row-gpc-helper-otp-channel'); const selectGpcHelperOtpChannel = document.getElementById('select-gpc-helper-otp-channel'); const rowGpcHelperLocalSmsEnabled = document.getElementById('row-gpc-helper-local-sms-enabled'); const inputGpcHelperLocalSmsEnabled = document.getElementById('input-gpc-helper-local-sms-enabled'); const rowGpcHelperLocalSmsUrl = document.getElementById('row-gpc-helper-local-sms-url'); const inputGpcHelperLocalSmsUrl = document.getElementById('input-gpc-helper-local-sms-url'); const rowGpcHelperPin = document.getElementById('row-gpc-helper-pin'); const inputGpcHelperPin = document.getElementById('input-gpc-helper-pin'); const btnToggleGpcHelperPin = document.getElementById('btn-toggle-gpc-helper-pin'); const rowGoPayCountryCode = document.getElementById('row-gopay-country-code'); const selectGoPayCountryCode = document.getElementById('select-gopay-country-code'); const rowGoPayPhone = document.getElementById('row-gopay-phone'); const inputGoPayPhone = document.getElementById('input-gopay-phone'); const rowGoPayOtp = document.getElementById('row-gopay-otp'); const inputGoPayOtp = document.getElementById('input-gopay-otp'); const rowGoPayPin = document.getElementById('row-gopay-pin'); const inputGoPayPin = document.getElementById('input-gopay-pin'); const selectMailProvider = document.getElementById('select-mail-provider'); const btnMailLogin = document.getElementById('btn-mail-login'); const rowCustomMailProviderPool = document.getElementById('row-custom-mail-provider-pool'); const inputCustomMailProviderPool = document.getElementById('input-custom-mail-provider-pool'); const rowMail2925Mode = document.getElementById('row-mail-2925-mode'); const rowMail2925PoolSettings = document.getElementById('row-mail2925-pool-settings'); const mail2925ModeButtons = Array.from(document.querySelectorAll('[data-mail2925-mode]')); const rowEmailGenerator = document.getElementById('row-email-generator'); const selectEmailGenerator = document.getElementById('select-email-generator'); const rowCustomEmailPool = document.getElementById('row-custom-email-pool'); const inputCustomEmailPool = document.getElementById('input-custom-email-pool'); const btnCustomEmailPoolRefresh = document.getElementById('btn-custom-email-pool-refresh'); const btnCustomEmailPoolClearUsed = document.getElementById('btn-custom-email-pool-clear-used'); const btnCustomEmailPoolDeleteAll = document.getElementById('btn-custom-email-pool-delete-all'); const inputCustomEmailPoolImport = document.getElementById('input-custom-email-pool-import'); const btnCustomEmailPoolImport = document.getElementById('btn-custom-email-pool-import'); const customEmailPoolSummary = document.getElementById('custom-email-pool-summary'); const inputCustomEmailPoolSearch = document.getElementById('input-custom-email-pool-search'); const selectCustomEmailPoolFilter = document.getElementById('select-custom-email-pool-filter'); const checkboxCustomEmailPoolSelectAll = document.getElementById('checkbox-custom-email-pool-select-all'); const customEmailPoolSelectionSummary = document.getElementById('custom-email-pool-selection-summary'); const btnCustomEmailPoolBulkUsed = document.getElementById('btn-custom-email-pool-bulk-used'); const btnCustomEmailPoolBulkUnused = document.getElementById('btn-custom-email-pool-bulk-unused'); const btnCustomEmailPoolBulkEnable = document.getElementById('btn-custom-email-pool-bulk-enable'); const btnCustomEmailPoolBulkDisable = document.getElementById('btn-custom-email-pool-bulk-disable'); const btnCustomEmailPoolBulkDelete = document.getElementById('btn-custom-email-pool-bulk-delete'); const customEmailPoolList = document.getElementById('custom-email-pool-list'); const rowTempEmailBaseUrl = document.getElementById('row-temp-email-base-url'); const inputTempEmailBaseUrl = document.getElementById('input-temp-email-base-url'); const rowTempEmailAdminAuth = document.getElementById('row-temp-email-admin-auth'); const inputTempEmailAdminAuth = document.getElementById('input-temp-email-admin-auth'); const rowTempEmailCustomAuth = document.getElementById('row-temp-email-custom-auth'); const inputTempEmailCustomAuth = document.getElementById('input-temp-email-custom-auth'); const rowTempEmailReceiveMailbox = document.getElementById('row-temp-email-receive-mailbox'); const inputTempEmailReceiveMailbox = document.getElementById('input-temp-email-receive-mailbox'); const rowTempEmailRandomSubdomainToggle = document.getElementById('row-temp-email-random-subdomain-toggle'); const inputTempEmailUseRandomSubdomain = document.getElementById('input-temp-email-use-random-subdomain'); const rowTempEmailDomain = document.getElementById('row-temp-email-domain'); const selectTempEmailDomain = document.getElementById('select-temp-email-domain'); const tempEmailDomainPickerRoot = document.getElementById('temp-email-domain-picker'); const btnTempEmailDomainMenu = document.getElementById('btn-temp-email-domain-menu'); const tempEmailDomainCurrent = document.getElementById('temp-email-domain-current'); const tempEmailDomainMenu = document.getElementById('temp-email-domain-menu'); const inputTempEmailDomain = document.getElementById('input-temp-email-domain'); const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode'); const cloudflareTempEmailSection = document.getElementById('cloudflare-temp-email-section'); const btnCloudflareTempEmailUsageGuide = document.getElementById('btn-cloudflare-temp-email-usage-guide'); const btnCloudflareTempEmailGithub = document.getElementById('btn-cloudflare-temp-email-github'); const cloudMailSection = document.getElementById('cloud-mail-section'); const rowCloudMailBaseUrl = document.getElementById('row-cloud-mail-base-url'); const rowCloudMailAdminEmail = document.getElementById('row-cloud-mail-admin-email'); const rowCloudMailAdminPassword = document.getElementById('row-cloud-mail-admin-password'); const rowCloudMailReceiveMailbox = document.getElementById('row-cloud-mail-receive-mailbox'); const rowCloudMailDomain = document.getElementById('row-cloud-mail-domain'); const inputCloudMailBaseUrl = document.getElementById('input-cloud-mail-base-url'); const inputCloudMailAdminEmail = document.getElementById('input-cloud-mail-admin-email'); const inputCloudMailAdminPassword = document.getElementById('input-cloud-mail-admin-password'); const inputCloudMailReceiveMailbox = document.getElementById('input-cloud-mail-receive-mailbox'); const inputCloudMailDomain = document.getElementById('input-cloud-mail-domain'); const hotmailSection = document.getElementById('hotmail-section'); const mail2925Section = document.getElementById('mail2925-section'); const luckmailSection = document.getElementById('luckmail-section'); const icloudSection = document.getElementById('icloud-section'); const icloudSummary = document.getElementById('icloud-summary'); const icloudList = document.getElementById('icloud-list'); const icloudLoginHelp = document.getElementById('icloud-login-help'); const icloudLoginHelpTitle = document.getElementById('icloud-login-help-title'); const icloudLoginHelpText = document.getElementById('icloud-login-help-text'); const btnIcloudLoginDone = document.getElementById('btn-icloud-login-done'); const btnIcloudRefresh = document.getElementById('btn-icloud-refresh'); const btnIcloudDeleteUsed = document.getElementById('btn-icloud-delete-used'); const selectIcloudHostPreference = document.getElementById('select-icloud-host-preference'); const rowIcloudTargetMailboxType = document.getElementById('row-icloud-target-mailbox-type'); const selectIcloudTargetMailboxType = document.getElementById('select-icloud-target-mailbox-type'); const rowIcloudForwardMailProvider = document.getElementById('row-icloud-forward-mail-provider'); const selectIcloudForwardMailProvider = document.getElementById('select-icloud-forward-mail-provider'); const selectIcloudFetchMode = document.getElementById('select-icloud-fetch-mode'); const checkboxAutoDeleteIcloud = document.getElementById('checkbox-auto-delete-icloud'); const inputIcloudSearch = document.getElementById('input-icloud-search'); const selectIcloudFilter = document.getElementById('select-icloud-filter'); const checkboxIcloudSelectAll = document.getElementById('checkbox-icloud-select-all'); const icloudSelectionSummary = document.getElementById('icloud-selection-summary'); const btnIcloudBulkUsed = document.getElementById('btn-icloud-bulk-used'); const btnIcloudBulkUnused = document.getElementById('btn-icloud-bulk-unused'); const btnIcloudBulkPreserve = document.getElementById('btn-icloud-bulk-preserve'); const btnIcloudBulkUnpreserve = document.getElementById('btn-icloud-bulk-unpreserve'); const btnIcloudBulkDelete = document.getElementById('btn-icloud-bulk-delete'); const rowHotmailServiceMode = document.getElementById('row-hotmail-service-mode'); const hotmailServiceModeButtons = Array.from(document.querySelectorAll('[data-hotmail-service-mode]')); const rowHotmailRemoteBaseUrl = document.getElementById('row-hotmail-remote-base-url'); const inputHotmailRemoteBaseUrl = document.getElementById('input-hotmail-remote-base-url'); const rowHotmailLocalBaseUrl = document.getElementById('row-hotmail-local-base-url'); const inputHotmailLocalBaseUrl = document.getElementById('input-hotmail-local-base-url'); const inputHotmailEmail = document.getElementById('input-hotmail-email'); const inputHotmailClientId = document.getElementById('input-hotmail-client-id'); const inputHotmailPassword = document.getElementById('input-hotmail-password'); const inputHotmailRefreshToken = document.getElementById('input-hotmail-refresh-token'); const inputHotmailImport = document.getElementById('input-hotmail-import'); const inputHotmailSearch = document.getElementById('input-hotmail-search'); const selectHotmailFilter = document.getElementById('select-hotmail-filter'); const btnAddHotmailAccount = document.getElementById('btn-add-hotmail-account'); const btnImportHotmailAccounts = document.getElementById('btn-import-hotmail-accounts'); const btnToggleHotmailForm = document.getElementById('btn-toggle-hotmail-form'); const btnHotmailUsageGuide = document.getElementById('btn-hotmail-usage-guide'); const btnClearUsedHotmailAccounts = document.getElementById('btn-clear-used-hotmail-accounts'); const btnDeleteAllHotmailAccounts = document.getElementById('btn-delete-all-hotmail-accounts'); const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list'); const hotmailFormShell = document.getElementById('hotmail-form-shell'); const hotmailListShell = document.getElementById('hotmail-list-shell'); const hotmailAccountsList = document.getElementById('hotmail-accounts-list'); const inputMail2925Email = document.getElementById('input-mail2925-email'); const inputMail2925Password = document.getElementById('input-mail2925-password'); const inputMail2925Import = document.getElementById('input-mail2925-import'); const inputMail2925Search = document.getElementById('input-mail2925-search'); const selectMail2925Filter = document.getElementById('select-mail2925-filter'); const btnAddMail2925Account = document.getElementById('btn-add-mail2925-account'); const btnToggleMail2925Form = document.getElementById('btn-toggle-mail2925-form'); const btnImportMail2925Accounts = document.getElementById('btn-import-mail2925-accounts'); const btnDeleteAllMail2925Accounts = document.getElementById('btn-delete-all-mail2925-accounts'); const btnToggleMail2925List = document.getElementById('btn-toggle-mail2925-list'); const mail2925FormShell = document.getElementById('mail2925-form-shell'); const mail2925ListShell = document.getElementById('mail2925-list-shell'); const mail2925AccountsList = document.getElementById('mail2925-accounts-list'); const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key'); const inputLuckmailBaseUrl = document.getElementById('input-luckmail-base-url'); const selectLuckmailEmailType = document.getElementById('select-luckmail-email-type'); const inputLuckmailDomain = document.getElementById('input-luckmail-domain'); const btnLuckmailRefresh = document.getElementById('btn-luckmail-refresh'); const btnLuckmailDisableUsed = document.getElementById('btn-luckmail-disable-used'); const luckmailSummary = document.getElementById('luckmail-summary'); const inputLuckmailSearch = document.getElementById('input-luckmail-search'); const selectLuckmailFilter = document.getElementById('select-luckmail-filter'); const checkboxLuckmailSelectAll = document.getElementById('checkbox-luckmail-select-all'); const luckmailSelectionSummary = document.getElementById('luckmail-selection-summary'); const btnLuckmailBulkUsed = document.getElementById('btn-luckmail-bulk-used'); const btnLuckmailBulkUnused = document.getElementById('btn-luckmail-bulk-unused'); const btnLuckmailBulkPreserve = document.getElementById('btn-luckmail-bulk-preserve'); const btnLuckmailBulkUnpreserve = document.getElementById('btn-luckmail-bulk-unpreserve'); const btnLuckmailBulkDisable = document.getElementById('btn-luckmail-bulk-disable'); const btnLuckmailBulkEnable = document.getElementById('btn-luckmail-bulk-enable'); const luckmailList = document.getElementById('luckmail-list'); const rowEmailPrefix = document.getElementById('row-email-prefix'); const labelEmailPrefix = document.getElementById('label-email-prefix'); const inputEmailPrefix = document.getElementById('input-email-prefix'); const selectMail2925PoolAccount = document.getElementById('select-mail2925-pool-account'); const inputMail2925UseAccountPool = document.getElementById('input-mail2925-use-account-pool'); const labelMail2925UseAccountPool = document.getElementById('label-mail2925-use-account-pool'); const rowInbucketHost = document.getElementById('row-inbucket-host'); const inputInbucketHost = document.getElementById('input-inbucket-host'); const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox'); const inputInbucketMailbox = document.getElementById('input-inbucket-mailbox'); const rowCfDomain = document.getElementById('row-cf-domain'); const selectCfDomain = document.getElementById('select-cf-domain'); const cfDomainPickerRoot = document.getElementById('cf-domain-picker'); const btnCfDomainMenu = document.getElementById('btn-cf-domain-menu'); const cfDomainCurrent = document.getElementById('cf-domain-current'); const cfDomainMenu = document.getElementById('cf-domain-menu'); const inputCfDomain = document.getElementById('input-cf-domain'); const btnCfDomainMode = document.getElementById('btn-cf-domain-mode'); const inputRunCount = document.getElementById('input-run-count'); const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures'); const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('input-auto-skip-failures-thread-interval-minutes'); const inputStep6CookieCleanupEnabled = document.getElementById('input-step6-cookie-cleanup-enabled'); const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled'); const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes'); const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds'); const inputOperationDelayEnabled = document.getElementById('input-operation-delay-enabled'); const inputOAuthFlowTimeoutEnabled = document.getElementById('input-oauth-flow-timeout-enabled'); const inputVerificationResendCount = document.getElementById('input-verification-resend-count'); const rowPhoneVerificationEnabled = document.getElementById('row-phone-verification-enabled'); const btnTogglePhoneVerificationSection = document.getElementById('btn-toggle-phone-verification-section'); const rowPhoneVerificationFold = document.getElementById('row-phone-verification-fold'); const inputPhoneVerificationEnabled = document.getElementById('input-phone-verification-enabled'); const rowSignupMethod = document.getElementById('row-signup-method'); const rowSignupPhone = document.getElementById('row-signup-phone'); const signupMethodButtons = Array.from(document.querySelectorAll('[data-signup-method]')); const selectPhoneSmsProvider = document.getElementById('select-phone-sms-provider'); const rowHeroSmsPlatform = document.getElementById('row-hero-sms-platform'); const rowHeroSmsCountry = document.getElementById('row-hero-sms-country'); const rowHeroSmsCountryFallback = document.getElementById('row-hero-sms-country-fallback'); const rowHeroSmsAcquirePriority = document.getElementById('row-hero-sms-acquire-priority'); const rowHeroSmsApiKey = document.getElementById('row-hero-sms-api-key'); const rowHeroSmsMaxPrice = document.getElementById('row-hero-sms-max-price'); const rowPhoneSmsProvider = document.getElementById('row-phone-sms-provider'); const rowPhoneSmsProviderOrder = document.getElementById('row-phone-sms-provider-order'); const rowPhoneSmsProviderOrderActions = document.getElementById('row-phone-sms-provider-order-actions'); const rowFiveSimApiKey = document.getElementById('row-five-sim-api-key'); const rowFiveSimCountry = document.getElementById('row-five-sim-country'); const rowFiveSimCountryFallback = document.getElementById('row-five-sim-country-fallback'); const rowFiveSimOperator = document.getElementById('row-five-sim-operator'); const rowFiveSimProduct = document.getElementById('row-five-sim-product'); const rowNexSmsApiKey = document.getElementById('row-nex-sms-api-key'); const rowNexSmsCountry = document.getElementById('row-nex-sms-country'); const rowNexSmsCountryFallback = document.getElementById('row-nex-sms-country-fallback'); const rowNexSmsServiceCode = document.getElementById('row-nex-sms-service-code'); const rowHeroSmsRuntimePair = document.getElementById('row-hero-sms-runtime-pair'); const rowHeroSmsCurrentNumber = document.getElementById('row-hero-sms-current-number'); const rowHeroSmsCurrentCountdown = document.getElementById('row-hero-sms-current-countdown'); const rowHeroSmsPriceTiers = document.getElementById('row-hero-sms-price-tiers'); const rowHeroSmsCurrentCode = document.getElementById('row-hero-sms-current-code'); const rowHeroSmsPreferredActivation = document.getElementById('row-hero-sms-preferred-activation'); const rowPhoneCodeSettingsGroup = document.getElementById('row-phone-code-settings-group'); const rowPhoneVerificationResendCount = document.getElementById('row-phone-verification-resend-count'); const rowPhoneReplacementLimit = document.getElementById('row-phone-replacement-limit'); const rowPhoneCodeWaitSeconds = document.getElementById('row-phone-code-wait-seconds'); const rowPhoneCodeTimeoutWindows = document.getElementById('row-phone-code-timeout-windows'); const rowPhoneCodePollIntervalSeconds = document.getElementById('row-phone-code-poll-interval-seconds'); const rowPhoneCodePollMaxRounds = document.getElementById('row-phone-code-poll-max-rounds'); const rowFreePhoneReuseEnabled = document.getElementById('row-free-phone-reuse-enabled'); const rowFreePhoneReuseAutoEnabled = document.getElementById('row-free-phone-reuse-auto-enabled'); const rowFreeReusablePhone = document.getElementById('row-free-reusable-phone'); const inputHeroSmsApiKey = document.getElementById('input-hero-sms-api-key'); const btnToggleHeroSmsApiKey = document.getElementById('btn-toggle-hero-sms-api-key'); const inputFiveSimApiKey = document.getElementById('input-five-sim-api-key'); const btnToggleFiveSimApiKey = document.getElementById('btn-toggle-five-sim-api-key'); const inputFiveSimOperator = document.getElementById('input-five-sim-operator'); const inputFiveSimProduct = document.getElementById('input-five-sim-product'); const inputNexSmsApiKey = document.getElementById('input-nex-sms-api-key'); const btnToggleNexSmsApiKey = document.getElementById('btn-toggle-nex-sms-api-key'); const inputNexSmsServiceCode = document.getElementById('input-nex-sms-service-code'); const inputHeroSmsMaxPrice = document.getElementById('input-hero-sms-max-price'); const inputHeroSmsPreferredPrice = document.getElementById('input-hero-sms-preferred-price'); const inputPhoneReplacementLimit = document.getElementById('input-phone-replacement-limit'); const inputPhoneCodeWaitSeconds = document.getElementById('input-phone-code-wait-seconds'); const inputPhoneCodeTimeoutWindows = document.getElementById('input-phone-code-timeout-windows'); const inputPhoneCodePollIntervalSeconds = document.getElementById('input-phone-code-poll-interval-seconds'); const inputPhoneCodePollMaxRounds = document.getElementById('input-phone-code-poll-max-rounds'); const inputHeroSmsReuseEnabled = document.getElementById('input-hero-sms-reuse-enabled'); const inputFreePhoneReuseEnabled = document.getElementById('input-free-phone-reuse-enabled'); const inputFreePhoneReuseAutoEnabled = document.getElementById('input-free-phone-reuse-auto-enabled'); const inputFreeReusablePhone = document.getElementById('input-free-reusable-phone'); const selectHeroSmsCountry = document.getElementById('select-hero-sms-country'); const selectHeroSmsCountryFallback = document.getElementById('select-hero-sms-country-fallback'); const selectHeroSmsAcquirePriority = document.getElementById('select-hero-sms-acquire-priority'); const selectHeroSmsPreferredActivation = document.getElementById('select-hero-sms-preferred-activation'); const selectFiveSimCountry = document.getElementById('select-five-sim-country'); const heroSmsCountryMenuShell = document.getElementById('hero-sms-country-menu-shell'); const btnHeroSmsCountryMenu = document.getElementById('btn-hero-sms-country-menu'); const heroSmsCountryMenu = document.getElementById('hero-sms-country-menu'); const btnHeroSmsCountryClear = document.getElementById('btn-hero-sms-country-clear'); const fiveSimCountryMenuShell = document.getElementById('five-sim-country-menu-shell'); const btnFiveSimCountryMenu = document.getElementById('btn-five-sim-country-menu'); const fiveSimCountryMenu = document.getElementById('five-sim-country-menu'); const btnFiveSimCountryClear = document.getElementById('btn-five-sim-country-clear'); const selectNexSmsCountry = document.getElementById('select-nex-sms-country'); const nexSmsCountryMenuShell = document.getElementById('nex-sms-country-menu-shell'); const btnNexSmsCountryMenu = document.getElementById('btn-nex-sms-country-menu'); const nexSmsCountryMenu = document.getElementById('nex-sms-country-menu'); const btnNexSmsCountryClear = document.getElementById('btn-nex-sms-country-clear'); const selectPhoneSmsProviderOrder = document.getElementById('select-phone-sms-provider-order'); const phoneSmsProviderOrderMenuShell = document.getElementById('phone-sms-provider-order-menu-shell'); const btnPhoneSmsProviderOrderMenu = document.getElementById('btn-phone-sms-provider-order-menu'); const phoneSmsProviderOrderMenu = document.getElementById('phone-sms-provider-order-menu'); const btnPhoneSmsProviderOrderReset = document.getElementById('btn-phone-sms-provider-order-reset'); const btnHeroSmsPricePreview = document.getElementById('btn-hero-sms-price-preview'); const btnPhoneSmsBalance = document.getElementById('btn-phone-sms-balance'); const displayHeroSmsPlatform = document.getElementById('display-hero-sms-platform'); const displayHeroSmsCurrentNumber = document.getElementById('display-hero-sms-current-number'); const displayHeroSmsCurrentCountdown = document.getElementById('display-hero-sms-current-countdown'); const displayHeroSmsPriceTiers = document.getElementById('display-hero-sms-price-tiers'); const displayPhoneSmsBalance = document.getElementById('display-phone-sms-balance'); const displayHeroSmsCurrentCode = document.getElementById('display-hero-sms-current-code'); const displayFreeReusablePhoneCountry = document.getElementById('display-free-reusable-phone-country'); const displayFreeReusablePhone = document.getElementById('display-free-reusable-phone'); const displayHeroSmsCountryFallbackOrder = document.getElementById('display-hero-sms-country-fallback-order'); const displayFiveSimCountryFallbackOrder = document.getElementById('display-five-sim-country-fallback-order'); const displayNexSmsCountryFallbackOrder = document.getElementById('display-nex-sms-country-fallback-order'); const displayPhoneSmsProviderOrder = document.getElementById('display-phone-sms-provider-order'); const btnSaveFreeReusablePhone = document.getElementById('btn-save-free-reusable-phone'); const btnClearFreeReusablePhone = document.getElementById('btn-clear-free-reusable-phone'); const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url'); const inputAccountRunHistoryHelperBaseUrl = document.getElementById('input-account-run-history-helper-base-url'); const autoStartModal = document.getElementById('auto-start-modal'); const sharedFormModal = document.getElementById('shared-form-modal'); const sharedFormModalTitle = document.getElementById('shared-form-modal-title'); const btnSharedFormModalClose = document.getElementById('btn-shared-form-modal-close'); const sharedFormModalMessage = document.getElementById('shared-form-modal-message'); const sharedFormModalAlert = document.getElementById('shared-form-modal-alert'); const sharedFormModalFields = document.getElementById('shared-form-modal-fields'); const btnSharedFormModalCancel = document.getElementById('btn-shared-form-modal-cancel'); const btnSharedFormModalConfirm = document.getElementById('btn-shared-form-modal-confirm'); const autoStartTitle = autoStartModal?.querySelector('.modal-title'); const autoStartMessage = document.getElementById('auto-start-message'); const autoStartAlert = document.getElementById('auto-start-alert'); const modalOptionRow = document.getElementById('modal-option-row'); const modalOptionInput = document.getElementById('modal-option-input'); const modalOptionText = document.getElementById('modal-option-text'); const btnAutoStartClose = document.getElementById('btn-auto-start-close'); const btnAutoStartCancel = document.getElementById('btn-auto-start-cancel'); const btnAutoStartRestart = document.getElementById('btn-auto-start-restart'); const btnAutoStartContinue = document.getElementById('btn-auto-start-continue'); const autoHintText = document.querySelector('.auto-hint'); const stepsList = document.querySelector('.steps-list'); const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/'; const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL; const SIGNUP_METHOD_EMAIL = 'email'; const SIGNUP_METHOD_PHONE = 'phone'; const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL; const DEFAULT_ACTIVE_FLOW_ID = 'openai'; let latestState = null; let currentPlusModeEnabled = false; let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD; let currentSignupMethod = DEFAULT_SIGNUP_METHOD; let lastConfirmedOperationDelayEnabled = true; let heroSmsCountrySelectionOrder = []; let phoneSmsProviderOrderSelection = []; let heroSmsCountryMenuSearchKeyword = ''; const heroSmsCountrySearchTextById = new Map(); const phonePreferredActivationOptionMap = new Map(); let phoneRuntimeCountdownTimer = null; let phoneRuntimeCountdownEndsAt = 0; let phoneRuntimeCountdownWindowIndex = 0; let phoneRuntimeCountdownWindowTotal = 0; let fiveSimCountrySelectionOrder = []; let fiveSimCountryMenuSearchKeyword = ''; const fiveSimCountrySearchTextByCode = new Map(); let nexSmsCountrySelectionOrder = []; let nexSmsCountryMenuSearchKeyword = ''; const nexSmsCountrySearchTextById = new Map(); let stepDefinitions = getStepDefinitionsForMode(false, { plusPaymentMethod: currentPlusPaymentMethod, signupMethod: currentSignupMethod, }); let STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite); let STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])); let SKIPPABLE_STEPS = new Set(STEP_IDS); const AUTO_DELAY_MIN_MINUTES = 1; const AUTO_DELAY_MAX_MINUTES = 1440; const AUTO_DELAY_DEFAULT_MINUTES = 30; const AUTO_FALLBACK_THREAD_INTERVAL_MIN_MINUTES = 0; const AUTO_FALLBACK_THREAD_INTERVAL_MAX_MINUTES = 1440; const AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES = 0; const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3; const AUTO_STEP_DELAY_MIN_SECONDS = 0; const AUTO_STEP_DELAY_MAX_SECONDS = 600; const VERIFICATION_RESEND_COUNT_MIN = 0; const VERIFICATION_RESEND_COUNT_MAX = 20; const DEFAULT_VERIFICATION_RESEND_COUNT = 4; const PHONE_REPLACEMENT_LIMIT_MIN = 1; const PHONE_REPLACEMENT_LIMIT_MAX = 20; const DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3; const PHONE_CODE_WAIT_SECONDS_MIN = 15; const PHONE_CODE_WAIT_SECONDS_MAX = 300; const DEFAULT_PHONE_CODE_WAIT_SECONDS = 60; const PHONE_CODE_TIMEOUT_WINDOWS_MIN = 1; const PHONE_CODE_TIMEOUT_WINDOWS_MAX = 10; const DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS = 2; const PHONE_CODE_POLL_INTERVAL_SECONDS_MIN = 1; const PHONE_CODE_POLL_INTERVAL_SECONDS_MAX = 30; const DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS = 5; const PHONE_CODE_POLL_MAX_ROUNDS_MIN = 1; const PHONE_CODE_POLL_MAX_ROUNDS_MAX = 120; const DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS = 4; const PHONE_SMS_PROVIDER_HERO = 'hero-sms'; const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim'; const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO; const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms'; const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO; const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([ PHONE_SMS_PROVIDER_HERO, PHONE_SMS_PROVIDER_FIVE_SIM, PHONE_SMS_PROVIDER_NEXSMS, ]); const DEFAULT_FIVE_SIM_COUNTRY_ORDER = Object.freeze(['thailand']); const DEFAULT_FIVE_SIM_OPERATOR = 'any'; const DEFAULT_FIVE_SIM_PRODUCT = 'openai'; const DEFAULT_NEX_SMS_COUNTRY_ORDER = Object.freeze([1]); const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot'; const HERO_SMS_COUNTRY_SELECTION_MAX = 3; const DEFAULT_HERO_SMS_REUSE_ENABLED = true; const HERO_SMS_ACQUIRE_PRIORITY_COUNTRY = 'country'; const HERO_SMS_ACQUIRE_PRIORITY_PRICE = 'price'; const HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH = 'price_high'; const DEFAULT_HERO_SMS_ACQUIRE_PRIORITY = HERO_SMS_ACQUIRE_PRIORITY_COUNTRY; const HERO_SMS_SUPPORTED_COUNTRY_ITEMS = Object.freeze([ { id: 6, chn: '印度尼西亚', eng: 'Indonesia' }, { id: 52, chn: '泰国', eng: 'Thailand' }, { id: 187, chn: '美国(物理)', eng: 'USA' }, { id: 16, chn: '英国', eng: 'United Kingdom' }, { id: 151, chn: '日本', eng: 'Japan' }, { id: 43, chn: '德国', eng: 'Germany' }, { id: 73, chn: '法国', eng: 'France' }, { id: 10, chn: '越南', eng: 'Vietnam' }, ]); const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(HERO_SMS_SUPPORTED_COUNTRY_ITEMS.map((item) => String(item.id))); const HERO_SMS_FALLBACK_COUNTRY_ITEMS = HERO_SMS_SUPPORTED_COUNTRY_ITEMS; const FIVE_SIM_COUNTRY_CN_BY_ID = Object.freeze({ afghanistan: '阿富汗', albania: '阿尔巴尼亚', algeria: '阿尔及利亚', angola: '安哥拉', argentina: '阿根廷', armenia: '亚美尼亚', australia: '澳大利亚', austria: '奥地利', azerbaijan: '阿塞拜疆', bahamas: '巴哈马', bahrain: '巴林', bangladesh: '孟加拉国', belarus: '白俄罗斯', belgium: '比利时', bolivia: '玻利维亚', bosnia: '波黑', brazil: '巴西', bulgaria: '保加利亚', cambodia: '柬埔寨', cameroon: '喀麦隆', canada: '加拿大', chile: '智利', china: '中国', colombia: '哥伦比亚', croatia: '克罗地亚', cyprus: '塞浦路斯', czech: '捷克', denmark: '丹麦', egypt: '埃及', england: '英国', estonia: '爱沙尼亚', ethiopia: '埃塞俄比亚', finland: '芬兰', france: '法国', georgia: '格鲁吉亚', germany: '德国', ghana: '加纳', greece: '希腊', hongkong: '中国香港', hungary: '匈牙利', india: '印度', indonesia: '印度尼西亚', ireland: '爱尔兰', israel: '以色列', italy: '意大利', japan: '日本', jordan: '约旦', kazakhstan: '哈萨克斯坦', kenya: '肯尼亚', kyrgyzstan: '吉尔吉斯斯坦', laos: '老挝', latvia: '拉脱维亚', lithuania: '立陶宛', malaysia: '马来西亚', mexico: '墨西哥', moldova: '摩尔多瓦', morocco: '摩洛哥', myanmar: '缅甸', nepal: '尼泊尔', netherlands: '荷兰', newzealand: '新西兰', nigeria: '尼日利亚', norway: '挪威', pakistan: '巴基斯坦', paraguay: '巴拉圭', peru: '秘鲁', philippines: '菲律宾', poland: '波兰', portugal: '葡萄牙', romania: '罗马尼亚', russia: '俄罗斯', saudiarabia: '沙特阿拉伯', serbia: '塞尔维亚', singapore: '新加坡', slovakia: '斯洛伐克', slovenia: '斯洛文尼亚', southafrica: '南非', spain: '西班牙', srilanka: '斯里兰卡', sweden: '瑞典', switzerland: '瑞士', taiwan: '中国台湾', tajikistan: '塔吉克斯坦', tanzania: '坦桑尼亚', thailand: '泰国', turkey: '土耳其', ukraine: '乌克兰', uruguay: '乌拉圭', usa: '美国', uzbekistan: '乌兹别克斯坦', venezuela: '委内瑞拉', vietnam: '越南', }); const HERO_SMS_COUNTRY_CODE_ALIAS_OVERRIDES = Object.freeze({ 'bahamas': ['BS'], 'bolivia': ['BO'], 'czech republic': ['CZ'], 'democratic republic of the congo': ['CD'], 'laos': ['LA'], 'moldova': ['MD'], 'north korea': ['KP'], 'south korea': ['KR'], 'russia': ['RU'], 'russian federation': ['RU'], 'syria': ['SY'], 'taiwan': ['TW'], 'tanzania': ['TZ'], 'united kingdom': ['GB', 'UK'], 'united states': ['US', 'USA'], 'venezuela': ['VE'], 'vietnam': ['VN'], }); const HERO_SMS_COUNTRY_ISO_CODE_BY_NAME = (() => { const lookup = new Map(); if (typeof Intl === 'undefined' || typeof Intl.DisplayNames !== 'function') { return lookup; } const displayNames = new Intl.DisplayNames(['en'], { type: 'region' }); for (let first = 65; first <= 90; first += 1) { for (let second = 65; second <= 90; second += 1) { const code = String.fromCharCode(first) + String.fromCharCode(second); const name = displayNames.of(code); const key = normalizeHeroSmsCountryAliasKey(name); if (!key || lookup.has(key)) { continue; } lookup.set(key, code); } } return lookup; })(); const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit'; const DEFAULT_CPA_CALLBACK_MODE = 'step8'; const MAIL_2925_MODE_PROVIDE = 'provide'; const MAIL_2925_MODE_RECEIVE = 'receive'; const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE; const NEW_USER_GUIDE_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-new-user-guide-prompt-dismissed'; const AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-skip-failures-prompt-dismissed'; const AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-fallback-risk-prompt-dismissed'; const CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-cpa-phone-signup-prompt-dismissed'; const CPA_PHONE_SIGNUP_WARNING_MESSAGE = 'CPA 未适配手机号注册模式,认证成功后无法使用。请使用 SUB2API,或者认证成功后重新登录一遍进行解决。'; const PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY = 'multipage-phone-verification-section-expanded'; let phoneVerificationSectionExpanded = false; function readPhoneVerificationSectionExpanded() { try { return window.localStorage?.getItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY) === '1'; } catch (err) { return false; } } function persistPhoneVerificationSectionExpanded(expanded) { try { if (expanded) { window.localStorage?.setItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY, '1'); } else { window.localStorage?.removeItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY); } } catch (err) { // Ignore storage errors; the current in-memory state is enough for this session. } } function setPhoneVerificationSectionExpanded(expanded) { phoneVerificationSectionExpanded = Boolean(expanded); persistPhoneVerificationSectionExpanded(phoneVerificationSectionExpanded); if (typeof updatePhoneVerificationSettingsUI === 'function') { updatePhoneVerificationSettingsUI(); } } function togglePhoneVerificationSectionExpanded() { setPhoneVerificationSectionExpanded(!phoneVerificationSectionExpanded); } function initPhoneVerificationSectionExpandedState() { phoneVerificationSectionExpanded = readPhoneVerificationSectionExpanded(); if (typeof updatePhoneVerificationSettingsUI === 'function') { updatePhoneVerificationSettingsUI(); } } function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) { const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID !== 'undefined' ? DEFAULT_ACTIVE_FLOW_ID : 'openai'; const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal'; const rawPaymentMethod = typeof options === 'string' ? options : (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod); const rawSignupMethod = typeof options === 'string' ? currentSignupMethod : (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD); const activeFlowId = typeof options === 'string' ? ((typeof latestState !== 'undefined' ? latestState?.activeFlowId : '') || defaultFlowId) : (options.activeFlowId || (typeof latestState !== 'undefined' ? latestState?.activeFlowId : '') || defaultFlowId); return (window.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: String(activeFlowId || '').trim().toLowerCase() || defaultFlowId, plusModeEnabled, plusPaymentMethod: normalizePlusPaymentMethod(rawPaymentMethod), signupMethod: normalizeSignupMethod(rawSignupMethod), }) || []) .sort((left, right) => { const leftOrder = Number.isFinite(left.order) ? left.order : left.id; const rightOrder = Number.isFinite(right.order) ? right.order : right.id; if (leftOrder !== rightOrder) return leftOrder - rightOrder; return left.id - right.id; }); } function getStepIdByKeyForCurrentMode(stepKey = '') { const normalizedKey = String(stepKey || '').trim(); if (!normalizedKey) { return 0; } const match = (stepDefinitions || []).find((step) => String(step?.key || '') === normalizedKey); return Number(match?.id) || 0; } function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) { currentPlusModeEnabled = Boolean(plusModeEnabled); const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal'; const rawPaymentMethod = typeof options === 'string' ? options : (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod); const rawSignupMethod = typeof options === 'string' ? currentSignupMethod : (options.signupMethod || currentSignupMethod || DEFAULT_SIGNUP_METHOD); currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod); currentSignupMethod = normalizeSignupMethod(rawSignupMethod); stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, { activeFlowId: options?.activeFlowId, plusPaymentMethod: currentPlusPaymentMethod, signupMethod: currentSignupMethod, }); STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite); STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])); SKIPPABLE_STEPS = new Set(STEP_IDS); } const CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY = 'multipage-contribution-content-prompt-dismissed-version'; const AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS = 3; const HOTMAIL_SERVICE_MODE_REMOTE = 'remote'; const HOTMAIL_SERVICE_MODE_LOCAL = 'local'; const ICLOUD_PROVIDER = 'icloud'; const GMAIL_PROVIDER = 'gmail'; const GMAIL_ALIAS_GENERATOR = 'gmail-alias'; const LUCKMAIL_PROVIDER = 'luckmail-api'; const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool'; const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com'; const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph'; const DISPLAY_TIMEZONE = 'Asia/Shanghai'; const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373'; const CONTRIBUTION_UPLOAD_URL = 'https://apikey.qzz.io/'; const DEFAULT_PHONE_VERIFICATION_ENABLED = false; const DEFAULT_HERO_SMS_COUNTRY_ID = 52; const DEFAULT_HERO_SMS_COUNTRY_LABEL = 'Thailand'; const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam'; const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)'; const FIVE_SIM_SUPPORTED_COUNTRY_ITEMS = Object.freeze([ { id: 'indonesia', chn: '印度尼西亚', eng: 'Indonesia', searchText: 'indonesia 印度尼西亚 印尼 Indonesia ID +62' }, { id: 'thailand', chn: '泰国', eng: 'Thailand', searchText: 'thailand 泰国 Thailand TH +66' }, { id: 'england', chn: '英国', eng: 'England', searchText: 'england 英国 England UK GB United Kingdom +44' }, { id: 'usa', chn: '美国', eng: 'United States', searchText: 'usa 美国 United States US +1' }, { id: 'japan', chn: '日本', eng: 'Japan', searchText: 'japan 日本 Japan JP +81' }, { id: 'germany', chn: '德国', eng: 'Germany', searchText: 'germany 德国 Germany DE +49' }, { id: 'vietnam', chn: '越南', eng: 'Vietnam', searchText: 'vietnam 越南 Vietnam VN +84' }, ]); const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => item.id)); const NEX_SMS_FALLBACK_COUNTRY_ITEMS = Object.freeze([ { id: 1, label: 'Ukraine (#1)', searchText: 'Ukraine 1 UA' }, { id: 6, label: 'Indonesia (#6)', searchText: 'Indonesia 6 ID' }, { id: 7, label: 'Malaysia (#7)', searchText: 'Malaysia 7 MY' }, ]); const DEFAULT_IP_PROXY_SERVICE = '711proxy'; const SUPPORTED_IP_PROXY_SERVICES = ['711proxy', 'lumiproxy', 'iproyal', 'omegaproxy']; const IP_PROXY_ENABLED_SERVICES = ['711proxy']; const DEFAULT_IP_PROXY_MODE = 'account'; const SUPPORTED_IP_PROXY_MODES = ['api', 'account']; const DEFAULT_IP_PROXY_PROTOCOL = 'http'; const SUPPORTED_IP_PROXY_PROTOCOLS = ['http', 'https', 'socks4', 'socks5']; const IP_PROXY_API_MODE_ENABLED = false; const IP_PROXY_ACCOUNT_LIST_ENABLED = false; function getManagedAliasUtils() { return window.MultiPageManagedAliasUtils || null; } function isManagedAliasProvider(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) { const utils = getManagedAliasUtils(); if (utils?.usesManagedAliasGeneration) { return utils.usesManagedAliasGeneration(provider, { mail2925Mode }); } if (utils?.isManagedAliasProvider) { const normalizedProvider = String(provider || '').trim().toLowerCase(); if (normalizedProvider === '2925') { return utils.isManagedAliasProvider(provider) && normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE; } return utils.isManagedAliasProvider(provider); } const normalizedProvider = String(provider || '').trim().toLowerCase(); if (normalizedProvider === '2925') { return normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE; } return normalizedProvider === GMAIL_PROVIDER; } function parseManagedAliasBaseEmail(rawValue, provider = selectMailProvider.value) { const utils = getManagedAliasUtils(); if (utils?.parseManagedAliasBaseEmail) { return utils.parseManagedAliasBaseEmail(rawValue, provider); } return null; } function isManagedAliasEmail(value, baseEmail = '', provider = selectMailProvider.value) { const utils = getManagedAliasUtils(); if (utils?.isManagedAliasEmail) { return utils.isManagedAliasEmail(value, provider, baseEmail); } return false; } function getManagedAliasProviderUiCopy(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) { if (!isManagedAliasProvider(provider, mail2925Mode)) { return null; } const utils = getManagedAliasUtils(); if (utils?.getManagedAliasProviderUiCopy) { return utils.getManagedAliasProviderUiCopy(provider); } if (String(provider || '').trim().toLowerCase() === GMAIL_PROVIDER) { return { baseLabel: '基邮箱', basePlaceholder: '例如 yourname@gmail.com', buttonLabel: '生成', successVerb: '生成', label: 'Gmail +tag 邮箱', placeholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱', hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。', }; } if (String(provider || '').trim().toLowerCase() === '2925') { return { baseLabel: '基邮箱', basePlaceholder: '例如 yourname@2925.com', buttonLabel: '生成', successVerb: '生成', label: '2925 邮箱', placeholder: '点击生成 2925 邮箱,或手动填写完整邮箱', hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。', }; } return null; } function getManagedAliasBaseEmailKey(provider = selectMailProvider.value) { const normalizedProvider = String(provider || '').trim().toLowerCase(); if (normalizedProvider === GMAIL_PROVIDER) { return 'gmailBaseEmail'; } if (normalizedProvider === '2925') { return 'mail2925BaseEmail'; } return ''; } function isMail2925AccountPoolEnabled(state = latestState) { return Boolean(state?.mail2925UseAccountPool); } function getPreferredMail2925PoolAccountId(state = latestState) { const currentId = String(state?.currentMail2925AccountId || '').trim(); if (currentId && getMail2925Accounts(state).some((account) => account.id === currentId)) { return currentId; } return ''; } function syncMail2925PoolAccountOptions(state = latestState) { if (!selectMail2925PoolAccount) { return; } const accounts = getMail2925Accounts(state); const selectedId = getPreferredMail2925PoolAccountId(state); const options = [''].concat( accounts.map((account) => ``) ); selectMail2925PoolAccount.innerHTML = options.join(''); selectMail2925PoolAccount.value = selectedId; } async function syncSelectedMail2925PoolAccount(options = {}) { const { silent = false } = options; if (!selectMail2925PoolAccount || !isMail2925AccountPoolEnabled(latestState)) { return null; } const accountId = String(selectMail2925PoolAccount.value || '').trim(); if (!accountId) { syncLatestState({ currentMail2925AccountId: null }); setManagedAliasBaseEmailInputForProvider('2925', latestState); return null; } const response = await chrome.runtime.sendMessage({ type: 'SELECT_MAIL2925_ACCOUNT', source: 'sidepanel', payload: { accountId }, }); if (response?.error) { throw new Error(response.error); } syncLatestState({ currentMail2925AccountId: response.account?.id || accountId, ...(response.account?.email ? { mail2925BaseEmail: String(response.account.email).trim() } : {}), }); setManagedAliasBaseEmailInputForProvider('2925', latestState); if (!silent) { showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800); } return response.account || null; } function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) { if (String(provider || '').trim().toLowerCase() === '2925' && isMail2925AccountPoolEnabled(state)) { const currentMail2925Email = getCurrentMail2925Email(state); if (currentMail2925Email) { return currentMail2925Email; } } const key = getManagedAliasBaseEmailKey(provider); if (!key) { return ''; } const providerValue = String(state?.[key] || '').trim(); if (providerValue) { return providerValue; } const legacyEmailPrefix = String(state?.emailPrefix || '').trim(); return parseManagedAliasBaseEmail(legacyEmailPrefix, provider) ? legacyEmailPrefix : ''; } function buildManagedAliasBaseEmailPayload(state = latestState) { const payload = { gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(), mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(), mail2925UseAccountPool: Boolean(state?.mail2925UseAccountPool), emailPrefix: '', }; const key = getManagedAliasBaseEmailKey(); if (key) { if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(state)) { payload[key] = String(state?.mail2925BaseEmail || '').trim(); } else { payload[key] = inputEmailPrefix.value.trim(); } } return payload; } function syncManagedAliasBaseEmailDraftFromInput(provider = selectMailProvider.value) { const key = getManagedAliasBaseEmailKey(provider); if (!key) { return; } if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(latestState)) { return; } syncLatestState({ [key]: inputEmailPrefix.value.trim() }); } function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) { syncMail2925PoolAccountOptions(state); inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state); } function getCurrentRegistrationEmailUiCopy() { if (isCustomMailProvider()) { return getCustomMailProviderUiCopy(); } if (usesGeneratedAliasMailProvider()) { return getManagedAliasProviderUiCopy(); } return getEmailGeneratorUiCopy(); } function isCurrentRegistrationEmailCompatible(email = inputEmail.value.trim(), provider = selectMailProvider.value, state = latestState) { if (!usesGeneratedAliasMailProvider(provider, getSelectedMail2925Mode()) || !email) { return true; } const baseEmail = getManagedAliasBaseEmailForProvider(provider, state); return isManagedAliasEmail(email, baseEmail, provider); } function validateCurrentRegistrationEmail(email = inputEmail.value.trim(), options = {}) { const { showToastOnFailure = false } = options; if (isCurrentRegistrationEmailCompatible(email)) { return true; } if (showToastOnFailure) { const uiCopy = getManagedAliasProviderUiCopy(); const baseEmail = getManagedAliasBaseEmailForProvider(); showToast( baseEmail ? `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,注册邮箱需与 ${uiCopy?.baseLabel || '基邮箱'} 对应。` : `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,请直接填写完整邮箱,或先填写基邮箱后点击“生成”。`, 'warn' ); } return false; } let currentAutoRun = { autoRunning: false, phase: 'idle', currentRun: 0, totalRuns: 1, attemptRun: 0, scheduledAt: null, countdownAt: null, countdownTitle: '', countdownNote: '', }; let pendingAutoRunStartTotalRuns = 0; let pendingAutoRunStartExpiresAt = 0; let settingsDirty = false; let settingsSaveInFlight = false; let settingsAutoSaveTimer = null; let settingsSaveRevision = 0; let signupPhoneInputDirty = false; let signupPhoneInputFocused = false; let signupPhoneInputPersistPromise = null; let cloudflareDomainEditMode = false; let cloudflareTempEmailDomainEditMode = false; let modalChoiceResolver = null; let currentModalActions = []; let modalResultBuilder = null; let activePlusManualConfirmationRequestId = ''; let plusManualConfirmationDialogInFlight = false; let scheduledCountdownTimer = null; let configMenuOpen = false; let configActionInFlight = false; let currentReleaseSnapshot = null; let currentContributionContentSnapshot = null; let contributionContentSnapshotRequestInFlight = null; function normalizeAutomationWindowId(value) { if (value === null || value === undefined || value === '') { return null; } const numeric = Number(value); return Number.isInteger(numeric) && numeric >= 0 ? numeric : null; } async function getCurrentSidepanelWindowId() { if (chrome?.windows?.getCurrent) { try { const currentWindow = await chrome.windows.getCurrent(); const windowId = normalizeAutomationWindowId(currentWindow?.id); if (windowId !== null) { return windowId; } } catch (error) { console.warn('Failed to get current sidepanel window:', error?.message || error); } } return normalizeAutomationWindowId(latestState?.automationWindowId); } function shouldAttachAutomationWindow(message = {}) { const source = String(message?.source || '').trim(); if (source && source !== 'sidepanel') { return false; } return [ 'EXECUTE_STEP', 'AUTO_RUN', 'SCHEDULE_AUTO_RUN', 'RESUME_AUTO_RUN', 'START_SCHEDULED_AUTO_RUN_NOW', 'SKIP_AUTO_RUN_COUNTDOWN', 'PROBE_IP_PROXY_EXIT', ].includes(String(message?.type || '').trim()); } async function sendSidepanelMessage(message = {}) { const payload = { ...(message || {}), source: message?.source || 'sidepanel', }; if (shouldAttachAutomationWindow(payload)) { const windowId = await getCurrentSidepanelWindowId(); if (windowId !== null) { payload.payload = { ...(payload.payload || {}), automationWindowId: windowId, }; syncLatestState({ automationWindowId: windowId }); } } return chrome.runtime.sendMessage(payload); } window.sendSidepanelMessage = sendSidepanelMessage; const DEFAULT_SUB2API_GROUP_OPTIONS = ['codex', 'openai-plus']; const editableListPickerModule = window.SidepanelEditableListPicker || {}; const normalizeEditableListValues = editableListPickerModule.normalizeEditableListValues || ((...sources) => { const values = []; const seen = new Set(); const append = (value) => { const items = Array.isArray(value) ? value : String(value || '').split(/[\r\n,,、]+/); items.forEach((item) => { const normalized = String(item || '').trim(); const key = normalized.toLowerCase(); if (key && !seen.has(key)) { seen.add(key); values.push(normalized); } }); }; sources.forEach(append); return values; }); const createEditableListPicker = editableListPickerModule.createEditableListPicker || (() => ({ close() { }, render() { }, setOpen() { }, setSelection() { }, setVisible() { }, })); const closeEditableListPickers = editableListPickerModule.closeEditableListPickers || (() => { }); const isClickInsideEditableListPicker = editableListPickerModule.isClickInsideEditableListPicker || (() => false); function normalizeSub2ApiGroupOptions(...sources) { return normalizeEditableListValues(...sources); } function normalizeSub2ApiAccountPriorityValue(value) { const rawValue = String(value ?? '').trim(); const numeric = Number(rawValue); if (!rawValue || !Number.isSafeInteger(numeric) || numeric < 1) { return 1; } return numeric; } function getSelectedSub2ApiGroupName() { return String(inputSub2ApiGroup?.value || '').trim() || DEFAULT_SUB2API_GROUP_OPTIONS[0]; } function getSub2ApiGroupOptionsState(state = latestState) { const options = normalizeSub2ApiGroupOptions( state?.sub2apiGroupNames, state?.sub2apiGroupName ); return options.length ? options : [...DEFAULT_SUB2API_GROUP_OPTIONS]; } const sub2ApiGroupPicker = createEditableListPicker({ root: sub2ApiGroupPickerRoot, input: inputSub2ApiGroup, trigger: btnSub2ApiGroupMenu, current: sub2ApiGroupCurrent, menu: sub2ApiGroupMenu, fallbackItems: DEFAULT_SUB2API_GROUP_OPTIONS, minItems: 1, itemLabel: '分组', onDelete: handleDeleteSub2ApiGroup, onDeleteError: (error) => showToast(error?.message || '删除 SUB2API 分组失败。', 'error'), }); const cfDomainPicker = createEditableListPicker({ root: cfDomainPickerRoot, input: selectCfDomain, trigger: btnCfDomainMenu, current: cfDomainCurrent, menu: cfDomainMenu, emptyLabel: '请先添加域名', itemLabel: '域名', normalizeItems: normalizeCloudflareDomains, normalizeValue: normalizeCloudflareDomainValue, onDelete: handleDeleteCloudflareDomain, onDeleteError: (error) => showToast(error?.message || '删除 Cloudflare 域名失败。', 'error'), }); const tempEmailDomainPicker = createEditableListPicker({ root: tempEmailDomainPickerRoot, input: selectTempEmailDomain, trigger: btnTempEmailDomainMenu, current: tempEmailDomainCurrent, menu: tempEmailDomainMenu, emptyLabel: '请先更新域名', itemLabel: '域名', normalizeItems: normalizeCloudflareTempEmailDomains, normalizeValue: normalizeCloudflareTempEmailDomainValue, onDelete: handleDeleteCloudflareTempEmailDomain, onDeleteError: (error) => showToast(error?.message || '删除 Cloudflare Temp Email 域名失败。', 'error'), }); function renderSub2ApiGroupOptions(state = latestState, selectedValue = '') { if (!inputSub2ApiGroup) { return; } const selected = String(selectedValue || state?.sub2apiGroupName || '').trim(); const options = getSub2ApiGroupOptionsState({ ...(state || {}), sub2apiGroupName: selected || state?.sub2apiGroupName, }); if (selected && !options.some((name) => name.toLowerCase() === selected.toLowerCase())) { options.unshift(selected); } sub2ApiGroupPicker.render(options, selected || options[0] || DEFAULT_SUB2API_GROUP_OPTIONS[0]); } let customEmailPoolEntriesState = []; const EYE_OPEN_ICON = ''; const EYE_CLOSED_ICON = ''; const COPY_ICON = ''; const parseHotmailImportText = window.HotmailUtils?.parseHotmailImportText; const normalizeHotmailServiceModeFromUtils = window.HotmailUtils?.normalizeHotmailServiceMode; const shouldClearHotmailCurrentSelection = window.HotmailUtils?.shouldClearHotmailCurrentSelection; const upsertHotmailAccountInList = window.HotmailUtils?.upsertHotmailAccountInList; const filterHotmailAccountsByUsage = window.HotmailUtils?.filterHotmailAccountsByUsage; const getHotmailBulkActionLabel = window.HotmailUtils?.getHotmailBulkActionLabel; const getHotmailListToggleLabel = window.HotmailUtils?.getHotmailListToggleLabel; const upsertPayPalAccountInList = window.PayPalUtils?.upsertPayPalAccountInList; const normalizeLuckmailTimestampValue = window.LuckMailUtils?.normalizeTimestamp || ((value) => { const timestamp = Date.parse(String(value || '')); return Number.isFinite(timestamp) ? timestamp : 0; }); const sidepanelUpdateService = window.SidepanelUpdateService; const contributionContentService = window.SidepanelContributionContentService; const sharedFormDialog = window.SidepanelFormDialog?.createFormDialog?.({ overlay: sharedFormModal, titleNode: sharedFormModalTitle, closeButton: btnSharedFormModalClose, messageNode: sharedFormModalMessage, alertNode: sharedFormModalAlert, fieldsContainer: sharedFormModalFields, cancelButton: btnSharedFormModalCancel, confirmButton: btnSharedFormModalConfirm, }); const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = window.LuckMailUtils?.DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME || '保留'; const normalizeIcloudHost = window.IcloudUtils?.normalizeIcloudHost || ((value) => { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : ''; }); const normalizeIcloudFetchMode = (value) => { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'always_new' ? 'always_new' : 'reuse_existing'; }; const normalizeIcloudTargetMailboxType = window.MailProviderUtils?.normalizeIcloudTargetMailboxType || ((value) => String(value || '').trim().toLowerCase() === 'forward-mailbox' ? 'forward-mailbox' : 'icloud-inbox'); const getIcloudForwardMailProviderOptions = window.MailProviderUtils?.getIcloudForwardMailProviderOptions || (() => Array.from(selectIcloudForwardMailProvider?.options || []) .map((option) => ({ value: String(option?.value || '').trim().toLowerCase(), label: String(option?.textContent || option?.label || option?.value || '').trim(), })) .filter((option) => option.value)); const normalizeIcloudForwardMailProvider = window.MailProviderUtils?.normalizeIcloudForwardMailProvider || ((value) => { const normalized = String(value || '').trim().toLowerCase(); const options = getIcloudForwardMailProviderOptions(); return options.some((option) => option.value === normalized) ? normalized : (options[0]?.value || 'qq'); }); const ICLOUD_FORWARD_MAIL_PROVIDER_LABELS = Object.fromEntries( getIcloudForwardMailProviderOptions().map((option) => [option.value, option.label]) ); const getIcloudLoginUrlForHost = window.IcloudUtils?.getIcloudLoginUrlForHost || ((host) => host === 'icloud.com.cn' ? 'https://www.icloud.com.cn/' : (host === 'icloud.com' ? 'https://www.icloud.com/' : '')); btnAutoCancelSchedule?.remove(); const MAIL_PROVIDER_LOGIN_CONFIGS = { [ICLOUD_PROVIDER]: { label: 'iCloud 邮箱', buttonLabel: '登录', }, [GMAIL_PROVIDER]: { label: 'Gmail 邮箱', url: 'https://mail.google.com/mail/u/0/#inbox', buttonLabel: '登录', }, '163': { label: '163 邮箱', url: 'https://mail.163.com/', buttonLabel: '登录', }, '163-vip': { label: '163 VIP 邮箱', url: 'https://webmail.vip.163.com/', buttonLabel: '登录', }, '126': { label: '126 邮箱', url: 'https://mail.126.com/', buttonLabel: '登录', }, qq: { label: 'QQ 邮箱', url: 'https://wx.mail.qq.com/', buttonLabel: '登录', }, 'cloudflare-temp-email': { label: 'Cloudflare Temp Email GitHub', url: 'https://github.com/dreamhunter2333/cloudflare_temp_email', buttonLabel: 'GitHub', }, '2925': { label: '2925 邮箱', url: 'https://2925.com/#/mailList', }, }; const IP_PROXY_SERVICE_LOGIN_CONFIGS = { '711proxy': { label: '711Proxy', url: 'https://www.711proxy.com/signup?code=AD2497', buttonLabel: '注册', }, }; // ============================================================ // Toast Notifications // ============================================================ const toastContainer = document.getElementById('toast-container'); const TOAST_ICONS = { error: '', warn: '', success: '', info: '', }; const LOG_LEVEL_LABELS = { info: '信息', ok: '成功', warn: '警告', error: '错误', }; const CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email'; function usesGeneratedAliasMailProvider( provider, mail2925Mode = getSelectedMail2925Mode(), generator = undefined ) { const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string' ? CUSTOM_EMAIL_POOL_GENERATOR : 'custom-pool'; const resolvedGenerator = generator !== undefined ? generator : (typeof getSelectedEmailGenerator === 'function' ? getSelectedEmailGenerator() : ''); return resolvedGenerator !== customEmailPoolGenerator && isManagedAliasProvider(provider, mail2925Mode); } function parseGmailBaseEmail(rawValue = '') { const value = String(rawValue || '').trim().toLowerCase(); const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i); if (!match) return null; return { localPart: match[1], domain: match[2].toLowerCase(), }; } function isManagedGmailAlias(value, baseEmail) { const parsedBase = parseGmailBaseEmail(baseEmail); if (!parsedBase) return false; const match = String(value || '').trim().toLowerCase().match(/^([^@\s+]+)(?:\+[^@\s]+)?@((?:gmail|googlemail)\.com)$/i); if (!match) return false; return match[1] === parsedBase.localPart && match[2] === parsedBase.domain; } function showToast(message, type = 'error', duration = 4000) { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = `${TOAST_ICONS[type] || ''}${escapeHtml(message)}`; toast.querySelector('.toast-close').addEventListener('click', () => dismissToast(toast)); toastContainer.appendChild(toast); if (duration > 0) { setTimeout(() => dismissToast(toast), duration); } } function dismissToast(toast) { if (!toast.parentNode) return; toast.classList.add('toast-exit'); toast.addEventListener('animationend', () => toast.remove()); } function resetActionModalOption() { if (!modalOptionRow || !modalOptionInput || !modalOptionText) { return; } modalOptionRow.hidden = true; modalOptionInput.checked = false; modalOptionInput.disabled = false; modalOptionText.textContent = '不再提示'; } function resetActionModalAlert() { if (!autoStartAlert) { return; } autoStartAlert.hidden = true; autoStartAlert.textContent = ''; autoStartAlert.className = 'modal-alert'; } function setActionModalMessageContent({ text = '', html = '' } = {}) { if (!autoStartMessage) { return; } if (html) { autoStartMessage.innerHTML = html; return; } autoStartMessage.textContent = text; } function resetActionModalButtons() { const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue]; buttons.forEach((button) => { if (!button) return; button.hidden = true; button.disabled = false; button.onclick = null; }); currentModalActions = []; } function configureActionModalButton(button, action) { if (!button) return; if (!action) { button.hidden = true; button.onclick = null; return; } button.hidden = false; button.disabled = false; button.textContent = action.label; button.className = `btn ${action.variant || 'btn-outline'} btn-sm`; button.onclick = () => resolveModalChoice(action.id); } function configureActionModalOption(option) { if (!modalOptionRow || !modalOptionInput || !modalOptionText) { return; } if (!option) { resetActionModalOption(); return; } modalOptionRow.hidden = false; modalOptionInput.checked = Boolean(option.checked); modalOptionInput.disabled = Boolean(option.disabled); modalOptionText.textContent = option.label || '不再提示'; } function configureActionModalAlert(alert) { if (!autoStartAlert) { return; } if (!alert?.text) { resetActionModalAlert(); return; } autoStartAlert.hidden = false; autoStartAlert.textContent = alert.text; autoStartAlert.className = `modal-alert${alert.tone === 'danger' ? ' is-danger' : ''}`; } function resolveModalChoice(choice) { const optionChecked = Boolean(modalOptionInput?.checked); const result = typeof modalResultBuilder === 'function' ? modalResultBuilder(choice, { optionChecked }) : choice; if (modalChoiceResolver) { modalChoiceResolver(result); modalChoiceResolver = null; } modalResultBuilder = null; resetActionModalButtons(); resetActionModalAlert(); resetActionModalOption(); if (autoStartModal) { autoStartModal.hidden = true; } } function openActionModal({ title, message, messageHtml, actions, option, alert, buildResult }) { if (!autoStartModal) { return Promise.resolve(null); } if (modalChoiceResolver) { resolveModalChoice(null); } resetActionModalButtons(); autoStartTitle.textContent = title; setActionModalMessageContent({ text: message, html: messageHtml }); currentModalActions = actions || []; modalResultBuilder = typeof buildResult === 'function' ? buildResult : null; const buttonSlots = currentModalActions.length <= 2 ? [btnAutoStartCancel, btnAutoStartContinue] : [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue]; buttonSlots.forEach((button, index) => { configureActionModalButton(button, currentModalActions[index]); }); configureActionModalAlert(alert); configureActionModalOption(option); autoStartModal.hidden = false; return new Promise((resolve) => { modalChoiceResolver = resolve; }); } function openAutoStartChoiceDialog(startStep, options = {}) { const runningStep = Number.isInteger(options.runningStep) ? options.runningStep : null; const continueMessage = runningStep ? `继续当前会先等待步骤 ${runningStep} 完成,再按最新进度自动执行。` : `继续当前会从步骤 ${startStep} 开始自动执行。`; return openActionModal({ title: '启动自动', message: `检测到当前已有流程进度。${continueMessage}重新开始会清空当前流程进度并从步骤 1 新开一轮。`, actions: [ { id: null, label: '取消', variant: 'btn-ghost' }, { id: 'restart', label: '重新开始', variant: 'btn-outline' }, { id: 'continue', label: '继续当前', variant: 'btn-primary' }, ], }); } async function openConfirmModal({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary', alert = null }) { const choice = await openActionModal({ title, message, alert, actions: [ { id: null, label: '取消', variant: 'btn-ghost' }, { id: 'confirm', label: confirmLabel, variant: confirmVariant }, ], }); return choice === 'confirm'; } async function openConfirmModalWithOption({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary', alert = null, optionLabel = '不再提示', optionChecked = false, optionDisabled = false, }) { const result = await openActionModal({ title, message, alert, actions: [ { id: null, label: '取消', variant: 'btn-ghost' }, { id: 'confirm', label: confirmLabel, variant: confirmVariant }, ], option: { label: optionLabel, checked: optionChecked, disabled: optionDisabled, }, buildResult: (choice, meta) => ({ choice, optionChecked: Boolean(meta?.optionChecked), }), }); return { confirmed: result?.choice === 'confirm', optionChecked: Boolean(result?.optionChecked), }; } async function openPlusManualConfirmationDialog(options = {}) { const method = String(options.method || '').trim().toLowerCase(); const title = String(options.title || '').trim() || (method === 'gopay' ? 'GoPay 订阅确认' : '手动确认'); const message = String(options.message || '').trim() || (method === 'gopay' ? '请在当前订阅页中手动完成 GoPay 订阅,完成后点击“我已完成订阅”继续。' : '请先在页面中完成当前手动操作,完成后点击确认继续。'); return openActionModal({ title, message, actions: [ { id: 'cancel', label: '取消等待', variant: 'btn-ghost' }, { id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' }, ], alert: method === 'gopay' ? { text: '确认后流程会直接继续到 Plus 模式第 10 步 OAuth 登录。', tone: 'info' } : null, }); } async function syncPlusManualConfirmationDialog() { const requestId = String(latestState?.plusManualConfirmationRequestId || '').trim(); const pending = Boolean(latestState?.plusManualConfirmationPending); if (!pending || !requestId || plusManualConfirmationDialogInFlight || activePlusManualConfirmationRequestId === requestId) { return; } const step = Number(latestState?.plusManualConfirmationStep) || 0; const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase(); const title = latestState?.plusManualConfirmationTitle; const message = latestState?.plusManualConfirmationMessage; activePlusManualConfirmationRequestId = requestId; plusManualConfirmationDialogInFlight = true; let shouldReopenDialog = false; try { const choice = await openPlusManualConfirmationDialog({ method, title, message, }); const currentRequestId = String(latestState?.plusManualConfirmationRequestId || '').trim(); const stillPending = Boolean(latestState?.plusManualConfirmationPending); if (!stillPending || currentRequestId !== requestId) { return; } if (choice == null) { shouldReopenDialog = true; showToast('当前订阅确认仍在等待中,将重新弹出确认窗口。', 'info', 1800); return; } const confirmed = choice === 'confirm'; const response = await chrome.runtime.sendMessage({ type: 'RESOLVE_PLUS_MANUAL_CONFIRMATION', source: 'sidepanel', payload: { step, requestId, confirmed, }, }); if (response?.error) { throw new Error(response.error); } if (confirmed) { showToast(method === 'gopay' ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...', 'info', 2200); } else { showToast(method === 'gopay' ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。', 'warn', 2200); } } catch (error) { showToast(error?.message || String(error || '未知错误'), 'error'); } finally { if (activePlusManualConfirmationRequestId === requestId) { activePlusManualConfirmationRequestId = ''; } plusManualConfirmationDialogInFlight = false; if ( shouldReopenDialog && latestState?.plusManualConfirmationPending && String(latestState?.plusManualConfirmationRequestId || '').trim() === requestId ) { setTimeout(() => { void syncPlusManualConfirmationDialog(); }, 0); } } } function isPromptDismissed(storageKey) { return localStorage.getItem(storageKey) === '1'; } function setPromptDismissed(storageKey, dismissed) { if (dismissed) { localStorage.setItem(storageKey, '1'); } else { localStorage.removeItem(storageKey); } } function isNewUserGuidePromptDismissed() { return isPromptDismissed(NEW_USER_GUIDE_PROMPT_DISMISSED_STORAGE_KEY); } function setNewUserGuidePromptDismissed(dismissed) { setPromptDismissed(NEW_USER_GUIDE_PROMPT_DISMISSED_STORAGE_KEY, dismissed); } function shouldPromptNewUserGuide() { if (isNewUserGuidePromptDismissed()) { return false; } if (!btnContributionMode || btnContributionMode.disabled) { return false; } if (latestState?.contributionMode) { return false; } return true; } function getContributionPortalUrl() { return String(contributionContentService?.portalUrl || 'https://apikey.qzz.io').trim(); } function openNewUserGuidePrompt() { return openActionModal({ title: '新手引导', message: '如果你是第一次使用,可以先查看贡献页里的公告和使用教程。点击“查看引导”会自动打开贡献页面。', alert: { text: '本提示仅出现一次。', }, actions: [ { id: null, label: '取消', variant: 'btn-ghost' }, { id: 'confirm', label: '查看引导', variant: 'btn-primary' }, ], }); } async function maybeShowNewUserGuidePrompt() { if (!shouldPromptNewUserGuide()) { return false; } setNewUserGuidePromptDismissed(true); const choice = await openNewUserGuidePrompt(); if (choice === 'confirm') { openExternalUrl(getContributionPortalUrl()); return true; } return false; } function getDismissedContributionContentPromptVersion() { return String(localStorage.getItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY) || '').trim(); } function setDismissedContributionContentPromptVersion(version) { const normalized = String(version || '').trim(); if (normalized) { localStorage.setItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY, normalized); } else { localStorage.removeItem(CONTRIBUTION_CONTENT_PROMPT_DISMISSED_VERSION_STORAGE_KEY); } } function isAutoSkipFailuresPromptDismissed() { return isPromptDismissed(AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY); } function setAutoSkipFailuresPromptDismissed(dismissed) { setPromptDismissed(AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY, dismissed); } function isAutoRunFallbackRiskPromptDismissed() { return isPromptDismissed(AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY); } function setAutoRunFallbackRiskPromptDismissed(dismissed) { setPromptDismissed(AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY, dismissed); } function isCpaPhoneSignupPromptDismissed() { return isPromptDismissed(CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY); } function setCpaPhoneSignupPromptDismissed(dismissed) { setPromptDismissed(CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY, dismissed); } function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) { return totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS; } function shouldWarnCpaPhoneSignup(signupMethod = null, panelMode = null) { const resolvedSignupMethod = normalizeSignupMethod( signupMethod ?? ( typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : DEFAULT_SIGNUP_METHOD ) ); const resolvedPanelMode = normalizePanelMode( panelMode ?? ( typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : 'cpa' ) ); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode, signupMethod: resolvedSignupMethod, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), panelMode: resolvedPanelMode, signupMethod: resolvedSignupMethod, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '', panelMode: resolvedPanelMode, signupMethod: resolvedSignupMethod, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), panelMode: resolvedPanelMode, signupMethod: resolvedSignupMethod, }, }) : null; })(); if (capabilityState && typeof capabilityState.shouldWarnCpaPhoneSignup === 'boolean') { return capabilityState.shouldWarnCpaPhoneSignup && !isCpaPhoneSignupPromptDismissed(); } return resolvedSignupMethod === SIGNUP_METHOD_PHONE && resolvedPanelMode === 'cpa' && !isCpaPhoneSignupPromptDismissed(); } async function openCpaPhoneSignupWarningModal() { const result = await openConfirmModalWithOption({ title: 'CPA 手机号注册提醒', message: CPA_PHONE_SIGNUP_WARNING_MESSAGE, confirmLabel: '继续', optionLabel: '不再提醒', }); return { confirmed: result.confirmed, dismissPrompt: result.optionChecked, }; } async function confirmCpaPhoneSignupIfNeeded(options = {}) { const signupMethod = Object.prototype.hasOwnProperty.call(options, 'signupMethod') ? options.signupMethod : null; const panelMode = Object.prototype.hasOwnProperty.call(options, 'panelMode') ? options.panelMode : null; if (!shouldWarnCpaPhoneSignup(signupMethod, panelMode)) { return true; } const result = await openCpaPhoneSignupWarningModal(); if (result.dismissPrompt) { setCpaPhoneSignupPromptDismissed(true); } return result.confirmed; } async function openAutoSkipFailuresConfirmModal() { const result = await openConfirmModalWithOption({ title: '自动重试说明', message: `开启后,自动模式在某一轮失败时,会先在当前轮自动重试;单轮最多重试 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次,仍失败则放弃当前轮并继续下一轮。线程间隔只在开启自动重试且总轮数大于 1 时生效。`, confirmLabel: '确认开启', }); return { confirmed: result.confirmed, dismissPrompt: result.optionChecked, }; } async function openAutoRunFallbackRiskConfirmModal(totalRuns) { const result = await openConfirmModalWithOption({ title: '自动运行风险提醒', message: `当前轮数已经不适合单节点情况,请确保已经配置并打开节点轮询功能(若没有配置,请点击贡献/使用按钮,根据网页中使用教程进行配置),避免连续使用一个节点注册,导致出现手机号验证。`, confirmLabel: '继续', }); return { confirmed: result.confirmed, dismissPrompt: result.optionChecked, }; } function updateConfigMenuControls() { const disabled = configActionInFlight || settingsSaveInFlight; const contributionModeEnabled = Boolean(latestState?.contributionMode); if (contributionModeEnabled && configMenuOpen) { configMenuOpen = false; } const importLocked = disabled || contributionModeEnabled || currentAutoRun.autoRunning || Object.values(getStepStatuses()).some((status) => status === 'running'); if (btnConfigMenu) { btnConfigMenu.disabled = disabled || contributionModeEnabled; btnConfigMenu.setAttribute('aria-expanded', String(configMenuOpen)); } if (configMenu) { configMenu.hidden = contributionModeEnabled || !configMenuOpen; } if (btnExportSettings) { btnExportSettings.disabled = disabled || contributionModeEnabled; } if (btnImportSettings) { btnImportSettings.disabled = importLocked; } } function closeConfigMenu() { configMenuOpen = false; updateConfigMenuControls(); } function openConfigMenu() { configMenuOpen = true; updateConfigMenuControls(); } function toggleConfigMenu() { configMenuOpen ? closeConfigMenu() : openConfigMenu(); } async function waitForSettingsSaveIdle() { while (settingsSaveInFlight) { await new Promise((resolve) => setTimeout(resolve, 50)); } } async function flushPendingSettingsBeforeExport() { clearTimeout(settingsAutoSaveTimer); await waitForSettingsSaveIdle(); if (settingsDirty) { await saveSettings({ silent: true }); } } async function settlePendingSettingsBeforeImport() { clearTimeout(settingsAutoSaveTimer); await waitForSettingsSaveIdle(); } function downloadTextFile(content, fileName, mimeType = 'application/json;charset=utf-8') { const blob = new Blob([content], { type: mimeType }); const objectUrl = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = objectUrl; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); anchor.remove(); setTimeout(() => URL.revokeObjectURL(objectUrl), 0); } function isDoneStatus(status) { return status === 'completed' || status === 'manual_completed' || status === 'skipped'; } function getStepStatuses(state = latestState) { const merged = { ...STEP_DEFAULT_STATUSES, ...(state?.stepStatuses || {}) }; return Object.fromEntries(STEP_IDS.map((stepId) => [stepId, merged[stepId] || 'pending'])); } function getFirstUnfinishedStep(state = latestState) { const statuses = getStepStatuses(state); for (const step of STEP_IDS) { if (!isDoneStatus(statuses[step])) { return step; } } return null; } function getRunningSteps(state = latestState) { const statuses = getStepStatuses(state); return Object.entries(statuses) .filter(([, status]) => status === 'running') .map(([step]) => Number(step)) .sort((a, b) => a - b); } function hasSavedProgress(state = latestState) { const statuses = getStepStatuses(state); return Object.values(statuses).some((status) => status !== 'pending'); } function isContributionModeSwitchBlocked(state = latestState) { const statuses = getStepStatuses(state); const anyRunning = Object.values(statuses).some((status) => status === 'running'); return anyRunning || isAutoRunLockedPhase() || isAutoRunPausedPhase() || isAutoRunScheduledPhase(); } function shouldOfferAutoModeChoice(state = latestState) { return hasSavedProgress(state) && getFirstUnfinishedStep(state) !== null; } function syncLatestState(nextState) { const mergedStepStatuses = nextState?.stepStatuses ? { ...STEP_DEFAULT_STATUSES, ...(latestState?.stepStatuses || {}), ...nextState.stepStatuses } : getStepStatuses(latestState); latestState = { ...(latestState || {}), ...(nextState || {}), stepStatuses: mergedStepStatuses, }; renderAccountRecords(latestState); } let accountRunHistoryRefreshTimer = null; function scheduleAccountRunHistoryRefresh(delayMs = 150) { if (accountRunHistoryRefreshTimer) { clearTimeout(accountRunHistoryRefreshTimer); } accountRunHistoryRefreshTimer = setTimeout(() => { accountRunHistoryRefreshTimer = null; chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(state => { syncLatestState(state); syncAutoRunState(state); updateStatusDisplay(latestState); updateButtonStates(); }).catch(() => { }); }, Math.max(0, Number(delayMs) || 0)); } function normalizeOperationDelayEnabled(value) { return typeof value === 'boolean' ? value : true; } function appendOperationDelayLog(enabled, level = 'info', message = '') { appendLog({ timestamp: Date.now(), level, message: message || (enabled ? '操作间延迟已开启:页面输入、选择、点击、提交、继续、授权后等待 2 秒。' : '操作间延迟已关闭:页面操作将连续执行。'), }); } function applyOperationDelayState(state = latestState, options = {}) { const enabled = options.restoreFailed ? true : normalizeOperationDelayEnabled(state?.operationDelayEnabled); lastConfirmedOperationDelayEnabled = enabled; if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = enabled; if (typeof syncLatestState === 'function') { syncLatestState({ operationDelayEnabled: enabled }); } if (options.restoreFailed) { appendOperationDelayLog(true, 'warn', '操作间延迟设置读取失败,已回退为默认开启。'); } } async function persistOperationDelayToggle() { const nextEnabled = normalizeOperationDelayEnabled(inputOperationDelayEnabled?.checked); try { const response = await chrome.runtime.sendMessage({ type: 'SAVE_SETTING', source: 'sidepanel', payload: { operationDelayEnabled: nextEnabled }, }); if (response?.error) throw new Error(response.error); const confirmed = normalizeOperationDelayEnabled(response?.state?.operationDelayEnabled ?? nextEnabled); lastConfirmedOperationDelayEnabled = confirmed; if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = confirmed; syncLatestState({ operationDelayEnabled: confirmed }); appendOperationDelayLog(confirmed); } catch (error) { if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = lastConfirmedOperationDelayEnabled; appendOperationDelayLog(lastConfirmedOperationDelayEnabled, 'error', `操作间延迟设置保存失败,已恢复为上一次确认的状态:${error.message}`); throw error; } } function normalizePlusPaymentMethod(value = '') { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.normalizePlusPaymentMethod) { return rootScope.GoPayUtils.normalizePlusPaymentMethod(value); } const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay'; const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper'; const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal'; const normalized = String(value || '').trim().toLowerCase(); if (normalized === gpcValue) { return gpcValue; } return normalized === gopayValue ? gopayValue : paypalValue; } function getSelectedPlusPaymentMethod(state = latestState) { const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal'; if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value) { return normalizePlusPaymentMethod(selectPlusPaymentMethod.value); } return normalizePlusPaymentMethod(state?.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod); } function normalizeGpcHelperPhoneModeValue(value = '') { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) { return rootScope.GoPayUtils.normalizeGpcHelperPhoneMode(value); } const normalized = String(value || '').trim().toLowerCase(); return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin' ? GPC_HELPER_PHONE_MODE_AUTO : GPC_HELPER_PHONE_MODE_MANUAL; } function getGpcHelperAutoModeEnabled(state = latestState) { return Boolean(state?.gopayHelperAutoModeEnabled); } function normalizeGpcAutoModePermissionValue(value) { if (typeof value === 'boolean') { return value; } if (typeof value === 'number') { if (value === 1) return true; if (value === 0) return false; } const normalized = String(value ?? '').trim().toLowerCase(); if (!normalized) { return null; } if (['true', '1', 'yes', 'y', 'on', 'enabled', 'enable'].includes(normalized)) { return true; } if (['false', '0', 'no', 'n', 'off', 'disabled', 'disable'].includes(normalized)) { return false; } return null; } function getGpcAutoModePermissionFromPayload(payload = {}) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return null; } for (const key of ['auto_mode_enabled', 'autoModeEnabled', 'auto_enabled', 'autoEnabled']) { if (payload[key] !== undefined) { return normalizeGpcAutoModePermissionValue(payload[key]); } } if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) { return getGpcAutoModePermissionFromPayload(payload.data); } return null; } function shouldPreserveSelectedGpcAutoMode(state = latestState) { const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload); return normalizeGpcHelperPhoneModeValue(state?.gopayHelperPhoneMode) === GPC_HELPER_PHONE_MODE_AUTO && (Boolean(state?.gopayHelperAutoModeEnabled) || payloadPermission === true); } function hasGpcAutoModePermissionField(payload = {}) { return getGpcAutoModePermissionFromPayload(payload) !== null; } function isGpcAutoModePermissionDenied(state = latestState) { const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload); return payloadPermission === false; } function normalizeGpcRemainingUsesValue(value) { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.normalizeGpcRemainingUses) { return rootScope.GoPayUtils.normalizeGpcRemainingUses(value); } if (value === undefined || value === null || String(value).trim() === '') { return null; } const numeric = Number(value); return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null; } function getGpcBalanceRemainingUsesFromResponse(response = {}) { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.getGpcBalanceRemainingUses) { const remaining = rootScope.GoPayUtils.getGpcBalanceRemainingUses(response?.data || response?.payload || response); if (remaining !== null && remaining !== undefined) { return remaining; } } return normalizeGpcRemainingUsesValue( response?.remainingUses ?? response?.data?.remaining_uses ?? response?.data?.remainingUses ?? response?.payload?.data?.remaining_uses ?? response?.payload?.remaining_uses ?? response?.payload?.remainingUses ); } function getGpcAutoModeEnabledFromResponse(response = {}) { if (typeof response?.autoModeEnabled === 'boolean') { return response.autoModeEnabled; } const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.isGpcAutoModeEnabled) { return rootScope.GoPayUtils.isGpcAutoModeEnabled(response?.data || response?.payload || response); } return Boolean( response?.data?.auto_mode_enabled ?? response?.data?.autoModeEnabled ?? response?.payload?.data?.auto_mode_enabled ?? response?.payload?.auto_mode_enabled ?? response?.payload?.autoModeEnabled ); } function normalizeGpcOtpChannelValue(value = '') { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) { return rootScope.GoPayUtils.normalizeGpcOtpChannel(value); } return String(value || '').trim().toLowerCase() === 'sms' ? 'sms' : 'whatsapp'; } function normalizeGpcLocalSmsHelperBaseUrlValue(value = '') { const fallback = 'http://127.0.0.1:18767'; const rawValue = String(value || fallback).trim(); try { const parsed = new URL(rawValue); if (!['http:', 'https:'].includes(parsed.protocol)) { return fallback; } const endpointPath = parsed.pathname.replace(/\/+$/g, '') || '/'; if (['/otp', '/latest-otp', '/health'].includes(endpointPath)) { parsed.pathname = ''; parsed.search = ''; parsed.hash = ''; } return parsed.toString().replace(/\/$/, ''); } catch { return fallback; } } function hasOwnStateValue(source, key) { return Object.prototype.hasOwnProperty.call(source, key); } function readAutoRunStateValue(source, keys, fallback) { for (const key of keys) { if (hasOwnStateValue(source, key)) { return source[key]; } } return fallback; } function normalizePendingAutoRunStartRunCount(value) { const numeric = Math.floor(Number(value) || 0); return numeric > 0 ? numeric : 0; } function registerPendingAutoRunStartRunCount(totalRuns) { pendingAutoRunStartTotalRuns = normalizePendingAutoRunStartRunCount(totalRuns); pendingAutoRunStartExpiresAt = pendingAutoRunStartTotalRuns > 0 ? Date.now() + 30000 : 0; } function clearPendingAutoRunStartRunCount() { pendingAutoRunStartTotalRuns = 0; pendingAutoRunStartExpiresAt = 0; } function getPendingAutoRunStartRunCount() { if (pendingAutoRunStartTotalRuns > 0 && pendingAutoRunStartExpiresAt > 0 && Date.now() > pendingAutoRunStartExpiresAt) { clearPendingAutoRunStartRunCount(); } return pendingAutoRunStartTotalRuns; } function getAutoRunSourceTotalRuns(source = {}) { return normalizePendingAutoRunStartRunCount(readAutoRunStateValue(source, ['autoRunTotalRuns', 'totalRuns'], 0)); } function syncAutoRunState(source = {}) { const phase = source.autoRunPhase ?? source.phase ?? currentAutoRun.phase; const autoRunning = source.autoRunning !== undefined ? Boolean(source.autoRunning) : (source.autoRunPhase !== undefined || source.phase !== undefined ? ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase) : currentAutoRun.autoRunning); currentAutoRun = { autoRunning, phase, currentRun: readAutoRunStateValue(source, ['autoRunCurrentRun', 'currentRun'], currentAutoRun.currentRun), totalRuns: readAutoRunStateValue(source, ['autoRunTotalRuns', 'totalRuns'], currentAutoRun.totalRuns), attemptRun: readAutoRunStateValue(source, ['autoRunAttemptRun', 'attemptRun'], currentAutoRun.attemptRun), scheduledAt: readAutoRunStateValue(source, ['scheduledAutoRunAt', 'scheduledAt'], currentAutoRun.scheduledAt), countdownAt: readAutoRunStateValue(source, ['autoRunCountdownAt', 'countdownAt'], currentAutoRun.countdownAt), countdownTitle: readAutoRunStateValue(source, ['autoRunCountdownTitle', 'countdownTitle'], currentAutoRun.countdownTitle), countdownNote: readAutoRunStateValue(source, ['autoRunCountdownNote', 'countdownNote'], currentAutoRun.countdownNote), }; } function isContributionButtonLocked() { const autoActive = currentAutoRun.autoRunning || isAutoRunLockedPhase() || isAutoRunPausedPhase() || isAutoRunScheduledPhase(); if (autoActive) { return false; } const statuses = getStepStatuses(); const anyRunning = Object.values(statuses).some((status) => status === 'running'); return anyRunning; } function isAutoRunLockedPhase() { return currentAutoRun.phase === 'running' || currentAutoRun.phase === 'waiting_step' || currentAutoRun.phase === 'retrying' || currentAutoRun.phase === 'waiting_interval'; } function isAutoRunPausedPhase() { return currentAutoRun.phase === 'waiting_email'; } function isAutoRunWaitingStepPhase() { return currentAutoRun.phase === 'waiting_step'; } function isAutoRunScheduledPhase() { return currentAutoRun.phase === 'scheduled'; } function isAutoRunSourceSyncPhase(phase) { return ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase); } function shouldSyncRunCountFromAutoRunSource(source = {}) { const phase = source.autoRunPhase ?? source.phase ?? currentAutoRun.phase; const autoRunning = source.autoRunning !== undefined ? Boolean(source.autoRunning) : isAutoRunSourceSyncPhase(phase); const shouldSync = autoRunning || isAutoRunSourceSyncPhase(phase); if (!shouldSync) { return false; } const pendingTotalRuns = getPendingAutoRunStartRunCount(); if (pendingTotalRuns > 0) { const sourceTotalRuns = getAutoRunSourceTotalRuns(source); if (sourceTotalRuns > 0 && sourceTotalRuns !== pendingTotalRuns) { return false; } if (sourceTotalRuns === pendingTotalRuns) { clearPendingAutoRunStartRunCount(); } } return true; } function getAutoRunLabel(payload = currentAutoRun) { if ((payload.phase ?? currentAutoRun.phase) === 'scheduled') { return (payload.totalRuns || 1) > 1 ? ` (${payload.totalRuns}轮)` : ''; } const attemptLabel = payload.attemptRun ? ` · 尝试${payload.attemptRun}` : ''; if ((payload.totalRuns || 1) > 1) { return ` (${payload.currentRun}/${payload.totalRuns}${attemptLabel})`; } return attemptLabel ? ` (${attemptLabel.slice(3)})` : ''; } function normalizeAutoDelayMinutes(value) { const numeric = Number(value); if (!Number.isFinite(numeric)) { return AUTO_DELAY_DEFAULT_MINUTES; } return Math.min(AUTO_DELAY_MAX_MINUTES, Math.max(AUTO_DELAY_MIN_MINUTES, Math.floor(numeric))); } function normalizeAutoRunThreadIntervalMinutes(value) { const rawValue = String(value ?? '').trim(); if (!rawValue) { return AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES; } const numeric = Number(rawValue); if (!Number.isFinite(numeric)) { return AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES; } return Math.min( AUTO_FALLBACK_THREAD_INTERVAL_MAX_MINUTES, Math.max(AUTO_FALLBACK_THREAD_INTERVAL_MIN_MINUTES, Math.floor(numeric)) ); } function normalizeAutoStepDelaySeconds(value) { const rawValue = String(value ?? '').trim(); if (!rawValue) { return null; } const numeric = Number(rawValue); if (!Number.isFinite(numeric)) { return null; } return Math.min(AUTO_STEP_DELAY_MAX_SECONDS, Math.max(AUTO_STEP_DELAY_MIN_SECONDS, Math.floor(numeric))); } function normalizeVerificationResendCount(value, fallback) { const rawValue = String(value ?? '').trim(); if (!rawValue) { return fallback; } const numeric = Number(rawValue); if (!Number.isFinite(numeric)) { return fallback; } return Math.min( VERIFICATION_RESEND_COUNT_MAX, Math.max(VERIFICATION_RESEND_COUNT_MIN, Math.floor(numeric)) ); } function formatAutoStepDelayInputValue(value) { const normalized = normalizeAutoStepDelaySeconds(value); return normalized === null ? '' : String(normalized); } function normalizeCustomEmailPoolEntries(value = '') { const source = Array.isArray(value) ? value : String(value || '').split(/[\r\n,,;;]+/); return source .map((item) => String(item || '').trim().toLowerCase()) .filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item)); } function normalizeCustomEmailPoolEntryEmail(value = '') { return String(value || '').trim().toLowerCase(); } function createCustomEmailPoolEntryId() { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } return `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; } function normalizeCustomEmailPoolEntryObjects(value = []) { const source = Array.isArray(value) ? value : []; const seenEmails = new Set(); const entries = []; for (const rawEntry of source) { const asObject = rawEntry && typeof rawEntry === 'object' ? rawEntry : { email: rawEntry }; const email = normalizeCustomEmailPoolEntryEmail(asObject.email || ''); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { continue; } if (seenEmails.has(email)) { continue; } seenEmails.add(email); entries.push({ id: String(asObject.id || createCustomEmailPoolEntryId()), email, enabled: asObject.enabled !== undefined ? Boolean(asObject.enabled) : true, used: Boolean(asObject.used), note: String(asObject.note || '').trim(), lastUsedAt: Number.isFinite(Number(asObject.lastUsedAt)) ? Number(asObject.lastUsedAt) : 0, }); } return entries; } function getNormalizedCustomEmailPoolEntriesState() { const entries = (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState)) ? customEmailPoolEntriesState : []; return normalizeCustomEmailPoolEntryObjects(entries); } function getActiveCustomEmailPoolEmails(entries = getNormalizedCustomEmailPoolEntriesState()) { return normalizeCustomEmailPoolEntryObjects(entries) .filter((entry) => entry.enabled && !entry.used) .map((entry) => entry.email); } function setCustomEmailPoolEntriesState(entries = [], options = {}) { const { syncInput = true } = options; customEmailPoolEntriesState = normalizeCustomEmailPoolEntryObjects(entries); if (syncInput && inputCustomEmailPool) { inputCustomEmailPool.value = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState).join('\n'); } } function restoreCustomEmailPoolEntriesFromState(state = {}) { const rawEntries = Array.isArray(state?.customEmailPoolEntries) ? state.customEmailPoolEntries : []; if (rawEntries.length > 0) { return normalizeCustomEmailPoolEntryObjects(rawEntries); } return normalizeCustomEmailPoolEntries(state?.customEmailPool).map((email) => ({ id: createCustomEmailPoolEntryId(), email, enabled: true, used: false, note: '', lastUsedAt: 0, })); } function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) { return !isCustomMailProvider(provider) && !isLuckmailProvider(provider) && getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR; } function getCustomMailProviderPoolSize() { return normalizeCustomEmailPoolEntries(inputCustomMailProviderPool?.value).length; } function usesCustomMailProviderPool(provider = selectMailProvider.value) { return isCustomMailProvider(provider) && getCustomMailProviderPoolSize() > 0; } function getCustomEmailPoolSize() { if (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState)) { const activeEntries = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState); if (activeEntries.length > 0 || customEmailPoolEntriesState.length > 0) { return activeEntries.length; } } return normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value).length; } function getLockedRunCountFromEmailPool(provider = selectMailProvider.value) { if (usesCustomMailProviderPool(provider)) { return getCustomMailProviderPoolSize(); } if (usesCustomEmailPoolGenerator(provider)) { return getCustomEmailPoolSize(); } return 0; } function shouldLockRunCountToEmailPool(provider = (typeof selectMailProvider !== 'undefined' ? selectMailProvider?.value : undefined)) { return getLockedRunCountFromEmailPool(provider) > 0; } function syncRunCountFromCustomEmailPool() { if (!usesCustomEmailPoolGenerator()) { return; } inputRunCount.value = String(getCustomEmailPoolSize()); } function syncRunCountFromCustomMailProviderPool() { if (!usesCustomMailProviderPool()) { return; } inputRunCount.value = String(getCustomMailProviderPoolSize()); } function syncRunCountFromConfiguredEmailPool(provider = selectMailProvider.value) { const poolSize = getLockedRunCountFromEmailPool(provider); if (poolSize > 0) { inputRunCount.value = String(poolSize); } } function getRunCountValue() { const lockedRunCount = typeof getLockedRunCountFromEmailPool === 'function' ? getLockedRunCountFromEmailPool() : 0; if (lockedRunCount > 0) { return lockedRunCount; } return Math.max(1, parseInt(inputRunCount.value, 10) || 1); } function updateFallbackThreadIntervalInputState() { if (!inputAutoSkipFailuresThreadIntervalMinutes) { return; } inputAutoSkipFailuresThreadIntervalMinutes.disabled = Boolean(inputAutoSkipFailures.disabled); } function updateAutoDelayInputState() { const scheduled = isAutoRunScheduledPhase(); inputAutoDelayEnabled.disabled = scheduled; inputAutoDelayMinutes.disabled = scheduled || !inputAutoDelayEnabled.checked; } function formatCountdown(remainingMs) { const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; } function formatScheduleTime(timestamp) { return new Date(timestamp).toLocaleString('zh-CN', { hour12: false, timeZone: DISPLAY_TIMEZONE, month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', }); } function stopScheduledCountdownTicker() { clearInterval(scheduledCountdownTimer); scheduledCountdownTimer = null; } function getActiveAutoRunCountdown() { if (isAutoRunScheduledPhase() && Number.isFinite(currentAutoRun.scheduledAt)) { return { at: currentAutoRun.scheduledAt, title: '已计划自动运行', note: `计划于 ${formatScheduleTime(currentAutoRun.scheduledAt)} 开始`, tone: 'scheduled', }; } if (currentAutoRun.phase !== 'waiting_interval') { return null; } if (!Number.isFinite(currentAutoRun.countdownAt)) { return null; } return { at: currentAutoRun.countdownAt, title: currentAutoRun.countdownTitle || '等待中', note: currentAutoRun.countdownNote || '', tone: 'running', }; } function renderScheduledAutoRunInfo() { if (!autoScheduleBar) { return; } const countdown = getActiveAutoRunCountdown(); if (!countdown) { autoScheduleBar.style.display = 'none'; return; } const remainingMs = countdown.at - Date.now(); autoScheduleBar.style.display = 'flex'; if (btnAutoRunNow) { btnAutoRunNow.hidden = false; btnAutoRunNow.textContent = currentAutoRun.phase === 'waiting_interval' ? '立即继续' : '立即开始'; } if (btnAutoCancelSchedule) { btnAutoCancelSchedule.hidden = true; } autoScheduleTitle.textContent = countdown.title; autoScheduleMeta.textContent = remainingMs > 0 ? `${countdown.note ? `${countdown.note},` : ''}剩余 ${formatCountdown(remainingMs)}` : '倒计时即将结束,正在准备继续...'; return; } function syncScheduledCountdownTicker() { renderScheduledAutoRunInfo(); if (getActiveAutoRunCountdown()) { if (scheduledCountdownTimer) { return; } scheduledCountdownTimer = setInterval(() => { renderScheduledAutoRunInfo(); updateStatusDisplay(latestState); }, 1000); return; } stopScheduledCountdownTicker(); return; } function setDefaultAutoRunButton() { btnAutoRun.disabled = false; inputRunCount.disabled = shouldLockRunCountToEmailPool(); btnAutoRun.innerHTML = ' 自动'; } function normalizeCloudflareDomainValue(value = '') { let normalized = String(value || '').trim().toLowerCase(); if (!normalized) return ''; normalized = normalized.replace(/^@+/, ''); normalized = normalized.replace(/^https?:\/\//, ''); normalized = normalized.replace(/\/.*$/, ''); if (!/^[a-z0-9.-]+\.[a-z]{2,}$/.test(normalized)) { return ''; } return normalized; } function normalizeCloudflareDomains(values = []) { const seen = new Set(); const domains = []; for (const value of Array.isArray(values) ? values : []) { const normalized = normalizeCloudflareDomainValue(value); if (!normalized || seen.has(normalized)) continue; seen.add(normalized); domains.push(normalized); } return domains; } function normalizeCloudflareTempEmailBaseUrlValue(value = '') { const raw = String(value || '').trim(); if (!raw) return ''; const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(raw) ? raw : `https://${raw}`; try { const parsed = new URL(candidate); parsed.hash = ''; parsed.search = ''; const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/, ''); return `${parsed.origin}${pathname}`; } catch { return ''; } } function normalizeCloudflareTempEmailReceiveMailboxValue(value = '') { const normalized = String(value || '').trim().toLowerCase(); if (!normalized) return ''; return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized) ? normalized : ''; } function normalizeCloudflareTempEmailDomainValue(value = '') { return normalizeCloudflareDomainValue(value); } function normalizeCloudflareTempEmailDomains(values = []) { const seen = new Set(); const domains = []; for (const value of Array.isArray(values) ? values : []) { const normalized = normalizeCloudflareTempEmailDomainValue(value); if (!normalized || seen.has(normalized)) continue; seen.add(normalized); domains.push(normalized); } return domains; } function normalizeCloudMailBaseUrlValue(value = '') { return normalizeCloudflareTempEmailBaseUrlValue(value); } function normalizeCloudMailReceiveMailboxValue(value = '') { return normalizeCloudflareTempEmailReceiveMailboxValue(value); } function normalizeCloudMailDomainValue(value = '') { return normalizeCloudflareDomainValue(value); } function getCloudflareDomainsFromState() { const domains = normalizeCloudflareDomains(latestState?.cloudflareDomains || []); const activeDomain = normalizeCloudflareDomainValue(latestState?.cloudflareDomain || ''); if (activeDomain && !domains.includes(activeDomain)) { domains.unshift(activeDomain); } return { domains, activeDomain: activeDomain || domains[0] || '' }; } function getCloudflareTempEmailDomainsFromState() { const domains = normalizeCloudflareTempEmailDomains(latestState?.cloudflareTempEmailDomains || []); const activeDomain = normalizeCloudflareTempEmailDomainValue(latestState?.cloudflareTempEmailDomain || ''); if (activeDomain && !domains.includes(activeDomain)) { domains.unshift(activeDomain); } return { domains, activeDomain: activeDomain || domains[0] || '' }; } function renderCloudflareDomainOptions(preferredDomain = '') { const preferred = normalizeCloudflareDomainValue(preferredDomain); const { domains, activeDomain } = getCloudflareDomainsFromState(); const selected = preferred || activeDomain; cfDomainPicker.render(domains, domains.includes(selected) ? selected : domains[0] || ''); } function renderCloudflareTempEmailDomainOptions(preferredDomain = '') { const preferred = normalizeCloudflareTempEmailDomainValue(preferredDomain); const { domains, activeDomain } = getCloudflareTempEmailDomainsFromState(); const selected = preferred || activeDomain; tempEmailDomainPicker.render(domains, domains.includes(selected) ? selected : domains[0] || ''); } function setCloudflareDomainEditMode(editing, options = {}) { const { clearInput = false } = options; cloudflareDomainEditMode = Boolean(editing); cfDomainPicker.setVisible(!cloudflareDomainEditMode); inputCfDomain.style.display = cloudflareDomainEditMode ? '' : 'none'; btnCfDomainMode.textContent = cloudflareDomainEditMode ? '保存' : '添加'; if (cloudflareDomainEditMode) { if (clearInput) { inputCfDomain.value = ''; } inputCfDomain.focus(); } else if (clearInput) { inputCfDomain.value = ''; } } function setCloudflareTempEmailDomainEditMode(editing, options = {}) { const { clearInput = false } = options; cloudflareTempEmailDomainEditMode = false; tempEmailDomainPicker.setVisible(true); inputTempEmailDomain.style.display = 'none'; btnTempEmailDomainMode.textContent = '更新'; if (clearInput) { inputTempEmailDomain.value = ''; } } function applyCloudflareTempEmailSettingsState(state = {}) { inputTempEmailBaseUrl.value = state?.cloudflareTempEmailBaseUrl || ''; inputTempEmailAdminAuth.value = state?.cloudflareTempEmailAdminAuth || ''; inputTempEmailCustomAuth.value = state?.cloudflareTempEmailCustomAuth || ''; inputTempEmailReceiveMailbox.value = state?.cloudflareTempEmailReceiveMailbox || ''; if (inputTempEmailUseRandomSubdomain) { inputTempEmailUseRandomSubdomain.checked = Boolean(state?.cloudflareTempEmailUseRandomSubdomain); } renderCloudflareTempEmailDomainOptions(state?.cloudflareTempEmailDomain || ''); setCloudflareTempEmailDomainEditMode(false, { clearInput: true }); } function applyCloudMailSettingsState(state = {}) { if (inputCloudMailBaseUrl) { inputCloudMailBaseUrl.value = state?.cloudMailBaseUrl || ''; } if (inputCloudMailAdminEmail) { inputCloudMailAdminEmail.value = state?.cloudMailAdminEmail || ''; } if (inputCloudMailAdminPassword) { inputCloudMailAdminPassword.value = state?.cloudMailAdminPassword || ''; } if (inputCloudMailReceiveMailbox) { inputCloudMailReceiveMailbox.value = state?.cloudMailReceiveMailbox || ''; } if (inputCloudMailDomain) { inputCloudMailDomain.value = state?.cloudMailDomain || ''; } } function collectSettingsPayload() { const defaultGpcHelperApiUrl = typeof DEFAULT_GPC_HELPER_API_URL !== 'undefined' ? DEFAULT_GPC_HELPER_API_URL : 'https://gpc.qlhazycoder.top'; const { domains, activeDomain } = getCloudflareDomainsFromState(); const selectedCloudflareDomain = normalizeCloudflareDomainValue( !cloudflareDomainEditMode ? selectCfDomain.value : activeDomain ) || activeDomain; const { domains: tempEmailDomains, activeDomain: tempEmailActiveDomain } = getCloudflareTempEmailDomainsFromState(); const selectedCloudflareTempEmailDomain = normalizeCloudflareTempEmailDomainValue( !cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain ) || tempEmailActiveDomain; const normalizeCloudMailBaseUrlInput = typeof normalizeCloudMailBaseUrlValue === 'function' ? normalizeCloudMailBaseUrlValue : normalizeCloudflareTempEmailBaseUrlValue; const normalizeCloudMailReceiveMailboxInput = typeof normalizeCloudMailReceiveMailboxValue === 'function' ? normalizeCloudMailReceiveMailboxValue : normalizeCloudflareTempEmailReceiveMailboxValue; const normalizeCloudMailDomainInput = typeof normalizeCloudMailDomainValue === 'function' ? normalizeCloudMailDomainValue : normalizeCloudflareTempEmailDomainValue; const contributionModeEnabled = Boolean(latestState?.contributionMode); const icloudFetchModeRawValue = typeof selectIcloudFetchMode !== 'undefined' ? String(selectIcloudFetchMode?.value || '') : ''; const icloudTargetMailboxTypeValue = typeof selectIcloudTargetMailboxType !== 'undefined' ? selectIcloudTargetMailboxType?.value : ''; const icloudForwardMailProviderValue = typeof selectIcloudForwardMailProvider !== 'undefined' ? selectIcloudForwardMailProvider?.value : ''; const normalizedIcloudTargetMailboxType = normalizeIcloudTargetMailboxType(icloudTargetMailboxTypeValue); const normalizedIcloudForwardMailProvider = normalizeIcloudForwardMailProvider(icloudForwardMailProviderValue); const normalizeIpProxyServiceSafe = typeof normalizeIpProxyService === 'function' ? normalizeIpProxyService : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); return ['711proxy'].includes(normalized) ? normalized : '711proxy'; }); const normalizeIpProxyModeSafe = typeof normalizeIpProxyMode === 'function' ? normalizeIpProxyMode : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); return ['api', 'account'].includes(normalized) ? normalized : 'account'; }); const normalizeIpProxyProtocolSafe = typeof normalizeIpProxyProtocol === 'function' ? normalizeIpProxyProtocol : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); return ['http', 'https', 'socks4', 'socks5'].includes(normalized) ? normalized : 'http'; }); const normalizeIpProxyPortSafe = typeof normalizeIpProxyPort === 'function' ? normalizeIpProxyPort : ((value = '') => { const numeric = Number.parseInt(String(value || '').trim(), 10); if (!Number.isInteger(numeric) || numeric <= 0 || numeric > 65535) { return 0; } return numeric; }); const normalizeIpProxyPoolTargetCountSafe = typeof normalizeIpProxyPoolTargetCount === 'function' ? normalizeIpProxyPoolTargetCount : ((value = '', fallback = 20) => { const rawValue = String(value ?? '').trim(); if (!rawValue) { return String(Math.max(1, Math.min(500, Number(fallback) || 20))); } const numeric = Number.parseInt(rawValue, 10); if (!Number.isInteger(numeric)) { return String(Math.max(1, Math.min(500, Number(fallback) || 20))); } return String(Math.max(1, Math.min(500, numeric))); }); const normalizeIpProxyAutoSyncIntervalMinutesSafe = (value = '', fallback = 15) => { const rawValue = String(value ?? '').trim(); if (!rawValue) { return Math.max(1, Math.min(1440, Number(fallback) || 15)); } const numeric = Number.parseInt(rawValue, 10); if (!Number.isFinite(numeric)) { return Math.max(1, Math.min(1440, Number(fallback) || 15)); } return Math.max(1, Math.min(1440, numeric)); }; const normalizeIpProxyAccountLifeMinutesSafe = typeof normalizeIpProxyAccountLifeMinutes === 'function' ? normalizeIpProxyAccountLifeMinutes : ((value = '', fallback = '') => { const rawValue = String(value ?? '').trim(); if (!rawValue) { return String(fallback || '').trim(); } const numeric = Number.parseInt(rawValue, 10); if (!Number.isInteger(numeric)) { return String(fallback || '').trim(); } return String(Math.max(1, Math.min(1440, numeric))); }); const normalizeIpProxyAccountSessionPrefixSafe = typeof normalizeIpProxyAccountSessionPrefix === 'function' ? normalizeIpProxyAccountSessionPrefix : ((value = '') => String(value || '').trim().replace(/[^A-Za-z0-9_-]/g, '').slice(0, 32)); const normalizeIpProxyAccountListSafe = typeof normalizeIpProxyAccountList === 'function' ? normalizeIpProxyAccountList : ((value = '') => String(value || '') .replace(/\r/g, '') .split('\n') .map((line) => line.trim()) .filter(Boolean) .join('\n')); const normalizeGpcOtpChannelSafe = typeof normalizeGpcOtpChannelValue === 'function' ? normalizeGpcOtpChannelValue : ((value = '') => { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) { return rootScope.GoPayUtils.normalizeGpcOtpChannel(value); } return String(value || '').trim().toLowerCase() === 'sms' ? 'sms' : 'whatsapp'; }); const normalizeGpcLocalSmsHelperBaseUrlSafe = typeof normalizeGpcLocalSmsHelperBaseUrlValue === 'function' ? normalizeGpcLocalSmsHelperBaseUrlValue : ((value = '') => { const fallback = 'http://127.0.0.1:18767'; const rawValue = String(value || fallback).trim(); try { const parsed = new URL(rawValue); if (!['http:', 'https:'].includes(parsed.protocol)) { return fallback; } const endpointPath = parsed.pathname.replace(/\/+$/g, '') || '/'; if (['/otp', '/latest-otp', '/health'].includes(endpointPath)) { parsed.pathname = ''; parsed.search = ''; parsed.hash = ''; } return parsed.toString().replace(/\/$/, ''); } catch { return fallback; } }); const getSelectedIpProxyEnabledSafe = typeof getSelectedIpProxyEnabled === 'function' ? getSelectedIpProxyEnabled : (() => false); const getSelectedIpProxyModeSafe = typeof getSelectedIpProxyMode === 'function' ? getSelectedIpProxyMode : (() => 'account'); const isIpProxyApiModeEnabledSafe = typeof isIpProxyApiModeAvailable === 'function' ? Boolean(isIpProxyApiModeAvailable()) : (typeof IP_PROXY_API_MODE_ENABLED !== 'undefined' ? Boolean(IP_PROXY_API_MODE_ENABLED) : false); const normalizeIpProxyServiceProfilesSafe = typeof normalizeIpProxyServiceProfiles === 'function' ? normalizeIpProxyServiceProfiles : ((rawValue = {}, fallbackState = {}) => { const raw = (rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) ? rawValue : {}; const services = ['711proxy']; const fallbackProfile = { mode: normalizeIpProxyModeSafe(fallbackState?.ipProxyMode || 'account'), apiUrl: String(fallbackState?.ipProxyApiUrl || '').trim(), accountList: normalizeIpProxyAccountListSafe(fallbackState?.ipProxyAccountList || ''), accountSessionPrefix: normalizeIpProxyAccountSessionPrefixSafe(fallbackState?.ipProxyAccountSessionPrefix || ''), accountLifeMinutes: normalizeIpProxyAccountLifeMinutesSafe(fallbackState?.ipProxyAccountLifeMinutes || ''), poolTargetCount: normalizeIpProxyPoolTargetCountSafe(fallbackState?.ipProxyPoolTargetCount || '', 20), host: String(fallbackState?.ipProxyHost || '').trim(), port: String(normalizeIpProxyPortSafe(fallbackState?.ipProxyPort || '') || ''), protocol: normalizeIpProxyProtocolSafe(fallbackState?.ipProxyProtocol || ''), username: String(fallbackState?.ipProxyUsername || '').trim(), password: String(fallbackState?.ipProxyPassword || ''), region: String(fallbackState?.ipProxyRegion || '').trim(), }; const result = {}; services.forEach((service) => { const candidate = raw?.[service]; const source = (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) ? candidate : fallbackProfile; result[service] = { mode: normalizeIpProxyModeSafe(source.mode || fallbackProfile.mode), apiUrl: String(source.apiUrl || fallbackProfile.apiUrl || '').trim(), accountList: normalizeIpProxyAccountListSafe(source.accountList || fallbackProfile.accountList), accountSessionPrefix: normalizeIpProxyAccountSessionPrefixSafe(source.accountSessionPrefix || fallbackProfile.accountSessionPrefix), accountLifeMinutes: normalizeIpProxyAccountLifeMinutesSafe(source.accountLifeMinutes || fallbackProfile.accountLifeMinutes), poolTargetCount: normalizeIpProxyPoolTargetCountSafe(source.poolTargetCount || fallbackProfile.poolTargetCount, 20), host: String(source.host || fallbackProfile.host || '').trim(), port: String(normalizeIpProxyPortSafe(source.port || fallbackProfile.port || '') || ''), protocol: normalizeIpProxyProtocolSafe(source.protocol || fallbackProfile.protocol), username: String(source.username || fallbackProfile.username || '').trim(), password: String(source.password || fallbackProfile.password || ''), region: String(source.region || fallbackProfile.region || '').trim(), }; }); return result; }); const ipProxyServiceRawValue = typeof selectIpProxyService !== 'undefined' ? selectIpProxyService?.value : ''; const ipProxyApiUrlRawValue = typeof inputIpProxyApiUrl !== 'undefined' ? inputIpProxyApiUrl?.value : ''; const ipProxyAccountListRawValue = typeof inputIpProxyAccountList !== 'undefined' ? inputIpProxyAccountList?.value : ''; const ipProxyAccountSessionPrefixRawValue = typeof inputIpProxyAccountSessionPrefix !== 'undefined' ? inputIpProxyAccountSessionPrefix?.value : ''; const ipProxyAccountLifeMinutesRawValue = typeof inputIpProxyAccountLifeMinutes !== 'undefined' ? inputIpProxyAccountLifeMinutes?.value : ''; const ipProxyPoolTargetCountRawValue = typeof inputIpProxyPoolTargetCount !== 'undefined' ? inputIpProxyPoolTargetCount?.value : ''; const ipProxyAutoSyncEnabledRawValue = typeof inputIpProxyAutoSyncEnabled !== 'undefined' ? Boolean(inputIpProxyAutoSyncEnabled?.checked) : Boolean(latestState?.ipProxyAutoSyncEnabled); const ipProxyAutoSyncIntervalMinutesRawValue = typeof inputIpProxyAutoSyncIntervalMinutes !== 'undefined' ? inputIpProxyAutoSyncIntervalMinutes?.value : ''; const ipProxyHostRawValue = typeof inputIpProxyHost !== 'undefined' ? inputIpProxyHost?.value : ''; const ipProxyPortRawValue = typeof inputIpProxyPort !== 'undefined' ? inputIpProxyPort?.value : ''; const ipProxyProtocolRawValue = typeof selectIpProxyProtocol !== 'undefined' ? selectIpProxyProtocol?.value : ''; const ipProxyUsernameRawValue = typeof inputIpProxyUsername !== 'undefined' ? inputIpProxyUsername?.value : ''; const ipProxyPasswordRawValue = typeof inputIpProxyPassword !== 'undefined' ? inputIpProxyPassword?.value : ''; const ipProxyRegionRawValue = typeof inputIpProxyRegion !== 'undefined' ? inputIpProxyRegion?.value : ''; const selectedIpProxyService = normalizeIpProxyServiceSafe( ipProxyServiceRawValue || latestState?.ipProxyService || '711proxy' ); const selectedIpProxyModeRaw = normalizeIpProxyModeSafe(getSelectedIpProxyModeSafe()); const selectedIpProxyMode = (!isIpProxyApiModeEnabledSafe && selectedIpProxyModeRaw === 'api') ? 'account' : selectedIpProxyModeRaw; const currentIpProxyServiceProfile = { mode: selectedIpProxyMode, apiUrl: String(ipProxyApiUrlRawValue || '').trim(), accountList: normalizeIpProxyAccountListSafe(ipProxyAccountListRawValue || ''), accountSessionPrefix: normalizeIpProxyAccountSessionPrefixSafe(ipProxyAccountSessionPrefixRawValue || ''), accountLifeMinutes: normalizeIpProxyAccountLifeMinutesSafe(ipProxyAccountLifeMinutesRawValue || ''), poolTargetCount: normalizeIpProxyPoolTargetCountSafe(ipProxyPoolTargetCountRawValue || '', 20), host: String(ipProxyHostRawValue || '').trim(), port: String(normalizeIpProxyPortSafe(ipProxyPortRawValue || '') || ''), protocol: normalizeIpProxyProtocolSafe(ipProxyProtocolRawValue), username: String(ipProxyUsernameRawValue || '').trim(), password: String(ipProxyPasswordRawValue || ''), region: String(ipProxyRegionRawValue || '').trim(), }; const ipProxyServiceProfiles = normalizeIpProxyServiceProfilesSafe({ ...(latestState?.ipProxyServiceProfiles || {}), [selectedIpProxyService]: currentIpProxyServiceProfile, }, { ...(latestState || {}), ipProxyService: selectedIpProxyService, ipProxyMode: currentIpProxyServiceProfile.mode, ipProxyApiUrl: currentIpProxyServiceProfile.apiUrl, ipProxyAccountList: currentIpProxyServiceProfile.accountList, ipProxyAccountSessionPrefix: currentIpProxyServiceProfile.accountSessionPrefix, ipProxyAccountLifeMinutes: currentIpProxyServiceProfile.accountLifeMinutes, ipProxyPoolTargetCount: currentIpProxyServiceProfile.poolTargetCount, ipProxyHost: currentIpProxyServiceProfile.host, ipProxyPort: currentIpProxyServiceProfile.port, ipProxyProtocol: currentIpProxyServiceProfile.protocol, ipProxyUsername: currentIpProxyServiceProfile.username, ipProxyPassword: currentIpProxyServiceProfile.password, ipProxyRegion: currentIpProxyServiceProfile.region, }); const mail2925UseAccountPool = typeof inputMail2925UseAccountPool !== 'undefined' ? Boolean(inputMail2925UseAccountPool?.checked) : Boolean(latestState?.mail2925UseAccountPool); const phoneSmsProviderValue = typeof selectPhoneSmsProvider !== 'undefined' && selectPhoneSmsProvider ? normalizePhoneSmsProvider(selectPhoneSmsProvider.value) : normalizePhoneSmsProvider(latestState?.phoneSmsProvider); const phoneSmsProviderOrderValue = typeof getSelectedPhoneSmsProviderOrder === 'function' ? getSelectedPhoneSmsProviderOrder() : (typeof normalizePhoneSmsProviderOrderValue === 'function' ? normalizePhoneSmsProviderOrderValue(latestState?.phoneSmsProviderOrder || [], []) : []); const currentPhoneSmsApiKeyValue = typeof inputHeroSmsApiKey !== 'undefined' && inputHeroSmsApiKey ? (inputHeroSmsApiKey.value || '') : ''; const heroSmsApiKeyValue = phoneSmsProviderValue === PHONE_SMS_PROVIDER_HERO_SMS ? currentPhoneSmsApiKeyValue : String(latestState?.heroSmsApiKey || ''); const fiveSimApiKeyValue = typeof inputFiveSimApiKey !== 'undefined' && inputFiveSimApiKey ? String(inputFiveSimApiKey.value || '') : String(latestState?.fiveSimApiKey || ''); const nexSmsApiKeyValue = typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey ? String(inputNexSmsApiKey.value || '') : String(latestState?.nexSmsApiKey || ''); const defaultHeroSmsReuseEnabled = typeof DEFAULT_HERO_SMS_REUSE_ENABLED !== 'undefined' ? DEFAULT_HERO_SMS_REUSE_ENABLED : true; const defaultPhoneCodeWaitSeconds = typeof DEFAULT_PHONE_CODE_WAIT_SECONDS !== 'undefined' ? DEFAULT_PHONE_CODE_WAIT_SECONDS : 60; const defaultPhoneCodeTimeoutWindows = typeof DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS !== 'undefined' ? DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS : 2; const defaultPhoneCodePollIntervalSeconds = typeof DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS !== 'undefined' ? DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS : 5; const defaultPhoneCodePollMaxRounds = typeof DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS !== 'undefined' ? DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS : 12; const phoneSmsReuseEnabledValue = typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled ? normalizeHeroSmsReuseEnabledValue(inputHeroSmsReuseEnabled.checked) : normalizeHeroSmsReuseEnabledValue( latestState?.phoneSmsReuseEnabled, latestState?.heroSmsReuseEnabled ); const heroSmsReuseEnabledValue = phoneSmsReuseEnabledValue; const freePhoneReuseEnabledValue = typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled ? Boolean(inputFreePhoneReuseEnabled.checked) : Boolean(latestState?.freePhoneReuseEnabled); const freePhoneReuseAutoEnabledValue = typeof inputFreePhoneReuseAutoEnabled !== 'undefined' && inputFreePhoneReuseAutoEnabled ? Boolean(inputFreePhoneReuseAutoEnabled.checked) : Boolean(latestState?.freePhoneReuseAutoEnabled); const defaultHeroSmsAcquirePriority = typeof DEFAULT_HERO_SMS_ACQUIRE_PRIORITY !== 'undefined' ? DEFAULT_HERO_SMS_ACQUIRE_PRIORITY : (typeof HERO_SMS_ACQUIRE_PRIORITY_COUNTRY !== 'undefined' ? HERO_SMS_ACQUIRE_PRIORITY_COUNTRY : 'country'); const heroSmsAcquirePriorityValue = typeof selectHeroSmsAcquirePriority !== 'undefined' && selectHeroSmsAcquirePriority ? normalizeHeroSmsAcquirePriority(selectHeroSmsAcquirePriority.value) : normalizeHeroSmsAcquirePriority( typeof DEFAULT_HERO_SMS_ACQUIRE_PRIORITY !== 'undefined' ? DEFAULT_HERO_SMS_ACQUIRE_PRIORITY : 'country' ); const currentPhoneSmsMaxPriceValue = typeof inputHeroSmsMaxPrice !== 'undefined' && inputHeroSmsMaxPrice ? normalizePhoneSmsMaxPriceValue(inputHeroSmsMaxPrice.value, phoneSmsProviderValue) : ''; const heroSmsMaxPriceValue = phoneSmsProviderValue === PHONE_SMS_PROVIDER_HERO_SMS ? currentPhoneSmsMaxPriceValue : normalizeHeroSmsMaxPriceValue(latestState?.heroSmsMaxPrice || ''); const fiveSimMaxPriceValue = phoneSmsProviderValue === PHONE_SMS_PROVIDER_FIVE_SIM ? currentPhoneSmsMaxPriceValue : normalizeFiveSimMaxPriceValue(latestState?.fiveSimMaxPrice || ''); const defaultFiveSimProduct = typeof DEFAULT_FIVE_SIM_PRODUCT !== 'undefined' ? DEFAULT_FIVE_SIM_PRODUCT : 'openai'; const defaultNexSmsServiceCode = typeof DEFAULT_NEX_SMS_SERVICE_CODE !== 'undefined' ? DEFAULT_NEX_SMS_SERVICE_CODE : 'ot'; const normalizeFiveSimProductForPayload = typeof normalizeFiveSimProductValue === 'function' ? normalizeFiveSimProductValue : ((value = '') => String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultFiveSimProduct); const normalizeNexSmsServiceCodeForPayload = typeof normalizeNexSmsServiceCodeValue === 'function' ? normalizeNexSmsServiceCodeValue : ((value = '') => String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultNexSmsServiceCode); const normalizeFiveSimCountryOrderForPayload = typeof normalizeFiveSimCountryOrderValue === 'function' ? normalizeFiveSimCountryOrderValue : ((value = []) => (Array.isArray(value) ? value : []) .map((entry) => normalizeFiveSimCountryId(entry, '')) .filter(Boolean)); const normalizeNexSmsCountryIdForPayload = typeof normalizeNexSmsCountryIdValue === 'function' ? normalizeNexSmsCountryIdValue : ((value, fallback = -1) => { const normalized = Math.floor(Number(value)); return Number.isFinite(normalized) && normalized >= 0 ? normalized : fallback; }); const normalizeNexSmsCountryOrderForPayload = typeof normalizeNexSmsCountryOrderValue === 'function' ? normalizeNexSmsCountryOrderValue : ((value = []) => (Array.isArray(value) ? value : []) .map((entry) => normalizeNexSmsCountryIdForPayload(entry, -1)) .filter((entry) => entry >= 0)); const phoneSmsProviderNexsms = typeof PHONE_SMS_PROVIDER_NEXSMS !== 'undefined' ? PHONE_SMS_PROVIDER_NEXSMS : 'nexsms'; const defaultNexSmsCountryOrder = typeof DEFAULT_NEX_SMS_COUNTRY_ORDER !== 'undefined' ? DEFAULT_NEX_SMS_COUNTRY_ORDER : [1]; const fiveSimOperatorValue = typeof inputFiveSimOperator !== 'undefined' && inputFiveSimOperator ? normalizeFiveSimOperator(inputFiveSimOperator.value || latestState?.fiveSimOperator) : normalizeFiveSimOperator(latestState?.fiveSimOperator); const fiveSimProductValue = typeof inputFiveSimProduct !== 'undefined' && inputFiveSimProduct ? normalizeFiveSimProductForPayload(inputFiveSimProduct.value || latestState?.fiveSimProduct) : normalizeFiveSimProductForPayload(latestState?.fiveSimProduct || defaultFiveSimProduct); const nexSmsServiceCodeValue = typeof inputNexSmsServiceCode !== 'undefined' && inputNexSmsServiceCode ? normalizeNexSmsServiceCodeForPayload(inputNexSmsServiceCode.value || latestState?.nexSmsServiceCode) : normalizeNexSmsServiceCodeForPayload(latestState?.nexSmsServiceCode || defaultNexSmsServiceCode); const heroSmsPreferredPriceValue = typeof inputHeroSmsPreferredPrice !== 'undefined' && inputHeroSmsPreferredPrice ? normalizeHeroSmsMaxPriceValue(inputHeroSmsPreferredPrice.value) : normalizeHeroSmsMaxPriceValue(latestState?.heroSmsPreferredPrice || ''); const phonePreferredActivationValue = typeof getSelectedPhonePreferredActivation === 'function' ? getSelectedPhonePreferredActivation() : null; const phoneVerificationReplacementLimitValue = typeof inputPhoneReplacementLimit !== 'undefined' && inputPhoneReplacementLimit ? normalizePhoneVerificationReplacementLimit( inputPhoneReplacementLimit.value, latestState?.phoneVerificationReplacementLimit ) : DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT; const phoneCodeWaitSecondsValue = typeof inputPhoneCodeWaitSeconds !== 'undefined' && inputPhoneCodeWaitSeconds ? normalizePhoneCodeWaitSecondsValue( inputPhoneCodeWaitSeconds.value, latestState?.phoneCodeWaitSeconds ) : defaultPhoneCodeWaitSeconds; const phoneCodeTimeoutWindowsValue = typeof inputPhoneCodeTimeoutWindows !== 'undefined' && inputPhoneCodeTimeoutWindows ? normalizePhoneCodeTimeoutWindowsValue( inputPhoneCodeTimeoutWindows.value, latestState?.phoneCodeTimeoutWindows ) : defaultPhoneCodeTimeoutWindows; const phoneCodePollIntervalSecondsValue = typeof inputPhoneCodePollIntervalSeconds !== 'undefined' && inputPhoneCodePollIntervalSeconds ? normalizePhoneCodePollIntervalSecondsValue( inputPhoneCodePollIntervalSeconds.value, latestState?.phoneCodePollIntervalSeconds ) : defaultPhoneCodePollIntervalSeconds; const phoneCodePollMaxRoundsValue = typeof inputPhoneCodePollMaxRounds !== 'undefined' && inputPhoneCodePollMaxRounds ? normalizePhoneCodePollMaxRoundsValue( inputPhoneCodePollMaxRounds.value, latestState?.phoneCodePollMaxRounds ) : defaultPhoneCodePollMaxRounds; const selectedPhoneSmsCountry = phoneSmsProviderValue === PHONE_SMS_PROVIDER_FIVE_SIM ? ((typeof getSelectedFiveSimCountries === 'function' ? getSelectedFiveSimCountries()[0] : null) || { id: DEFAULT_FIVE_SIM_COUNTRY_ID, code: DEFAULT_FIVE_SIM_COUNTRY_ID, label: DEFAULT_FIVE_SIM_COUNTRY_LABEL }) : (phoneSmsProviderValue === phoneSmsProviderNexsms ? ((typeof getSelectedNexSmsCountries === 'function' ? getSelectedNexSmsCountries()[0] : null) || { id: defaultNexSmsCountryOrder[0], label: `Country #${defaultNexSmsCountryOrder[0]}` }) : (typeof getSelectedHeroSmsCountryOption === 'function' ? getSelectedHeroSmsCountryOption() : { id: typeof DEFAULT_HERO_SMS_COUNTRY_ID !== 'undefined' ? DEFAULT_HERO_SMS_COUNTRY_ID : 52, label: typeof DEFAULT_HERO_SMS_COUNTRY_LABEL !== 'undefined' ? DEFAULT_HERO_SMS_COUNTRY_LABEL : 'Thailand', })); const heroSmsCountry = phoneSmsProviderValue === PHONE_SMS_PROVIDER_HERO_SMS ? selectedPhoneSmsCountry : { id: normalizeHeroSmsCountryId(latestState?.heroSmsCountryId), label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel), }; const fiveSimCountry = phoneSmsProviderValue === PHONE_SMS_PROVIDER_FIVE_SIM ? selectedPhoneSmsCountry : { id: normalizeFiveSimCountryId(latestState?.fiveSimCountryId), label: normalizeFiveSimCountryLabel(latestState?.fiveSimCountryLabel), }; const normalizedCustomEmailPool = typeof getActiveCustomEmailPoolEmails === 'function' ? getActiveCustomEmailPoolEmails() : (typeof normalizeCustomEmailPoolEntries === 'function' ? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value) : []); const normalizedCustomEmailPoolEntries = typeof getNormalizedCustomEmailPoolEntriesState === 'function' ? getNormalizedCustomEmailPoolEntriesState() : []; const selectedPhoneSmsCountryFallback = typeof syncHeroSmsFallbackSelectionOrderFromSelect === 'function' ? syncHeroSmsFallbackSelectionOrderFromSelect() .filter((country) => String(country.id) !== String(selectedPhoneSmsCountry.id)) : []; const fiveSimCountryOrderValue = typeof getSelectedFiveSimCountries === 'function' ? getSelectedFiveSimCountries() .map((country) => normalizeFiveSimCountryCode(country.code || country.id, '')) .filter(Boolean) : normalizeFiveSimCountryOrderForPayload(latestState?.fiveSimCountryOrder || []); const nexSmsCountryOrderValue = typeof getSelectedNexSmsCountries === 'function' ? getSelectedNexSmsCountries() .map((country) => normalizeNexSmsCountryIdForPayload(country.id, -1)) .filter((countryId) => countryId >= 0) : normalizeNexSmsCountryOrderForPayload(latestState?.nexSmsCountryOrder || []); const heroSmsCountryFallback = phoneSmsProviderValue === PHONE_SMS_PROVIDER_HERO_SMS ? selectedPhoneSmsCountryFallback : normalizeHeroSmsCountryFallbackList(latestState?.heroSmsCountryFallback || []); const fiveSimCountryFallback = phoneSmsProviderValue === PHONE_SMS_PROVIDER_FIVE_SIM ? selectedPhoneSmsCountryFallback : normalizeFiveSimCountryFallbackList(latestState?.fiveSimCountryFallback || []); const payPalAccounts = typeof getPayPalAccounts === 'function' ? getPayPalAccounts(latestState) : (Array.isArray(latestState?.paypalAccounts) ? latestState.paypalAccounts : []); const currentPayPalAccount = typeof getCurrentPayPalAccount === 'function' ? getCurrentPayPalAccount(latestState) : payPalAccounts.find((account) => account?.id === String(latestState?.currentPayPalAccountId || '').trim()) || null; const selectedSignupMethod = typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : ( (typeof normalizeSignupMethod === 'function' ? normalizeSignupMethod(latestState?.signupMethod) : (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email')) ); const normalizePanelModeSafe = typeof normalizePanelMode === 'function' ? normalizePanelMode : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa'; }); const rawPanelMode = normalizePanelModeSafe(selectPanelMode?.value || latestState?.panelMode || 'cpa'); const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled); const rawPhoneVerificationEnabled = Boolean(inputPhoneVerificationEnabled?.checked); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: rawPanelMode, signupMethod: selectedSignupMethod, state: { ...(latestState || {}), panelMode: rawPanelMode, plusModeEnabled: rawPlusModeEnabled, phoneVerificationEnabled: rawPhoneVerificationEnabled, signupMethod: selectedSignupMethod, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: latestState?.activeFlowId, panelMode: rawPanelMode, signupMethod: selectedSignupMethod, state: { ...(latestState || {}), panelMode: rawPanelMode, plusModeEnabled: rawPlusModeEnabled, phoneVerificationEnabled: rawPhoneVerificationEnabled, signupMethod: selectedSignupMethod, }, }) : null; })(); const effectivePanelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode; const effectivePlusModeEnabled = capabilityState ? Boolean(capabilityState.runtimeLocks?.plusModeEnabled) : rawPlusModeEnabled; const effectivePhoneVerificationEnabled = capabilityState ? Boolean(capabilityState.runtimeLocks?.phoneVerificationEnabled) : rawPhoneVerificationEnabled; const effectiveSignupMethod = capabilityState?.effectiveSignupMethod || selectedSignupMethod; const plusPaymentMethod = getSelectedPlusPaymentMethod(); const normalizeGpcHelperPhoneModeSafe = typeof normalizeGpcHelperPhoneModeValue === 'function' ? normalizeGpcHelperPhoneModeValue : ((value = '') => String(value || '').trim().toLowerCase() === 'auto' || String(value || '').trim().toLowerCase() === 'builtin' ? 'auto' : 'manual'); const selectedGpcPhoneMode = normalizeGpcHelperPhoneModeSafe( typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode ? selectGpcHelperPhoneMode.value : (latestState?.gopayHelperPhoneMode || 'manual') ); const effectiveGpcPhoneMode = selectedGpcPhoneMode; const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe( typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel ? selectGpcHelperOtpChannel.value : (latestState?.gopayHelperOtpChannel || 'whatsapp') ); const selectedGpcLocalSmsHelperEnabled = effectiveGpcPhoneMode === 'auto' ? false : Boolean( typeof inputGpcHelperLocalSmsEnabled !== 'undefined' && inputGpcHelperLocalSmsEnabled ? inputGpcHelperLocalSmsEnabled.checked : latestState?.gopayHelperLocalSmsHelperEnabled ); const selectedSub2ApiGroupName = String(inputSub2ApiGroup.value || '').trim(); const sub2apiGroupNames = []; const seenSub2ApiGroupNames = new Set(); const appendSub2ApiGroupNames = (value) => { if (Array.isArray(value)) { value.forEach(appendSub2ApiGroupNames); return; } String(value || '') .split(/[\r\n,,、]+/) .map((name) => name.trim()) .filter(Boolean) .forEach((name) => { const key = name.toLowerCase(); if (!key || seenSub2ApiGroupNames.has(key)) { return; } seenSub2ApiGroupNames.add(key); sub2apiGroupNames.push(name); }); }; [ latestState?.sub2apiGroupNames, latestState?.sub2apiGroupName, selectedSub2ApiGroupName, ].forEach(appendSub2ApiGroupNames); if (sub2apiGroupNames.length === 0) { appendSub2ApiGroupNames(['codex', 'openai-plus']); } const sub2apiAccountPriorityNormalizer = typeof normalizeSub2ApiAccountPriorityValue === 'function' ? normalizeSub2ApiAccountPriorityValue : ((value) => { const numeric = Number(String(value ?? '').trim()); return Number.isSafeInteger(numeric) && numeric >= 1 ? numeric : 1; }); return { ...(contributionModeEnabled ? {} : { panelMode: effectivePanelMode, }), vpsUrl: inputVpsUrl.value.trim(), vpsPassword: inputVpsPassword.value, localCpaStep9Mode: getSelectedLocalCpaStep9Mode(), sub2apiUrl: inputSub2ApiUrl.value.trim(), sub2apiEmail: inputSub2ApiEmail.value.trim(), sub2apiPassword: inputSub2ApiPassword.value, sub2apiGroupName: selectedSub2ApiGroupName, sub2apiGroupNames, sub2apiAccountPriority: sub2apiAccountPriorityNormalizer( typeof inputSub2ApiAccountPriority !== 'undefined' && inputSub2ApiAccountPriority ? inputSub2ApiAccountPriority.value : latestState?.sub2apiAccountPriority ), sub2apiDefaultProxyName: inputSub2ApiDefaultProxy.value.trim(), ipProxyEnabled: getSelectedIpProxyEnabledSafe(), ipProxyService: selectedIpProxyService, ipProxyMode: currentIpProxyServiceProfile.mode, ipProxyApiUrl: currentIpProxyServiceProfile.apiUrl, ipProxyServiceProfiles, ipProxyAccountList: currentIpProxyServiceProfile.accountList, ipProxyAccountSessionPrefix: currentIpProxyServiceProfile.accountSessionPrefix, ipProxyAccountLifeMinutes: currentIpProxyServiceProfile.accountLifeMinutes, ipProxyPoolTargetCount: currentIpProxyServiceProfile.poolTargetCount, ipProxyAutoSyncEnabled: Boolean(ipProxyAutoSyncEnabledRawValue), ipProxyAutoSyncIntervalMinutes: normalizeIpProxyAutoSyncIntervalMinutesSafe( ipProxyAutoSyncIntervalMinutesRawValue, latestState?.ipProxyAutoSyncIntervalMinutes ), ipProxyHost: currentIpProxyServiceProfile.host, ipProxyPort: normalizeIpProxyPortSafe(currentIpProxyServiceProfile.port), ipProxyProtocol: currentIpProxyServiceProfile.protocol, ipProxyUsername: currentIpProxyServiceProfile.username, ipProxyPassword: currentIpProxyServiceProfile.password, ipProxyRegion: currentIpProxyServiceProfile.region, codex2apiUrl: inputCodex2ApiUrl.value.trim(), codex2apiAdminKey: inputCodex2ApiAdminKey.value.trim(), plusModeEnabled: effectivePlusModeEnabled, plusPaymentMethod, paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(), paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''), currentPayPalAccountId: String(latestState?.currentPayPalAccountId || '').trim(), paypalAccounts: payPalAccounts, gopayCountryCode: window.GoPayUtils?.normalizeGoPayCountryCode ? window.GoPayUtils.normalizeGoPayCountryCode(typeof selectGoPayCountryCode !== 'undefined' && selectGoPayCountryCode ? selectGoPayCountryCode.value : latestState?.gopayCountryCode) : (typeof selectGoPayCountryCode !== 'undefined' && selectGoPayCountryCode ? String(selectGoPayCountryCode.value || '+86').trim() : String(latestState?.gopayCountryCode || '+86').trim()), gopayPhone: window.GoPayUtils?.normalizeGoPayPhone ? window.GoPayUtils.normalizeGoPayPhone(typeof inputGoPayPhone !== 'undefined' && inputGoPayPhone ? inputGoPayPhone.value : latestState?.gopayPhone) : (typeof inputGoPayPhone !== 'undefined' && inputGoPayPhone ? String(inputGoPayPhone.value || '').trim() : String(latestState?.gopayPhone || '').trim()), gopayOtp: window.GoPayUtils?.normalizeGoPayOtp ? window.GoPayUtils.normalizeGoPayOtp(typeof inputGoPayOtp !== 'undefined' && inputGoPayOtp ? inputGoPayOtp.value : latestState?.gopayOtp) : (typeof inputGoPayOtp !== 'undefined' && inputGoPayOtp ? String(inputGoPayOtp.value || '').trim().replace(/[^\d]/g, '') : String(latestState?.gopayOtp || '').trim().replace(/[^\d]/g, '')), gopayPin: window.GoPayUtils?.normalizeGoPayPin ? window.GoPayUtils.normalizeGoPayPin(typeof inputGoPayPin !== 'undefined' && inputGoPayPin ? inputGoPayPin.value : latestState?.gopayPin) : (typeof inputGoPayPin !== 'undefined' && inputGoPayPin ? String(inputGoPayPin.value || '') : String(latestState?.gopayPin || '')), gopayHelperApiUrl: window.GoPayUtils?.normalizeGpcHelperBaseUrl ? window.GoPayUtils.normalizeGpcHelperBaseUrl(defaultGpcHelperApiUrl) : String(defaultGpcHelperApiUrl).trim().replace(/\/+$/g, ''), gopayHelperApiKey: typeof inputGpcHelperCardKey !== 'undefined' && inputGpcHelperCardKey ? String(inputGpcHelperCardKey.value || '').trim() : String(latestState?.gopayHelperApiKey || latestState?.gopayHelperCardKey || '').trim(), gopayHelperCardKey: '', gopayHelperPhoneMode: effectiveGpcPhoneMode, gopayHelperCountryCode: window.GoPayUtils?.normalizeGoPayCountryCode ? window.GoPayUtils.normalizeGoPayCountryCode(typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode ? selectGpcHelperCountryCode.value : latestState?.gopayHelperCountryCode) : (typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode ? String(selectGpcHelperCountryCode.value || '+86').trim() : String(latestState?.gopayHelperCountryCode || '+86').trim()), gopayHelperPhoneNumber: window.GoPayUtils?.normalizeGoPayPhone ? window.GoPayUtils.normalizeGoPayPhone(typeof inputGpcHelperPhone !== 'undefined' && inputGpcHelperPhone ? inputGpcHelperPhone.value : latestState?.gopayHelperPhoneNumber) : (typeof inputGpcHelperPhone !== 'undefined' && inputGpcHelperPhone ? String(inputGpcHelperPhone.value || '').trim() : String(latestState?.gopayHelperPhoneNumber || '').trim()), gopayHelperPin: window.GoPayUtils?.normalizeGoPayPin ? window.GoPayUtils.normalizeGoPayPin(typeof inputGpcHelperPin !== 'undefined' && inputGpcHelperPin ? inputGpcHelperPin.value : latestState?.gopayHelperPin) : (typeof inputGpcHelperPin !== 'undefined' && inputGpcHelperPin ? String(inputGpcHelperPin.value || '') : String(latestState?.gopayHelperPin || '')), gopayHelperOtpChannel: selectedGpcOtpChannel, gopayHelperLocalSmsHelperEnabled: selectedGpcLocalSmsHelperEnabled, gopayHelperLocalSmsHelperUrl: normalizeGpcLocalSmsHelperBaseUrlSafe( typeof inputGpcHelperLocalSmsUrl !== 'undefined' && inputGpcHelperLocalSmsUrl ? inputGpcHelperLocalSmsUrl.value : (latestState?.gopayHelperLocalSmsHelperUrl || '') ), ...(contributionModeEnabled ? {} : { customPassword: inputPassword.value, }), mailProvider: selectMailProvider.value, mail2925Mode: getSelectedMail2925Mode(), mail2925UseAccountPool, currentMail2925AccountId: String(latestState?.currentMail2925AccountId || '').trim(), emailGenerator: selectEmailGenerator.value, customMailProviderPool: typeof normalizeCustomEmailPoolEntries === 'function' ? normalizeCustomEmailPoolEntries(inputCustomMailProviderPool?.value) : [], customEmailPool: normalizedCustomEmailPool, customEmailPoolEntries: normalizedCustomEmailPoolEntries, autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked, icloudHostPreference: selectIcloudHostPreference?.value || 'auto', icloudTargetMailboxType: normalizedIcloudTargetMailboxType, icloudForwardMailProvider: normalizedIcloudForwardMailProvider, icloudFetchMode: (icloudFetchModeRawValue.trim().toLowerCase() === 'always_new' ? 'always_new' : 'reuse_existing'), ...(contributionModeEnabled ? {} : { accountRunHistoryTextEnabled: true, accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value), }), ...buildManagedAliasBaseEmailPayload(), inbucketHost: inputInbucketHost.value.trim(), inbucketMailbox: inputInbucketMailbox.value.trim(), hotmailServiceMode: getSelectedHotmailServiceMode(), hotmailRemoteBaseUrl: inputHotmailRemoteBaseUrl.value.trim(), hotmailLocalBaseUrl: inputHotmailLocalBaseUrl.value.trim(), luckmailApiKey: inputLuckmailApiKey.value, luckmailBaseUrl: normalizeLuckmailBaseUrl(inputLuckmailBaseUrl.value), luckmailEmailType: normalizeLuckmailEmailType(selectLuckmailEmailType.value), luckmailDomain: inputLuckmailDomain.value.trim(), cloudflareDomain: selectedCloudflareDomain, cloudflareDomains: domains, cloudflareTempEmailBaseUrl: normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl.value), cloudflareTempEmailAdminAuth: inputTempEmailAdminAuth.value, cloudflareTempEmailCustomAuth: inputTempEmailCustomAuth.value, cloudflareTempEmailReceiveMailbox: normalizeCloudflareTempEmailReceiveMailboxValue(inputTempEmailReceiveMailbox.value), cloudflareTempEmailUseRandomSubdomain: Boolean(inputTempEmailUseRandomSubdomain?.checked), cloudflareTempEmailDomain: selectedCloudflareTempEmailDomain, cloudflareTempEmailDomains: tempEmailDomains, cloudMailBaseUrl: normalizeCloudMailBaseUrlInput((typeof inputCloudMailBaseUrl !== 'undefined' && inputCloudMailBaseUrl) ? inputCloudMailBaseUrl.value : ''), cloudMailAdminEmail: ((typeof inputCloudMailAdminEmail !== 'undefined' && inputCloudMailAdminEmail) ? inputCloudMailAdminEmail.value : '').trim(), cloudMailAdminPassword: (typeof inputCloudMailAdminPassword !== 'undefined' && inputCloudMailAdminPassword) ? inputCloudMailAdminPassword.value : '', cloudMailReceiveMailbox: normalizeCloudMailReceiveMailboxInput((typeof inputCloudMailReceiveMailbox !== 'undefined' && inputCloudMailReceiveMailbox) ? inputCloudMailReceiveMailbox.value : ''), cloudMailDomain: normalizeCloudMailDomainInput((typeof inputCloudMailDomain !== 'undefined' && inputCloudMailDomain) ? inputCloudMailDomain.value : ''), autoRunSkipFailures: inputAutoSkipFailures.checked, autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value), step6CookieCleanupEnabled: typeof inputStep6CookieCleanupEnabled !== 'undefined' && inputStep6CookieCleanupEnabled ? Boolean(inputStep6CookieCleanupEnabled.checked) : false, autoRunDelayEnabled: inputAutoDelayEnabled.checked, autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value), autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value), oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled ? Boolean(inputOAuthFlowTimeoutEnabled.checked) : true, phoneVerificationEnabled: effectivePhoneVerificationEnabled, signupMethod: effectiveSignupMethod, phoneSmsProvider: phoneSmsProviderValue, phoneSmsProviderOrder: phoneSmsProviderOrderValue, verificationResendCount: normalizeVerificationResendCount( inputVerificationResendCount?.value, DEFAULT_VERIFICATION_RESEND_COUNT ), heroSmsApiKey: heroSmsApiKeyValue, fiveSimApiKey: fiveSimApiKeyValue, fiveSimCountryOrder: fiveSimCountryOrderValue, fiveSimOperator: fiveSimOperatorValue, fiveSimProduct: fiveSimProductValue, nexSmsApiKey: nexSmsApiKeyValue, nexSmsCountryOrder: nexSmsCountryOrderValue, nexSmsServiceCode: nexSmsServiceCodeValue, phoneSmsReuseEnabled: phoneSmsReuseEnabledValue, heroSmsReuseEnabled: heroSmsReuseEnabledValue, freePhoneReuseEnabled: freePhoneReuseEnabledValue, freePhoneReuseAutoEnabled: freePhoneReuseAutoEnabledValue, heroSmsAcquirePriority: heroSmsAcquirePriorityValue, heroSmsMaxPrice: heroSmsMaxPriceValue, heroSmsPreferredPrice: heroSmsPreferredPriceValue, phonePreferredActivation: phonePreferredActivationValue, phoneVerificationReplacementLimit: phoneVerificationReplacementLimitValue, phoneCodeWaitSeconds: phoneCodeWaitSecondsValue, phoneCodeTimeoutWindows: phoneCodeTimeoutWindowsValue, phoneCodePollIntervalSeconds: phoneCodePollIntervalSecondsValue, phoneCodePollMaxRounds: phoneCodePollMaxRoundsValue, heroSmsCountryId: heroSmsCountry.id, heroSmsCountryLabel: heroSmsCountry.label, heroSmsCountryFallback, fiveSimCountryId: fiveSimCountry.id, fiveSimCountryLabel: fiveSimCountry.label, fiveSimCountryFallback, fiveSimMaxPrice: fiveSimMaxPriceValue, }; } function normalizeLocalCpaStep9Mode(value = '') { return String(value || '').trim().toLowerCase() === 'bypass' ? 'bypass' : DEFAULT_LOCAL_CPA_STEP9_MODE; } function normalizeMail2925Mode(value = '') { return String(value || '').trim().toLowerCase() === MAIL_2925_MODE_RECEIVE ? MAIL_2925_MODE_RECEIVE : DEFAULT_MAIL_2925_MODE; } function normalizeHotmailServiceMode(value = '') { if (typeof normalizeHotmailServiceModeFromUtils === 'function') { return normalizeHotmailServiceModeFromUtils(value); } return String(value || '').trim().toLowerCase() === HOTMAIL_SERVICE_MODE_REMOTE ? HOTMAIL_SERVICE_MODE_REMOTE : HOTMAIL_SERVICE_MODE_LOCAL; } function normalizeAccountRunHistoryHelperBaseUrlValue(value = '') { const trimmed = String(value || '').trim(); if (!trimmed) { return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL; } try { const parsed = new URL(trimmed); if (!['http:', 'https:'].includes(parsed.protocol)) { return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL; } if (parsed.pathname === '/append-account-log' || parsed.pathname === '/sync-account-run-records') { parsed.pathname = ''; parsed.search = ''; parsed.hash = ''; } return parsed.toString().replace(/\/$/, ''); } catch { return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL; } } function normalizePhoneSmsProvider(value = '') { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) { return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value); } const nexSmsProvider = typeof PHONE_SMS_PROVIDER_NEXSMS !== 'undefined' ? PHONE_SMS_PROVIDER_NEXSMS : 'nexsms'; const normalized = String(value || '').trim().toLowerCase(); if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) { return PHONE_SMS_PROVIDER_FIVE_SIM; } if (normalized === nexSmsProvider) { return nexSmsProvider; } return PHONE_SMS_PROVIDER_HERO_SMS; } function setPhoneSmsProviderSelectValue(provider) { const normalizedProvider = normalizePhoneSmsProvider(provider); if (selectPhoneSmsProvider) { selectPhoneSmsProvider.value = normalizedProvider; if (selectPhoneSmsProvider.dataset) { selectPhoneSmsProvider.dataset.activeProvider = normalizedProvider; } } return normalizedProvider; } function getLastAppliedPhoneSmsProvider() { return normalizePhoneSmsProvider( selectPhoneSmsProvider?.dataset?.activeProvider || latestState?.phoneSmsProvider || selectPhoneSmsProvider?.value ); } function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); } function getPhoneSmsProviderLabel(provider = getSelectedPhoneSmsProvider()) { if (typeof window !== 'undefined' && window.PhoneSmsProviderRegistry?.getProviderLabel) { return window.PhoneSmsProviderRegistry.getProviderLabel(provider); } return normalizePhoneSmsProvider(provider) === PHONE_SMS_PROVIDER_FIVE_SIM ? '5sim' : 'HeroSMS'; } function isFiveSimProviderSelected() { return getSelectedPhoneSmsProvider() === PHONE_SMS_PROVIDER_FIVE_SIM; } function normalizePhoneSmsCountryId(value, provider = getSelectedPhoneSmsProvider()) { if (normalizePhoneSmsProvider(provider) === PHONE_SMS_PROVIDER_FIVE_SIM) { return normalizeFiveSimCountryId(value); } return normalizeHeroSmsCountryId(value); } function normalizePhoneSmsCountryLabel(value = '', provider = getSelectedPhoneSmsProvider()) { if (normalizePhoneSmsProvider(provider) === PHONE_SMS_PROVIDER_FIVE_SIM) { return normalizeFiveSimCountryLabel(value); } return normalizeHeroSmsCountryLabel(value); } function normalizePhoneSmsMaxPriceValue(value = '', provider = getSelectedPhoneSmsProvider()) { if (normalizePhoneSmsProvider(provider) === PHONE_SMS_PROVIDER_FIVE_SIM) { return normalizeFiveSimMaxPriceValue(value); } return normalizeHeroSmsMaxPriceValue(value); } function normalizeFiveSimCountryId(value, fallback = DEFAULT_FIVE_SIM_COUNTRY_ID) { const fallbackSource = fallback === undefined || fallback === null ? DEFAULT_FIVE_SIM_COUNTRY_ID : fallback; const normalizedFallback = String(fallbackSource).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); const rawNormalized = typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.normalizeFiveSimCountryId ? window.PhoneSmsFiveSimProvider.normalizeFiveSimCountryId(value, '') : String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); const normalized = String(rawNormalized || '').trim().toLowerCase(); if (normalized) { return normalized; } if (!normalizedFallback) { return ''; } return normalizedFallback || DEFAULT_FIVE_SIM_COUNTRY_ID; } function normalizeFiveSimCountryLabel(value = '', fallback = DEFAULT_FIVE_SIM_COUNTRY_LABEL) { if (typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.normalizeFiveSimCountryLabel) { return window.PhoneSmsFiveSimProvider.normalizeFiveSimCountryLabel(value, fallback); } return String(value || '').trim() || fallback; } function formatFiveSimCountryDisplayLabel(id = '', englishValue = '', fallback = DEFAULT_FIVE_SIM_COUNTRY_LABEL) { if (typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.formatFiveSimCountryLabel) { return window.PhoneSmsFiveSimProvider.formatFiveSimCountryLabel(id, englishValue, fallback); } const countryId = normalizeFiveSimCountryId(id, ''); const english = normalizeFiveSimCountryLabel(englishValue || countryId || fallback, fallback); const chinese = FIVE_SIM_COUNTRY_CN_BY_ID[countryId] || ''; if (chinese && english) { if (String(english).includes(chinese)) { return english; } if (chinese.toLowerCase() !== english.toLowerCase()) { return `${chinese} (${english})`; } } return chinese || english; } function normalizeFiveSimOperator(value = '', fallback = DEFAULT_FIVE_SIM_OPERATOR) { if (typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.normalizeFiveSimOperator) { return window.PhoneSmsFiveSimProvider.normalizeFiveSimOperator(value || fallback); } return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || fallback; } function normalizeFiveSimMaxPriceValue(value = '') { if (typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.normalizeFiveSimMaxPrice) { return window.PhoneSmsFiveSimProvider.normalizeFiveSimMaxPrice(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 normalizeFiveSimCountryFallbackList(value = []) { if (typeof window !== 'undefined' && window.PhoneSmsFiveSimProvider?.normalizeFiveSimCountryFallback) { return window.PhoneSmsFiveSimProvider.normalizeFiveSimCountryFallback(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 = []; source.forEach((entry) => { let id = ''; let label = ''; if (entry && typeof entry === 'object' && !Array.isArray(entry)) { id = normalizeFiveSimCountryId(entry.id ?? entry.countryId ?? entry.slug, ''); label = normalizeFiveSimCountryLabel( entry.label ?? entry.countryLabel ?? formatFiveSimCountryDisplayLabel(id, entry.text_en ?? entry.name, id), id ); } else { const text = String(entry || '').trim(); const structured = text.match(/^([a-z0-9_-]+)\s*(?:[:|/-]\s*(.+))?$/i); id = normalizeFiveSimCountryId(structured?.[1] || text, ''); label = String(structured?.[2] || '').trim() || formatFiveSimCountryDisplayLabel(id, id, id); } if (!id || seen.has(id)) { return; } seen.add(id); normalized.push({ id, label: label || formatFiveSimCountryDisplayLabel(id, id, id) }); }); return normalized; } function normalizeHeroSmsCountryId(value, fallback = DEFAULT_HERO_SMS_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_HERO_SMS_COUNTRY_ID; } function normalizeHeroSmsCountryLabel(value = '') { return String(value || '').trim() || DEFAULT_HERO_SMS_COUNTRY_LABEL; } function normalizeHeroSmsMaxPriceValue(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 normalizePhoneSmsProviderValue(value = '') { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderId) { return rootScope.PhoneSmsProviderRegistry.normalizeProviderId(value); } return normalizePhoneSmsProvider(value); } function normalizePhoneSmsProviderOrderValue(value = [], fallbackOrder = []) { const rootScope = typeof window !== 'undefined' ? window : globalThis; if (rootScope.PhoneSmsProviderRegistry?.normalizeProviderOrder) { return rootScope.PhoneSmsProviderRegistry.normalizeProviderOrder(value, fallbackOrder); } const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const normalized = []; const seen = new Set(); source.forEach((entry) => { const provider = normalizePhoneSmsProviderValue(entry); if (seen.has(provider)) { return; } seen.add(provider); normalized.push(provider); }); if (normalized.length) { return normalized.slice(0, 3); } const fallback = Array.isArray(fallbackOrder) ? fallbackOrder : []; if (!fallback.length) { return []; } const fallbackNormalized = []; fallback.forEach((entry) => { const provider = normalizePhoneSmsProviderValue(entry); if (!provider || fallbackNormalized.includes(provider)) { return; } fallbackNormalized.push(provider); }); return fallbackNormalized.slice(0, 3); } function formatPhoneSmsProviderOrderSummary(order = []) { const normalized = normalizePhoneSmsProviderOrderValue(order, []); if (!normalized.length) { return '未设置'; } return normalized .map((provider, index) => `${index + 1}. ${getPhoneSmsProviderLabel(provider)}`) .join(' → '); } function updatePhoneSmsProviderOrderSummary(order = []) { const normalized = normalizePhoneSmsProviderOrderValue(order, []); if (displayPhoneSmsProviderOrder) { displayPhoneSmsProviderOrder.textContent = formatPhoneSmsProviderOrderSummary(normalized); } if (btnPhoneSmsProviderOrderMenu) { btnPhoneSmsProviderOrderMenu.textContent = normalized.length ? `${normalized.map((provider) => getPhoneSmsProviderLabel(provider)).join(' / ')} (${normalized.length}/3)` : `未选择 (0/3)`; } } function resolveNormalizedProviderOrderForRuntime(state = {}) { const rawOrder = Array.isArray(state?.phoneSmsProviderOrder) ? state.phoneSmsProviderOrder : []; const normalizedOrder = normalizePhoneSmsProviderOrderValue(rawOrder, []); if (normalizedOrder.length) { return normalizedOrder; } const fallbackProvider = normalizePhoneSmsProviderValue( state?.phoneSmsProvider || selectPhoneSmsProvider?.value || DEFAULT_PHONE_SMS_PROVIDER ); return [fallbackProvider]; } function setPhoneSmsProviderOrderMenuOpen(open) { const nextOpen = Boolean(open); if (btnPhoneSmsProviderOrderMenu) { btnPhoneSmsProviderOrderMenu.setAttribute('aria-expanded', String(nextOpen)); } if (phoneSmsProviderOrderMenu) { phoneSmsProviderOrderMenu.hidden = !nextOpen; } } function renderPhoneSmsProviderOrderMenu() { if (!phoneSmsProviderOrderMenu || !selectPhoneSmsProviderOrder) { return; } phoneSmsProviderOrderMenu.innerHTML = ''; const selectedOrder = normalizePhoneSmsProviderOrderValue(phoneSmsProviderOrderSelection, []); const selectedSet = new Set(selectedOrder); Array.from(selectPhoneSmsProviderOrder.options || []).forEach((option) => { const provider = normalizePhoneSmsProviderValue(option.value || ''); const active = selectedSet.has(provider); const orderIndex = active ? selectedOrder.findIndex((entry) => entry === provider) + 1 : 0; const item = document.createElement('button'); item.type = 'button'; item.className = 'header-dropdown-item hero-sms-country-menu-item'; item.classList.toggle('is-active', active); const labelText = document.createElement('span'); labelText.className = 'hero-sms-country-menu-item-label'; labelText.textContent = getPhoneSmsProviderLabel(provider); const badge = document.createElement('span'); badge.className = 'hero-sms-country-menu-item-badge'; badge.textContent = active ? `✓ ${orderIndex}` : ''; item.appendChild(labelText); item.appendChild(badge); item.addEventListener('click', () => { option.selected = !option.selected; const nextOrder = syncPhoneSmsProviderOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, syncProvider: false, }); updatePhoneSmsProviderOrderSummary(nextOrder); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); }); phoneSmsProviderOrderMenu.appendChild(item); }); } function syncPhoneSmsProviderOrderFromSelect(options = {}) { const selectionLimit = Math.max(1, Math.floor(Number(options.maxSelection) || 3)); const enforceMax = options.enforceMax !== false; const ensureDefault = options.ensureDefault !== false; const syncProvider = Boolean(options.syncProvider); const showLimitToast = Boolean(options.showLimitToast); if (!selectPhoneSmsProviderOrder) { phoneSmsProviderOrderSelection = []; updatePhoneSmsProviderOrderSummary([]); return []; } const selectedProviders = Array.from(selectPhoneSmsProviderOrder.options) .filter((option) => option.selected) .map((option) => normalizePhoneSmsProviderValue(option.value || '')) .filter(Boolean); const selectedSet = new Set(selectedProviders); let nextOrder = phoneSmsProviderOrderSelection.filter((provider) => selectedSet.has(provider)); selectedProviders.forEach((provider) => { if (!nextOrder.includes(provider)) { nextOrder.push(provider); } }); if (ensureDefault && !nextOrder.length) { const fallbackProvider = getSelectedPhoneSmsProvider(); nextOrder = normalizePhoneSmsProviderOrderValue([fallbackProvider], []); } if (enforceMax && nextOrder.length > selectionLimit) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { showToast(`服务商顺序最多 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); } } const nextOrderSet = new Set(nextOrder); Array.from(selectPhoneSmsProviderOrder.options).forEach((option) => { const provider = normalizePhoneSmsProviderValue(option.value || ''); option.selected = nextOrderSet.has(provider); }); phoneSmsProviderOrderSelection = [...nextOrder]; if (syncProvider && nextOrder.length && selectPhoneSmsProvider) { selectPhoneSmsProvider.value = normalizePhoneSmsProviderValue(nextOrder[0]); } renderPhoneSmsProviderOrderMenu(); updatePhoneSmsProviderOrderSummary(nextOrder); return nextOrder; } function applyPhoneSmsProviderOrderSelection(order = [], options = {}) { const ensureDefault = options.ensureDefault !== false; const syncProvider = Boolean(options.syncProvider); const normalizedOrder = normalizePhoneSmsProviderOrderValue(order, []); phoneSmsProviderOrderSelection = [...normalizedOrder]; if (selectPhoneSmsProviderOrder) { const selectedSet = new Set(normalizedOrder); Array.from(selectPhoneSmsProviderOrder.options).forEach((option) => { const provider = normalizePhoneSmsProviderValue(option.value || ''); option.selected = selectedSet.has(provider); }); } return syncPhoneSmsProviderOrderFromSelect({ ensureDefault, enforceMax: true, syncProvider, showLimitToast: false, }); } function clearPhoneSmsProviderOrderSelection(options = {}) { const syncProvider = Boolean(options.syncProvider); phoneSmsProviderOrderSelection = []; if (selectPhoneSmsProviderOrder) { Array.from(selectPhoneSmsProviderOrder.options || []).forEach((option) => { option.selected = false; }); } const nextOrder = syncPhoneSmsProviderOrderFromSelect({ ensureDefault: false, enforceMax: true, syncProvider, showLimitToast: false, }); if (!nextOrder.length && !syncProvider && selectPhoneSmsProvider) { selectPhoneSmsProvider.value = normalizePhoneSmsProviderValue( selectPhoneSmsProvider.value || DEFAULT_PHONE_SMS_PROVIDER ); } return nextOrder; } function getSelectedPhoneSmsProviderOrder() { const normalized = syncPhoneSmsProviderOrderFromSelect({ ensureDefault: false, enforceMax: true, syncProvider: false, showLimitToast: false, }); return normalizePhoneSmsProviderOrderValue(normalized, []); } function getSelectedPhoneSmsProvider() { if (!selectPhoneSmsProvider) { return DEFAULT_PHONE_SMS_PROVIDER; } const normalized = normalizePhoneSmsProviderValue(selectPhoneSmsProvider.value); selectPhoneSmsProvider.value = normalized; return normalized; } function normalizeFiveSimCountryCode(value = '', fallback = 'thailand') { const normalized = String(value || '') .trim() .toLowerCase() .replace(/[^a-z0-9_-]/g, ''); return normalized || fallback; } function normalizeFiveSimCountryOrderValue(value = []) { const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,,;;]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const normalized = []; const seen = new Set(); source.forEach((entry) => { const code = normalizeFiveSimCountryCode( entry && typeof entry === 'object' && !Array.isArray(entry) ? (entry.code || entry.country || entry.id || '') : entry, '' ); if (!code || seen.has(code)) { return; } seen.add(code); normalized.push(code); }); if (!normalized.length) { return []; } return normalized.slice(0, 10); } function formatFiveSimCountryOrderValue(value = []) { return normalizeFiveSimCountryOrderValue(value).join(','); } function normalizeFiveSimOperatorValue(value = '') { return normalizeFiveSimCountryCode(value, DEFAULT_FIVE_SIM_OPERATOR); } function normalizeFiveSimProductValue(value = '') { return normalizeFiveSimCountryCode(value, DEFAULT_FIVE_SIM_PRODUCT); } function normalizeNexSmsCountryIdValue(value, fallback = 0) { const parsed = Math.floor(Number(value)); if (Number.isFinite(parsed) && parsed >= 0) { return parsed; } const fallbackParsed = Math.floor(Number(fallback)); if (Number.isFinite(fallbackParsed) && fallbackParsed >= 0) { return fallbackParsed; } return 0; } function normalizeNexSmsCountryOrderValue(value = []) { const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,,;;]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const normalized = []; const seen = new Set(); source.forEach((entry) => { const countryId = normalizeNexSmsCountryIdValue( entry && typeof entry === 'object' && !Array.isArray(entry) ? (entry.id || entry.countryId || entry.country || '') : entry, -1 ); if (countryId < 0 || seen.has(countryId)) { return; } seen.add(countryId); normalized.push(countryId); }); if (!normalized.length) { return []; } return normalized.slice(0, 10); } function normalizeNexSmsServiceCodeValue(value = '') { const normalized = String(value || '') .trim() .toLowerCase() .replace(/[^a-z0-9_-]/g, ''); return normalized || DEFAULT_NEX_SMS_SERVICE_CODE; } function normalizeFiveSimCountryLabel(value = '', fallback = 'Thailand') { return String(value || '').trim() || fallback; } function normalizeFiveSimCountryFallbackList(value = []) { const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,,;;]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const normalized = []; const seen = new Set(); source.forEach((entry) => { let code = ''; let label = ''; if (entry && typeof entry === 'object' && !Array.isArray(entry)) { code = normalizeFiveSimCountryCode(entry.code || entry.id || entry.country || '', ''); label = normalizeFiveSimCountryLabel(entry.label || entry.countryLabel || ''); } else { code = normalizeFiveSimCountryCode(entry, ''); } if (!code || seen.has(code)) { return; } seen.add(code); normalized.push({ id: code, code, label: label || code, }); }); return normalized; } function normalizeNexSmsCountryLabel(value = '', fallback = '') { return String(value || '').trim() || fallback; } function normalizeNexSmsCountryFallbackList(value = []) { const source = Array.isArray(value) ? value : String(value || '') .split(/[\r\n,,;;]+/) .map((entry) => String(entry || '').trim()) .filter(Boolean); const normalized = []; const seen = new Set(); source.forEach((entry) => { let countryId = -1; let label = ''; if (entry && typeof entry === 'object' && !Array.isArray(entry)) { countryId = normalizeNexSmsCountryIdValue(entry.id || entry.countryId || entry.country || '', -1); label = normalizeNexSmsCountryLabel(entry.label || entry.countryLabel || ''); } else { countryId = normalizeNexSmsCountryIdValue(entry, -1); } if (countryId < 0 || seen.has(countryId)) { return; } seen.add(countryId); normalized.push({ id: countryId, label: label || `Country #${countryId}`, }); }); return normalized; } function getFiveSimCountryDisplayNameByIso(isoCode = '') { const normalizedIso = String(isoCode || '').trim().toUpperCase(); if (!normalizedIso || typeof Intl === 'undefined' || typeof Intl.DisplayNames !== 'function') { return ''; } try { const zhDisplayNames = new Intl.DisplayNames(['zh-CN'], { type: 'region' }); return String(zhDisplayNames.of(normalizedIso) || '').trim(); } catch { return ''; } } function buildFiveSimCountryDisplayLabel(country = {}) { const code = normalizeFiveSimCountryCode(country.code || country.id || country.country || '', 'thailand'); const english = String(country.text_en || country.eng || country.english || code).trim(); const iso = String( country.isoCode || country.iso || country.countryCode || '' ).trim().toUpperCase(); const chinese = getFiveSimCountryDisplayNameByIso(iso); const base = chinese && english ? `${chinese} (${english})` : (chinese || english || code); return iso ? `${base} [${iso}]` : base; } function buildFiveSimCountrySearchText(country = {}, label = '', code = '') { const tokens = new Set(); const iso = String(country.isoCode || country.iso || country.countryCode || '').trim().toUpperCase(); const chinese = getFiveSimCountryDisplayNameByIso(iso); [label, code, iso, chinese, country.text_en, country.english, country.text_ru].forEach((entry) => { const token = String(entry || '').trim(); if (token) { tokens.add(token); } }); return Array.from(tokens).join(' '); } function parseFiveSimCountriesPayload(payload = {}) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return []; } const entries = Object.entries(payload) .map(([countryCode, details]) => { const isoObject = details?.iso && typeof details.iso === 'object' ? details.iso : {}; const isoCode = Object.keys(isoObject)[0] || ''; const normalizedCode = normalizeFiveSimCountryCode(countryCode, ''); if (!normalizedCode) { return null; } const option = { code: normalizedCode, id: normalizedCode, isoCode: String(isoCode || '').trim().toUpperCase(), text_en: String(details?.text_en || normalizedCode).trim(), }; const label = buildFiveSimCountryDisplayLabel(option); return { id: normalizedCode, code: normalizedCode, isoCode: option.isoCode, label, searchText: buildFiveSimCountrySearchText(option, label, normalizedCode), }; }) .filter(Boolean); return entries.sort((left, right) => String(left.label || '').localeCompare(String(right.label || ''))); } function normalizePhoneVerificationReplacementLimit(value, fallback = DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT) { const rawValue = String(value ?? '').trim(); const parsed = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsed)) { return Math.max( PHONE_REPLACEMENT_LIMIT_MIN, Math.min(PHONE_REPLACEMENT_LIMIT_MAX, Number(fallback) || DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT) ); } return Math.max(PHONE_REPLACEMENT_LIMIT_MIN, Math.min(PHONE_REPLACEMENT_LIMIT_MAX, parsed)); } function normalizePhoneCodeWaitSecondsValue(value, fallback = DEFAULT_PHONE_CODE_WAIT_SECONDS) { const rawValue = String(value ?? '').trim(); const parsed = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsed)) { return Math.max( PHONE_CODE_WAIT_SECONDS_MIN, Math.min(PHONE_CODE_WAIT_SECONDS_MAX, Number(fallback) || DEFAULT_PHONE_CODE_WAIT_SECONDS) ); } return Math.max(PHONE_CODE_WAIT_SECONDS_MIN, Math.min(PHONE_CODE_WAIT_SECONDS_MAX, parsed)); } function normalizePhoneCodeTimeoutWindowsValue(value, fallback = DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS) { const rawValue = String(value ?? '').trim(); const parsed = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsed)) { return Math.max( PHONE_CODE_TIMEOUT_WINDOWS_MIN, Math.min(PHONE_CODE_TIMEOUT_WINDOWS_MAX, Number(fallback) || DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS) ); } return Math.max(PHONE_CODE_TIMEOUT_WINDOWS_MIN, Math.min(PHONE_CODE_TIMEOUT_WINDOWS_MAX, parsed)); } function normalizePhoneCodePollIntervalSecondsValue(value, fallback = DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS) { const rawValue = String(value ?? '').trim(); const parsed = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsed)) { return Math.max( PHONE_CODE_POLL_INTERVAL_SECONDS_MIN, Math.min(PHONE_CODE_POLL_INTERVAL_SECONDS_MAX, Number(fallback) || DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS) ); } return Math.max(PHONE_CODE_POLL_INTERVAL_SECONDS_MIN, Math.min(PHONE_CODE_POLL_INTERVAL_SECONDS_MAX, parsed)); } function normalizePhoneCodePollMaxRoundsValue(value, fallback = DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS) { const rawValue = String(value ?? '').trim(); const parsed = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsed)) { return Math.max( PHONE_CODE_POLL_MAX_ROUNDS_MIN, Math.min(PHONE_CODE_POLL_MAX_ROUNDS_MAX, Number(fallback) || DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS) ); } return Math.max(PHONE_CODE_POLL_MAX_ROUNDS_MIN, Math.min(PHONE_CODE_POLL_MAX_ROUNDS_MAX, parsed)); } function normalizeHeroSmsReuseEnabledValue(value, fallbackValue = undefined) { if (value === undefined || value === null) { if (fallbackValue !== undefined && fallbackValue !== null) { return Boolean(fallbackValue); } return DEFAULT_HERO_SMS_REUSE_ENABLED; } return Boolean(value); } 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 normalizeHeroSmsCountryFallbackList(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 = []; source.forEach((entry) => { let id = 0; let label = ''; if (entry && typeof entry === 'object' && !Array.isArray(entry)) { const parsedId = Math.floor(Number(entry.id ?? entry.countryId)); id = Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0; label = normalizeHeroSmsCountryLabel(entry.label ?? entry.countryLabel); } else { const text = String(entry || '').trim(); const structured = text.match(/^(\d+)\s*(?:[:|/-]\s*(.+))?$/); if (structured) { const parsedId = Math.floor(Number(structured[1])); id = Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0; label = normalizeHeroSmsCountryLabel(structured[2]); } else { const parsedId = Math.floor(Number(text)); id = Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0; } } if (!id || seen.has(id)) { return; } seen.add(id); normalized.push({ id, label: label || `Country #${id}`, }); }); return normalized; } function collectHeroSmsCountrySearchTokens(value, tokens, depth = 0) { if (depth > 2 || value === null || value === undefined) { return; } if (typeof value === 'string') { const normalized = value.trim(); if (normalized) { tokens.add(normalized); } return; } if (Array.isArray(value)) { value.forEach((entry) => collectHeroSmsCountrySearchTokens(entry, tokens, depth + 1)); return; } if (typeof value === 'object') { Object.values(value).forEach((entry) => collectHeroSmsCountrySearchTokens(entry, tokens, depth + 1)); } } function normalizeHeroSmsCountryAliasKey(value = '') { return String(value || '') .trim() .toLowerCase() .replace(/[’'`]/g, '') .replace(/&/g, ' and ') .replace(/\(.*?\)/g, ' ') .replace(/[^a-z0-9]+/g, ' ') .replace(/\s+/g, ' ') .trim(); } function collectHeroSmsCountryCodeAliases(country = {}, label = '') { const aliases = new Set(); const candidateLabels = [ String(label || '').trim(), String(country?.eng || '').trim(), String(country?.name || '').trim(), String(country?.country || '').trim(), ].filter(Boolean); candidateLabels.forEach((candidate) => { const normalized = normalizeHeroSmsCountryAliasKey(candidate); if (!normalized) { return; } const code = HERO_SMS_COUNTRY_ISO_CODE_BY_NAME.get(normalized); if (code) { aliases.add(code); } const overrideAliases = HERO_SMS_COUNTRY_CODE_ALIAS_OVERRIDES[normalized]; if (Array.isArray(overrideAliases)) { overrideAliases.forEach((entry) => { const token = String(entry || '').trim().toUpperCase(); if (token) { aliases.add(token); } }); } }); return Array.from(aliases); } function buildHeroSmsCountrySearchText(country = {}, label = '', id = '') { const tokens = new Set(); collectHeroSmsCountrySearchTokens(country, tokens, 0); if (label) { tokens.add(String(label).trim()); } if (id) { tokens.add(String(id).trim()); } collectHeroSmsCountryCodeAliases(country, label).forEach((alias) => tokens.add(alias)); return Array.from(tokens).join(' '); } function buildHeroSmsCountryDisplayLabel(country = {}) { const english = String(country?.eng || '').trim(); const chinese = String(country?.chn || '').trim(); if (chinese && english) { if (chinese.toLowerCase() === english.toLowerCase()) { return english; } return `${chinese} (${english})`; } return chinese || english; } function normalizeHeroSmsFetchErrorMessage(error) { const message = String(error?.message || error || '').trim(); if (!message) { return '未知网络错误'; } if (/aborted|abort|timed out|timeout/i.test(message)) { return '请求超时,请稍后重试'; } if (/failed to fetch|networkerror|network request failed/i.test(message)) { return '网络不可用或被拦截'; } return message; } function normalizeHeroSmsPriceForPreview(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(',', '.')); if (!Number.isFinite(parsed) || parsed < 0) { return null; } return parsed; } function formatHeroSmsPriceForPreview(value) { const price = Number(value); if (!Number.isFinite(price) || price < 0) { return ''; } const rounded = Math.round(price * 10000) / 10000; return rounded.toFixed(4).replace(/\.?0+$/, ''); } function isHeroSmsPreviewEmptyPayload(payload) { if (payload === undefined || payload === null) { return true; } if (typeof payload === 'string') { return !payload.trim(); } if (Array.isArray(payload)) { return payload.length === 0; } if (typeof payload === 'object') { return Object.keys(payload).length === 0; } return false; } function collectHeroSmsPriceEntriesForPreview(payload, entries = []) { if (Array.isArray(payload)) { payload.forEach((entry) => collectHeroSmsPriceEntriesForPreview(entry, entries)); return entries; } if (!payload || typeof payload !== 'object') { return entries; } const cost = normalizeHeroSmsPriceForPreview(payload.cost ?? payload.price ?? payload.Price); if (cost !== null) { const count = Number(payload.count ?? payload.qty ?? payload.Qty); const physicalCount = Number(payload.physicalCount); const hasCount = Number.isFinite(count); const hasPhysicalCount = Number.isFinite(physicalCount); const stockCount = hasPhysicalCount ? physicalCount : (hasCount ? count : 0); const hasStockField = hasCount || hasPhysicalCount; entries.push({ cost, hasStockField, stockCount: Number.isFinite(stockCount) ? stockCount : 0, inStock: !hasStockField || stockCount > 0, }); } const pushTierMapEntries = (tierMap) => { if (!tierMap || typeof tierMap !== 'object') { return; } Object.entries(tierMap).forEach(([priceKey, countRaw]) => { const priceValue = normalizeHeroSmsPriceForPreview(priceKey); if (priceValue === null) { return; } const stockCount = Number(countRaw); if (!Number.isFinite(stockCount)) { return; } entries.push({ cost: priceValue, hasStockField: true, stockCount: Math.max(0, stockCount), inStock: stockCount > 0, }); }); }; pushTierMapEntries(payload.freePriceMap); pushTierMapEntries(payload.priceMap); // Some HeroSMS payloads encode tiers as object keys, e.g.: // { "0.05": { count: 0 }, "0.35": { count: 12 } } // Parse those keyed tiers as well, so preview does not miss higher-price stock. Object.entries(payload).forEach(([key, value]) => { const keyedPrice = normalizeHeroSmsPriceForPreview(key); if (keyedPrice === null) { return; } if (value && typeof value === 'object') { const stockCandidates = [ value.count, value.physicalCount, value.stock, value.available, value.quantity, value.qty, value.left, value.free, ] .map((raw) => Number(raw)) .filter((raw) => Number.isFinite(raw)); const hasStockField = stockCandidates.length > 0; const trimmedKey = String(key || '').trim(); const looksLikeCountryOrServiceKey = /^[0-9]{1,4}$/.test(trimmedKey); const looksLikeDecimalPriceKey = /^-?\d+[.,]\d+$/.test(trimmedKey) || /^[^\d-]*-?\d+[.,]\d+[^\d]*$/.test(trimmedKey); // Guard against country/service IDs encoded as numeric keys (e.g. "52", "16"). // For numeric keys we require explicit stock fields, while decimal price keys can // still be consumed even when the provider only returns a scalar quantity. if (looksLikeCountryOrServiceKey && !looksLikeDecimalPriceKey) { return; } const stockCount = hasStockField ? Math.max(...stockCandidates) : 0; entries.push({ cost: keyedPrice, hasStockField, stockCount: Number.isFinite(stockCount) ? stockCount : 0, inStock: stockCount > 0, }); return; } const numericCount = Number(value); if (Number.isFinite(numericCount)) { entries.push({ cost: keyedPrice, hasStockField: true, stockCount: Math.max(0, numericCount), inStock: numericCount > 0, }); } }); Object.values(payload).forEach((entry) => collectHeroSmsPriceEntriesForPreview(entry, entries)); return entries; } function collectHeroSmsPriceEntriesFromTopCountriesPayload(payload, countryId, entries = []) { const normalizedCountryId = normalizeHeroSmsCountryId(countryId, 0); if (normalizedCountryId <= 0) { return entries; } if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return entries; } Object.values(payload).forEach((entry) => { if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { return; } const entryCountryId = normalizeHeroSmsCountryId( entry.country ?? entry.countryId ?? entry.country_id ?? entry.id, 0 ); if (entryCountryId !== normalizedCountryId) { return; } collectHeroSmsPriceEntriesForPreview(entry, entries); }); return entries; } function collectHeroSmsPriceEntriesFromVerificationPayload(payload, countryId, serviceCode = 'dr', entries = []) { const normalizedCountryId = normalizeHeroSmsCountryId(countryId, 0); if (normalizedCountryId <= 0) { return entries; } if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return entries; } const normalizedServiceCode = String(serviceCode || '').trim(); if (!normalizedServiceCode) { return entries; } const serviceNode = payload?.[normalizedServiceCode]; if (!serviceNode || typeof serviceNode !== 'object' || Array.isArray(serviceNode)) { return entries; } const countryNode = serviceNode?.[String(normalizedCountryId)]; if (!countryNode || typeof countryNode !== 'object' || Array.isArray(countryNode)) { return entries; } const priceValue = normalizeHeroSmsPriceForPreview(countryNode.price); if (priceValue === null) { return entries; } const stockCount = Number(countryNode.count); entries.push({ cost: priceValue, hasStockField: Number.isFinite(stockCount), stockCount: Number.isFinite(stockCount) ? Math.max(0, stockCount) : 0, inStock: !Number.isFinite(stockCount) || stockCount > 0, }); return entries; } function collectHeroSmsPriceCandidatesForPreview(payload, candidates = []) { collectHeroSmsPriceEntriesForPreview(payload, []) .filter((entry) => entry.inStock) .forEach((entry) => { candidates.push(entry.cost); }); return candidates; } function formatPhoneSmsPriceEntriesSummary(payload) { const entries = collectHeroSmsPriceEntriesForPreview(payload, []) .filter((entry) => Number.isFinite(Number(entry.cost)) && Number(entry.cost) > 0); const inStockPrices = Array.from(new Set( entries .filter((entry) => entry.inStock) .map((entry) => Math.round(Number(entry.cost) * 10000) / 10000) )).sort((left, right) => left - right); const allPrices = Array.from(new Set( entries.map((entry) => Math.round(Number(entry.cost) * 10000) / 10000) )).sort((left, right) => left - right); return { entries, inStockPrices, allPrices }; } function describeHeroSmsPreviewPayload(payload) { if (payload === undefined || payload === null) { return ''; } if (typeof payload === 'string') { return payload.trim(); } if (typeof payload === 'number' || typeof payload === 'boolean') { return String(payload); } if (typeof payload === 'object') { const directMessage = String( payload.message || payload.msg || payload.error || payload.title || payload.statusText || '' ).trim(); if (directMessage) { const extra = String(payload?.info?.text || payload?.info?.description || '').trim(); return extra ? `${directMessage}: ${extra}` : directMessage; } try { return JSON.stringify(payload); } catch { return '[object]'; } } return String(payload); } function summarizeHeroSmsPreviewError(payload, responseStatus = 0) { if (isHeroSmsPreviewEmptyPayload(payload)) { return '未返回有效价格'; } const text = describeHeroSmsPreviewPayload(payload); if (text === '{}' || text === '[]') { return '未返回有效价格'; } if (/UNPROCESSABLE_ENTITY/i.test(text) && /api_key/i.test(text) && /REQUIRED/i.test(text)) { return '请先填写接码 API Key'; } if (/BAD_KEY|WRONG_KEY|INVALID_KEY/i.test(text)) { return 'API Key 无效'; } if (/NO_BALANCE|NOT_ENOUGH_BALANCE/i.test(text)) { return '余额不足'; } if (/BANNED|ACCOUNT_BANNED/i.test(text)) { return '账号已被封禁'; } if (/WRONG_SERVICE|SERVICE_NOT_FOUND/i.test(text)) { return '服务代码无效'; } if (/WRONG_COUNTRY|COUNTRY_NOT_FOUND/i.test(text)) { return '国家参数无效'; } if (/NO_NUMBERS/i.test(text)) { return '暂无可用号源'; } if (responseStatus && responseStatus >= 400) { return `HTTP ${responseStatus}`; } return text || '未知错误'; } function formatPriceTiersForPreview(entries = [], options = {}) { const maxPrice = Number(options?.maxPrice); const hasMaxPrice = Number.isFinite(maxPrice) && maxPrice > 0; const normalized = Array.from( new Map( (Array.isArray(entries) ? entries : []) .map((entry) => { const price = Number(entry?.price ?? entry?.cost); if (!Number.isFinite(price) || price <= 0) { return null; } const normalizedPrice = Math.round(price * 10000) / 10000; const countRaw = Number(entry?.count); const count = Number.isFinite(countRaw) ? Math.max(0, Math.floor(countRaw)) : null; return [normalizedPrice, count]; }) .filter(Boolean) ).entries() ) .map(([price, count]) => ({ price: Number(price), count })) .sort((left, right) => left.price - right.price); if (!normalized.length) { return ''; } const limit = 16; const displayed = normalized.slice(0, limit); const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} 档` : ''; return displayed.map((entry) => { const priceText = formatHeroSmsPriceForPreview(entry.price) || String(entry.price); const countText = entry.count === null ? '' : `x${entry.count}`; const overLimitText = hasMaxPrice && entry.price > maxPrice ? '↑' : ''; return `${priceText}${countText ? `(${countText})` : ''}${overLimitText}`; }).join(', ') + suffix; } function formatPriceTiersWithZeroStockForPreview(entries = [], options = {}) { const maxPrice = Number(options?.maxPrice); const hasMaxPrice = Number.isFinite(maxPrice) && maxPrice > 0; const normalized = Array.from( new Map( (Array.isArray(entries) ? entries : []) .map((entry) => { const price = Number(entry?.price ?? entry?.cost); if (!Number.isFinite(price) || price <= 0) { return null; } const normalizedPrice = Math.round(price * 10000) / 10000; const countRaw = Number(entry?.count); const count = Number.isFinite(countRaw) ? Math.max(0, Math.floor(countRaw)) : null; return [normalizedPrice, count]; }) .filter(Boolean) ).entries() ) .map(([price, count]) => ({ price: Number(price), count })) .sort((left, right) => left.price - right.price); if (!normalized.length) { return ''; } const limit = 16; const displayed = normalized.slice(0, limit); const suffix = normalized.length > limit ? ` ... +${normalized.length - limit} 档` : ''; return displayed.map((entry) => { const priceText = formatHeroSmsPriceForPreview(entry.price) || String(entry.price); const countText = entry.count === null ? '' : `x${entry.count}`; const overLimitText = hasMaxPrice && entry.price > maxPrice ? '↑' : ''; return `${priceText}${countText ? `(${countText})` : ''}${overLimitText}`; }).join(', ') + suffix; } function describeNexSmsPreviewPayload(payload) { if (payload === undefined || payload === null) { return ''; } if (typeof payload === 'string') { return payload.trim(); } if (typeof payload === 'number' || typeof payload === 'boolean') { return String(payload); } if (typeof payload === 'object') { const directMessage = String( payload.message || payload.msg || payload.error || payload.title || payload.statusText || payload?.data?.message || payload?.data?.error || '' ).trim(); if (directMessage) { return directMessage; } try { return JSON.stringify(payload); } catch { return '[object]'; } } return String(payload); } function getSelectedHeroSmsCountryOption() { const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); if (selectedCountries.length) { return selectedCountries[0]; } return isFiveSimProviderSelected() ? { id: DEFAULT_FIVE_SIM_COUNTRY_ID, label: DEFAULT_FIVE_SIM_COUNTRY_LABEL } : { id: DEFAULT_HERO_SMS_COUNTRY_ID, label: DEFAULT_HERO_SMS_COUNTRY_LABEL }; } function getFiveSimCountryOptionLabel(code = '') { const normalizedCode = normalizeFiveSimCountryCode(code, ''); if (!normalizedCode) { return ''; } const matched = Array.from(selectFiveSimCountry?.options || []) .find((option) => normalizeFiveSimCountryCode(option.value, '') === normalizedCode); if (matched) { return normalizeFiveSimCountryLabel(matched.textContent, normalizedCode); } const fallback = FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.find((item) => item.id === normalizedCode); return fallback ? formatFiveSimCountryDisplayLabel(fallback.id, fallback.eng, fallback.id) : normalizedCode; } function getNexSmsCountryLabelById(id) { const countryId = normalizeNexSmsCountryIdValue(id, -1); if (countryId < 0) { return ''; } const matched = Array.from(selectNexSmsCountry?.options || []) .find((option) => normalizeNexSmsCountryIdValue(option.value, -1) === countryId); return normalizeNexSmsCountryLabel(matched?.textContent || '', `Country #${countryId}`); } function getSelectedFiveSimCountries() { return syncFiveSimCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); } function getSelectedNexSmsCountries() { return syncNexSmsCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); } function updateHeroSmsPlatformDisplay() { if (!displayHeroSmsPlatform) { return; } const provider = getSelectedPhoneSmsProvider(); const selected = provider === PHONE_SMS_PROVIDER_FIVE_SIM ? (getSelectedFiveSimCountries()[0] || { id: DEFAULT_FIVE_SIM_COUNTRY_ID, label: DEFAULT_FIVE_SIM_COUNTRY_LABEL }) : (provider === PHONE_SMS_PROVIDER_NEXSMS ? (getSelectedNexSmsCountries()[0] || { id: DEFAULT_NEX_SMS_COUNTRY_ORDER[0], label: `Country #${DEFAULT_NEX_SMS_COUNTRY_ORDER[0]}` }) : getSelectedHeroSmsCountryOption()); const countryText = selected?.label ? ` / ${selected.label}` : ''; displayHeroSmsPlatform.textContent = `${getPhoneSmsProviderLabel(provider)} / OpenAI${countryText}`; if (inputHeroSmsApiKey) { inputHeroSmsApiKey.placeholder = provider === PHONE_SMS_PROVIDER_FIVE_SIM ? '请输入 5sim API Key' : (provider === PHONE_SMS_PROVIDER_NEXSMS ? '请输入 NexSMS API Key' : '请输入 HeroSMS API Key'); } } function getHeroSmsCountryLabelById(id) { const targetId = String(id || '').trim(); const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback; if (!targetId || !countrySelect) { return ''; } const matched = Array.from(countrySelect.options).find((option) => option.value === targetId); return String(matched?.textContent || '').trim() || (isFiveSimProviderSelected() ? targetId : `Country #${targetId}`); } function renderHeroSmsCountryFallbackOrder(countries = []) { if (!displayHeroSmsCountryFallbackOrder) { return; } displayHeroSmsCountryFallbackOrder.textContent = ''; const normalized = isFiveSimProviderSelected() ? normalizeFiveSimCountryFallbackList(countries) : normalizeHeroSmsCountryFallbackList(countries); if (!normalized.length) { displayHeroSmsCountryFallbackOrder.textContent = '未设置'; return; } normalized.forEach((country, index) => { const chip = document.createElement('span'); chip.className = 'country-order-chip'; const label = document.createElement('span'); label.className = 'country-order-chip-label'; label.textContent = `${index + 1}. ${country.label}(${country.id})`; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; removeBtn.title = `移除 ${country.label}`; removeBtn.setAttribute('aria-label', `移除 ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); removeHeroSmsCountryFromOrder(country.id); }); chip.appendChild(label); chip.appendChild(removeBtn); displayHeroSmsCountryFallbackOrder.appendChild(chip); if (index < normalized.length - 1) { const separator = document.createElement('span'); separator.className = 'country-order-separator'; separator.textContent = '→'; displayHeroSmsCountryFallbackOrder.appendChild(separator); } }); } function setHeroSmsCountryMenuOpen(open) { const nextOpen = Boolean(open); if (btnHeroSmsCountryMenu) { btnHeroSmsCountryMenu.setAttribute('aria-expanded', String(nextOpen)); } if (heroSmsCountryMenu) { heroSmsCountryMenu.hidden = !nextOpen; if (nextOpen) { const searchInput = heroSmsCountryMenu.querySelector('.hero-sms-country-menu-search-input'); if (searchInput) { // Always reset previous keyword on open to avoid accidental "empty list" state. heroSmsCountryMenuSearchKeyword = ''; searchInput.value = ''; applyHeroSmsCountryMenuFilter(''); setTimeout(() => { searchInput.focus(); searchInput.select(); }, 0); } } } } function applyHeroSmsCountryMenuFilter(keyword = '') { if (!heroSmsCountryMenu) { return; } const normalizedKeyword = String(keyword || '').trim().toLowerCase(); const items = Array.from(heroSmsCountryMenu.querySelectorAll('.hero-sms-country-menu-item')); let visibleCount = 0; items.forEach((item) => { const haystack = String(item.dataset.searchText || '').toLowerCase(); const visible = !normalizedKeyword || haystack.includes(normalizedKeyword); item.hidden = !visible; if (visible) { visibleCount += 1; } }); let empty = heroSmsCountryMenu.querySelector('.hero-sms-country-menu-empty'); if (visibleCount === 0) { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '没有匹配国家'; heroSmsCountryMenu.appendChild(empty); } } else if (empty) { empty.remove(); } } function updateHeroSmsCountryMenuSummary(selectedCountries = []) { if (!btnHeroSmsCountryMenu) { return; } const normalized = isFiveSimProviderSelected() ? normalizeFiveSimCountryFallbackList(selectedCountries) : normalizeHeroSmsCountryFallbackList(selectedCountries); if (!normalized.length) { btnHeroSmsCountryMenu.textContent = `\u672a\u9009\u62e9 (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; return; } const labels = normalized.map((country) => country.label); btnHeroSmsCountryMenu.textContent = `${labels.join(' / ')} (${normalized.length}/${HERO_SMS_COUNTRY_SELECTION_MAX})`; } function renderHeroSmsCountryChoiceButtons() { if (!heroSmsCountryMenu || !selectHeroSmsCountry) { return; } const options = Array.from(selectHeroSmsCountry.options || []); const selectedOrder = [...heroSmsCountrySelectionOrder]; const selectedSet = new Set(selectedOrder.map((id) => String(id))); heroSmsCountryMenu.innerHTML = ''; const searchWrap = document.createElement('div'); searchWrap.className = 'hero-sms-country-menu-search'; const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; searchInput.placeholder = '搜索国家(中/英/代码/ID)'; searchInput.value = heroSmsCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { heroSmsCountryMenuSearchKeyword = String(searchInput.value || '').trim(); applyHeroSmsCountryMenuFilter(heroSmsCountryMenuSearchKeyword); }); searchWrap.appendChild(searchInput); heroSmsCountryMenu.appendChild(searchWrap); if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '暂无国家选项'; heroSmsCountryMenu.appendChild(empty); updateHeroSmsCountryMenuSummary([]); return; } options.forEach((option) => { const countryId = String(option.value || '').trim(); if (!countryId) { return; } const item = document.createElement('button'); item.type = 'button'; item.className = 'header-dropdown-item hero-sms-country-menu-item'; const active = selectedSet.has(countryId); const orderIndex = active ? selectedOrder.findIndex((id) => String(id) === countryId) + 1 : 0; const label = String(option.textContent || '').trim() || `Country #${countryId}`; item.classList.toggle('is-active', active); const labelText = document.createElement('span'); labelText.className = 'hero-sms-country-menu-item-label'; labelText.textContent = label; const badge = document.createElement('span'); badge.className = 'hero-sms-country-menu-item-badge'; badge.textContent = active ? `✓ ${orderIndex}` : ''; item.appendChild(labelText); item.appendChild(badge); item.dataset.searchText = `${label} ${countryId} ${heroSmsCountrySearchTextById.get(countryId) || ''}`; item.addEventListener('click', () => { option.selected = !option.selected; syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: true, }); updateHeroSmsPlatformDisplay(); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); }); heroSmsCountryMenu.appendChild(item); }); applyHeroSmsCountryMenuFilter(heroSmsCountryMenuSearchKeyword); updateHeroSmsCountryMenuSummary( selectedOrder.map((id) => ({ id, label: getHeroSmsCountryLabelById(id), })) ); } function syncHeroSmsFallbackSelectionOrderFromSelect(options = {}) { const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback; const selectionLimit = Math.max(1, Math.floor(Number(options.maxSelection) || HERO_SMS_COUNTRY_SELECTION_MAX)); const enforceMax = options.enforceMax !== false; const ensureDefault = options.ensureDefault !== false; const showLimitToast = Boolean(options.showLimitToast); if (!countrySelect) { const defaultCountry = isFiveSimProviderSelected() ? { id: DEFAULT_FIVE_SIM_COUNTRY_ID, label: DEFAULT_FIVE_SIM_COUNTRY_LABEL } : { id: normalizeHeroSmsCountryId(DEFAULT_HERO_SMS_COUNTRY_ID), label: DEFAULT_HERO_SMS_COUNTRY_LABEL }; heroSmsCountrySelectionOrder = [defaultCountry.id]; renderHeroSmsCountryFallbackOrder([defaultCountry]); return [defaultCountry]; } const selectedIds = Array.from(countrySelect.options) .filter((option) => option.selected) .map((option) => normalizePhoneSmsCountryId(option.value, getSelectedPhoneSmsProvider())) .filter(Boolean); if (!selectedIds.length && !countrySelect.multiple) { const fallbackId = normalizePhoneSmsCountryId(countrySelect.value, getSelectedPhoneSmsProvider()); if (fallbackId) { selectedIds.push(fallbackId); } } const selectedSet = new Set(selectedIds); let nextOrder = heroSmsCountrySelectionOrder.filter((id) => selectedSet.has(id)); selectedIds.forEach((id) => { if (!nextOrder.includes(id)) { nextOrder.push(id); } }); if (ensureDefault && !nextOrder.length) { const defaultId = normalizePhoneSmsCountryId(countrySelect.value || (isFiveSimProviderSelected() ? DEFAULT_FIVE_SIM_COUNTRY_ID : DEFAULT_HERO_SMS_COUNTRY_ID)); nextOrder = [defaultId]; } if (enforceMax && nextOrder.length > selectionLimit) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { showToast(`接码国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); } } const nextOrderSet = new Set(nextOrder.map((id) => String(id))); Array.from(countrySelect.options).forEach((option) => { option.selected = nextOrderSet.has(String(option.value)); }); heroSmsCountrySelectionOrder = nextOrder; const selectedCountries = heroSmsCountrySelectionOrder.map((id) => ({ id, label: getHeroSmsCountryLabelById(id), })); renderHeroSmsCountryFallbackOrder(selectedCountries); renderHeroSmsCountryChoiceButtons(); return selectedCountries; } function applyHeroSmsFallbackSelection(countries = [], options = {}) { const includePrimary = Boolean(options.includePrimary); const normalizeFallbackForCurrentProvider = isFiveSimProviderSelected() ? normalizeFiveSimCountryFallbackList : normalizeHeroSmsCountryFallbackList; const sourceCountries = includePrimary ? countries : [ getSelectedHeroSmsCountryOption(), ...normalizeFallbackForCurrentProvider(countries), ]; const normalized = (isFiveSimProviderSelected() ? normalizeFiveSimCountryFallbackList(sourceCountries) : normalizeHeroSmsCountryFallbackList(sourceCountries) ).slice(0, HERO_SMS_COUNTRY_SELECTION_MAX); const selectedIds = normalized .map((entry) => normalizePhoneSmsCountryId(entry.id, getSelectedPhoneSmsProvider())) .filter(Boolean); const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback; if (countrySelect) { const selectedSet = new Set(selectedIds.map((id) => String(id))); Array.from(countrySelect.options).forEach((option) => { option.selected = selectedSet.has(String(option.value)); }); } heroSmsCountrySelectionOrder = [...selectedIds]; return syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); } function removeHeroSmsCountryFromOrder(id) { const provider = getSelectedPhoneSmsProvider(); const normalizedId = normalizePhoneSmsCountryId(id, provider); if (!normalizedId) { return []; } heroSmsCountrySelectionOrder = heroSmsCountrySelectionOrder .filter((entry) => String(entry) !== String(normalizedId)); [selectHeroSmsCountry, selectHeroSmsCountryFallback].forEach((selectEl) => { if (!selectEl) { return; } Array.from(selectEl.options || []).forEach((option) => { if (String(normalizePhoneSmsCountryId(option.value, provider)) === String(normalizedId)) { option.selected = false; } }); }); const nextOrder = syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); updateHeroSmsPlatformDisplay(); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); return nextOrder; } function normalizePhoneActivationState(record = {}) { if (!record || typeof record !== 'object' || Array.isArray(record)) { return null; } const provider = normalizePhoneSmsProviderValue(record.provider || latestState?.phoneSmsProvider || PHONE_SMS_PROVIDER_HERO); const activationId = String(record.activationId ?? record.id ?? record.activation ?? '').trim(); const phoneNumber = String(record.phoneNumber ?? record.phone ?? record.number ?? '').trim(); if (!activationId || !phoneNumber) { return null; } const normalized = { provider, activationId, phoneNumber, serviceCode: String(record.serviceCode || '').trim(), successfulUses: Math.max(0, Math.floor(Number(record.successfulUses) || 0)), maxUses: Math.max(1, Math.floor(Number(record.maxUses) || 3)), }; if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) { normalized.countryId = normalizeFiveSimCountryCode( record.countryCode || record.countryId || '', '' ); } else if (provider === PHONE_SMS_PROVIDER_NEXSMS) { normalized.countryId = normalizeNexSmsCountryId(record.countryId, -1); } else { normalized.countryId = normalizeHeroSmsCountryId(record.countryId, 0); } const countryLabel = String(record.countryLabel || '').trim(); if (countryLabel) { normalized.countryLabel = countryLabel; } const expiresAt = Number(record.expiresAt); if (Number.isFinite(expiresAt) && expiresAt > 0) { normalized.expiresAt = Math.floor(expiresAt); } return normalized; } function buildPhoneActivationOptionKey(activation) { const normalized = normalizePhoneActivationState(activation); if (!normalized) { return ''; } return [ normalized.provider, String(normalized.activationId || '').trim(), String(normalized.phoneNumber || '').trim(), ].join('::'); } function resolvePhoneActivationCountryLabel(activation = null) { const normalized = normalizePhoneActivationState(activation); if (!normalized) { return ''; } if (normalized.countryLabel) { return normalized.countryLabel; } if (normalized.provider === PHONE_SMS_PROVIDER_FIVE_SIM) { return String(normalized.countryId || '').trim(); } if (normalized.provider === PHONE_SMS_PROVIDER_NEXSMS) { const countryId = normalizeNexSmsCountryId(normalized.countryId, -1); return countryId >= 0 ? `Country #${countryId}` : ''; } return normalizeHeroSmsCountryLabel( getHeroSmsCountryLabelById(normalized.countryId || '') ); } function getPhoneSmsProviderLabel(provider = '') { const normalized = normalizePhoneSmsProviderValue(provider); if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) { return '5sim'; } if (normalized === PHONE_SMS_PROVIDER_NEXSMS) { return 'NexSMS'; } return 'HeroSMS'; } function buildPhoneActivationOptionLabel(activation = null, sourceLabel = '') { const normalized = normalizePhoneActivationState(activation); if (!normalized) { return ''; } const countryLabel = resolvePhoneActivationCountryLabel(normalized); const providerLabel = getPhoneSmsProviderLabel(normalized.provider); const reuseLabel = normalized.maxUses > 1 ? ` / ${Math.max(0, normalized.successfulUses) + 1}/${normalized.maxUses}` : ''; const sourcePrefix = sourceLabel ? `[${sourceLabel}] ` : ''; return `${sourcePrefix}${normalized.phoneNumber} / ${providerLabel}${countryLabel ? ` / ${countryLabel}` : ''} (#${normalized.activationId})${reuseLabel}`; } function stopPhoneRuntimeCountdownTicker() { clearInterval(phoneRuntimeCountdownTimer); phoneRuntimeCountdownTimer = null; } function renderPhoneRuntimeCountdown() { if (!displayHeroSmsCurrentCountdown) { return; } const hasActivation = Boolean(normalizePhoneActivationState(latestState?.currentPhoneActivation)); const endsAt = Number(phoneRuntimeCountdownEndsAt) || 0; const now = Date.now(); if (!hasActivation || !Number.isFinite(endsAt) || endsAt <= 0) { displayHeroSmsCurrentCountdown.textContent = hasActivation ? '等待中' : '未启动'; stopPhoneRuntimeCountdownTicker(); return; } const remainingMs = endsAt - now; const windowHint = ( phoneRuntimeCountdownWindowTotal > 0 ? `(${Math.max(1, phoneRuntimeCountdownWindowIndex)}/${phoneRuntimeCountdownWindowTotal})` : '' ); if (remainingMs <= 0) { displayHeroSmsCurrentCountdown.textContent = `00:00:00${windowHint}`; stopPhoneRuntimeCountdownTicker(); return; } displayHeroSmsCurrentCountdown.textContent = `${formatCountdown(remainingMs)}${windowHint}`; } function syncPhoneRuntimeCountdown(state = {}) { phoneRuntimeCountdownEndsAt = Math.max(0, Number(state?.currentPhoneVerificationCountdownEndsAt) || 0); phoneRuntimeCountdownWindowIndex = Math.max(0, Math.floor(Number(state?.currentPhoneVerificationCountdownWindowIndex) || 0)); phoneRuntimeCountdownWindowTotal = Math.max(0, Math.floor(Number(state?.currentPhoneVerificationCountdownWindowTotal) || 0)); renderPhoneRuntimeCountdown(); if (phoneRuntimeCountdownEndsAt > Date.now()) { if (!phoneRuntimeCountdownTimer) { phoneRuntimeCountdownTimer = setInterval(() => { renderPhoneRuntimeCountdown(); }, 1000); } } else { stopPhoneRuntimeCountdownTicker(); } } function renderPhonePreferredActivationOptions(state = {}) { if (!selectHeroSmsPreferredActivation) { return; } const provider = normalizePhoneSmsProviderValue( state?.phoneSmsProvider || latestState?.phoneSmsProvider || getSelectedPhoneSmsProvider() ); const selectedPreferred = normalizePhoneActivationState(state?.phonePreferredActivation || latestState?.phonePreferredActivation); const reusablePool = Array.isArray(state?.phoneReusableActivationPool) ? state.phoneReusableActivationPool : (Array.isArray(latestState?.phoneReusableActivationPool) ? latestState.phoneReusableActivationPool : []); const optionEntries = []; const seen = new Set(); const pushActivationOption = (activation, sourceLabel) => { const normalized = normalizePhoneActivationState(activation); if (!normalized || normalized.provider !== provider) { return; } const key = buildPhoneActivationOptionKey(normalized); if (!key || seen.has(key)) { return; } seen.add(key); optionEntries.push({ key, activation: normalized, label: buildPhoneActivationOptionLabel(normalized, sourceLabel), }); }; pushActivationOption(state?.currentPhoneActivation || latestState?.currentPhoneActivation, '当前'); pushActivationOption(state?.reusablePhoneActivation || latestState?.reusablePhoneActivation, '复用'); reusablePool.forEach((activation) => pushActivationOption(activation, '池')); pushActivationOption(selectedPreferred, '已选'); phonePreferredActivationOptionMap.clear(); selectHeroSmsPreferredActivation.innerHTML = ''; const autoOption = document.createElement('option'); autoOption.value = ''; autoOption.textContent = '自动(先复用已有可用号,再创建新号)'; selectHeroSmsPreferredActivation.appendChild(autoOption); optionEntries.forEach((entry) => { phonePreferredActivationOptionMap.set(entry.key, entry.activation); const option = document.createElement('option'); option.value = entry.key; option.textContent = entry.label; selectHeroSmsPreferredActivation.appendChild(option); }); const selectedKey = buildPhoneActivationOptionKey(selectedPreferred); if (selectedKey && phonePreferredActivationOptionMap.has(selectedKey)) { selectHeroSmsPreferredActivation.value = selectedKey; } else { selectHeroSmsPreferredActivation.value = ''; } } function getSelectedPhonePreferredActivation() { if (!selectHeroSmsPreferredActivation) { return null; } const selectedKey = String(selectHeroSmsPreferredActivation.value || '').trim(); if (!selectedKey) { return null; } const activation = phonePreferredActivationOptionMap.get(selectedKey); return activation ? { ...activation } : null; } function updateHeroSmsRuntimeDisplay(state = {}) { if (displayHeroSmsCurrentNumber) { const activation = normalizePhoneActivationState(state?.currentPhoneActivation || latestState?.currentPhoneActivation); const phoneNumber = String(activation?.phoneNumber || '').trim(); const activationId = String(activation?.activationId || '').trim(); const countryLabel = normalizePhoneSmsCountryLabel( activation?.countryLabel || getHeroSmsCountryLabelById(activation?.countryId || ''), activation?.provider || getSelectedPhoneSmsProvider() ); displayHeroSmsCurrentNumber.textContent = phoneNumber ? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${countryLabel ? ` / ${countryLabel}` : ''}` : '未分配'; } if (displayHeroSmsCurrentCode) { const code = String(state?.currentPhoneVerificationCode ?? latestState?.currentPhoneVerificationCode ?? '').trim(); displayHeroSmsCurrentCode.textContent = code || '未获取'; } if (displayFreeReusablePhone || displayFreeReusablePhoneCountry || inputFreeReusablePhone) { const activation = state?.freeReusablePhoneActivation ?? latestState?.freeReusablePhoneActivation ?? null; const phoneNumber = String(activation?.phoneNumber || '').trim(); const activationId = String(activation?.activationId || '').trim(); const successfulUses = Number.isFinite(Number(activation?.successfulUses)) ? Number(activation.successfulUses) : null; const maxUses = Number.isFinite(Number(activation?.maxUses)) ? Number(activation.maxUses) : null; const countryLabel = normalizePhoneSmsCountryLabel( activation?.countryLabel || getHeroSmsCountryLabelById(activation?.countryId || '') || state?.heroSmsCountryLabel || latestState?.heroSmsCountryLabel || getHeroSmsCountryLabelById(state?.heroSmsCountryId || latestState?.heroSmsCountryId || ''), activation?.provider || getSelectedPhoneSmsProvider() ); const usesText = successfulUses !== null || maxUses !== null ? ` / ${successfulUses ?? 0}/${maxUses ?? 3}` : ''; if (displayFreeReusablePhone) { displayFreeReusablePhone.textContent = phoneNumber ? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${usesText}` : '未保存'; } if (displayFreeReusablePhoneCountry) { displayFreeReusablePhoneCountry.textContent = phoneNumber ? `地区:${countryLabel || '未保存'}` : '地区:未保存'; } if (inputFreeReusablePhone) { inputFreeReusablePhone.value = phoneNumber; } } syncPhoneRuntimeCountdown(state); renderPhonePreferredActivationOptions(state); } function applyFreeReusablePhoneMutationResult(refreshedState = {}, mutationResult = {}, options = {}) { const { clear = false } = options; const explicitActivation = clear ? null : ( mutationResult?.freeReusablePhoneActivation || mutationResult?.state?.freeReusablePhoneActivation || null ); const statePatch = { ...(refreshedState || {}), ...(mutationResult?.state || {}), ...(clear || explicitActivation ? { freeReusablePhoneActivation: explicitActivation } : {}), }; syncLatestState(statePatch); updateHeroSmsRuntimeDisplay(latestState || statePatch); } async function refreshFreeReusablePhoneStateFallback(mutationResult = {}, options = {}) { if (mutationResult?.state || mutationResult?.freeReusablePhoneActivation || options.clear) { applyFreeReusablePhoneMutationResult(latestState, mutationResult, options); return; } try { const refreshedState = await chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }); applyFreeReusablePhoneMutationResult(refreshedState || latestState, mutationResult, options); } catch (error) { console.warn('Failed to refresh free reusable phone state:', error); applyFreeReusablePhoneMutationResult(latestState, mutationResult, options); } } async function loadHeroSmsCountries() { const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback; if (!countrySelect) { return; } const provider = getSelectedPhoneSmsProvider(); const previousSelectionOrder = [...heroSmsCountrySelectionOrder]; const previousSelectedIds = previousSelectionOrder.length ? previousSelectionOrder : Array.from(countrySelect.options) .filter((option) => option.selected) .map((option) => normalizePhoneSmsCountryId(option.value, provider)) .filter(Boolean); const applyOptions = (optionItems = [], selectEl) => { if (!selectEl) { return; } selectEl.innerHTML = ''; optionItems.forEach((entry) => { const option = document.createElement('option'); option.value = String(entry.id); option.textContent = entry.label; selectEl.appendChild(option); }); }; if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch('https://5sim.net/v1/guest/countries', { signal: controller.signal, cache: 'no-store', headers: { Accept: 'application/json' }, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const payload = await response.json(); const countriesPayload = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {}; const optionItems = Object.entries(countriesPayload) .map(([slug, entry]) => { const id = normalizeFiveSimCountryId(slug, ''); const label = formatFiveSimCountryDisplayLabel(id, entry?.text_en || slug, slug); return { id, label, searchText: [ slug, label, entry?.text_en, entry?.text_ru, Object.keys(entry?.iso || {}).join(' '), Object.keys(entry?.prefix || {}).join(' '), ].filter(Boolean).join(' '), }; }) .filter((entry) => entry.id) .sort((left, right) => String(left.label || '').localeCompare(String(right.label || ''))); if (!optionItems.length) { throw new Error('empty country list'); } heroSmsCountrySearchTextById.clear(); optionItems.forEach((entry) => heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText)); applyOptions(optionItems, selectHeroSmsCountry); applyOptions(optionItems, selectHeroSmsCountryFallback); } catch (error) { console.warn('Failed to load 5sim countries:', error); const fallbackItems = FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => ({ id: item.id, label: formatFiveSimCountryDisplayLabel(item.id, item.eng), searchText: item.searchText, })); applyOptions(fallbackItems, selectHeroSmsCountry); applyOptions(fallbackItems, selectHeroSmsCountryFallback); heroSmsCountrySearchTextById.clear(); fallbackItems.forEach((entry) => heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText)); if (typeof showToast === 'function') { showToast(`5sim 国家列表加载失败:${normalizeHeroSmsFetchErrorMessage(error)}(已切换为内置国家列表)`, 'warn', 2800); } } } else try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch('https://hero-sms.com/stubs/handler_api.php?action=getCountries', { signal: controller.signal, cache: 'no-store', }); clearTimeout(timeoutId); const payload = await response.json(); const countries = Array.isArray(payload?.value) ? payload.value : (Array.isArray(payload) ? payload : []); if (!countries.length) { throw new Error('empty country list'); } const optionItems = countries .filter((item) => Number(item?.id) > 0 && (String(item?.eng || '').trim() || String(item?.chn || '').trim())) .sort((left, right) => String(left.eng || '').localeCompare(String(right.eng || ''))) .map((item) => { const id = normalizeHeroSmsCountryId(item.id); const label = buildHeroSmsCountryDisplayLabel(item); return { id, label: String(label || '').trim() || `Country #${id}`, searchText: buildHeroSmsCountrySearchText(item, label, String(id)), }; }); if (!optionItems.length) { throw new Error('empty country list'); } heroSmsCountrySearchTextById.clear(); optionItems.forEach((entry) => { heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText); }); applyOptions(optionItems, selectHeroSmsCountry); applyOptions(optionItems, selectHeroSmsCountryFallback); } catch (error) { console.warn('Failed to load HeroSMS countries:', error); const fallbackItems = HERO_SMS_FALLBACK_COUNTRY_ITEMS .map((item) => { const id = normalizeHeroSmsCountryId(item.id); const label = buildHeroSmsCountryDisplayLabel(item); return { id, label: String(label || '').trim() || `Country #${id}`, searchText: buildHeroSmsCountrySearchText(item, label, String(id)), }; }) .filter((item) => item.id > 0); if (!fallbackItems.some((item) => item.id === DEFAULT_HERO_SMS_COUNTRY_ID)) { fallbackItems.unshift({ id: DEFAULT_HERO_SMS_COUNTRY_ID, label: DEFAULT_HERO_SMS_COUNTRY_LABEL, searchText: `${DEFAULT_HERO_SMS_COUNTRY_LABEL} ${DEFAULT_HERO_SMS_COUNTRY_ID}`, }); } applyOptions(fallbackItems, selectHeroSmsCountry); applyOptions(fallbackItems, selectHeroSmsCountryFallback); heroSmsCountrySearchTextById.clear(); fallbackItems.forEach((entry) => { heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText); }); if (typeof showToast === 'function') { showToast(`国家列表加载失败:${normalizeHeroSmsFetchErrorMessage(error)}(已切换为内置国家列表)`, 'warn', 2800); } } const availableIds = new Set(Array.from(countrySelect.options).map((option) => String(option.value))); const normalizedSelectedIds = previousSelectedIds .map((id) => String(id)) .filter((id) => availableIds.has(id)) .map((id) => normalizePhoneSmsCountryId(id, provider)); heroSmsCountrySelectionOrder = normalizedSelectedIds; const selectedSet = new Set(normalizedSelectedIds.map((id) => String(id))); Array.from(countrySelect.options).forEach((option) => { option.selected = selectedSet.has(String(option.value)); }); syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); updateHeroSmsPlatformDisplay(); } function getFiveSimCountryLabelByCode(code = '') { const normalizedCode = normalizeFiveSimCountryCode(code, ''); if (!normalizedCode || !selectFiveSimCountry) { return ''; } const matched = Array.from(selectFiveSimCountry.options || []) .find((option) => String(option.value || '').trim() === normalizedCode); return normalizeFiveSimCountryLabel(matched?.textContent || '', normalizedCode); } function renderFiveSimCountryFallbackOrder(countries = []) { if (!displayFiveSimCountryFallbackOrder) { return; } const normalized = normalizeFiveSimCountryFallbackList(countries); displayFiveSimCountryFallbackOrder.textContent = ''; if (!normalized.length) { displayFiveSimCountryFallbackOrder.textContent = '未设置'; return; } normalized.forEach((country, index) => { const chip = document.createElement('span'); chip.className = 'country-order-chip'; const label = document.createElement('span'); label.className = 'country-order-chip-label'; label.textContent = `${index + 1}. ${country.label}`; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; removeBtn.title = `移除 ${country.label}`; removeBtn.setAttribute('aria-label', `移除 ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); removeFiveSimCountryFromOrder(country.code || country.id); }); chip.appendChild(label); chip.appendChild(removeBtn); displayFiveSimCountryFallbackOrder.appendChild(chip); if (index < normalized.length - 1) { const separator = document.createElement('span'); separator.className = 'country-order-separator'; separator.textContent = '→'; displayFiveSimCountryFallbackOrder.appendChild(separator); } }); } function setFiveSimCountryMenuOpen(open) { const nextOpen = Boolean(open); if (btnFiveSimCountryMenu) { btnFiveSimCountryMenu.setAttribute('aria-expanded', String(nextOpen)); } if (fiveSimCountryMenu) { fiveSimCountryMenu.hidden = !nextOpen; if (nextOpen) { const searchInput = fiveSimCountryMenu.querySelector('.hero-sms-country-menu-search-input'); if (searchInput) { fiveSimCountryMenuSearchKeyword = ''; searchInput.value = ''; applyFiveSimCountryMenuFilter(''); setTimeout(() => { searchInput.focus(); searchInput.select(); }, 0); } } } } function applyFiveSimCountryMenuFilter(keyword = '') { if (!fiveSimCountryMenu) { return; } const normalizedKeyword = String(keyword || '').trim().toLowerCase(); const items = Array.from(fiveSimCountryMenu.querySelectorAll('.hero-sms-country-menu-item')); let visibleCount = 0; items.forEach((item) => { const haystack = String(item.dataset.searchText || '').toLowerCase(); const visible = !normalizedKeyword || haystack.includes(normalizedKeyword); item.hidden = !visible; if (visible) { visibleCount += 1; } }); let empty = fiveSimCountryMenu.querySelector('.hero-sms-country-menu-empty'); if (visibleCount === 0) { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '没有匹配国家'; fiveSimCountryMenu.appendChild(empty); } } else if (empty) { empty.remove(); } } function updateFiveSimCountryMenuSummary(selectedCountries = []) { if (!btnFiveSimCountryMenu) { return; } const normalized = normalizeFiveSimCountryFallbackList(selectedCountries); if (!normalized.length) { btnFiveSimCountryMenu.textContent = `未选择 (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; return; } const labels = normalized.map((country) => country.label); btnFiveSimCountryMenu.textContent = `${labels.join(' / ')} (${normalized.length}/${HERO_SMS_COUNTRY_SELECTION_MAX})`; } function renderFiveSimCountryChoiceButtons() { if (!fiveSimCountryMenu || !selectFiveSimCountry) { return; } const options = Array.from(selectFiveSimCountry.options || []); const selectedOrder = [...fiveSimCountrySelectionOrder]; const selectedSet = new Set(selectedOrder); fiveSimCountryMenu.innerHTML = ''; const searchWrap = document.createElement('div'); searchWrap.className = 'hero-sms-country-menu-search'; const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; searchInput.placeholder = '搜索国家(中/英/代码)'; searchInput.value = fiveSimCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { fiveSimCountryMenuSearchKeyword = String(searchInput.value || '').trim(); applyFiveSimCountryMenuFilter(fiveSimCountryMenuSearchKeyword); }); searchWrap.appendChild(searchInput); fiveSimCountryMenu.appendChild(searchWrap); if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '暂无国家选项'; fiveSimCountryMenu.appendChild(empty); updateFiveSimCountryMenuSummary([]); return; } options.forEach((option) => { const countryId = normalizeFiveSimCountryId(option.value, ''); if (!countryId) { return; } const item = document.createElement('button'); item.type = 'button'; item.className = 'hero-sms-country-menu-item'; item.dataset.countryId = countryId; item.dataset.searchText = fiveSimCountrySearchTextByCode.get(countryId) || `${countryId} ${option.textContent || ''}`; const selected = selectedSet.has(countryId); item.setAttribute('aria-pressed', selected ? 'true' : 'false'); item.textContent = option.textContent || countryId; item.addEventListener('click', (event) => { event.preventDefault(); toggleFiveSimCountryInOrder(countryId); }); fiveSimCountryMenu.appendChild(item); }); applyFiveSimCountryMenuFilter(fiveSimCountryMenuSearchKeyword); } function syncFiveSimCountrySelectionOrderFromSelect(options = {}) { const selectionLimit = Math.max(1, Math.floor(Number(options.maxSelection) || HERO_SMS_COUNTRY_SELECTION_MAX)); const enforceMax = options.enforceMax !== false; const ensureDefault = options.ensureDefault !== false; const showLimitToast = Boolean(options.showLimitToast); const countrySelect = selectFiveSimCountry; if (!countrySelect) { fiveSimCountrySelectionOrder = []; renderFiveSimCountryFallbackOrder([]); updateFiveSimCountryMenuSummary([]); return []; } const selectedCodes = Array.from(countrySelect.options || []) .filter((option) => option.selected) .map((option) => normalizeFiveSimCountryCode(option.value, '')) .filter(Boolean); const selectedSet = new Set(selectedCodes); let nextOrder = fiveSimCountrySelectionOrder.filter((code) => selectedSet.has(code)); selectedCodes.forEach((code) => { if (!nextOrder.includes(code)) { nextOrder.push(code); } }); if (ensureDefault && !nextOrder.length) { nextOrder = [normalizeFiveSimCountryCode(countrySelect.value || DEFAULT_FIVE_SIM_COUNTRY_ID)]; } if (enforceMax && nextOrder.length > selectionLimit) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { showToast(`5sim 国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); } } const nextOrderSet = new Set(nextOrder); Array.from(countrySelect.options || []).forEach((option) => { option.selected = nextOrderSet.has(normalizeFiveSimCountryCode(option.value, '')); }); fiveSimCountrySelectionOrder = [...nextOrder]; const selectedCountries = fiveSimCountrySelectionOrder.map((code) => ({ id: code, code, label: getFiveSimCountryOptionLabel(code), })); renderFiveSimCountryFallbackOrder(selectedCountries); updateFiveSimCountryMenuSummary(selectedCountries); renderFiveSimCountryChoiceButtons(); return selectedCountries; } function applyFiveSimCountrySelection(countries = [], options = {}) { const normalized = normalizeFiveSimCountryFallbackList(countries).slice(0, HERO_SMS_COUNTRY_SELECTION_MAX); const selectedCodes = normalized .map((entry) => normalizeFiveSimCountryCode(entry.code || entry.id, '')) .filter(Boolean); fiveSimCountrySelectionOrder = [...selectedCodes]; if (selectFiveSimCountry) { const selectedSet = new Set(selectedCodes); Array.from(selectFiveSimCountry.options || []).forEach((option) => { option.selected = selectedSet.has(normalizeFiveSimCountryCode(option.value, '')); }); } return syncFiveSimCountrySelectionOrderFromSelect({ ensureDefault: options.ensureDefault !== false, enforceMax: true, showLimitToast: false, }); } function toggleFiveSimCountryInOrder(code = '') { const normalizedCode = normalizeFiveSimCountryCode(code, ''); if (!normalizedCode || !selectFiveSimCountry) { return []; } const option = Array.from(selectFiveSimCountry.options || []) .find((entry) => normalizeFiveSimCountryCode(entry.value, '') === normalizedCode); if (option) { option.selected = !option.selected; } const nextOrder = syncFiveSimCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: true, }); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); return nextOrder; } function removeFiveSimCountryFromOrder(code = '') { const normalizedCode = normalizeFiveSimCountryCode(code, ''); fiveSimCountrySelectionOrder = fiveSimCountrySelectionOrder.filter((entry) => entry !== normalizedCode); if (selectFiveSimCountry) { Array.from(selectFiveSimCountry.options || []).forEach((option) => { if (normalizeFiveSimCountryCode(option.value, '') === normalizedCode) { option.selected = false; } }); } const nextOrder = syncFiveSimCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); return nextOrder; } async function loadFiveSimCountries() { if (!selectFiveSimCountry) { return; } const previousOrder = [...fiveSimCountrySelectionOrder]; const applyOptions = (items = []) => { selectFiveSimCountry.innerHTML = ''; fiveSimCountrySearchTextByCode.clear(); items.forEach((entry) => { const code = normalizeFiveSimCountryCode(entry.code || entry.id, ''); if (!code) { return; } const option = document.createElement('option'); option.value = code; option.textContent = entry.label || getFiveSimCountryOptionLabel(code) || code; selectFiveSimCountry.appendChild(option); fiveSimCountrySearchTextByCode.set(code, entry.searchText || `${option.textContent} ${code}`); }); }; const fallbackItems = FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => ({ id: item.id, code: item.id, label: formatFiveSimCountryDisplayLabel(item.id, item.eng, item.id), searchText: item.searchText, })); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch('https://5sim.net/v1/guest/countries', { signal: controller.signal, cache: 'no-store', headers: { Accept: 'application/json' }, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const payload = await response.json(); const items = parseFiveSimCountriesPayload(payload); applyOptions(items.length ? items : fallbackItems); } catch (error) { console.warn('Failed to load 5sim countries:', error); applyOptions(fallbackItems); } applyFiveSimCountrySelection(previousOrder.length ? previousOrder : latestState?.fiveSimCountryOrder || [], { ensureDefault: false, }); } function renderNexSmsCountryFallbackOrder(countries = []) { if (!displayNexSmsCountryFallbackOrder) { return; } const normalized = normalizeNexSmsCountryFallbackList(countries); displayNexSmsCountryFallbackOrder.textContent = ''; if (!normalized.length) { displayNexSmsCountryFallbackOrder.textContent = '未设置'; return; } normalized.forEach((country, index) => { const chip = document.createElement('span'); chip.className = 'country-order-chip'; const label = document.createElement('span'); label.className = 'country-order-chip-label'; label.textContent = `${index + 1}. ${country.label}`; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'country-order-remove'; removeBtn.title = `移除 ${country.label}`; removeBtn.setAttribute('aria-label', `移除 ${country.label}`); removeBtn.textContent = '×'; removeBtn.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); removeNexSmsCountryFromOrder(country.id); }); chip.appendChild(label); chip.appendChild(removeBtn); displayNexSmsCountryFallbackOrder.appendChild(chip); if (index < normalized.length - 1) { const separator = document.createElement('span'); separator.className = 'country-order-separator'; separator.textContent = '→'; displayNexSmsCountryFallbackOrder.appendChild(separator); } }); } function setNexSmsCountryMenuOpen(open) { const nextOpen = Boolean(open); if (btnNexSmsCountryMenu) { btnNexSmsCountryMenu.setAttribute('aria-expanded', String(nextOpen)); } if (nexSmsCountryMenu) { nexSmsCountryMenu.hidden = !nextOpen; if (nextOpen) { const searchInput = nexSmsCountryMenu.querySelector('.hero-sms-country-menu-search-input'); if (searchInput) { nexSmsCountryMenuSearchKeyword = ''; searchInput.value = ''; applyNexSmsCountryMenuFilter(''); setTimeout(() => { searchInput.focus(); searchInput.select(); }, 0); } } } } function applyNexSmsCountryMenuFilter(keyword = '') { if (!nexSmsCountryMenu) { return; } const normalizedKeyword = String(keyword || '').trim().toLowerCase(); const items = Array.from(nexSmsCountryMenu.querySelectorAll('.hero-sms-country-menu-item')); let visibleCount = 0; items.forEach((item) => { const haystack = String(item.dataset.searchText || '').toLowerCase(); const visible = !normalizedKeyword || haystack.includes(normalizedKeyword); item.hidden = !visible; if (visible) { visibleCount += 1; } }); let empty = nexSmsCountryMenu.querySelector('.hero-sms-country-menu-empty'); if (visibleCount === 0) { if (!empty) { empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '没有匹配国家'; nexSmsCountryMenu.appendChild(empty); } } else if (empty) { empty.remove(); } } function updateNexSmsCountryMenuSummary(selectedCountries = []) { if (!btnNexSmsCountryMenu) { return; } const normalized = normalizeNexSmsCountryFallbackList(selectedCountries); btnNexSmsCountryMenu.textContent = normalized.length ? `${normalized.map((country) => country.label).join(' / ')} (${normalized.length}/${HERO_SMS_COUNTRY_SELECTION_MAX})` : `未选择 (0/${HERO_SMS_COUNTRY_SELECTION_MAX})`; } function renderNexSmsCountryChoiceButtons() { if (!nexSmsCountryMenu || !selectNexSmsCountry) { return; } const options = Array.from(selectNexSmsCountry.options || []); const selectedOrder = [...nexSmsCountrySelectionOrder]; const selectedSet = new Set(selectedOrder); nexSmsCountryMenu.innerHTML = ''; const searchWrap = document.createElement('div'); searchWrap.className = 'hero-sms-country-menu-search'; const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.className = 'data-input mono hero-sms-country-menu-search-input'; searchInput.placeholder = '搜索国家 ID'; searchInput.value = nexSmsCountryMenuSearchKeyword; searchInput.addEventListener('input', () => { nexSmsCountryMenuSearchKeyword = String(searchInput.value || '').trim(); applyNexSmsCountryMenuFilter(nexSmsCountryMenuSearchKeyword); }); searchWrap.appendChild(searchInput); nexSmsCountryMenu.appendChild(searchWrap); if (!options.length) { const empty = document.createElement('span'); empty.className = 'data-value hero-sms-country-menu-empty'; empty.textContent = '暂无国家选项'; nexSmsCountryMenu.appendChild(empty); updateNexSmsCountryMenuSummary([]); return; } options.forEach((option) => { const countryId = normalizeNexSmsCountryIdValue(option.value, -1); if (countryId < 0) { return; } const item = document.createElement('button'); item.type = 'button'; item.className = 'hero-sms-country-menu-item'; item.dataset.countryId = String(countryId); item.dataset.searchText = nexSmsCountrySearchTextById.get(countryId) || `${countryId} ${option.textContent || ''}`; const selected = selectedSet.has(countryId); item.setAttribute('aria-pressed', selected ? 'true' : 'false'); item.textContent = option.textContent || `Country #${countryId}`; item.addEventListener('click', (event) => { event.preventDefault(); toggleNexSmsCountryInOrder(countryId); }); nexSmsCountryMenu.appendChild(item); }); applyNexSmsCountryMenuFilter(nexSmsCountryMenuSearchKeyword); } function syncNexSmsCountrySelectionOrderFromSelect(options = {}) { const selectionLimit = Math.max(1, Math.floor(Number(options.maxSelection) || HERO_SMS_COUNTRY_SELECTION_MAX)); const enforceMax = options.enforceMax !== false; const ensureDefault = options.ensureDefault !== false; const showLimitToast = Boolean(options.showLimitToast); const countrySelect = selectNexSmsCountry; if (!countrySelect) { nexSmsCountrySelectionOrder = []; renderNexSmsCountryFallbackOrder([]); updateNexSmsCountryMenuSummary([]); return []; } const selectedIds = Array.from(countrySelect.options || []) .filter((option) => option.selected) .map((option) => normalizeNexSmsCountryIdValue(option.value, -1)) .filter((id) => id >= 0); const selectedSet = new Set(selectedIds); let nextOrder = nexSmsCountrySelectionOrder.filter((id) => selectedSet.has(id)); selectedIds.forEach((id) => { if (!nextOrder.includes(id)) { nextOrder.push(id); } }); if (ensureDefault && !nextOrder.length) { nextOrder = [normalizeNexSmsCountryIdValue(DEFAULT_NEX_SMS_COUNTRY_ORDER[0], 1)]; } if (enforceMax && nextOrder.length > selectionLimit) { const droppedCount = nextOrder.length - selectionLimit; nextOrder = nextOrder.slice(0, selectionLimit); if (showLimitToast && droppedCount > 0 && typeof showToast === 'function') { showToast(`NexSMS 国家最多选择 ${selectionLimit} 个,已保留前 ${selectionLimit} 个。`, 'warn', 2200); } } const nextOrderSet = new Set(nextOrder); Array.from(countrySelect.options || []).forEach((option) => { option.selected = nextOrderSet.has(normalizeNexSmsCountryIdValue(option.value, -1)); }); nexSmsCountrySelectionOrder = [...nextOrder]; const selectedCountries = nexSmsCountrySelectionOrder.map((id) => ({ id, label: getNexSmsCountryLabelById(id), })); renderNexSmsCountryFallbackOrder(selectedCountries); updateNexSmsCountryMenuSummary(selectedCountries); renderNexSmsCountryChoiceButtons(); return selectedCountries; } function applyNexSmsCountrySelection(countries = [], options = {}) { const normalized = normalizeNexSmsCountryFallbackList(countries).slice(0, HERO_SMS_COUNTRY_SELECTION_MAX); const selectedIds = normalized .map((entry) => normalizeNexSmsCountryIdValue(entry.id, -1)) .filter((id) => id >= 0); nexSmsCountrySelectionOrder = [...selectedIds]; if (selectNexSmsCountry) { const selectedSet = new Set(selectedIds); Array.from(selectNexSmsCountry.options || []).forEach((option) => { option.selected = selectedSet.has(normalizeNexSmsCountryIdValue(option.value, -1)); }); } return syncNexSmsCountrySelectionOrderFromSelect({ ensureDefault: options.ensureDefault !== false, enforceMax: true, showLimitToast: false, }); } function toggleNexSmsCountryInOrder(id = -1) { const countryId = normalizeNexSmsCountryIdValue(id, -1); if (countryId < 0 || !selectNexSmsCountry) { return []; } const option = Array.from(selectNexSmsCountry.options || []) .find((entry) => normalizeNexSmsCountryIdValue(entry.value, -1) === countryId); if (option) { option.selected = !option.selected; } const nextOrder = syncNexSmsCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: true, }); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); return nextOrder; } function removeNexSmsCountryFromOrder(id = -1) { const countryId = normalizeNexSmsCountryIdValue(id, -1); nexSmsCountrySelectionOrder = nexSmsCountrySelectionOrder.filter((entry) => entry !== countryId); if (selectNexSmsCountry) { Array.from(selectNexSmsCountry.options || []).forEach((option) => { if (normalizeNexSmsCountryIdValue(option.value, -1) === countryId) { option.selected = false; } }); } const nextOrder = syncNexSmsCountrySelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); markSettingsDirty(true); saveSettings({ silent: true }).catch(() => { }); return nextOrder; } async function loadNexSmsCountries() { if (!selectNexSmsCountry) { return; } const previousOrder = [...nexSmsCountrySelectionOrder]; selectNexSmsCountry.innerHTML = ''; nexSmsCountrySearchTextById.clear(); NEX_SMS_FALLBACK_COUNTRY_ITEMS.forEach((entry) => { const id = normalizeNexSmsCountryIdValue(entry.id, -1); if (id < 0) { return; } const option = document.createElement('option'); option.value = String(id); option.textContent = entry.label || `Country #${id}`; selectNexSmsCountry.appendChild(option); nexSmsCountrySearchTextById.set(id, entry.searchText || `${option.textContent} ${id}`); }); applyNexSmsCountrySelection(previousOrder.length ? previousOrder : latestState?.nexSmsCountryOrder || [], { ensureDefault: false, }); } async function buildNexSmsPricePreviewLines(options = {}) { const countryIds = ( typeof getSelectedNexSmsCountries === 'function' ? getSelectedNexSmsCountries() : normalizeNexSmsCountryFallbackList( Array.isArray(latestState?.nexSmsCountryOrder) ? latestState.nexSmsCountryOrder : [] ) ) .map((country) => normalizeNexSmsCountryIdValue(country.id, -1)) .filter((id) => id >= 0); const serviceCode = normalizeNexSmsServiceCodeValue( inputNexSmsServiceCode?.value || latestState?.nexSmsServiceCode || DEFAULT_NEX_SMS_SERVICE_CODE ); const maxPriceText = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || ''); const maxPrice = maxPriceText ? Number(maxPriceText) : null; const apiKey = String(inputNexSmsApiKey?.value || '').trim(); const providerLabel = String(options?.providerLabel || 'NexSMS').trim(); if (!apiKey) { return [`${providerLabel}: 请先填写 NexSMS API Key`]; } if (!countryIds.length) { return [`${providerLabel}: 请先选择至少 1 个国家`]; } const previews = []; for (const countryId of countryIds) { const countryLabel = getNexSmsCountryLabelById(countryId) || `Country #${countryId}`; try { const url = new URL('/api/getCountryByService', 'https://api.nexsms.net'); url.searchParams.set('apiKey', apiKey); url.searchParams.set('serviceCode', serviceCode); url.searchParams.set('countryId', String(countryId)); const response = await fetch(url.toString(), { cache: 'no-store' }); const payload = await response.json().catch(() => ({})); if (!response.ok || Number(payload?.code) !== 0) { previews.push(`${countryLabel}: ${describeNexSmsPreviewPayload(payload) || `HTTP ${response.status}`}`); continue; } const data = payload?.data || {}; const tierEntries = data?.priceMap && typeof data.priceMap === 'object' ? Object.entries(data.priceMap) .map(([price, count]) => { const numericPrice = Number(price); const numericCount = Number(count); if (!Number.isFinite(numericPrice) || numericPrice <= 0) { return null; } return { price: Math.round(numericPrice * 10000) / 10000, count: Number.isFinite(numericCount) ? Math.max(0, Math.floor(numericCount)) : null, }; }) .filter(Boolean) : []; const availablePrices = tierEntries .filter((entry) => entry.count === null || entry.count > 0) .map((entry) => entry.price); const uniqueSorted = Array.from(new Set(availablePrices)).sort((left, right) => left - right); const fallbackMin = Number(data?.minPrice); const lowest = uniqueSorted.length ? uniqueSorted[0] : (Number.isFinite(fallbackMin) && fallbackMin > 0 ? Math.round(fallbackMin * 10000) / 10000 : null); if (!Number.isFinite(lowest)) { previews.push(`${countryLabel}: 暂无可用号源`); continue; } const tierText = formatPriceTiersForPreview(tierEntries, { maxPrice }); if (Number.isFinite(maxPrice) && maxPrice > 0 && lowest > maxPrice) { previews.push(`${countryLabel}: 最低 ${lowest}(高于上限 ${maxPrice})${tierText ? `;档位:${tierText}` : ''}`); } else { previews.push(`${countryLabel}: 最低 ${lowest}${tierText ? `;档位:${tierText}` : ''}`); } } catch (error) { previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (!previews.length) { previews.push('未获取'); } return [`${providerLabel}:`, ...previews]; } async function previewFiveSimPriceTiers() { if (!displayHeroSmsPriceTiers) { return; } const countryCodes = ( typeof getSelectedFiveSimCountries === 'function' ? getSelectedFiveSimCountries() : normalizeFiveSimCountryFallbackList( Array.isArray(latestState?.fiveSimCountryOrder) ? latestState.fiveSimCountryOrder : [] ) ) .map((country) => normalizeFiveSimCountryCode(country.code || country.id, '')) .filter(Boolean); const product = normalizeFiveSimProductValue( inputFiveSimProduct?.value || latestState?.fiveSimProduct || DEFAULT_FIVE_SIM_PRODUCT ); const maxPriceText = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || ''); const maxPrice = maxPriceText ? Number(maxPriceText) : null; displayHeroSmsPriceTiers.textContent = '查询中...'; if (rowHeroSmsPriceTiers) { rowHeroSmsPriceTiers.style.display = ''; } if (!countryCodes.length) { displayHeroSmsPriceTiers.textContent = '请先选择至少 1 个国家,再查询价格'; return; } const collectPriceEntries = (payload, entries = []) => { if (Array.isArray(payload)) { payload.forEach((entry) => collectPriceEntries(entry, entries)); return entries; } if (!payload || typeof payload !== 'object') { return entries; } const cost = Number(payload.cost); const count = Number(payload.count); if (Number.isFinite(cost) && cost > 0) { entries.push({ cost: Math.round(cost * 10000) / 10000, count: Number.isFinite(count) ? count : 0, }); } Object.entries(payload).forEach(([key, value]) => { const keyedPrice = Number(key); if (!Number.isFinite(keyedPrice) || keyedPrice <= 0) { return; } if (value && typeof value === 'object') { const keyedCount = Number(value.count); entries.push({ cost: Math.round(keyedPrice * 10000) / 10000, count: Number.isFinite(keyedCount) ? keyedCount : 0, }); return; } const numericCount = Number(value); entries.push({ cost: Math.round(keyedPrice * 10000) / 10000, count: Number.isFinite(numericCount) ? numericCount : 0, }); }); Object.values(payload).forEach((entry) => collectPriceEntries(entry, entries)); return entries; }; const previews = []; for (const countryCode of countryCodes) { const countryLabel = getFiveSimCountryLabelByCode(countryCode) || countryCode; try { const url = new URL('https://5sim.net/v1/guest/prices'); url.searchParams.set('country', countryCode); url.searchParams.set('product', product); const response = await fetch(url.toString(), { cache: 'no-store' }); const payload = await response.json().catch(() => ({})); if (!response.ok) { previews.push(`${countryLabel}: HTTP ${response.status}`); continue; } const productRoot = payload?.[product] || payload; const countryRoot = productRoot?.[countryCode] || productRoot; const tierEntries = collectPriceEntries(countryRoot, []) .filter((entry) => Number.isFinite(entry.cost) && entry.cost > 0) .map((entry) => ({ price: entry.cost, count: Number.isFinite(entry.count) ? Math.max(0, Math.floor(entry.count)) : null, })); const prices = tierEntries .filter((entry) => entry.count === null || entry.count > 0) .map((entry) => entry.price); const uniqueSorted = Array.from(new Set(prices)).sort((left, right) => left - right); if (!uniqueSorted.length) { previews.push(`${countryLabel}: 暂无可用号源`); continue; } const lowest = uniqueSorted[0]; const tierText = formatPriceTiersForPreview(tierEntries, { maxPrice }); if (Number.isFinite(maxPrice) && maxPrice > 0 && lowest > maxPrice) { previews.push(`${countryLabel}: 最低 ${lowest}(高于上限 ${maxPrice})${tierText ? `;档位:${tierText}` : ''}`); } else { previews.push(`${countryLabel}: 最低 ${lowest}${tierText ? `;档位:${tierText}` : ''}`); } } catch (error) { previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); } } displayHeroSmsPriceTiers.textContent = previews.join('\n') || '未获取'; } async function buildFiveSimPricePreviewLines(options = {}) { const countryCodes = ( typeof getSelectedFiveSimCountries === 'function' ? getSelectedFiveSimCountries() : normalizeFiveSimCountryFallbackList( Array.isArray(latestState?.fiveSimCountryOrder) ? latestState.fiveSimCountryOrder : [] ) ) .map((country) => normalizeFiveSimCountryCode(country.code || country.id, '')) .filter(Boolean); const product = normalizeFiveSimProductValue( inputFiveSimProduct?.value || latestState?.fiveSimProduct || DEFAULT_FIVE_SIM_PRODUCT ); const maxPriceText = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || ''); const maxPrice = maxPriceText ? Number(maxPriceText) : null; const providerLabel = String(options?.providerLabel || '5sim').trim(); if (!countryCodes.length) { return [`${providerLabel}: 请先选择至少 1 个国家`]; } const collectPriceEntries = (payload, entries = []) => { if (Array.isArray(payload)) { payload.forEach((entry) => collectPriceEntries(entry, entries)); return entries; } if (!payload || typeof payload !== 'object') { return entries; } const cost = Number(payload.cost); const count = Number(payload.count); if (Number.isFinite(cost) && cost > 0) { entries.push({ cost: Math.round(cost * 10000) / 10000, count: Number.isFinite(count) ? count : 0, }); } Object.entries(payload).forEach(([key, value]) => { const keyedPrice = Number(key); if (!Number.isFinite(keyedPrice) || keyedPrice <= 0) { return; } if (value && typeof value === 'object') { const keyedCount = Number(value.count); entries.push({ cost: Math.round(keyedPrice * 10000) / 10000, count: Number.isFinite(keyedCount) ? keyedCount : 0, }); return; } const numericCount = Number(value); entries.push({ cost: Math.round(keyedPrice * 10000) / 10000, count: Number.isFinite(numericCount) ? numericCount : 0, }); }); Object.values(payload).forEach((entry) => collectPriceEntries(entry, entries)); return entries; }; const previews = []; for (const countryCode of countryCodes) { const countryLabel = getFiveSimCountryLabelByCode(countryCode) || countryCode; try { const url = new URL('https://5sim.net/v1/guest/prices'); url.searchParams.set('country', countryCode); url.searchParams.set('product', product); const response = await fetch(url.toString(), { cache: 'no-store' }); const payload = await response.json().catch(() => ({})); if (!response.ok) { previews.push(`${countryLabel}: HTTP ${response.status}`); continue; } const productRoot = payload?.[product] || payload; const countryRoot = productRoot?.[countryCode] || productRoot; const tierEntries = collectPriceEntries(countryRoot, []) .filter((entry) => Number.isFinite(entry.cost) && entry.cost > 0) .map((entry) => ({ price: entry.cost, count: Number.isFinite(entry.count) ? Math.max(0, Math.floor(entry.count)) : null, })); const prices = tierEntries .filter((entry) => entry.count === null || entry.count > 0) .map((entry) => entry.price); const uniqueSorted = Array.from(new Set(prices)).sort((left, right) => left - right); if (!uniqueSorted.length) { previews.push(`${countryLabel}: 暂无可用号源`); continue; } const lowest = uniqueSorted[0]; const tierText = formatPriceTiersForPreview(tierEntries, { maxPrice }); if (Number.isFinite(maxPrice) && maxPrice > 0 && lowest > maxPrice) { previews.push(`${countryLabel}: 最低 ${lowest}(高于上限 ${maxPrice})${tierText ? `;档位:${tierText}` : ''}`); } else { previews.push(`${countryLabel}: 最低 ${lowest}${tierText ? `;档位:${tierText}` : ''}`); } } catch (error) { previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (!previews.length) { previews.push('未获取'); } return [`${providerLabel}:`, ...previews]; } async function previewHeroSmsPriceTiers() { const normalizeProvider = typeof normalizePhoneSmsProviderValue === 'function' ? normalizePhoneSmsProviderValue : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); if (normalized === '5sim') return '5sim'; if (normalized === 'nexsms') return 'nexsms'; return 'hero-sms'; }); const fiveSimProviderValue = typeof PHONE_SMS_PROVIDER_FIVE_SIM !== 'undefined' ? PHONE_SMS_PROVIDER_FIVE_SIM : '5sim'; const nexSmsProviderValue = typeof PHONE_SMS_PROVIDER_NEXSMS !== 'undefined' ? PHONE_SMS_PROVIDER_NEXSMS : 'nexsms'; const heroProviderValue = typeof PHONE_SMS_PROVIDER_HERO !== 'undefined' ? PHONE_SMS_PROVIDER_HERO : 'hero-sms'; const defaultProviderValue = typeof DEFAULT_PHONE_SMS_PROVIDER !== 'undefined' ? DEFAULT_PHONE_SMS_PROVIDER : 'hero-sms'; const activeProvider = typeof getSelectedPhoneSmsProvider === 'function' ? getSelectedPhoneSmsProvider() : normalizeProvider(selectPhoneSmsProvider?.value || defaultProviderValue); displayHeroSmsPriceTiers.textContent = '查询中...'; if (rowHeroSmsPriceTiers) { rowHeroSmsPriceTiers.style.display = ''; } if (!displayHeroSmsPriceTiers) { return; } const selectedProviderOrder = ( typeof getSelectedPhoneSmsProviderOrder === 'function' ? getSelectedPhoneSmsProviderOrder() : normalizePhoneSmsProviderOrderValue(phoneSmsProviderOrderSelection, []) ); const providerOrder = selectedProviderOrder.length ? selectedProviderOrder : [normalizeProvider(activeProvider)]; const previews = []; for (const provider of providerOrder) { if (provider === fiveSimProviderValue) { const lines = await buildFiveSimPricePreviewLines({ providerLabel: '5sim' }); previews.push(...lines, ''); continue; } if (provider === nexSmsProviderValue) { const lines = await buildNexSmsPricePreviewLines({ providerLabel: 'NexSMS' }); previews.push(...lines, ''); continue; } if (provider !== heroProviderValue) { continue; } const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({ enforceMax: true, ensureDefault: false, showLimitToast: false, }); const candidates = selectedCountries .map((country) => ({ id: normalizeHeroSmsCountryId(country?.id, 0), label: normalizeHeroSmsCountryLabel(country?.label, ''), })) .filter((country) => country.id > 0); const maxPriceText = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || ''); const maxPrice = maxPriceText ? Number(maxPriceText) : null; const apiKey = String(inputHeroSmsApiKey?.value || '').trim(); const heroLines = ['HeroSMS:']; if (!apiKey) { heroLines.push('请先填写接码 API Key'); previews.push(...heroLines, ''); continue; } if (!candidates.length) { heroLines.push('请先选择至少 1 个国家'); previews.push(...heroLines, ''); continue; } for (const country of candidates) { const countryId = normalizeHeroSmsCountryId(country.id, 0); if (countryId <= 0) { continue; } const countryLabel = normalizeHeroSmsCountryLabel( country.label || getHeroSmsCountryLabelById(countryId), `Country #${countryId}` ); try { const parsedPayloads = []; const failedPriceActions = []; const fetchPriceAction = async (action) => { const url = new URL('https://hero-sms.com/stubs/handler_api.php'); url.searchParams.set('action', action); url.searchParams.set('service', 'dr'); url.searchParams.set('country', String(countryId)); if (action === 'getPricesExtended') { url.searchParams.set('freePrice', 'true'); } if (apiKey) { url.searchParams.set('api_key', apiKey); } const response = await fetch(url.toString(), { cache: 'no-store' }); const rawText = await response.text(); let payload = rawText; try { payload = rawText ? JSON.parse(rawText) : ''; } catch { payload = rawText; } if (!response.ok) { const reason = summarizeHeroSmsPreviewError(payload, response.status); failedPriceActions.push(`${action}: ${reason}`); return; } parsedPayloads.push(payload); }; const fetchTopCountriesPayload = async () => { const url = new URL('https://hero-sms.com/stubs/handler_api.php'); url.searchParams.set('action', 'getTopCountriesByService'); url.searchParams.set('service', 'dr'); url.searchParams.set('freePrice', 'true'); if (apiKey) { url.searchParams.set('api_key', apiKey); } const response = await fetch(url.toString(), { cache: 'no-store' }); const rawText = await response.text(); let payload = rawText; try { payload = rawText ? JSON.parse(rawText) : ''; } catch { payload = rawText; } if (!response.ok) { const reason = summarizeHeroSmsPreviewError(payload, response.status); failedPriceActions.push(`getTopCountriesByService: ${reason}`); return null; } return payload; }; const fetchVerificationPricesPayload = async () => { const url = new URL('https://hero-sms.com/stubs/handler_api.php'); url.searchParams.set('action', 'getPricesVerification'); url.searchParams.set('service', 'dr'); url.searchParams.set('country', String(countryId)); if (apiKey) { url.searchParams.set('api_key', apiKey); } const response = await fetch(url.toString(), { cache: 'no-store' }); const rawText = await response.text(); let payload = rawText; try { payload = rawText ? JSON.parse(rawText) : ''; } catch { payload = rawText; } if (!response.ok) { const reason = summarizeHeroSmsPreviewError(payload, response.status); failedPriceActions.push(`getPricesVerification: ${reason}`); return null; } return payload; }; await fetchPriceAction('getPricesExtended'); await fetchPriceAction('getPrices'); // Additional visibility probe: some regions expose richer tier maps on getPricesForVerification. await fetchPriceAction('getPricesForVerification'); const [topCountriesPayload, verificationPayload] = await Promise.all([ fetchTopCountriesPayload(), fetchVerificationPricesPayload(), ]); const priceEntries = parsedPayloads .flatMap((payload) => collectHeroSmsPriceEntriesForPreview(payload, [])) .concat( collectHeroSmsPriceEntriesFromTopCountriesPayload(topCountriesPayload, countryId, []), collectHeroSmsPriceEntriesFromVerificationPayload(verificationPayload, countryId, 'dr', []) ) .filter((entry) => Number.isFinite(Number(entry.cost)) && Number(entry.cost) > 0); const tierStockByPrice = new Map(); for (const entry of priceEntries) { const cost = Math.round(Number(entry.cost) * 10000) / 10000; if (!Number.isFinite(cost) || cost <= 0) { continue; } const stockCount = Number.isFinite(Number(entry.stockCount)) ? Math.max(0, Number(entry.stockCount)) : 0; const previous = tierStockByPrice.get(cost); tierStockByPrice.set(cost, Number.isFinite(previous) ? Math.max(previous, stockCount) : stockCount); } const allPrices = Array.from(tierStockByPrice.keys()).sort((left, right) => left - right); const inStockPrices = allPrices.filter((price) => Number(tierStockByPrice.get(price)) > 0); const tierEntries = allPrices.map((price) => ({ price, count: Math.max(0, Number(tierStockByPrice.get(price)) || 0), })); const withinLimitInStockPrices = Number.isFinite(maxPrice) && maxPrice > 0 ? inStockPrices.filter((price) => price <= maxPrice) : inStockPrices; const tierPreviewText = formatPriceTiersWithZeroStockForPreview(tierEntries, { maxPrice }); if (!inStockPrices.length) { if (allPrices.length) { const lowestKnown = formatHeroSmsPriceForPreview(allPrices[0]) || String(allPrices[0]); heroLines.push(`${countryLabel}: 全档位均无库存(最低标价 ${lowestKnown})${tierPreviewText ? `;档位:${tierPreviewText}` : ''}`); continue; } if (failedPriceActions.length) { heroLines.push(`${countryLabel}: ${failedPriceActions.join(' | ')}`); continue; } heroLines.push(`${countryLabel}: 无可用价格`); continue; } if (Number.isFinite(maxPrice) && maxPrice > 0 && !withinLimitInStockPrices.length) { const lowestInStockText = formatHeroSmsPriceForPreview(inStockPrices[0]) || String(inStockPrices[0]); heroLines.push(`${countryLabel}: 上限内无可用号源(上限 ${formatHeroSmsPriceForPreview(maxPrice) || maxPrice},最低可用 ${lowestInStockText})${tierPreviewText ? `;档位:${tierPreviewText}` : ''}`); } else { const lowestWithinLimit = withinLimitInStockPrices[0]; const lowestText = formatHeroSmsPriceForPreview(lowestWithinLimit) || String(lowestWithinLimit); heroLines.push(`${countryLabel}: 上限内最低 ${lowestText}${tierPreviewText ? `;档位:${tierPreviewText}` : ''}`); } } catch (error) { heroLines.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`); } } if (heroLines.length === 1) { heroLines.push('未获取'); } previews.push(...heroLines, ''); } while (previews.length && previews[previews.length - 1] === '') { previews.pop(); } displayHeroSmsPriceTiers.textContent = previews.join('\n') || '未获取'; } async function previewPhoneSmsBalance() { if (!displayPhoneSmsBalance) { return; } const provider = getSelectedPhoneSmsProvider(); const apiKey = String(inputHeroSmsApiKey?.value || '').trim(); if (!apiKey) { displayPhoneSmsBalance.textContent = '请先填写接码 API Key'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = ''; return; } displayPhoneSmsBalance.textContent = '余额查询中...'; if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = ''; try { const url = provider === PHONE_SMS_PROVIDER_FIVE_SIM ? new URL('https://5sim.net/v1/user/profile') : new URL('https://hero-sms.com/stubs/handler_api.php'); const requestOptions = {}; if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) { requestOptions.headers = { Accept: 'application/json', Authorization: `Bearer ${apiKey}`, }; } else { url.searchParams.set('action', 'getBalance'); url.searchParams.set('api_key', apiKey); } const response = await fetch(url.toString(), requestOptions); const rawText = await response.text(); let payload = rawText; try { payload = rawText ? JSON.parse(rawText) : ''; } catch { payload = rawText; } if (!response.ok) { displayPhoneSmsBalance.textContent = `余额查询失败:${summarizeHeroSmsPreviewError(payload, response.status)}`; return; } if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) { const balance = Number(payload?.balance); const frozen = Number(payload?.frozen_balance); displayPhoneSmsBalance.textContent = Number.isFinite(balance) ? `5sim 余额 ${formatHeroSmsPriceForPreview(balance) || balance}${Number.isFinite(frozen) ? `,冻结 ${formatHeroSmsPriceForPreview(frozen) || frozen}` : ''}` : `5sim 余额:${describeHeroSmsPreviewPayload(payload) || '未知'}`; } else { const text = describeHeroSmsPreviewPayload(payload).replace(/^ACCESS_BALANCE:/i, '').trim(); displayPhoneSmsBalance.textContent = `HeroSMS 余额 ${text || '未知'}`; } } catch (error) { displayPhoneSmsBalance.textContent = `余额查询失败:${normalizeHeroSmsFetchErrorMessage(error)}`; } } function getSelectedLocalCpaStep9Mode() { const activeButton = localCpaStep9ModeButtons.find((button) => button.classList.contains('is-active')); return normalizeLocalCpaStep9Mode(activeButton?.dataset.localCpaStep9Mode); } function setLocalCpaStep9Mode(mode) { const resolvedMode = normalizeLocalCpaStep9Mode(mode); localCpaStep9ModeButtons.forEach((button) => { const active = button.dataset.localCpaStep9Mode === resolvedMode; button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', String(active)); }); } function getSelectedMail2925Mode() { const activeButton = mail2925ModeButtons.find((button) => button.classList.contains('is-active')); return normalizeMail2925Mode(activeButton?.dataset.mail2925Mode); } function setMail2925Mode(mode) { const resolvedMode = normalizeMail2925Mode(mode); mail2925ModeButtons.forEach((button) => { const active = button.dataset.mail2925Mode === resolvedMode; button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', String(active)); }); } function getSelectedHotmailServiceMode() { const activeButton = hotmailServiceModeButtons.find((button) => button.classList.contains('is-active')); return normalizeHotmailServiceMode(activeButton?.dataset.hotmailServiceMode); } function setHotmailServiceMode(mode) { const resolvedMode = normalizeHotmailServiceMode(mode); hotmailServiceModeButtons.forEach((button) => { const active = button.dataset.hotmailServiceMode === resolvedMode; button.disabled = false; button.setAttribute('aria-disabled', 'false'); button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', String(active)); }); } function updateAccountRunHistorySettingsUI() { if (!rowAccountRunHistoryHelperBaseUrl) { return; } rowAccountRunHistoryHelperBaseUrl.style.display = 'none'; } function normalizeSignupMethod(value = '') { return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'; } function normalizePanelMode(value = '') { const normalized = String(value || '').trim().toLowerCase(); if (normalized === 'sub2api' || normalized === 'codex2api') { return normalized; } return 'cpa'; } let flowCapabilityRegistry = null; function getFlowCapabilityRegistry() { if (flowCapabilityRegistry) { return flowCapabilityRegistry; } const rootScope = typeof window !== 'undefined' ? window : globalThis; flowCapabilityRegistry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: DEFAULT_ACTIVE_FLOW_ID, }) || null; return flowCapabilityRegistry; } function resolveCurrentSidepanelCapabilities(options = {}) { const registry = getFlowCapabilityRegistry(); if (!registry?.resolveSidepanelCapabilities) { return null; } const state = { ...(latestState || {}), ...(options?.state || {}), }; return registry.resolveSidepanelCapabilities({ activeFlowId: options?.activeFlowId ?? state?.activeFlowId, panelMode: options?.panelMode ?? state?.panelMode, signupMethod: options?.signupMethod ?? state?.signupMethod, state, }); } function resolveStepDefinitionCapabilityState(state = latestState, options = {}) { const nextState = { ...(state || {}), ...(options?.state || {}), }; const capabilityState = resolveCurrentSidepanelCapabilities({ activeFlowId: options?.activeFlowId ?? nextState?.activeFlowId, panelMode: options?.panelMode ?? nextState?.panelMode, signupMethod: options?.signupMethod ?? nextState?.signupMethod, state: nextState, }); return { capabilityState, plusModeEnabled: capabilityState ? Boolean(capabilityState.runtimeLocks?.plusModeEnabled) : Boolean(nextState?.plusModeEnabled), signupMethod: capabilityState?.effectiveSignupMethod || normalizeSignupMethod((options?.signupMethod ?? nextState?.signupMethod) || DEFAULT_SIGNUP_METHOD), }; } function getSelectedPanelMode() { const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode ? selectPanelMode.value : (typeof latestState !== 'undefined' ? latestState?.panelMode : ''); const resolvedPanelMode = normalizePanelMode(selectedValue || 'cpa'); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode }) : null; return capabilityState?.effectivePanelMode || capabilityState?.panelMode || resolvedPanelMode; } function getSelectedSignupMethod() { const activeButton = signupMethodButtons.find((button) => button.classList.contains('is-active')); return normalizeSignupMethod(activeButton?.dataset.signupMethod || latestState?.signupMethod || DEFAULT_SIGNUP_METHOD); } function setSignupMethod(method) { const resolvedMethod = normalizeSignupMethod(method); signupMethodButtons.forEach((button) => { const active = normalizeSignupMethod(button.dataset.signupMethod) === resolvedMethod; button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', String(active)); }); syncLatestState({ signupMethod: resolvedMethod }); return resolvedMethod; } function canSelectPhoneSignupMethod() { const phoneEnabled = Boolean(inputPhoneVerificationEnabled?.checked); const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled); const contributionModeEnabled = Boolean(latestState?.contributionMode); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), phoneVerificationEnabled: phoneEnabled, plusModeEnabled, contributionMode: contributionModeEnabled, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '', panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), state: { ...(typeof latestState !== 'undefined' ? latestState : {}), phoneVerificationEnabled: phoneEnabled, plusModeEnabled, contributionMode: contributionModeEnabled, }, }) : null; })(); if (capabilityState && typeof capabilityState.canSelectPhoneSignup === 'boolean') { return capabilityState.canSelectPhoneSignup; } return phoneEnabled && !plusModeEnabled && !contributionModeEnabled; } function isSignupMethodSwitchLocked() { return isAutoRunLockedPhase() || isAutoRunPausedPhase() || isAutoRunScheduledPhase(); } function updateSignupMethodUI(options = {}) { if (!signupMethodButtons.length) { if (typeof syncSignupPhoneInputFromState === 'function') { syncSignupPhoneInputFromState(latestState); } return; } const showSignupMethod = Boolean(inputPhoneVerificationEnabled?.checked); if (rowSignupMethod) { rowSignupMethod.style.display = showSignupMethod ? '' : 'none'; } let selectedMethod = normalizeSignupMethod(getSelectedSignupMethod()); const phoneSelectable = canSelectPhoneSignupMethod(); if (!phoneSelectable && selectedMethod === SIGNUP_METHOD_PHONE) { selectedMethod = setSignupMethod(SIGNUP_METHOD_EMAIL); if (options.notify && typeof showToast === 'function') { showToast('已切回邮箱注册', 'info', 1600); } } else { setSignupMethod(selectedMethod); } const locked = isSignupMethodSwitchLocked(); signupMethodButtons.forEach((button) => { const method = normalizeSignupMethod(button.dataset.signupMethod); const disabled = locked || (method === SIGNUP_METHOD_PHONE && !phoneSelectable); button.disabled = disabled; button.setAttribute('aria-disabled', String(disabled)); if (method === SIGNUP_METHOD_PHONE) { if (!Boolean(inputPhoneVerificationEnabled?.checked)) { button.title = '开启接码后可选择手机号注册'; } else if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled?.checked) { button.title = 'Plus 模式第一版暂不支持手机号注册'; } else if (latestState?.contributionMode) { button.title = '贡献模式第一版暂不支持手机号注册'; } else if (locked) { button.title = '自动流程运行中不能切换注册方式'; } else { button.title = ''; } } }); const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function' ? resolveStepDefinitionCapabilityState({ ...(latestState || {}), plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled), signupMethod: selectedMethod, }, { signupMethod: selectedMethod, }) : { plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled), signupMethod: selectedMethod, }; syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, { plusPaymentMethod: getSelectedPlusPaymentMethod(latestState), signupMethod: selectedMethod, }); if (typeof syncSignupPhoneInputFromState === 'function') { syncSignupPhoneInputFromState(latestState); } } function updatePhoneVerificationSettingsUI() { const rawEnabled = Boolean(inputPhoneVerificationEnabled?.checked); const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod, state: { ...(latestState || {}), phoneVerificationEnabled: rawEnabled, plusModeEnabled: rawPlusModeEnabled, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: latestState?.activeFlowId, panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod, state: { ...(latestState || {}), phoneVerificationEnabled: rawEnabled, plusModeEnabled: rawPlusModeEnabled, }, }) : null; })(); const canShowPhoneSettings = capabilityState ? Boolean(capabilityState.canShowPhoneSettings) : true; const enabled = canShowPhoneSettings && rawEnabled; const showSettings = enabled && phoneVerificationSectionExpanded; const normalizeProvider = typeof normalizePhoneSmsProviderValue === 'function' ? normalizePhoneSmsProviderValue : ((value = '') => { const normalized = String(value || '').trim().toLowerCase(); if (normalized === '5sim') return '5sim'; if (normalized === 'nexsms') return 'nexsms'; return 'hero-sms'; }); const heroProviderValue = typeof PHONE_SMS_PROVIDER_HERO !== 'undefined' ? PHONE_SMS_PROVIDER_HERO : 'hero-sms'; const fiveSimProviderValue = typeof PHONE_SMS_PROVIDER_FIVE_SIM !== 'undefined' ? PHONE_SMS_PROVIDER_FIVE_SIM : '5sim'; const nexSmsProviderValue = typeof PHONE_SMS_PROVIDER_NEXSMS !== 'undefined' ? PHONE_SMS_PROVIDER_NEXSMS : 'nexsms'; const providerOrderForDisplay = resolveNormalizedProviderOrderForRuntime(latestState || {}); const provider = providerOrderForDisplay[0] || ( typeof getSelectedPhoneSmsProvider === 'function' ? getSelectedPhoneSmsProvider() : normalizeProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider || heroProviderValue) ); const heroProvider = provider === heroProviderValue; const fiveSimProvider = provider === fiveSimProviderValue; const nexSmsProvider = provider === nexSmsProviderValue; if (rowPhoneVerificationEnabled) { rowPhoneVerificationEnabled.style.display = canShowPhoneSettings ? '' : 'none'; } if (rowHeroSmsPlatform) { rowHeroSmsPlatform.style.display = canShowPhoneSettings ? '' : 'none'; } updateSignupMethodUI(); if (btnTogglePhoneVerificationSection) { btnTogglePhoneVerificationSection.disabled = !enabled; btnTogglePhoneVerificationSection.textContent = showSettings ? '收起设置' : '展开设置'; btnTogglePhoneVerificationSection.title = enabled ? (showSettings ? '收起接码设置' : '展开接码设置') : '开启接码后可展开设置'; btnTogglePhoneVerificationSection.setAttribute('aria-expanded', String(showSettings)); } if (rowPhoneVerificationFold) { rowPhoneVerificationFold.style.display = showSettings ? '' : 'none'; } const phoneVerificationRows = [ typeof rowPhoneSmsProvider !== 'undefined' ? rowPhoneSmsProvider : null, typeof rowPhoneSmsProviderOrder !== 'undefined' ? rowPhoneSmsProviderOrder : null, typeof rowPhoneSmsProviderOrderActions !== 'undefined' ? rowPhoneSmsProviderOrderActions : null, typeof rowHeroSmsCountry !== 'undefined' ? rowHeroSmsCountry : null, typeof rowHeroSmsCountryFallback !== 'undefined' ? rowHeroSmsCountryFallback : null, typeof rowHeroSmsAcquirePriority !== 'undefined' ? rowHeroSmsAcquirePriority : null, typeof rowHeroSmsApiKey !== 'undefined' ? rowHeroSmsApiKey : null, typeof rowFiveSimApiKey !== 'undefined' ? rowFiveSimApiKey : null, typeof rowFiveSimCountry !== 'undefined' ? rowFiveSimCountry : null, typeof rowFiveSimCountryFallback !== 'undefined' ? rowFiveSimCountryFallback : null, typeof rowFiveSimProduct !== 'undefined' ? rowFiveSimProduct : null, typeof rowNexSmsApiKey !== 'undefined' ? rowNexSmsApiKey : null, typeof rowNexSmsCountry !== 'undefined' ? rowNexSmsCountry : null, typeof rowNexSmsCountryFallback !== 'undefined' ? rowNexSmsCountryFallback : null, typeof rowNexSmsServiceCode !== 'undefined' ? rowNexSmsServiceCode : null, typeof rowHeroSmsMaxPrice !== 'undefined' ? rowHeroSmsMaxPrice : null, typeof rowFiveSimOperator !== 'undefined' ? rowFiveSimOperator : null, typeof rowPhoneCodeSettingsGroup !== 'undefined' ? rowPhoneCodeSettingsGroup : null, typeof rowPhoneVerificationResendCount !== 'undefined' ? rowPhoneVerificationResendCount : null, typeof rowPhoneReplacementLimit !== 'undefined' ? rowPhoneReplacementLimit : null, typeof rowPhoneCodeWaitSeconds !== 'undefined' ? rowPhoneCodeWaitSeconds : null, typeof rowPhoneCodeTimeoutWindows !== 'undefined' ? rowPhoneCodeTimeoutWindows : null, typeof rowPhoneCodePollIntervalSeconds !== 'undefined' ? rowPhoneCodePollIntervalSeconds : null, typeof rowPhoneCodePollMaxRounds !== 'undefined' ? rowPhoneCodePollMaxRounds : null, typeof rowFreePhoneReuseEnabled !== 'undefined' ? rowFreePhoneReuseEnabled : null, typeof rowFreePhoneReuseAutoEnabled !== 'undefined' ? rowFreePhoneReuseAutoEnabled : null, ]; phoneVerificationRows.forEach((row) => { if (row) { row.style.display = showSettings ? '' : 'none'; } }); if (rowHeroSmsCountry) rowHeroSmsCountry.style.display = showSettings && heroProvider ? '' : 'none'; if (rowHeroSmsCountryFallback) rowHeroSmsCountryFallback.style.display = showSettings && heroProvider ? '' : 'none'; if (rowHeroSmsAcquirePriority) rowHeroSmsAcquirePriority.style.display = showSettings && heroProvider ? '' : 'none'; if (rowHeroSmsApiKey) rowHeroSmsApiKey.style.display = showSettings && heroProvider ? '' : 'none'; if (rowFiveSimApiKey) rowFiveSimApiKey.style.display = showSettings && fiveSimProvider ? '' : 'none'; if (rowFiveSimCountry) rowFiveSimCountry.style.display = showSettings && fiveSimProvider ? '' : 'none'; if (rowFiveSimCountryFallback) rowFiveSimCountryFallback.style.display = showSettings && fiveSimProvider ? '' : 'none'; if (rowFiveSimOperator) rowFiveSimOperator.style.display = showSettings && fiveSimProvider ? '' : 'none'; if (rowFiveSimProduct) rowFiveSimProduct.style.display = showSettings && fiveSimProvider ? '' : 'none'; if (rowNexSmsApiKey) rowNexSmsApiKey.style.display = showSettings && nexSmsProvider ? '' : 'none'; if (rowNexSmsCountry) rowNexSmsCountry.style.display = showSettings && nexSmsProvider ? '' : 'none'; if (rowNexSmsCountryFallback) rowNexSmsCountryFallback.style.display = showSettings && nexSmsProvider ? '' : 'none'; if (rowNexSmsServiceCode) rowNexSmsServiceCode.style.display = showSettings && nexSmsProvider ? '' : 'none'; if (rowFiveSimOperator) { rowFiveSimOperator.style.display = showSettings && fiveSimProvider ? '' : 'none'; } if (typeof rowFreePhoneReuseEnabled !== 'undefined' && rowFreePhoneReuseEnabled) { rowFreePhoneReuseEnabled.style.display = showSettings ? '' : 'none'; } if (typeof rowFreePhoneReuseAutoEnabled !== 'undefined' && rowFreePhoneReuseAutoEnabled) { rowFreePhoneReuseAutoEnabled.style.display = showSettings ? '' : 'none'; } const freePhoneReuseEnabled = Boolean( typeof inputFreePhoneReuseEnabled !== 'undefined' && inputFreePhoneReuseEnabled?.checked ); const freePhoneReuseAutoAvailable = showSettings && freePhoneReuseEnabled; if (typeof inputFreePhoneReuseAutoEnabled !== 'undefined' && inputFreePhoneReuseAutoEnabled) { inputFreePhoneReuseAutoEnabled.disabled = !freePhoneReuseAutoAvailable; if (!freePhoneReuseAutoAvailable) { inputFreePhoneReuseAutoEnabled.checked = false; } } setFreePhoneReuseControlsLocked(isAutoRunLockedPhase() || isAutoRunScheduledPhase()); if (typeof rowFreePhoneReuseAutoEnabled !== 'undefined' && rowFreePhoneReuseAutoEnabled) { rowFreePhoneReuseAutoEnabled.classList.toggle('is-disabled', !freePhoneReuseAutoAvailable); } const runtimeVisible = enabled; [ typeof rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null, typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null, typeof rowHeroSmsCurrentCountdown !== 'undefined' ? rowHeroSmsCurrentCountdown : null, typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : null, typeof rowFreeReusablePhone !== 'undefined' ? rowFreeReusablePhone : null, typeof rowHeroSmsPreferredActivation !== 'undefined' ? rowHeroSmsPreferredActivation : null, ].forEach((row) => { if (row) { row.style.display = runtimeVisible ? '' : 'none'; } }); if (typeof syncSignupPhoneInputFromState === 'function') { syncSignupPhoneInputFromState(latestState); } if (!showSettings && typeof rowHeroSmsPriceTiers !== 'undefined' && rowHeroSmsPriceTiers) { rowHeroSmsPriceTiers.style.display = 'none'; } updateHeroSmsPlatformDisplay(); } function updatePlusModeUI() { const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal'; const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay'; const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper'; const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue; const rawEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : false; const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, state: { ...(latestState || {}), plusModeEnabled: rawEnabled, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: latestState?.activeFlowId, panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), state: { ...(latestState || {}), plusModeEnabled: rawEnabled, }, }) : null; })(); const supportsPlusMode = capabilityState ? Boolean(capabilityState.canShowPlusSettings) : true; const enabled = supportsPlusMode && rawEnabled; const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod; const gpcPhoneMode = normalizeGpcHelperPhoneModeValue( typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode ? selectGpcHelperPhoneMode.value : (latestState?.gopayHelperPhoneMode || 'manual') ); const gpcAutoModeDenied = isGpcAutoModePermissionDenied(latestState); const isGpcAutoMode = gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO; const gpcAutoModeBlocked = isGpcAutoMode && gpcAutoModeDenied; const gpcOtpChannel = normalizeGpcOtpChannelValue( typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel ? selectGpcHelperOtpChannel.value : (latestState?.gopayHelperOtpChannel || 'whatsapp') ); const localSmsEnabled = Boolean( typeof inputGpcHelperLocalSmsEnabled !== 'undefined' && inputGpcHelperLocalSmsEnabled ? inputGpcHelperLocalSmsEnabled.checked : latestState?.gopayHelperLocalSmsHelperEnabled ); const selectedMethod = typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value ? normalizePlusPaymentMethod(selectPlusPaymentMethod.value) : method; const gpcRowsVisible = enabled && selectedMethod === gpcValue; const canShowGpcModeSelector = gpcRowsVisible; const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode; const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled; if (typeof rowPlusMode !== 'undefined' && rowPlusMode) { rowPlusMode.style.display = supportsPlusMode ? '' : 'none'; } if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) { selectPlusPaymentMethod.value = method; if (selectPlusPaymentMethod.style) { selectPlusPaymentMethod.style.display = enabled ? '' : 'none'; } } if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption) { plusPaymentMethodCaption.textContent = method === gpcValue ? `GPC ${isGpcAutoMode ? '自动' : '手动'}订阅链路` : method === gopayValue ? 'GoPay 印尼订阅链路' : 'PayPal 订阅链路'; } if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption && method === gpcValue && gpcAutoModeBlocked) { plusPaymentMethodCaption.textContent = 'GPC 自动订阅链路(需手动切换)'; } [ typeof rowPlusPaymentMethod !== 'undefined' ? rowPlusPaymentMethod : null, ].forEach((row) => { if (!row) { return; } row.style.display = enabled ? '' : 'none'; }); [ typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null, ].forEach((row) => { if (!row) { return; } row.style.display = enabled && selectedMethod === paypalValue ? '' : 'none'; }); [ typeof rowGpcHelperApi !== 'undefined' ? rowGpcHelperApi : null, typeof rowGpcHelperCardKey !== 'undefined' ? rowGpcHelperCardKey : null, ].forEach((row) => { if (!row) { return; } row.style.display = gpcRowsVisible ? '' : 'none'; }); if (typeof rowGpcHelperPhoneMode !== 'undefined' && rowGpcHelperPhoneMode) { rowGpcHelperPhoneMode.style.display = canShowGpcModeSelector ? '' : 'none'; } if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) { selectGpcHelperPhoneMode.value = gpcPhoneMode; } [ typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null, typeof rowGpcHelperPhone !== 'undefined' ? rowGpcHelperPhone : null, typeof rowGpcHelperOtpChannel !== 'undefined' ? rowGpcHelperOtpChannel : null, typeof rowGpcHelperPin !== 'undefined' ? rowGpcHelperPin : null, ].forEach((row) => { if (!row) { return; } row.style.display = gpcRowsVisible && !isGpcAutoMode ? '' : 'none'; }); if (typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel) { selectGpcHelperOtpChannel.value = gpcOtpChannel; } if (typeof inputGpcHelperLocalSmsEnabled !== 'undefined' && inputGpcHelperLocalSmsEnabled) { inputGpcHelperLocalSmsEnabled.checked = effectiveLocalSmsEnabled; } if (typeof rowGpcHelperLocalSmsEnabled !== 'undefined' && rowGpcHelperLocalSmsEnabled) { rowGpcHelperLocalSmsEnabled.style.display = localSmsControlsVisible ? '' : 'none'; } if (typeof rowGpcHelperLocalSmsUrl !== 'undefined' && rowGpcHelperLocalSmsUrl) { rowGpcHelperLocalSmsUrl.style.display = localSmsControlsVisible && effectiveLocalSmsEnabled ? '' : 'none'; } if (typeof btnGpcCardKeyPurchase !== 'undefined' && btnGpcCardKeyPurchase) { btnGpcCardKeyPurchase.style.display = gpcRowsVisible ? '' : 'none'; } [ typeof rowGoPayCountryCode !== 'undefined' ? rowGoPayCountryCode : null, typeof rowGoPayPhone !== 'undefined' ? rowGoPayPhone : null, typeof rowGoPayOtp !== 'undefined' ? rowGoPayOtp : null, typeof rowGoPayPin !== 'undefined' ? rowGoPayPin : null, ].forEach((row) => { if (!row) { return; } row.style.display = enabled && selectedMethod === gopayValue ? '' : 'none'; }); } function setSettingsCardLocked(locked) { if (!settingsCard) { return; } settingsCard.classList.toggle('is-locked', locked); settingsCard.toggleAttribute('inert', false); Array.from(settingsCard.children).forEach((child) => { const keepInteractive = child?.id === 'row-custom-email-pool'; child.toggleAttribute('inert', Boolean(locked && !keepInteractive)); }); } function setFreePhoneReuseControlsLocked(locked) { if (inputFreePhoneReuseEnabled) { inputFreePhoneReuseEnabled.disabled = locked; } if (inputFreePhoneReuseAutoEnabled) { inputFreePhoneReuseAutoEnabled.disabled = locked || !Boolean(inputFreePhoneReuseEnabled?.checked) || !Boolean(inputPhoneVerificationEnabled?.checked && phoneVerificationSectionExpanded); } } async function setRuntimeEmailState(email) { const normalizedEmail = String(email || '').trim() || null; const response = await chrome.runtime.sendMessage({ type: 'SET_EMAIL_STATE', source: 'sidepanel', payload: { email: normalizedEmail }, }); if (response?.error) { throw new Error(response.error); } return normalizedEmail; } function getRuntimeSignupPhoneValue(state = latestState) { const identifierType = String(state?.accountIdentifierType || '').trim().toLowerCase(); return String( state?.signupPhoneNumber || (identifierType === 'phone' ? state?.accountIdentifier : '') || '' ).trim(); } function shouldExecuteStep3WithSignupPhoneIdentity(state = latestState) { const identifierType = String(state?.accountIdentifierType || '').trim().toLowerCase(); const resolvedMethod = normalizeSignupMethod( state?.resolvedSignupMethod || state?.signupMethod || (typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : DEFAULT_SIGNUP_METHOD) ); return identifierType === 'phone' || Boolean(getRuntimeSignupPhoneValue(state)) || resolvedMethod === SIGNUP_METHOD_PHONE; } function getSignupPhoneInputValue() { return typeof inputSignupPhone !== 'undefined' && inputSignupPhone ? String(inputSignupPhone.value || '').trim() : ''; } function shouldPreserveSignupPhoneInputValue(stateSignupPhone = '') { if (typeof inputSignupPhone === 'undefined' || !inputSignupPhone || !signupPhoneInputDirty) { return false; } if (getSignupPhoneInputValue() === String(stateSignupPhone || '').trim()) { signupPhoneInputDirty = false; return false; } return signupPhoneInputFocused || (typeof document !== 'undefined' && document.activeElement === inputSignupPhone); } function syncSignupPhoneInputFromState(state = latestState) { const signupPhone = getRuntimeSignupPhoneValue(state); if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { if (!shouldPreserveSignupPhoneInputValue(signupPhone)) { inputSignupPhone.value = signupPhone; } } if (typeof rowSignupPhone !== 'undefined' && rowSignupPhone) { const phoneVerificationEnabled = typeof inputPhoneVerificationEnabled !== 'undefined' && inputPhoneVerificationEnabled ? Boolean(inputPhoneVerificationEnabled.checked) : Boolean(state?.phoneVerificationEnabled || latestState?.phoneVerificationEnabled); const rawSignupMethod = state?.signupMethod || ( typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : 'email' ); const selectedMethod = typeof normalizeSignupMethod === 'function' ? normalizeSignupMethod(rawSignupMethod) : (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ panelMode: state?.panelMode || latestState?.panelMode, signupMethod: selectedMethod, state: { ...(latestState || {}), ...(state || {}), phoneVerificationEnabled, }, }) : (() => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({ defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai', }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: state?.activeFlowId || latestState?.activeFlowId, panelMode: state?.panelMode || latestState?.panelMode, signupMethod: selectedMethod, state: { ...(latestState || {}), ...(state || {}), phoneVerificationEnabled, }, }) : null; })(); const canShowPhoneSettings = capabilityState ? Boolean(capabilityState.canShowPhoneSettings) : true; rowSignupPhone.style.display = canShowPhoneSettings && phoneVerificationEnabled && (selectedMethod === 'phone' || Boolean(signupPhone) || Boolean(getSignupPhoneInputValue()) || signupPhoneInputDirty) ? '' : 'none'; } } async function setRuntimeSignupPhoneState(phoneNumber) { const normalizedPhone = String(phoneNumber || '').trim() || null; const response = await chrome.runtime.sendMessage({ type: 'SET_SIGNUP_PHONE_STATE', source: 'sidepanel', payload: { phoneNumber: normalizedPhone }, }); if (response?.error) { throw new Error(response.error); } return normalizedPhone; } async function persistSignupPhoneInputValue(options = {}) { const { final = true, silent = true } = options; if (typeof inputSignupPhone === 'undefined' || !inputSignupPhone) { return getRuntimeSignupPhoneValue(latestState); } if (signupPhoneInputPersistPromise) { return signupPhoneInputPersistPromise; } const phoneNumber = getSignupPhoneInputValue(); inputSignupPhone.value = phoneNumber; const currentPhone = getRuntimeSignupPhoneValue(latestState); if (!signupPhoneInputDirty && phoneNumber === currentPhone) { return phoneNumber; } signupPhoneInputPersistPromise = (async () => { const response = await chrome.runtime.sendMessage({ type: final ? 'SAVE_SIGNUP_PHONE' : 'SET_SIGNUP_PHONE_STATE', source: 'sidepanel', payload: { phoneNumber }, }); if (response?.error) { throw new Error(response.error); } const normalizedPhone = String(response?.phoneNumber || phoneNumber || '').trim(); signupPhoneInputDirty = getSignupPhoneInputValue() !== normalizedPhone; syncLatestState({ signupPhoneNumber: normalizedPhone, phoneNumber: '', ...(normalizedPhone ? { accountIdentifierType: 'phone', accountIdentifier: normalizedPhone, } : (String(latestState?.accountIdentifierType || '').trim().toLowerCase() === 'phone' ? { accountIdentifierType: null, accountIdentifier: '', } : {})), }); syncSignupPhoneInputFromState(latestState); if (!silent) { showToast(normalizedPhone ? '注册手机号已保存。' : '注册手机号已清空。', 'success', 1600); } return normalizedPhone; })(); try { return await signupPhoneInputPersistPromise; } finally { signupPhoneInputPersistPromise = null; } } async function persistSignupPhoneInputForAction() { if (typeof inputSignupPhone === 'undefined' || !inputSignupPhone) { return; } const phoneNumber = getSignupPhoneInputValue(); const currentPhone = getRuntimeSignupPhoneValue(latestState); if (!signupPhoneInputDirty && phoneNumber === currentPhone) { return; } await persistSignupPhoneInputValue({ final: true, silent: true }); } function isGpcHelperCheckoutSelected() { const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper'; const plusEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled); return plusEnabled && getSelectedPlusPaymentMethod() === gpcValue; } function getSelectedGpcHelperPhoneMode() { return normalizeGpcHelperPhoneModeValue( typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode ? selectGpcHelperPhoneMode.value : (latestState?.gopayHelperPhoneMode || GPC_HELPER_PHONE_MODE_MANUAL) ); } async function showGpcStartBlockedDialog(message) { await openConfirmModal({ title: 'GPC 任务无法开启', message, confirmLabel: '知道了', }); } async function refreshGpcBalanceForStart() { const response = await chrome.runtime.sendMessage({ type: 'REFRESH_GPC_CARD_BALANCE', source: 'sidepanel', payload: { gopayHelperApiUrl: inputGpcHelperApi?.value || DEFAULT_GPC_HELPER_API_URL, gopayHelperApiKey: inputGpcHelperCardKey?.value || latestState?.gopayHelperApiKey || '', reason: 'before_start', }, }); if (response?.error) { throw new Error(response.error); } const nextState = { gopayHelperBalance: response?.balance || latestState?.gopayHelperBalance || '', gopayHelperBalancePayload: response?.data || response?.payload?.data || response?.payload || latestState?.gopayHelperBalancePayload || null, gopayHelperBalanceUpdatedAt: response?.updatedAt || Date.now(), gopayHelperBalanceError: '', gopayHelperRemainingUses: getGpcBalanceRemainingUsesFromResponse(response) ?? 0, gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response), gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '', }; syncLatestState(nextState); if (displayGpcHelperBalance && nextState.gopayHelperBalance) { displayGpcHelperBalance.textContent = nextState.gopayHelperBalance; } updatePlusModeUI(); return nextState; } async function ensureGpcApiKeyReadyForStart(options = {}) { if (!isGpcHelperCheckoutSelected()) { return true; } const selectedMode = getSelectedGpcHelperPhoneMode(); let balanceState; try { balanceState = await refreshGpcBalanceForStart(); } catch (error) { await showGpcStartBlockedDialog(`API Key 余额校验失败:${error?.message || '未知错误'}。请先确认 API Key 是否正确。`); return false; } const remainingUses = normalizeGpcRemainingUsesValue(balanceState.gopayHelperRemainingUses); const apiKeyStatus = String(balanceState.gopayHelperApiKeyStatus || '').trim().toLowerCase(); if (apiKeyStatus && apiKeyStatus !== 'active') { await showGpcStartBlockedDialog(`当前 GPC API Key 状态为 ${balanceState.gopayHelperApiKeyStatus},不能开启任务。`); return false; } if (remainingUses !== null && remainingUses <= 0) { await showGpcStartBlockedDialog('当前 GPC API Key 剩余次数不足,不能开启任务。'); return false; } if (selectedMode === GPC_HELPER_PHONE_MODE_AUTO && isGpcAutoModePermissionDenied(balanceState)) { await showGpcStartBlockedDialog('当前 GPC API Key 未开通自动模式,已保留你的当前选择。如需继续,请由你手动切换到手动模式后再开启任务。'); return false; } if (options?.notify) { showToast('GPC API Key 余额和权限校验通过。', 'success', 1800); } return true; } async function openPlusManualConfirmationDialog(options = {}) { const method = String(options.method || '').trim().toLowerCase(); const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay'; if (method === 'gopay-otp') { if (!sharedFormDialog?.open) { return null; } const result = await sharedFormDialog.open({ title: String(options.title || '').trim() || 'GPC OTP 验证', message: String(options.message || '').trim() || '请在WhatsApp里面获取验证码(耐心等待三十秒左右)', fields: [ { key: 'otp', label: 'OTP', type: 'text', placeholder: '请输入 OTP 验证码', inputMode: 'numeric', autocomplete: 'one-time-code', required: true, requiredMessage: '请输入 OTP 验证码。', normalize: (value) => String(value || '').trim().replace(/[^\d]/g, ''), validate: (value) => { const normalized = String(value || '').trim().replace(/[^\d]/g, ''); if (!normalized) return '请输入 OTP 验证码。'; if (!/^\d{6}$/.test(normalized)) return 'OTP 必须是 6 位数字,请检查。'; return ''; }, }, ], confirmLabel: '提交 OTP', }); return result ? { action: 'confirm', otp: String(result.otp || '').trim().replace(/[^\d]/g, '') } : { action: 'cancel' }; } const title = String(options.title || '').trim() || (method === gopayValue ? 'GoPay 订阅确认' : '手动确认'); const message = String(options.message || '').trim() || (method === gopayValue ? '请在当前订阅页中手动完成 GoPay 订阅,完成后点击“我已完成订阅”继续。' : '请先在页面中完成当前手动操作,完成后点击确认继续。'); return openActionModal({ title, message, actions: [ { id: 'cancel', label: '取消等待', variant: 'btn-ghost' }, { id: 'confirm', label: '我已完成订阅', variant: 'btn-primary' }, ], alert: method === gopayValue ? { text: '确认后流程会直接继续到 Plus 模式第 10 步 OAuth 登录。', tone: 'info' } : null, }); } async function syncPlusManualConfirmationDialog() { const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay'; const requestId = String(latestState?.plusManualConfirmationRequestId || '').trim(); const pending = Boolean(latestState?.plusManualConfirmationPending); if (!pending || !requestId || plusManualConfirmationDialogInFlight || activePlusManualConfirmationRequestId === requestId) { return; } const step = Number(latestState?.plusManualConfirmationStep) || 0; const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase(); const title = latestState?.plusManualConfirmationTitle; const message = latestState?.plusManualConfirmationMessage; activePlusManualConfirmationRequestId = requestId; plusManualConfirmationDialogInFlight = true; let shouldReopenDialog = false; try { const choice = await openPlusManualConfirmationDialog({ method, title, message, }); const currentRequestId = String(latestState?.plusManualConfirmationRequestId || '').trim(); const stillPending = Boolean(latestState?.plusManualConfirmationPending); if (!stillPending || currentRequestId !== requestId) { return; } if (choice == null) { shouldReopenDialog = true; showToast('当前订阅确认仍在等待中,将重新弹出确认窗口。', 'info', 1800); return; } const confirmed = choice === 'confirm' || choice?.action === 'confirm'; const response = await chrome.runtime.sendMessage({ type: 'RESOLVE_PLUS_MANUAL_CONFIRMATION', source: 'sidepanel', payload: { step, requestId, confirmed, ...(choice?.otp ? { otp: choice.otp } : {}), }, }); if (response?.error) { throw new Error(response.error); } if (confirmed) { showToast( method === 'gopay-otp' ? 'GPC OTP 已提交,正在继续验证...' : (method === gopayValue ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...'), 'info', 2200 ); } else { showToast( method === 'gopay-otp' ? '已取消 GPC OTP 输入。' : (method === gopayValue ? '已取消 GoPay 订阅等待。' : '已取消当前手动确认。'), 'warn', 2200 ); } } catch (error) { showToast(error?.message || String(error || '未知错误'), 'error'); } finally { if (activePlusManualConfirmationRequestId === requestId) { activePlusManualConfirmationRequestId = ''; } plusManualConfirmationDialogInFlight = false; if ( shouldReopenDialog && latestState?.plusManualConfirmationPending && String(latestState?.plusManualConfirmationRequestId || '').trim() === requestId ) { setTimeout(() => { void syncPlusManualConfirmationDialog(); }, 0); } } } async function clearRegistrationEmail(options = {}) { const { silent = false } = options; if (!inputEmail.value.trim() && !latestState?.email) { return; } inputEmail.value = ''; syncLatestState({ email: null }); try { await setRuntimeEmailState(null); } catch (err) { if (!silent) { showToast(`清空邮箱失败:${err.message}`, 'error'); } throw err; } } async function clearRegistrationSignupPhone(options = {}) { const { silent = false } = options; if (!getRuntimeSignupPhoneValue(latestState)) { if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { inputSignupPhone.value = ''; } signupPhoneInputDirty = false; syncSignupPhoneInputFromState(latestState); return; } if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { inputSignupPhone.value = ''; } signupPhoneInputDirty = false; syncLatestState({ signupPhoneNumber: '', ...(String(latestState?.accountIdentifierType || '').trim().toLowerCase() === 'phone' ? { accountIdentifierType: null, accountIdentifier: '', } : {}), }); syncSignupPhoneInputFromState(latestState); try { await setRuntimeSignupPhoneState(null); } catch (err) { if (!silent) { showToast(`清空注册手机号失败:${err.message}`, 'error'); } throw err; } } function markSettingsDirty(isDirty = true) { settingsDirty = isDirty; if (isDirty) { settingsSaveRevision += 1; } updateSaveButtonState(); } function updateSaveButtonState() { btnSaveSettings.disabled = settingsSaveInFlight || !settingsDirty; updateConfigMenuControls(); btnSaveSettings.textContent = settingsSaveInFlight ? '保存中' : '保存'; } function scheduleSettingsAutoSave() { clearTimeout(settingsAutoSaveTimer); settingsAutoSaveTimer = setTimeout(() => { saveSettings({ silent: true }).catch(() => { }); }, 500); } async function saveSettings(options = {}) { const { silent = false, force = false } = options; clearTimeout(settingsAutoSaveTimer); if (!force && !settingsDirty && !settingsSaveInFlight && silent) { return; } const payload = collectSettingsPayload(); const saveRevision = settingsSaveRevision; settingsSaveInFlight = true; updateSaveButtonState(); try { const response = await chrome.runtime.sendMessage({ type: 'SAVE_SETTING', source: 'sidepanel', payload, }); if (response?.error) { throw new Error(response.error); } if (response?.state && saveRevision === settingsSaveRevision) { applySettingsState(response.state); } else { syncLatestState(payload); if (saveRevision === settingsSaveRevision) { markSettingsDirty(false); } updatePanelModeUI(); updateMailProviderUI(); updateButtonStates(); } if (!silent) { showToast('配置已保存', 'success', 1800); } } catch (err) { markSettingsDirty(true); if (!silent) { showToast(`保存失败:${err.message}`, 'error'); } throw err; } finally { settingsSaveInFlight = false; updateSaveButtonState(); } } async function persistCurrentSettingsForAction() { clearTimeout(settingsAutoSaveTimer); await waitForSettingsSaveIdle(); await persistSignupPhoneInputForAction(); await saveSettings({ silent: true, force: true }); } function applyAutoRunStatus(payload = currentAutoRun) { syncAutoRunState(payload); const runLabel = getAutoRunLabel(currentAutoRun); const locked = isAutoRunLockedPhase(); const paused = isAutoRunPausedPhase(); const scheduled = isAutoRunScheduledPhase(); const settingsCardLocked = scheduled || locked; setSettingsCardLocked(settingsCardLocked); setFreePhoneReuseControlsLocked(settingsCardLocked); inputRunCount.disabled = currentAutoRun.autoRunning || ( typeof shouldLockRunCountToEmailPool === 'function' ? shouldLockRunCountToEmailPool() : getLockedRunCountFromEmailPool() > 0 ); btnAutoRun.disabled = currentAutoRun.autoRunning; btnFetchEmail.disabled = locked || isCustomMailProvider() || usesCustomEmailPoolGenerator(); inputEmail.disabled = locked; if (typeof inputSignupPhone !== 'undefined' && inputSignupPhone) { inputSignupPhone.disabled = locked; } if (typeof inputSub2ApiAccountPriority !== 'undefined' && inputSub2ApiAccountPriority) { inputSub2ApiAccountPriority.disabled = locked; } inputAutoSkipFailures.disabled = scheduled; const lockedRunCount = typeof getLockedRunCountFromEmailPool === 'function' ? getLockedRunCountFromEmailPool() : 0; const isSyncPhase = typeof isAutoRunSourceSyncPhase === 'function' ? isAutoRunSourceSyncPhase : (phase) => ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase); const shouldSyncRunCount = typeof shouldSyncRunCountFromAutoRunSource === 'function' ? shouldSyncRunCountFromAutoRunSource(currentAutoRun) : (currentAutoRun.autoRunning || isSyncPhase(currentAutoRun.phase)); if (lockedRunCount > 0) { inputRunCount.value = String(lockedRunCount); } else if (shouldSyncRunCount && currentAutoRun.totalRuns > 0) { inputRunCount.value = String(currentAutoRun.totalRuns); } switch (currentAutoRun.phase) { case 'scheduled': autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `已计划${runLabel}`; break; case 'waiting_step': autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `等待中${runLabel}`; break; case 'waiting_email': autoContinueBar.style.display = 'flex'; btnAutoRun.innerHTML = `已暂停${runLabel}`; break; case 'running': autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `运行中${runLabel}`; break; case 'retrying': autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `重试中${runLabel}`; break; case 'waiting_interval': autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `等待中${runLabel}`; break; default: autoContinueBar.style.display = 'none'; setDefaultAutoRunButton(); inputEmail.disabled = false; if (!locked) { btnFetchEmail.disabled = isCustomMailProvider() || usesCustomEmailPoolGenerator(); } break; } updateAutoDelayInputState(); updateFallbackThreadIntervalInputState(); syncScheduledCountdownTicker(); updateStopButtonState(scheduled || paused || locked || Object.values(getStepStatuses()).some(status => status === 'running')); updateConfigMenuControls(); renderContributionMode(); } function initializeManualStepActions() { document.querySelectorAll('.step-row').forEach((row) => { if (row.querySelector('.step-actions')) { return; } const step = Number(row.dataset.step); const statusEl = row.querySelector('.step-status'); if (!statusEl) return; const actions = document.createElement('div'); actions.className = 'step-actions'; const manualBtn = document.createElement('button'); manualBtn.type = 'button'; manualBtn.className = 'step-manual-btn'; manualBtn.dataset.step = String(step); manualBtn.title = '跳过此步'; manualBtn.setAttribute('aria-label', `跳过步骤 ${step}`); manualBtn.innerHTML = ''; manualBtn.addEventListener('click', async (event) => { event.stopPropagation(); try { await handleSkipStep(step); } catch (err) { showToast(err.message, 'error'); } }); statusEl.parentNode.replaceChild(actions, statusEl); actions.appendChild(manualBtn); actions.appendChild(statusEl); }); } function renderStepsList() { if (!stepsList) return; stepsList.innerHTML = stepDefinitions.map((step) => `