12762 lines
495 KiB
JavaScript
12762 lines
495 KiB
JavaScript
// 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 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 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 plusPaymentMethodCaption = document.getElementById('plus-payment-method-caption');
|
||
const rowPayPalAccount = document.getElementById('row-paypal-account');
|
||
const selectPayPalAccount = document.getElementById('select-paypal-account');
|
||
const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account');
|
||
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 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 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 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 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 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 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 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 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 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 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 DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
||
let currentPlusModeEnabled = false;
|
||
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
||
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, currentPlusPaymentMethod);
|
||
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 SIGNUP_METHOD_EMAIL = 'email';
|
||
const SIGNUP_METHOD_PHONE = 'phone';
|
||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||
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 AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-run-plus-risk-prompt-dismissed';
|
||
const PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY = 'multipage-plus-contribution-prompt-ledger';
|
||
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 defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
||
const rawPaymentMethod = typeof options === 'string'
|
||
? options
|
||
: (options.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||
return (window.MultiPageStepDefinitions?.getSteps?.({
|
||
plusModeEnabled,
|
||
plusPaymentMethod: normalizePlusPaymentMethod(rawPaymentMethod),
|
||
}) || [])
|
||
.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 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);
|
||
currentPlusPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod);
|
||
stepDefinitions = getStepDefinitionsForMode(currentPlusModeEnabled, currentPlusPaymentMethod);
|
||
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 AUTO_RUN_PLUS_RISK_WARNING_MAX_SAFE_RUNS = 3;
|
||
const PLUS_CONTRIBUTION_PROMPT_THRESHOLD = 5;
|
||
const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
|
||
const PLUS_CONTRIBUTION_DONATION_CREDIT = 20;
|
||
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 = ['<option value="">请选择号池邮箱</option>'].concat(
|
||
accounts.map((account) => `<option value="${escapeHtml(account.id)}">${escapeHtml(account.email || '(未命名账号)')}</option>`)
|
||
);
|
||
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 latestState = null;
|
||
let currentAutoRun = {
|
||
autoRunning: false,
|
||
phase: 'idle',
|
||
currentRun: 0,
|
||
totalRuns: 1,
|
||
attemptRun: 0,
|
||
scheduledAt: null,
|
||
countdownAt: null,
|
||
countdownTitle: '',
|
||
countdownNote: '',
|
||
};
|
||
let settingsDirty = false;
|
||
let settingsSaveInFlight = false;
|
||
let settingsAutoSaveTimer = null;
|
||
let settingsSaveRevision = 0;
|
||
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;
|
||
let customEmailPoolEntriesState = [];
|
||
|
||
const EYE_OPEN_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
|
||
const EYE_CLOSED_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.94 10.94 0 0 1 12 19C5 19 1 12 1 12a21.77 21.77 0 0 1 5.06-6.94"/><path d="M9.9 4.24A10.94 10.94 0 0 1 12 5c7 0 11 7 11 7a21.86 21.86 0 0 1-2.16 3.19"/><path d="M1 1l22 22"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>';
|
||
const COPY_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||
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: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
||
warn: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||
success: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
||
info: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
||
};
|
||
|
||
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] || ''}<span class="toast-msg">${escapeHtml(message)}</span><button class="toast-close">×</button>`;
|
||
|
||
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 isAutoRunPlusRiskPromptDismissed() {
|
||
return isPromptDismissed(AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY);
|
||
}
|
||
|
||
function setAutoRunPlusRiskPromptDismissed(dismissed) {
|
||
setPromptDismissed(AUTO_RUN_PLUS_RISK_PROMPT_DISMISSED_STORAGE_KEY, dismissed);
|
||
}
|
||
|
||
function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) {
|
||
return totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS;
|
||
}
|
||
|
||
function shouldWarnPlusAutoRunRisk(totalRuns, plusModeEnabled) {
|
||
return Boolean(plusModeEnabled)
|
||
&& Math.floor(Number(totalRuns) || 0) > AUTO_RUN_PLUS_RISK_WARNING_MAX_SAFE_RUNS;
|
||
}
|
||
|
||
function normalizePlusContributionPromptNumber(value) {
|
||
const number = Math.floor(Number(value) || 0);
|
||
return Number.isFinite(number) ? number : 0;
|
||
}
|
||
|
||
function normalizePlusContributionPromptLedger(value = {}) {
|
||
const source = value && typeof value === 'object' ? value : {};
|
||
return {
|
||
promptBaseline: normalizePlusContributionPromptNumber(source.promptBaseline),
|
||
donationCredit: Math.max(0, normalizePlusContributionPromptNumber(source.donationCredit)),
|
||
};
|
||
}
|
||
|
||
function getPlusContributionPromptLedger() {
|
||
try {
|
||
return normalizePlusContributionPromptLedger(
|
||
JSON.parse(localStorage.getItem(PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY) || '{}')
|
||
);
|
||
} catch {
|
||
return normalizePlusContributionPromptLedger();
|
||
}
|
||
}
|
||
|
||
function setPlusContributionPromptLedger(ledger) {
|
||
localStorage.setItem(
|
||
PLUS_CONTRIBUTION_PROMPT_LEDGER_STORAGE_KEY,
|
||
JSON.stringify(normalizePlusContributionPromptLedger(ledger))
|
||
);
|
||
}
|
||
|
||
function isSuccessfulPlusAccountRecord(record = {}) {
|
||
return record?.finalStatus === 'success' && Boolean(record.plusModeEnabled);
|
||
}
|
||
|
||
function getPlusContributionPromptTotals(records = []) {
|
||
return (Array.isArray(records) ? records : []).reduce((totals, record) => {
|
||
if (!isSuccessfulPlusAccountRecord(record)) {
|
||
return totals;
|
||
}
|
||
if (record.contributionMode) {
|
||
totals.contributionSuccess += 1;
|
||
} else {
|
||
totals.plusSuccess += 1;
|
||
}
|
||
return totals;
|
||
}, {
|
||
plusSuccess: 0,
|
||
contributionSuccess: 0,
|
||
});
|
||
}
|
||
|
||
function getPlusContributionPromptProgress(records = [], ledger = getPlusContributionPromptLedger()) {
|
||
const totals = getPlusContributionPromptTotals(records);
|
||
const normalizedLedger = normalizePlusContributionPromptLedger(ledger);
|
||
const credit = (totals.contributionSuccess * PLUS_CONTRIBUTION_ACCOUNT_CREDIT)
|
||
+ normalizedLedger.donationCredit;
|
||
const netCount = totals.plusSuccess - credit;
|
||
const sinceLastPrompt = netCount - normalizedLedger.promptBaseline;
|
||
return {
|
||
...totals,
|
||
credit,
|
||
netCount,
|
||
sinceLastPrompt,
|
||
shouldPrompt: sinceLastPrompt >= PLUS_CONTRIBUTION_PROMPT_THRESHOLD,
|
||
};
|
||
}
|
||
|
||
function shouldShowPlusContributionPrompt(records = [], plusModeEnabled = false, ledger = getPlusContributionPromptLedger()) {
|
||
return Boolean(plusModeEnabled)
|
||
&& getPlusContributionPromptProgress(records, ledger).shouldPrompt;
|
||
}
|
||
|
||
function markPlusContributionPromptShown(records = [], ledger = getPlusContributionPromptLedger()) {
|
||
const progress = getPlusContributionPromptProgress(records, ledger);
|
||
const nextLedger = {
|
||
...normalizePlusContributionPromptLedger(ledger),
|
||
promptBaseline: progress.netCount,
|
||
};
|
||
setPlusContributionPromptLedger(nextLedger);
|
||
return nextLedger;
|
||
}
|
||
|
||
function addPlusContributionPromptCredit(credit, ledger = getPlusContributionPromptLedger()) {
|
||
const normalizedLedger = normalizePlusContributionPromptLedger(ledger);
|
||
const nextLedger = {
|
||
...normalizedLedger,
|
||
donationCredit: normalizedLedger.donationCredit + Math.max(0, normalizePlusContributionPromptNumber(credit)),
|
||
};
|
||
setPlusContributionPromptLedger(nextLedger);
|
||
return nextLedger;
|
||
}
|
||
|
||
function getPlusContributionSupportImageUrl() {
|
||
if (typeof chrome !== 'undefined' && chrome.runtime?.getURL) {
|
||
return chrome.runtime.getURL('docs/images/微信.png');
|
||
}
|
||
return '../docs/images/微信.png';
|
||
}
|
||
|
||
function buildPlusContributionSupportPromptHtml() {
|
||
const imageUrl = getPlusContributionSupportImageUrl();
|
||
return [
|
||
'<span class="plus-contribution-prompt-copy">您觉得这个 Plus 功能怎么样?您的账户数量应该已经够个人使用啦。</span>',
|
||
'<span class="plus-contribution-prompt-copy">可以打开贡献给作者贡献几个账号,以便于让作者开发更好的功能出来吗?或者打赏一下作者?</span>',
|
||
`<img class="plus-contribution-prompt-image" src="${escapeHtml(imageUrl)}" alt="微信打赏二维码" />`,
|
||
].join('');
|
||
}
|
||
|
||
function openPlusContributionSupportModal() {
|
||
return openActionModal({
|
||
title: 'Plus 功能使用反馈',
|
||
messageHtml: buildPlusContributionSupportPromptHtml(),
|
||
actions: [
|
||
{ id: null, label: '取消', variant: 'btn-ghost' },
|
||
{ id: 'contribute', label: '去贡献账号', variant: 'btn-outline' },
|
||
{ id: 'donated', label: '已打赏', variant: 'btn-primary' },
|
||
],
|
||
});
|
||
}
|
||
|
||
async function enterContributionModeFromPlusPrompt() {
|
||
if (typeof chrome === 'undefined' || !chrome.runtime?.sendMessage) {
|
||
return null;
|
||
}
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'SET_CONTRIBUTION_MODE',
|
||
source: 'sidepanel',
|
||
payload: { enabled: true },
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
if (response?.state && typeof applySettingsState === 'function') {
|
||
applySettingsState(response.state);
|
||
}
|
||
if (typeof renderContributionMode === 'function') {
|
||
renderContributionMode();
|
||
}
|
||
return response?.state || null;
|
||
}
|
||
|
||
async function maybeShowPlusContributionPromptBeforeAutoRun(plusModeEnabled) {
|
||
const records = Array.isArray(latestState?.accountRunHistory) ? latestState.accountRunHistory : [];
|
||
if (!shouldShowPlusContributionPrompt(records, plusModeEnabled)) {
|
||
return true;
|
||
}
|
||
|
||
const choice = await openPlusContributionSupportModal();
|
||
const ledger = markPlusContributionPromptShown(records);
|
||
if (choice === 'donated') {
|
||
addPlusContributionPromptCredit(PLUS_CONTRIBUTION_DONATION_CREDIT, ledger);
|
||
showToast('感谢打赏支持,已延后下一次 Plus 提醒。', 'success', 2200);
|
||
return true;
|
||
}
|
||
if (choice === 'contribute') {
|
||
openExternalUrl(getContributionPortalUrl());
|
||
try {
|
||
await enterContributionModeFromPlusPrompt();
|
||
showToast('已进入贡献模式,并打开贡献页面。', 'info', 2200);
|
||
} catch (error) {
|
||
showToast(`贡献模式开启失败:${error.message}`, 'error', 2600);
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
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,
|
||
};
|
||
}
|
||
|
||
async function openPlusAutoRunRiskConfirmModal(totalRuns) {
|
||
const result = await openConfirmModalWithOption({
|
||
title: 'Plus 自动轮数提醒',
|
||
message: `Plus 模式下当前设置为 ${totalRuns} 轮。轮数过多可能造成 PayPal 或账号快速封号。建议够用就好:我注册了几个使用,没多注册,完全足够使用,并且没有封号。这个模式下只要可以注册成功就能使用,所以不要贪杯哦。`,
|
||
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);
|
||
}
|
||
|
||
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 paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
||
return String(value || '').trim().toLowerCase() === 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 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 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);
|
||
return autoRunning || isAutoRunSourceSyncPhase(phase);
|
||
}
|
||
|
||
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 = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> 自动';
|
||
}
|
||
|
||
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 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;
|
||
|
||
selectCfDomain.innerHTML = '';
|
||
if (domains.length === 0) {
|
||
const option = document.createElement('option');
|
||
option.value = '';
|
||
option.textContent = '请先添加域名';
|
||
selectCfDomain.appendChild(option);
|
||
selectCfDomain.disabled = true;
|
||
selectCfDomain.value = '';
|
||
return;
|
||
}
|
||
|
||
for (const domain of domains) {
|
||
const option = document.createElement('option');
|
||
option.value = domain;
|
||
option.textContent = domain;
|
||
selectCfDomain.appendChild(option);
|
||
}
|
||
selectCfDomain.disabled = false;
|
||
selectCfDomain.value = domains.includes(selected) ? selected : domains[0];
|
||
}
|
||
|
||
function renderCloudflareTempEmailDomainOptions(preferredDomain = '') {
|
||
const preferred = normalizeCloudflareTempEmailDomainValue(preferredDomain);
|
||
const { domains, activeDomain } = getCloudflareTempEmailDomainsFromState();
|
||
const selected = preferred || activeDomain;
|
||
|
||
selectTempEmailDomain.innerHTML = '';
|
||
if (domains.length === 0) {
|
||
const option = document.createElement('option');
|
||
option.value = '';
|
||
option.textContent = '请先添加域名';
|
||
selectTempEmailDomain.appendChild(option);
|
||
selectTempEmailDomain.disabled = true;
|
||
selectTempEmailDomain.value = '';
|
||
return;
|
||
}
|
||
|
||
for (const domain of domains) {
|
||
const option = document.createElement('option');
|
||
option.value = domain;
|
||
option.textContent = domain;
|
||
selectTempEmailDomain.appendChild(option);
|
||
}
|
||
selectTempEmailDomain.disabled = false;
|
||
selectTempEmailDomain.value = domains.includes(selected) ? selected : domains[0];
|
||
}
|
||
|
||
function setCloudflareDomainEditMode(editing, options = {}) {
|
||
const { clearInput = false } = options;
|
||
cloudflareDomainEditMode = Boolean(editing);
|
||
selectCfDomain.style.display = cloudflareDomainEditMode ? 'none' : '';
|
||
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 = Boolean(editing);
|
||
selectTempEmailDomain.style.display = cloudflareTempEmailDomainEditMode ? 'none' : '';
|
||
inputTempEmailDomain.style.display = cloudflareTempEmailDomainEditMode ? '' : 'none';
|
||
btnTempEmailDomainMode.textContent = cloudflareTempEmailDomainEditMode ? '保存' : '添加';
|
||
if (cloudflareTempEmailDomainEditMode) {
|
||
if (clearInput) {
|
||
inputTempEmailDomain.value = '';
|
||
}
|
||
inputTempEmailDomain.focus();
|
||
} else 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 collectSettingsPayload() {
|
||
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 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 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 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 heroSmsReuseEnabledValue = typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled
|
||
? normalizeHeroSmsReuseEnabledValue(inputHeroSmsReuseEnabled.checked)
|
||
: defaultHeroSmsReuseEnabled;
|
||
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 plusPaymentMethod = getSelectedPlusPaymentMethod();
|
||
return {
|
||
...(contributionModeEnabled ? {} : {
|
||
panelMode: selectPanelMode.value,
|
||
}),
|
||
vpsUrl: inputVpsUrl.value.trim(),
|
||
vpsPassword: inputVpsPassword.value,
|
||
localCpaStep9Mode: getSelectedLocalCpaStep9Mode(),
|
||
sub2apiUrl: inputSub2ApiUrl.value.trim(),
|
||
sub2apiEmail: inputSub2ApiEmail.value.trim(),
|
||
sub2apiPassword: inputSub2ApiPassword.value,
|
||
sub2apiGroupName: inputSub2ApiGroup.value.trim(),
|
||
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: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||
? Boolean(inputPlusModeEnabled.checked)
|
||
: Boolean(latestState?.plusModeEnabled),
|
||
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 || '')),
|
||
...(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,
|
||
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
||
autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value),
|
||
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
||
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
||
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
||
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
|
||
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
|
||
: true,
|
||
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
|
||
signupMethod: selectedSignupMethod,
|
||
phoneSmsProvider: phoneSmsProviderValue,
|
||
verificationResendCount: normalizeVerificationResendCount(
|
||
inputVerificationResendCount?.value,
|
||
DEFAULT_VERIFICATION_RESEND_COUNT
|
||
),
|
||
heroSmsApiKey: heroSmsApiKeyValue,
|
||
fiveSimApiKey: fiveSimApiKeyValue,
|
||
fiveSimCountryOrder: fiveSimCountryOrderValue,
|
||
fiveSimOperator: fiveSimOperatorValue,
|
||
fiveSimProduct: fiveSimProductValue,
|
||
nexSmsApiKey: nexSmsApiKeyValue,
|
||
nexSmsCountryOrder: nexSmsCountryOrderValue,
|
||
nexSmsServiceCode: nexSmsServiceCodeValue,
|
||
heroSmsReuseEnabled: heroSmsReuseEnabledValue,
|
||
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 = '') {
|
||
if (typeof window !== 'undefined' && window.PhoneSmsProviderRegistry?.normalizeProviderId) {
|
||
return window.PhoneSmsProviderRegistry.normalizeProviderId(value);
|
||
}
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
return normalized === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? PHONE_SMS_PROVIDER_FIVE_SIM
|
||
: 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 normalized = String(value || '').trim().toLowerCase();
|
||
if (normalized === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
||
return PHONE_SMS_PROVIDER_FIVE_SIM;
|
||
}
|
||
if (normalized === PHONE_SMS_PROVIDER_NEXSMS) {
|
||
return PHONE_SMS_PROVIDER_NEXSMS;
|
||
}
|
||
return PHONE_SMS_PROVIDER_HERO;
|
||
}
|
||
|
||
function normalizePhoneSmsProviderOrderValue(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) {
|
||
if (value === undefined || value === null) {
|
||
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 || '未获取';
|
||
}
|
||
syncPhoneRuntimeCountdown(state);
|
||
renderPhonePreferredActivationOptions(state);
|
||
}
|
||
|
||
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() === SIGNUP_METHOD_PHONE
|
||
? SIGNUP_METHOD_PHONE
|
||
: SIGNUP_METHOD_EMAIL;
|
||
}
|
||
|
||
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);
|
||
return phoneEnabled && !plusModeEnabled && !contributionModeEnabled;
|
||
}
|
||
|
||
function isSignupMethodSwitchLocked() {
|
||
return isAutoRunLockedPhase() || isAutoRunPausedPhase() || isAutoRunScheduledPhase();
|
||
}
|
||
|
||
function updateSignupMethodUI(options = {}) {
|
||
if (!signupMethodButtons.length) {
|
||
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 = '';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function updatePhoneVerificationSettingsUI() {
|
||
const enabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||
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 = '';
|
||
}
|
||
if (rowHeroSmsPlatform) {
|
||
rowHeroSmsPlatform.style.display = '';
|
||
}
|
||
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 rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null,
|
||
typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null,
|
||
typeof rowHeroSmsCurrentCountdown !== 'undefined' ? rowHeroSmsCurrentCountdown : null,
|
||
typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : null,
|
||
typeof rowHeroSmsPreferredActivation !== 'undefined' ? rowHeroSmsPreferredActivation : 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,
|
||
];
|
||
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 (!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 defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
||
const enabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||
? Boolean(inputPlusModeEnabled.checked)
|
||
: false;
|
||
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||
selectPlusPaymentMethod.value = method;
|
||
if (selectPlusPaymentMethod.style) {
|
||
selectPlusPaymentMethod.style.display = enabled ? '' : 'none';
|
||
}
|
||
}
|
||
if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption) {
|
||
plusPaymentMethodCaption.textContent = method === gopayValue
|
||
? 'GoPay 印尼订阅链路'
|
||
: 'PayPal 订阅链路';
|
||
}
|
||
[
|
||
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;
|
||
}
|
||
const selectedMethod = typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value
|
||
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
||
: method;
|
||
row.style.display = enabled && selectedMethod === paypalValue ? '' : '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;
|
||
}
|
||
const selectedMethod = typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value
|
||
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
||
: method;
|
||
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));
|
||
});
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
async function openPlusManualConfirmationDialog(options = {}) {
|
||
const method = String(options.method || '').trim().toLowerCase();
|
||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||
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';
|
||
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 === gopayValue ? 'GoPay 订阅已确认,正在继续 OAuth 登录...' : '已确认,流程继续执行中...', 'info', 2200);
|
||
} else {
|
||
showToast(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;
|
||
}
|
||
}
|
||
|
||
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 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);
|
||
|
||
inputRunCount.disabled = currentAutoRun.autoRunning || (
|
||
typeof shouldLockRunCountToEmailPool === 'function'
|
||
? shouldLockRunCountToEmailPool()
|
||
: getLockedRunCountFromEmailPool() > 0
|
||
);
|
||
btnAutoRun.disabled = currentAutoRun.autoRunning;
|
||
btnFetchEmail.disabled = locked
|
||
|| isCustomMailProvider()
|
||
|| usesCustomEmailPoolGenerator();
|
||
inputEmail.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 = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
|
||
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) => `
|
||
<div class="step-row" data-step="${step.id}" data-step-key="${escapeHtml(step.key)}">
|
||
<div class="step-indicator" data-step="${step.id}"><span class="step-num">${step.id}</span></div>
|
||
<button class="step-btn" data-step="${step.id}" data-step-key="${escapeHtml(step.key)}">${escapeHtml(step.title)}</button>
|
||
<span class="step-status" data-step="${step.id}"></span>
|
||
</div>
|
||
`).join('');
|
||
|
||
if (stepsProgress) {
|
||
stepsProgress.textContent = `0 / ${STEP_IDS.length}`;
|
||
}
|
||
|
||
initializeManualStepActions();
|
||
renderStepStatuses();
|
||
updateButtonStates();
|
||
}
|
||
|
||
function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOrOptions = {}, maybeOptions = {}) {
|
||
const nextPlusModeEnabled = Boolean(plusModeEnabled);
|
||
const options = typeof plusPaymentMethodOrOptions === 'string'
|
||
? maybeOptions
|
||
: (plusPaymentMethodOrOptions || {});
|
||
const rawPaymentMethod = typeof plusPaymentMethodOrOptions === 'string'
|
||
? plusPaymentMethodOrOptions
|
||
: (options.plusPaymentMethod || getSelectedPlusPaymentMethod(latestState));
|
||
const nextPaymentMethod = normalizePlusPaymentMethod(rawPaymentMethod);
|
||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||
const currentPaymentStep = stepDefinitions.find((step) => step.key === 'paypal-approve');
|
||
const nextPaymentTitle = rootScope.MultiPageStepDefinitions?.getPlusPaymentStepTitle?.({
|
||
plusModeEnabled: nextPlusModeEnabled,
|
||
plusPaymentMethod: nextPaymentMethod,
|
||
});
|
||
const paymentTitleChanged = Boolean(nextPlusModeEnabled && currentPaymentStep && nextPaymentTitle && currentPaymentStep.title !== nextPaymentTitle);
|
||
const shouldRender = Boolean(options.render)
|
||
|| nextPlusModeEnabled !== currentPlusModeEnabled
|
||
|| nextPaymentMethod !== currentPlusPaymentMethod
|
||
|| paymentTitleChanged;
|
||
if (!shouldRender) {
|
||
return;
|
||
}
|
||
|
||
rebuildStepDefinitionState(nextPlusModeEnabled, nextPaymentMethod);
|
||
renderStepsList();
|
||
}
|
||
|
||
// ============================================================
|
||
// State Restore on load
|
||
// ============================================================
|
||
|
||
function applySettingsState(state) {
|
||
if (typeof syncStepDefinitionsForMode === 'function') {
|
||
syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled), state?.plusPaymentMethod);
|
||
}
|
||
const fallbackIpProxyService = '711proxy';
|
||
const fallbackIpProxyMode = 'account';
|
||
const fallbackIpProxyProtocol = 'http';
|
||
const resolveIpProxyService = (value) => (typeof normalizeIpProxyService === 'function'
|
||
? normalizeIpProxyService(value)
|
||
: String(value || fallbackIpProxyService).trim().toLowerCase() || fallbackIpProxyService);
|
||
const resolveIpProxyMode = (value) => {
|
||
if (typeof normalizeIpProxyModeForCurrentRelease === 'function') {
|
||
return normalizeIpProxyModeForCurrentRelease(value);
|
||
}
|
||
if (typeof normalizeIpProxyMode === 'function') {
|
||
return normalizeIpProxyMode(value);
|
||
}
|
||
const normalized = String(value || fallbackIpProxyMode).trim().toLowerCase();
|
||
return normalized || fallbackIpProxyMode;
|
||
};
|
||
const resolveIpProxyProtocol = (value) => (typeof normalizeIpProxyProtocol === 'function'
|
||
? normalizeIpProxyProtocol(value)
|
||
: String(value || fallbackIpProxyProtocol).trim().toLowerCase() || fallbackIpProxyProtocol);
|
||
const resolveIpProxyPort = (value) => {
|
||
if (typeof normalizeIpProxyPort === 'function') {
|
||
return normalizeIpProxyPort(value);
|
||
}
|
||
const numeric = Number.parseInt(String(value || '').trim(), 10);
|
||
return Number.isInteger(numeric) && numeric > 0 && numeric <= 65535 ? numeric : 0;
|
||
};
|
||
const resolveIpProxyAccountList = (value) => (typeof normalizeIpProxyAccountList === 'function'
|
||
? normalizeIpProxyAccountList(value || '')
|
||
: String(value || '').replace(/\r/g, '').trim());
|
||
const resolveIpProxySessionPrefix = (value) => (typeof normalizeIpProxyAccountSessionPrefix === 'function'
|
||
? normalizeIpProxyAccountSessionPrefix(value || '')
|
||
: String(value || '').trim());
|
||
const resolveIpProxyAccountLifeMinutes = (value) => (typeof normalizeIpProxyAccountLifeMinutes === 'function'
|
||
? normalizeIpProxyAccountLifeMinutes(value || '')
|
||
: String(value || '').trim());
|
||
const resolveIpProxyPoolTargetCount = (value) => (typeof normalizeIpProxyPoolTargetCount === 'function'
|
||
? normalizeIpProxyPoolTargetCount(value || '', 20)
|
||
: String(value || '20').trim() || '20');
|
||
const resolveIpProxyAutoSyncEnabled = (value) => Boolean(value);
|
||
const resolveIpProxyAutoSyncIntervalMinutes = (value) => {
|
||
const numeric = Number.parseInt(String(value ?? '').trim(), 10);
|
||
if (!Number.isFinite(numeric)) {
|
||
return 15;
|
||
}
|
||
return Math.max(1, Math.min(1440, numeric));
|
||
};
|
||
syncLatestState(state);
|
||
syncAutoRunState(state);
|
||
renderStepStatuses(latestState);
|
||
|
||
inputEmail.value = state?.email || '';
|
||
syncPasswordField(state || {});
|
||
if (typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled) {
|
||
inputPlusModeEnabled.checked = Boolean(state?.plusModeEnabled);
|
||
}
|
||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
||
}
|
||
if (typeof selectGoPayCountryCode !== 'undefined' && selectGoPayCountryCode) {
|
||
const normalizedGoPayCountryCode = window.GoPayUtils?.normalizeGoPayCountryCode
|
||
? window.GoPayUtils.normalizeGoPayCountryCode(state?.gopayCountryCode)
|
||
: String(state?.gopayCountryCode || '+86').trim();
|
||
const hasOption = Array.from(selectGoPayCountryCode.options || [])
|
||
.some((option) => option.value === normalizedGoPayCountryCode);
|
||
if (!hasOption && normalizedGoPayCountryCode) {
|
||
const option = document.createElement('option');
|
||
option.value = normalizedGoPayCountryCode;
|
||
option.textContent = `自定义 ${normalizedGoPayCountryCode}`;
|
||
selectGoPayCountryCode.appendChild(option);
|
||
}
|
||
selectGoPayCountryCode.value = normalizedGoPayCountryCode || '+86';
|
||
}
|
||
if (typeof inputGoPayPhone !== 'undefined' && inputGoPayPhone) {
|
||
inputGoPayPhone.value = state?.gopayPhone || '';
|
||
}
|
||
if (typeof inputGoPayOtp !== 'undefined' && inputGoPayOtp) {
|
||
inputGoPayOtp.value = state?.gopayOtp || '';
|
||
}
|
||
if (typeof inputGoPayPin !== 'undefined' && inputGoPayPin) {
|
||
inputGoPayPin.value = state?.gopayPin || '';
|
||
}
|
||
inputVpsUrl.value = state?.vpsUrl || '';
|
||
inputVpsPassword.value = state?.vpsPassword || '';
|
||
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
|
||
selectPanelMode.value = state?.panelMode || 'cpa';
|
||
inputSub2ApiUrl.value = state?.sub2apiUrl || '';
|
||
inputSub2ApiEmail.value = state?.sub2apiEmail || '';
|
||
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
||
inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
|
||
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
||
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
||
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
||
? normalizeIpProxyServiceProfiles(state?.ipProxyServiceProfiles || {}, state || {})
|
||
: (state?.ipProxyServiceProfiles || {});
|
||
const activeIpProxyProfile = typeof getIpProxyServiceProfile === 'function'
|
||
? getIpProxyServiceProfile(normalizedIpProxyService, {
|
||
...(state || {}),
|
||
ipProxyService: normalizedIpProxyService,
|
||
ipProxyServiceProfiles: normalizedIpProxyServiceProfiles,
|
||
})
|
||
: {
|
||
mode: resolveIpProxyMode(state?.ipProxyMode),
|
||
apiUrl: String(state?.ipProxyApiUrl || '').trim(),
|
||
accountList: resolveIpProxyAccountList(state?.ipProxyAccountList || ''),
|
||
accountSessionPrefix: resolveIpProxySessionPrefix(state?.ipProxyAccountSessionPrefix || ''),
|
||
accountLifeMinutes: resolveIpProxyAccountLifeMinutes(state?.ipProxyAccountLifeMinutes || ''),
|
||
poolTargetCount: resolveIpProxyPoolTargetCount(state?.ipProxyPoolTargetCount || ''),
|
||
host: String(state?.ipProxyHost || '').trim(),
|
||
port: String(resolveIpProxyPort(state?.ipProxyPort || '') || ''),
|
||
protocol: resolveIpProxyProtocol(state?.ipProxyProtocol),
|
||
username: String(state?.ipProxyUsername || '').trim(),
|
||
password: String(state?.ipProxyPassword || ''),
|
||
region: String(state?.ipProxyRegion || '').trim(),
|
||
};
|
||
if (typeof selectIpProxyService !== 'undefined' && selectIpProxyService) {
|
||
selectIpProxyService.value = normalizedIpProxyService;
|
||
}
|
||
if (typeof inputIpProxyApiUrl !== 'undefined' && inputIpProxyApiUrl) {
|
||
inputIpProxyApiUrl.value = String(activeIpProxyProfile.apiUrl || '').trim();
|
||
}
|
||
if (typeof inputIpProxyAccountList !== 'undefined' && inputIpProxyAccountList) {
|
||
inputIpProxyAccountList.value = activeIpProxyProfile.accountList;
|
||
}
|
||
if (typeof inputIpProxyAccountSessionPrefix !== 'undefined' && inputIpProxyAccountSessionPrefix) {
|
||
inputIpProxyAccountSessionPrefix.value = activeIpProxyProfile.accountSessionPrefix;
|
||
}
|
||
if (typeof inputIpProxyAccountLifeMinutes !== 'undefined' && inputIpProxyAccountLifeMinutes) {
|
||
inputIpProxyAccountLifeMinutes.value = activeIpProxyProfile.accountLifeMinutes;
|
||
}
|
||
if (typeof inputIpProxyPoolTargetCount !== 'undefined' && inputIpProxyPoolTargetCount) {
|
||
inputIpProxyPoolTargetCount.value = activeIpProxyProfile.poolTargetCount;
|
||
}
|
||
if (typeof inputIpProxyAutoSyncEnabled !== 'undefined' && inputIpProxyAutoSyncEnabled) {
|
||
inputIpProxyAutoSyncEnabled.checked = resolveIpProxyAutoSyncEnabled(state?.ipProxyAutoSyncEnabled);
|
||
}
|
||
if (typeof inputIpProxyAutoSyncIntervalMinutes !== 'undefined' && inputIpProxyAutoSyncIntervalMinutes) {
|
||
inputIpProxyAutoSyncIntervalMinutes.value = String(
|
||
resolveIpProxyAutoSyncIntervalMinutes(state?.ipProxyAutoSyncIntervalMinutes)
|
||
);
|
||
}
|
||
if (typeof inputIpProxyHost !== 'undefined' && inputIpProxyHost) {
|
||
inputIpProxyHost.value = activeIpProxyProfile.host;
|
||
}
|
||
if (typeof inputIpProxyPort !== 'undefined' && inputIpProxyPort) {
|
||
const normalizedPort = resolveIpProxyPort(activeIpProxyProfile.port || '');
|
||
inputIpProxyPort.value = normalizedPort > 0 ? String(normalizedPort) : '';
|
||
}
|
||
if (typeof selectIpProxyProtocol !== 'undefined' && selectIpProxyProtocol) {
|
||
selectIpProxyProtocol.value = resolveIpProxyProtocol(activeIpProxyProfile.protocol);
|
||
}
|
||
if (typeof inputIpProxyUsername !== 'undefined' && inputIpProxyUsername) {
|
||
inputIpProxyUsername.value = activeIpProxyProfile.username;
|
||
}
|
||
if (typeof inputIpProxyPassword !== 'undefined' && inputIpProxyPassword) {
|
||
inputIpProxyPassword.value = activeIpProxyProfile.password;
|
||
}
|
||
if (typeof inputIpProxyRegion !== 'undefined' && inputIpProxyRegion) {
|
||
inputIpProxyRegion.value = activeIpProxyProfile.region;
|
||
}
|
||
if (typeof setIpProxyMode === 'function') {
|
||
setIpProxyMode(activeIpProxyProfile.mode);
|
||
}
|
||
if (typeof setIpProxyEnabled === 'function') {
|
||
setIpProxyEnabled(Boolean(state?.ipProxyEnabled));
|
||
}
|
||
syncLatestState({
|
||
ipProxyService: normalizedIpProxyService,
|
||
ipProxyServiceProfiles: normalizedIpProxyServiceProfiles,
|
||
...(typeof buildIpProxyStatePatchFromServiceProfile === 'function'
|
||
? buildIpProxyStatePatchFromServiceProfile(normalizedIpProxyService, activeIpProxyProfile)
|
||
: {}),
|
||
});
|
||
if (typeof updateIpProxyUI === 'function') {
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
|
||
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
|
||
const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
|
||
|| [ICLOUD_PROVIDER, 'hotmail-api', GMAIL_PROVIDER, 'luckmail-api', '163', '163-vip', '126', 'qq', 'inbucket', '2925', 'cloudflare-temp-email'].includes(String(state?.mailProvider || '').trim())
|
||
? String(state?.mailProvider || '163').trim()
|
||
: (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom'
|
||
|| String(state?.emailGenerator || '').trim().toLowerCase() === 'manual'
|
||
? 'custom'
|
||
: '163');
|
||
selectMailProvider.value = restoredMailProvider;
|
||
setMail2925Mode(state?.mail2925Mode);
|
||
{
|
||
const restoredEmailGenerator = String(state?.emailGenerator || '').trim().toLowerCase();
|
||
if (restoredMailProvider === GMAIL_PROVIDER) {
|
||
selectEmailGenerator.value = restoredEmailGenerator === CUSTOM_EMAIL_POOL_GENERATOR
|
||
? CUSTOM_EMAIL_POOL_GENERATOR
|
||
: GMAIL_ALIAS_GENERATOR;
|
||
} else if (restoredEmailGenerator === CUSTOM_EMAIL_POOL_GENERATOR) {
|
||
selectEmailGenerator.value = CUSTOM_EMAIL_POOL_GENERATOR;
|
||
} else if (restoredEmailGenerator === 'icloud') {
|
||
selectEmailGenerator.value = 'icloud';
|
||
} else if (restoredEmailGenerator === 'cloudflare') {
|
||
selectEmailGenerator.value = 'cloudflare';
|
||
} else if (restoredEmailGenerator === 'cloudflare-temp-email') {
|
||
selectEmailGenerator.value = 'cloudflare-temp-email';
|
||
} else {
|
||
selectEmailGenerator.value = 'duck';
|
||
}
|
||
}
|
||
if (selectIcloudHostPreference) {
|
||
selectIcloudHostPreference.value = String(state?.icloudHostPreference || '').trim().toLowerCase() === 'icloud.com'
|
||
? 'icloud.com'
|
||
: (String(state?.icloudHostPreference || '').trim().toLowerCase() === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
||
}
|
||
if (selectIcloudFetchMode) {
|
||
selectIcloudFetchMode.value = normalizeIcloudFetchMode(state?.icloudFetchMode);
|
||
}
|
||
if (selectIcloudTargetMailboxType) {
|
||
selectIcloudTargetMailboxType.value = normalizeIcloudTargetMailboxType(state?.icloudTargetMailboxType);
|
||
}
|
||
if (selectIcloudForwardMailProvider) {
|
||
selectIcloudForwardMailProvider.value = normalizeIcloudForwardMailProvider(state?.icloudForwardMailProvider);
|
||
}
|
||
if (checkboxAutoDeleteIcloud) {
|
||
checkboxAutoDeleteIcloud.checked = Boolean(state?.autoDeleteUsedIcloudAlias);
|
||
}
|
||
if (inputAccountRunHistoryHelperBaseUrl) {
|
||
inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(state?.accountRunHistoryHelperBaseUrl);
|
||
}
|
||
if (inputContributionNickname) {
|
||
inputContributionNickname.value = state?.contributionNickname || '';
|
||
}
|
||
if (inputContributionQq) {
|
||
inputContributionQq.value = state?.contributionQq || '';
|
||
}
|
||
if (inputMail2925UseAccountPool) {
|
||
inputMail2925UseAccountPool.checked = Boolean(state?.mail2925UseAccountPool);
|
||
}
|
||
setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
|
||
inputInbucketHost.value = state?.inbucketHost || '';
|
||
inputInbucketMailbox.value = state?.inbucketMailbox || '';
|
||
if (inputCustomMailProviderPool) {
|
||
inputCustomMailProviderPool.value = normalizeCustomEmailPoolEntries(state?.customMailProviderPool).join('\n');
|
||
}
|
||
const restoredCustomEmailPoolEntries = typeof restoreCustomEmailPoolEntriesFromState === 'function'
|
||
? restoreCustomEmailPoolEntriesFromState(state)
|
||
: normalizeCustomEmailPoolEntries(state?.customEmailPool);
|
||
if (typeof setCustomEmailPoolEntriesState === 'function') {
|
||
setCustomEmailPoolEntriesState(restoredCustomEmailPoolEntries);
|
||
} else if (inputCustomEmailPool) {
|
||
inputCustomEmailPool.value = restoredCustomEmailPoolEntries.join('\n');
|
||
}
|
||
setHotmailServiceMode(state?.hotmailServiceMode);
|
||
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
|
||
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
|
||
inputLuckmailApiKey.value = state?.luckmailApiKey || '';
|
||
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(state?.luckmailBaseUrl);
|
||
selectLuckmailEmailType.value = normalizeLuckmailEmailType(state?.luckmailEmailType);
|
||
inputLuckmailDomain.value = state?.luckmailDomain || '';
|
||
applyCloudflareTempEmailSettingsState(state);
|
||
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
||
setCloudflareDomainEditMode(false, { clearInput: true });
|
||
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
||
inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
|
||
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
||
if (typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
|
||
inputOAuthFlowTimeoutEnabled.checked = state?.oauthFlowTimeoutEnabled !== undefined
|
||
? Boolean(state.oauthFlowTimeoutEnabled)
|
||
: true;
|
||
}
|
||
if (inputVerificationResendCount) {
|
||
const restoredVerificationResendCount = state?.verificationResendCount !== undefined
|
||
? state.verificationResendCount
|
||
: (state?.signupVerificationResendCount ?? state?.loginVerificationResendCount);
|
||
inputVerificationResendCount.value = String(
|
||
normalizeVerificationResendCount(restoredVerificationResendCount, DEFAULT_VERIFICATION_RESEND_COUNT)
|
||
);
|
||
}
|
||
if (inputPhoneVerificationEnabled) {
|
||
inputPhoneVerificationEnabled.checked = state?.phoneVerificationEnabled !== undefined
|
||
? Boolean(state.phoneVerificationEnabled)
|
||
: DEFAULT_PHONE_VERIFICATION_ENABLED;
|
||
}
|
||
if (typeof setSignupMethod === 'function') {
|
||
setSignupMethod(state?.signupMethod || DEFAULT_SIGNUP_METHOD);
|
||
}
|
||
const restoredPhoneSmsProvider = normalizePhoneSmsProvider(state?.phoneSmsProvider);
|
||
const previousPhoneSmsProvider = selectPhoneSmsProvider ? normalizePhoneSmsProvider(selectPhoneSmsProvider.value) : restoredPhoneSmsProvider;
|
||
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';
|
||
setPhoneSmsProviderSelectValue(restoredPhoneSmsProvider);
|
||
if (previousPhoneSmsProvider !== restoredPhoneSmsProvider) {
|
||
heroSmsCountrySelectionOrder = [];
|
||
loadHeroSmsCountries().catch((error) => {
|
||
console.warn('Failed to reload SMS countries after provider restore:', error);
|
||
});
|
||
}
|
||
if (inputHeroSmsApiKey) {
|
||
inputHeroSmsApiKey.value = restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? (state?.fiveSimApiKey || '')
|
||
: (state?.heroSmsApiKey || '');
|
||
}
|
||
if (typeof inputFiveSimApiKey !== 'undefined' && inputFiveSimApiKey) {
|
||
inputFiveSimApiKey.value = String(state?.fiveSimApiKey || '');
|
||
}
|
||
if (typeof inputFiveSimOperator !== 'undefined' && inputFiveSimOperator) {
|
||
inputFiveSimOperator.value = typeof normalizeFiveSimOperatorValue === 'function'
|
||
? normalizeFiveSimOperatorValue(state?.fiveSimOperator || DEFAULT_FIVE_SIM_OPERATOR)
|
||
: normalizeFiveSimOperator(state?.fiveSimOperator || DEFAULT_FIVE_SIM_OPERATOR);
|
||
}
|
||
if (typeof inputFiveSimProduct !== 'undefined' && inputFiveSimProduct) {
|
||
inputFiveSimProduct.value = typeof normalizeFiveSimProductValue === 'function'
|
||
? normalizeFiveSimProductValue(state?.fiveSimProduct || defaultFiveSimProduct)
|
||
: String(state?.fiveSimProduct || defaultFiveSimProduct).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultFiveSimProduct;
|
||
}
|
||
if (typeof inputNexSmsApiKey !== 'undefined' && inputNexSmsApiKey) {
|
||
inputNexSmsApiKey.value = String(state?.nexSmsApiKey || '');
|
||
}
|
||
if (typeof inputNexSmsServiceCode !== 'undefined' && inputNexSmsServiceCode) {
|
||
inputNexSmsServiceCode.value = typeof normalizeNexSmsServiceCodeValue === 'function'
|
||
? normalizeNexSmsServiceCodeValue(state?.nexSmsServiceCode || defaultNexSmsServiceCode)
|
||
: String(state?.nexSmsServiceCode || defaultNexSmsServiceCode).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || defaultNexSmsServiceCode;
|
||
}
|
||
if (typeof inputHeroSmsReuseEnabled !== 'undefined' && inputHeroSmsReuseEnabled) {
|
||
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(state?.heroSmsReuseEnabled);
|
||
}
|
||
if (typeof selectHeroSmsAcquirePriority !== 'undefined' && selectHeroSmsAcquirePriority) {
|
||
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(state?.heroSmsAcquirePriority);
|
||
}
|
||
if (inputHeroSmsMaxPrice) {
|
||
inputHeroSmsMaxPrice.value = restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimMaxPriceValue(state?.fiveSimMaxPrice || '')
|
||
: normalizeHeroSmsMaxPriceValue(state?.heroSmsMaxPrice || '');
|
||
}
|
||
if (inputFiveSimOperator) {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperator(state?.fiveSimOperator);
|
||
}
|
||
if (typeof inputHeroSmsPreferredPrice !== 'undefined' && inputHeroSmsPreferredPrice) {
|
||
inputHeroSmsPreferredPrice.value = normalizeHeroSmsMaxPriceValue(state?.heroSmsPreferredPrice || '');
|
||
}
|
||
if (typeof inputPhoneReplacementLimit !== 'undefined' && inputPhoneReplacementLimit) {
|
||
inputPhoneReplacementLimit.value = String(
|
||
normalizePhoneVerificationReplacementLimit(
|
||
state?.phoneVerificationReplacementLimit,
|
||
DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT
|
||
)
|
||
);
|
||
}
|
||
if (typeof inputPhoneCodeWaitSeconds !== 'undefined' && inputPhoneCodeWaitSeconds) {
|
||
inputPhoneCodeWaitSeconds.value = String(
|
||
normalizePhoneCodeWaitSecondsValue(state?.phoneCodeWaitSeconds, DEFAULT_PHONE_CODE_WAIT_SECONDS)
|
||
);
|
||
}
|
||
if (typeof inputPhoneCodeTimeoutWindows !== 'undefined' && inputPhoneCodeTimeoutWindows) {
|
||
inputPhoneCodeTimeoutWindows.value = String(
|
||
normalizePhoneCodeTimeoutWindowsValue(state?.phoneCodeTimeoutWindows, DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS)
|
||
);
|
||
}
|
||
if (typeof inputPhoneCodePollIntervalSeconds !== 'undefined' && inputPhoneCodePollIntervalSeconds) {
|
||
inputPhoneCodePollIntervalSeconds.value = String(
|
||
normalizePhoneCodePollIntervalSecondsValue(
|
||
state?.phoneCodePollIntervalSeconds,
|
||
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS
|
||
)
|
||
);
|
||
}
|
||
if (typeof inputPhoneCodePollMaxRounds !== 'undefined' && inputPhoneCodePollMaxRounds) {
|
||
inputPhoneCodePollMaxRounds.value = String(
|
||
normalizePhoneCodePollMaxRoundsValue(state?.phoneCodePollMaxRounds, DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS)
|
||
);
|
||
}
|
||
if (typeof applyHeroSmsFallbackSelection === 'function') {
|
||
const primaryCountry = restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? {
|
||
id: normalizeFiveSimCountryId(state?.fiveSimCountryId),
|
||
label: normalizeFiveSimCountryLabel(state?.fiveSimCountryLabel),
|
||
}
|
||
: {
|
||
id: normalizeHeroSmsCountryId(state?.heroSmsCountryId),
|
||
label: normalizeHeroSmsCountryLabel(state?.heroSmsCountryLabel),
|
||
};
|
||
applyHeroSmsFallbackSelection(
|
||
[
|
||
primaryCountry,
|
||
...(restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimCountryFallbackList(state?.fiveSimCountryFallback || [])
|
||
: normalizeHeroSmsCountryFallbackList(state?.heroSmsCountryFallback || [])),
|
||
],
|
||
{ includePrimary: true }
|
||
);
|
||
updateHeroSmsPlatformDisplay();
|
||
} else if (selectHeroSmsCountry) {
|
||
const restoredCountryId = restoredPhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? String(normalizeFiveSimCountryId(state?.fiveSimCountryId))
|
||
: String(normalizeHeroSmsCountryId(state?.heroSmsCountryId));
|
||
if (Array.from(selectHeroSmsCountry.options).some((option) => option.value === restoredCountryId)) {
|
||
selectHeroSmsCountry.value = restoredCountryId;
|
||
} else {
|
||
Array.from(selectHeroSmsCountry.options).forEach((option) => {
|
||
option.selected = false;
|
||
});
|
||
}
|
||
updateHeroSmsPlatformDisplay();
|
||
}
|
||
updateHeroSmsPlatformDisplay();
|
||
if (typeof updateHeroSmsRuntimeDisplay === 'function') {
|
||
updateHeroSmsRuntimeDisplay(state);
|
||
}
|
||
const isSyncPhase = typeof isAutoRunSourceSyncPhase === 'function'
|
||
? isAutoRunSourceSyncPhase
|
||
: (phase) => ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase);
|
||
const shouldSyncInitialRunCount = typeof shouldSyncRunCountFromAutoRunSource === 'function'
|
||
? shouldSyncRunCountFromAutoRunSource(state)
|
||
: (Boolean(state?.autoRunning) || isSyncPhase(state?.autoRunPhase ?? state?.phase));
|
||
if (state?.autoRunTotalRuns && shouldSyncInitialRunCount) {
|
||
inputRunCount.value = String(state.autoRunTotalRuns);
|
||
}
|
||
|
||
applyAutoRunStatus(state);
|
||
markSettingsDirty(false);
|
||
updateAutoDelayInputState();
|
||
updateFallbackThreadIntervalInputState();
|
||
updateAccountRunHistorySettingsUI();
|
||
updatePhoneVerificationSettingsUI();
|
||
if (typeof renderPayPalAccounts === 'function') {
|
||
renderPayPalAccounts();
|
||
}
|
||
if (typeof updatePlusModeUI === 'function') {
|
||
updatePlusModeUI();
|
||
}
|
||
updatePanelModeUI();
|
||
updateMailProviderUI();
|
||
if (typeof queueCustomEmailPoolRefresh === 'function') {
|
||
queueCustomEmailPoolRefresh();
|
||
}
|
||
if (isLuckmailProvider(state?.mailProvider)) {
|
||
if (typeof queueLuckmailPurchaseRefresh === 'function') {
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
}
|
||
updateButtonStates();
|
||
if (typeof syncPlusManualConfirmationDialog === 'function') {
|
||
void syncPlusManualConfirmationDialog();
|
||
}
|
||
}
|
||
|
||
async function restoreState() {
|
||
try {
|
||
const state = await chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' });
|
||
applySettingsState(state);
|
||
if (getSelectedEmailGenerator() === 'icloud' && icloudSection?.style.display !== 'none') {
|
||
refreshIcloudAliases({ silent: true }).catch(() => { });
|
||
}
|
||
|
||
if (state.oauthUrl) {
|
||
displayOauthUrl.textContent = state.oauthUrl;
|
||
displayOauthUrl.classList.add('has-value');
|
||
}
|
||
if (state.localhostUrl) {
|
||
displayLocalhostUrl.textContent = state.localhostUrl;
|
||
displayLocalhostUrl.classList.add('has-value');
|
||
}
|
||
if (state.stepStatuses) {
|
||
for (const [step, status] of Object.entries(state.stepStatuses)) {
|
||
updateStepUI(Number(step), status);
|
||
}
|
||
}
|
||
|
||
if (state.logs) {
|
||
for (const entry of state.logs) {
|
||
appendLog(entry);
|
||
}
|
||
}
|
||
|
||
updateStatusDisplay(latestState);
|
||
updateProgressCounter();
|
||
renderContributionMode();
|
||
} catch (err) {
|
||
console.error('Failed to restore state:', err);
|
||
}
|
||
}
|
||
|
||
function openExternalUrl(url) {
|
||
const targetUrl = String(url || '').trim();
|
||
if (!targetUrl) {
|
||
return;
|
||
}
|
||
|
||
if (chrome?.tabs?.create) {
|
||
chrome.tabs.create({ url: targetUrl, active: true }).catch(() => {
|
||
window.open(targetUrl, '_blank', 'noopener');
|
||
});
|
||
return;
|
||
}
|
||
|
||
window.open(targetUrl, '_blank', 'noopener');
|
||
}
|
||
|
||
function getRepositoryHomeUrl() {
|
||
const serviceRepositoryUrl = String(sidepanelUpdateService?.repositoryUrl || '').trim();
|
||
if (serviceRepositoryUrl) {
|
||
return serviceRepositoryUrl;
|
||
}
|
||
|
||
const releasesPageUrl = String(sidepanelUpdateService?.releasesPageUrl || '').trim();
|
||
if (releasesPageUrl) {
|
||
return releasesPageUrl.replace(/\/releases\/?$/, '');
|
||
}
|
||
|
||
return 'https://github.com/QLHazyCoder/codex-oauth-automation-extension';
|
||
}
|
||
|
||
function getReleaseListUrl() {
|
||
const snapshotReleaseListUrl = String(currentReleaseSnapshot?.releasesPageUrl || '').trim();
|
||
if (snapshotReleaseListUrl) {
|
||
return snapshotReleaseListUrl;
|
||
}
|
||
|
||
const serviceReleaseListUrl = String(sidepanelUpdateService?.releasesPageUrl || '').trim();
|
||
if (serviceReleaseListUrl) {
|
||
return serviceReleaseListUrl;
|
||
}
|
||
|
||
return `${getRepositoryHomeUrl()}/releases`;
|
||
}
|
||
|
||
function openRepositoryHomePage() {
|
||
openExternalUrl(getRepositoryHomeUrl());
|
||
}
|
||
|
||
function openReleaseListPage() {
|
||
openExternalUrl(getReleaseListUrl());
|
||
}
|
||
|
||
function ignoreCurrentReleaseUpdate() {
|
||
if (!sidepanelUpdateService?.ignoreReleaseSnapshot) {
|
||
return;
|
||
}
|
||
|
||
const ignoredVersion = sidepanelUpdateService.ignoreReleaseSnapshot(currentReleaseSnapshot);
|
||
if (!ignoredVersion) {
|
||
return;
|
||
}
|
||
|
||
renderReleaseSnapshot({
|
||
...currentReleaseSnapshot,
|
||
status: 'ignored',
|
||
ignoredVersion,
|
||
});
|
||
showToast(`已忽略 ${ignoredVersion} 更新,有新版本时会再次提醒。`, 'info', 2200);
|
||
}
|
||
|
||
function openCloudflareTempEmailUsageGuidePage() {
|
||
const targetUrl = getContributionPortalUrl();
|
||
if (!targetUrl) {
|
||
return;
|
||
}
|
||
openExternalUrl(targetUrl);
|
||
}
|
||
|
||
function openCloudflareTempEmailRepositoryPage() {
|
||
openExternalUrl(CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL);
|
||
}
|
||
|
||
function createUpdateNoteList(notes = []) {
|
||
if (!Array.isArray(notes) || notes.length === 0) {
|
||
const empty = document.createElement('p');
|
||
empty.className = 'update-release-empty';
|
||
empty.textContent = '该版本未提供可解析的更新说明,请查看完整更新日志。';
|
||
return empty;
|
||
}
|
||
|
||
const list = document.createElement('ul');
|
||
list.className = 'update-release-notes';
|
||
|
||
notes.forEach((note) => {
|
||
const item = document.createElement('li');
|
||
item.textContent = note;
|
||
list.appendChild(item);
|
||
});
|
||
|
||
return list;
|
||
}
|
||
|
||
function renderUpdateReleaseList(releases = []) {
|
||
if (!updateReleaseList) {
|
||
return;
|
||
}
|
||
|
||
updateReleaseList.innerHTML = '';
|
||
|
||
releases.forEach((release) => {
|
||
const item = document.createElement('article');
|
||
item.className = 'update-release-item';
|
||
|
||
const head = document.createElement('div');
|
||
head.className = 'update-release-head';
|
||
|
||
const titleRow = document.createElement('div');
|
||
titleRow.className = 'update-release-title-row';
|
||
|
||
const version = document.createElement('span');
|
||
version.className = 'update-release-version';
|
||
version.textContent = release.displayVersion || `Ultra${release.version}`;
|
||
titleRow.appendChild(version);
|
||
|
||
if (release.title) {
|
||
const name = document.createElement('span');
|
||
name.className = 'update-release-name';
|
||
name.textContent = release.title;
|
||
titleRow.appendChild(name);
|
||
}
|
||
|
||
head.appendChild(titleRow);
|
||
|
||
const publishedAt = sidepanelUpdateService?.formatReleaseDate?.(release.publishedAt) || '';
|
||
if (publishedAt) {
|
||
const date = document.createElement('span');
|
||
date.className = 'update-release-date';
|
||
date.textContent = publishedAt;
|
||
head.appendChild(date);
|
||
}
|
||
|
||
item.appendChild(head);
|
||
item.appendChild(createUpdateNoteList(release.notes));
|
||
updateReleaseList.appendChild(item);
|
||
});
|
||
}
|
||
|
||
function resetUpdateCard() {
|
||
if (updateSection) {
|
||
updateSection.hidden = true;
|
||
}
|
||
if (updateCardVersion) {
|
||
updateCardVersion.textContent = '';
|
||
}
|
||
if (updateCardSummary) {
|
||
updateCardSummary.textContent = '';
|
||
}
|
||
if (updateReleaseList) {
|
||
updateReleaseList.innerHTML = '';
|
||
}
|
||
if (btnOpenRelease) {
|
||
btnOpenRelease.hidden = true;
|
||
btnOpenRelease.onclick = null;
|
||
}
|
||
if (btnIgnoreRelease) {
|
||
btnIgnoreRelease.hidden = true;
|
||
btnIgnoreRelease.onclick = null;
|
||
}
|
||
}
|
||
|
||
function renderReleaseSnapshot(snapshot) {
|
||
currentReleaseSnapshot = snapshot;
|
||
|
||
if (!extensionUpdateStatus || !extensionVersionMeta) {
|
||
return;
|
||
}
|
||
|
||
extensionUpdateStatus.classList.remove('is-update-available', 'is-check-failed', 'is-version-label');
|
||
|
||
const localVersionText = snapshot?.localVersion || '';
|
||
const logUrl = snapshot?.logUrl || snapshot?.releasesPageUrl || sidepanelUpdateService?.releasesPageUrl || '';
|
||
|
||
if (btnReleaseLog) {
|
||
btnReleaseLog.onclick = () => openExternalUrl(logUrl);
|
||
btnReleaseLog.hidden = true;
|
||
}
|
||
extensionVersionMeta.hidden = true;
|
||
extensionVersionMeta.textContent = '';
|
||
|
||
switch (snapshot?.status) {
|
||
case 'update-available': {
|
||
extensionUpdateStatus.textContent = '有更新';
|
||
extensionUpdateStatus.classList.add('is-update-available');
|
||
if (btnReleaseLog) {
|
||
btnReleaseLog.hidden = false;
|
||
}
|
||
|
||
if (updateSection) {
|
||
updateSection.hidden = false;
|
||
}
|
||
if (updateCardVersion) {
|
||
updateCardVersion.textContent = `最新版本 ${snapshot.latestVersion}`;
|
||
}
|
||
if (updateCardSummary) {
|
||
const updateCount = Array.isArray(snapshot.newerReleases) ? snapshot.newerReleases.length : 0;
|
||
updateCardSummary.textContent = updateCount > 1
|
||
? `当前 ${localVersionText},共有 ${updateCount} 个新版本可更新。`
|
||
: `当前 ${localVersionText},可更新到 ${snapshot.latestVersion}。`;
|
||
}
|
||
renderUpdateReleaseList(snapshot.newerReleases || []);
|
||
if (btnOpenRelease) {
|
||
btnOpenRelease.hidden = false;
|
||
btnOpenRelease.textContent = '前往更新';
|
||
btnOpenRelease.onclick = () => openExternalUrl(logUrl);
|
||
}
|
||
if (btnIgnoreRelease) {
|
||
btnIgnoreRelease.hidden = false;
|
||
btnIgnoreRelease.onclick = ignoreCurrentReleaseUpdate;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 'ignored': {
|
||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||
extensionUpdateStatus.classList.add('is-version-label');
|
||
resetUpdateCard();
|
||
break;
|
||
}
|
||
|
||
case 'latest': {
|
||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||
extensionUpdateStatus.classList.add('is-version-label');
|
||
resetUpdateCard();
|
||
break;
|
||
}
|
||
|
||
case 'empty': {
|
||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||
extensionUpdateStatus.classList.add('is-version-label');
|
||
resetUpdateCard();
|
||
break;
|
||
}
|
||
|
||
case 'error':
|
||
default: {
|
||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||
extensionUpdateStatus.classList.add('is-version-label', 'is-check-failed');
|
||
extensionVersionMeta.textContent = snapshot?.errorMessage || 'GitHub Releases 检查失败';
|
||
extensionVersionMeta.hidden = false;
|
||
resetUpdateCard();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function initializeReleaseInfo() {
|
||
const fallbackReleaseUrl = sidepanelUpdateService?.releasesPageUrl || 'https://github.com/QLHazyCoder/codex-oauth-automation-extension/releases';
|
||
|
||
if (btnReleaseLog) {
|
||
btnReleaseLog.onclick = () => openExternalUrl(currentReleaseSnapshot?.logUrl || fallbackReleaseUrl);
|
||
}
|
||
|
||
if (!extensionUpdateStatus || !extensionVersionMeta) {
|
||
return;
|
||
}
|
||
|
||
const localVersion = sidepanelUpdateService?.getLocalVersionLabel?.(chrome.runtime.getManifest())
|
||
|| chrome.runtime.getManifest()?.version_name
|
||
|| (chrome.runtime.getManifest()?.version ? `Ultra${chrome.runtime.getManifest().version}` : '');
|
||
extensionUpdateStatus.textContent = localVersion || 'Ultra0.0';
|
||
extensionUpdateStatus.classList.remove('is-update-available', 'is-check-failed');
|
||
extensionUpdateStatus.classList.add('is-version-label');
|
||
extensionVersionMeta.hidden = true;
|
||
extensionVersionMeta.textContent = '';
|
||
if (btnReleaseLog) {
|
||
btnReleaseLog.hidden = true;
|
||
}
|
||
resetUpdateCard();
|
||
|
||
if (!sidepanelUpdateService) {
|
||
extensionVersionMeta.textContent = '更新检查服务不可用';
|
||
extensionVersionMeta.hidden = false;
|
||
return;
|
||
}
|
||
|
||
const snapshot = await sidepanelUpdateService.getReleaseSnapshot();
|
||
renderReleaseSnapshot(snapshot);
|
||
}
|
||
|
||
function getContributionUpdateHintMessage(snapshot = currentContributionContentSnapshot) {
|
||
const lines = getContributionUpdatePromptLines(snapshot);
|
||
if (!lines.length) {
|
||
return '';
|
||
}
|
||
if (lines.length === 1) {
|
||
return lines[0];
|
||
}
|
||
return lines.map((line, index) => `${index + 1}. ${line}`).join('\n');
|
||
}
|
||
|
||
function getContributionUpdatePromptLines(snapshot = currentContributionContentSnapshot) {
|
||
if (!snapshot?.promptVersion) {
|
||
return [];
|
||
}
|
||
|
||
const items = Array.isArray(snapshot.items) ? snapshot.items : [];
|
||
const autoRunNoticeItem = items.find((item) =>
|
||
item
|
||
&& String(item.slug || '').trim().toLowerCase() === 'auto_run_notice'
|
||
);
|
||
if (autoRunNoticeItem) {
|
||
const noticeText = String(autoRunNoticeItem.text || '').trim();
|
||
return autoRunNoticeItem.isVisible && noticeText ? [noticeText] : [];
|
||
}
|
||
|
||
const hasAnnouncementOrTutorial = items.some((item) =>
|
||
item
|
||
&& item.isVisible
|
||
&& ['announcement', 'tutorial'].includes(String(item.slug || '').trim().toLowerCase())
|
||
);
|
||
const hasQuestionnaire = items.some((item) =>
|
||
item
|
||
&& item.isVisible
|
||
&& String(item.slug || '').trim().toLowerCase() === 'questionnaire'
|
||
);
|
||
|
||
const lines = [];
|
||
if (hasAnnouncementOrTutorial) {
|
||
lines.push('公告 / 使用教程有更新了,可点上方“贡献/使用”查看。');
|
||
}
|
||
if (hasQuestionnaire) {
|
||
lines.push('有新的征求意见,请佬友共同参与选择。');
|
||
}
|
||
return lines;
|
||
}
|
||
|
||
function positionContributionUpdateHint() {
|
||
if (!contributionUpdateLayer || !contributionUpdateHint || !btnContributionMode) {
|
||
return;
|
||
}
|
||
if (contributionUpdateLayer.hidden || contributionUpdateHint.hidden) {
|
||
return;
|
||
}
|
||
|
||
const buttonRect = btnContributionMode.getBoundingClientRect();
|
||
const viewportWidth = Math.max(document.documentElement?.clientWidth || 0, window.innerWidth || 0);
|
||
const viewportHeight = Math.max(document.documentElement?.clientHeight || 0, window.innerHeight || 0);
|
||
const hintWidth = contributionUpdateHint.offsetWidth || 220;
|
||
const hintHeight = contributionUpdateHint.offsetHeight || 56;
|
||
const viewportPadding = 12;
|
||
const gap = 10;
|
||
|
||
const maxLeft = Math.max(viewportPadding, viewportWidth - hintWidth - viewportPadding);
|
||
const left = Math.min(Math.max(viewportPadding, Math.round(buttonRect.left)), maxLeft);
|
||
const shouldPlaceAbove = (buttonRect.bottom + gap + hintHeight) > (viewportHeight - viewportPadding)
|
||
&& buttonRect.top > (hintHeight + gap + viewportPadding);
|
||
const top = shouldPlaceAbove
|
||
? Math.max(viewportPadding, Math.round(buttonRect.top - hintHeight - gap))
|
||
: Math.max(viewportPadding, Math.round(buttonRect.bottom + gap));
|
||
const buttonCenter = Math.round(buttonRect.left + (buttonRect.width / 2));
|
||
const arrowOffset = Math.min(Math.max(16, buttonCenter - left), Math.max(16, hintWidth - 16));
|
||
|
||
contributionUpdateHint.style.left = `${left}px`;
|
||
contributionUpdateHint.style.top = `${top}px`;
|
||
contributionUpdateHint.style.setProperty('--contribution-update-arrow-left', `${arrowOffset}px`);
|
||
}
|
||
|
||
function shouldShowContributionUpdateHint(snapshot = currentContributionContentSnapshot) {
|
||
const promptVersion = String(snapshot?.promptVersion || '').trim();
|
||
if (!contributionUpdateLayer || !contributionUpdateHint || !contributionUpdateHintText || !btnContributionMode) {
|
||
return false;
|
||
}
|
||
if (!promptVersion) {
|
||
return false;
|
||
}
|
||
if (!getContributionUpdatePromptLines(snapshot).length) {
|
||
return false;
|
||
}
|
||
if (promptVersion === getDismissedContributionContentPromptVersion()) {
|
||
return false;
|
||
}
|
||
if (latestState?.contributionMode) {
|
||
return false;
|
||
}
|
||
return !btnContributionMode.disabled;
|
||
}
|
||
|
||
function renderContributionUpdateHint(snapshot = currentContributionContentSnapshot) {
|
||
if (!contributionUpdateLayer || !contributionUpdateHint) {
|
||
return;
|
||
}
|
||
|
||
const visible = shouldShowContributionUpdateHint(snapshot);
|
||
contributionUpdateLayer.hidden = !visible;
|
||
contributionUpdateHint.hidden = !visible;
|
||
if (!visible || !contributionUpdateHintText) {
|
||
return;
|
||
}
|
||
|
||
contributionUpdateHintText.textContent = getContributionUpdateHintMessage(snapshot);
|
||
if (typeof window.requestAnimationFrame === 'function') {
|
||
window.requestAnimationFrame(() => positionContributionUpdateHint());
|
||
return;
|
||
}
|
||
positionContributionUpdateHint();
|
||
}
|
||
|
||
function dismissContributionUpdateHint() {
|
||
const promptVersion = String(currentContributionContentSnapshot?.promptVersion || '').trim();
|
||
if (promptVersion) {
|
||
setDismissedContributionContentPromptVersion(promptVersion);
|
||
}
|
||
renderContributionUpdateHint();
|
||
}
|
||
|
||
async function refreshContributionContentHint() {
|
||
if (!contributionContentService?.getContentUpdateSnapshot) {
|
||
currentContributionContentSnapshot = null;
|
||
renderContributionUpdateHint();
|
||
return null;
|
||
}
|
||
if (contributionContentSnapshotRequestInFlight) {
|
||
return contributionContentSnapshotRequestInFlight;
|
||
}
|
||
|
||
contributionContentSnapshotRequestInFlight = contributionContentService.getContentUpdateSnapshot()
|
||
.then((snapshot) => {
|
||
currentContributionContentSnapshot = snapshot;
|
||
renderContributionUpdateHint(snapshot);
|
||
return snapshot;
|
||
})
|
||
.catch((error) => {
|
||
currentContributionContentSnapshot = null;
|
||
renderContributionUpdateHint(null);
|
||
throw error;
|
||
})
|
||
.finally(() => {
|
||
contributionContentSnapshotRequestInFlight = null;
|
||
});
|
||
|
||
return contributionContentSnapshotRequestInFlight;
|
||
}
|
||
|
||
function syncPasswordField(state) {
|
||
inputPassword.value = state?.contributionMode ? '' : (state.customPassword || state.password || '');
|
||
}
|
||
|
||
function isCustomMailProvider(provider = selectMailProvider.value) {
|
||
return String(provider || '').trim().toLowerCase() === 'custom';
|
||
}
|
||
|
||
function isLuckmailProvider(provider = selectMailProvider.value) {
|
||
return String(provider || '').trim().toLowerCase() === LUCKMAIL_PROVIDER;
|
||
}
|
||
|
||
function isIcloudMailProvider(provider = selectMailProvider.value) {
|
||
return String(provider || '').trim().toLowerCase() === ICLOUD_PROVIDER;
|
||
}
|
||
|
||
function normalizeLuckmailBaseUrl(value = '') {
|
||
const trimmed = String(value || '').trim();
|
||
if (!trimmed) {
|
||
return DEFAULT_LUCKMAIL_BASE_URL;
|
||
}
|
||
|
||
try {
|
||
const parsed = new URL(trimmed);
|
||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||
return DEFAULT_LUCKMAIL_BASE_URL;
|
||
}
|
||
parsed.pathname = parsed.pathname.replace(/\/+$/, '');
|
||
parsed.search = '';
|
||
parsed.hash = '';
|
||
return parsed.toString().replace(/\/$/, '');
|
||
} catch {
|
||
return DEFAULT_LUCKMAIL_BASE_URL;
|
||
}
|
||
}
|
||
|
||
function normalizeLuckmailEmailType(value = '') {
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(normalized)
|
||
? normalized
|
||
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
|
||
}
|
||
|
||
function getSelectedEmailGenerator() {
|
||
const generator = String(selectEmailGenerator.value || '').trim().toLowerCase();
|
||
if (generator === 'custom' || generator === 'manual') {
|
||
return 'custom';
|
||
}
|
||
if (generator === GMAIL_ALIAS_GENERATOR) {
|
||
return GMAIL_ALIAS_GENERATOR;
|
||
}
|
||
if (generator === CUSTOM_EMAIL_POOL_GENERATOR) {
|
||
return CUSTOM_EMAIL_POOL_GENERATOR;
|
||
}
|
||
if (generator === 'icloud') {
|
||
return 'icloud';
|
||
}
|
||
if (generator === 'cloudflare') return 'cloudflare';
|
||
if (generator === 'cloudflare-temp-email') return 'cloudflare-temp-email';
|
||
return 'duck';
|
||
}
|
||
|
||
function getEmailGeneratorUiCopy() {
|
||
if (getSelectedEmailGenerator() === 'custom') {
|
||
return getCustomMailProviderUiCopy();
|
||
}
|
||
if (getSelectedEmailGenerator() === GMAIL_ALIAS_GENERATOR) {
|
||
return {
|
||
buttonLabel: '生成',
|
||
placeholder: '步骤 3 自动生成 Gmail +tag 邮箱并回填',
|
||
successVerb: '生成',
|
||
label: 'Gmail +tag 邮箱',
|
||
};
|
||
}
|
||
if (getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR) {
|
||
return {
|
||
buttonLabel: '取下一个',
|
||
placeholder: '按邮箱池顺序自动回填,也可以手动粘贴当前轮邮箱',
|
||
successVerb: '取用',
|
||
label: '自定义邮箱池',
|
||
};
|
||
}
|
||
if (getSelectedEmailGenerator() === 'icloud') {
|
||
return {
|
||
buttonLabel: '获取',
|
||
placeholder: '点击获取 iCloud 隐私邮箱,或手动粘贴邮箱',
|
||
successVerb: '获取',
|
||
label: 'iCloud 隐私邮箱',
|
||
};
|
||
}
|
||
if (getSelectedEmailGenerator() === 'cloudflare') {
|
||
return {
|
||
buttonLabel: '生成',
|
||
placeholder: '点击生成 Cloudflare 邮箱,或手动粘贴邮箱',
|
||
successVerb: '生成',
|
||
label: 'Cloudflare 邮箱',
|
||
};
|
||
}
|
||
if (getSelectedEmailGenerator() === 'cloudflare-temp-email') {
|
||
return {
|
||
buttonLabel: '生成 Temp',
|
||
placeholder: '点击生成 Cloudflare Temp Email,或手动粘贴邮箱',
|
||
successVerb: '生成',
|
||
label: 'Cloudflare Temp Email',
|
||
};
|
||
}
|
||
|
||
return {
|
||
buttonLabel: '获取',
|
||
placeholder: '点击获取 DuckDuckGo 邮箱,或手动粘贴邮箱',
|
||
successVerb: '获取',
|
||
label: 'Duck 邮箱',
|
||
};
|
||
}
|
||
|
||
function getCustomMailProviderUiCopy() {
|
||
if (usesCustomMailProviderPool()) {
|
||
return {
|
||
buttonLabel: '自定义邮箱',
|
||
placeholder: '号池会按顺序自动回填,也可以手动覆盖当前轮邮箱',
|
||
successVerb: '使用',
|
||
label: '自定义邮箱',
|
||
};
|
||
}
|
||
return {
|
||
buttonLabel: '自定义邮箱',
|
||
placeholder: '请填写本轮要使用的注册邮箱',
|
||
successVerb: '使用',
|
||
label: '自定义邮箱',
|
||
};
|
||
}
|
||
|
||
function getCustomVerificationPromptCopy(step) {
|
||
const verificationLabel = step === 4 ? '注册验证码' : '登录验证码';
|
||
const isLoginVerificationStep = step === 8 || step === 11;
|
||
return {
|
||
title: `手动处理${verificationLabel}`,
|
||
message: `当前邮箱服务为“自定义邮箱”。请先在页面中手动输入${verificationLabel},并确认已经进入下一页面后,再点击确认。`,
|
||
alert: {
|
||
text: `点击确认后会跳过步骤 ${step}。`,
|
||
tone: 'danger',
|
||
},
|
||
...(isLoginVerificationStep ? {
|
||
phoneActionLabel: '出现手机号验证',
|
||
phoneActionAlert: {
|
||
text: '如果当前页面已经进入手机号验证,可直接标记为失败并继续下一个邮箱。',
|
||
tone: 'danger',
|
||
},
|
||
} : {}),
|
||
};
|
||
}
|
||
|
||
function normalizeGoPayOtpInputValue(value = '') {
|
||
return window.GoPayUtils?.normalizeGoPayOtp
|
||
? window.GoPayUtils.normalizeGoPayOtp(value)
|
||
: String(value || '').trim().replace(/[^\d]/g, '');
|
||
}
|
||
|
||
async function openGoPayOtpInputDialog(payload = {}) {
|
||
if (!sharedFormDialog?.open) {
|
||
throw new Error('验证码输入弹窗未加载,请刷新扩展后重试。');
|
||
}
|
||
|
||
const initialCode = normalizeGoPayOtpInputValue(payload.code || inputGoPayOtp?.value || latestState?.gopayOtp || '');
|
||
const result = await sharedFormDialog.open({
|
||
title: '输入 GoPay 验证码',
|
||
message: '请把当前 GoPay 页面收到的验证码填到这里,确认后插件会继续填写验证码并进入 PIN 步骤。',
|
||
confirmLabel: '提交验证码',
|
||
confirmVariant: 'btn-primary',
|
||
fields: [
|
||
{
|
||
key: 'code',
|
||
label: '验证码',
|
||
type: 'text',
|
||
required: true,
|
||
requiredMessage: '请输入 GoPay 验证码。',
|
||
placeholder: '请输入数字验证码',
|
||
inputMode: 'numeric',
|
||
autocomplete: 'one-time-code',
|
||
value: initialCode,
|
||
validate: (value) => {
|
||
const normalized = normalizeGoPayOtpInputValue(value);
|
||
if (!normalized) return '请输入 GoPay 验证码。';
|
||
if (normalized.length < 4) return 'GoPay 验证码长度过短,请检查。';
|
||
return '';
|
||
},
|
||
},
|
||
],
|
||
});
|
||
const code = normalizeGoPayOtpInputValue(result?.code || '');
|
||
if (!code) {
|
||
return { cancelled: true, code: '' };
|
||
}
|
||
if (inputGoPayOtp) {
|
||
inputGoPayOtp.value = code;
|
||
}
|
||
syncLatestState({ gopayOtp: code });
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
return { code };
|
||
}
|
||
|
||
async function openCustomVerificationConfirmDialog(step) {
|
||
const promptCopy = getCustomVerificationPromptCopy(step);
|
||
if (step === 8 || step === 11) {
|
||
return openActionModal({
|
||
title: promptCopy.title,
|
||
message: promptCopy.message,
|
||
alert: promptCopy.alert,
|
||
actions: [
|
||
{ id: null, label: '取消', variant: 'btn-ghost' },
|
||
{ id: 'add_phone', label: promptCopy.phoneActionLabel || '出现手机号验证', variant: 'btn-outline' },
|
||
{ id: 'confirm', label: '确认跳过', variant: 'btn-danger' },
|
||
],
|
||
buildResult: (choice) => ({
|
||
confirmed: choice === 'confirm',
|
||
addPhoneDetected: choice === 'add_phone',
|
||
}),
|
||
});
|
||
}
|
||
|
||
const confirmed = await openConfirmModal({
|
||
title: promptCopy.title,
|
||
message: promptCopy.message,
|
||
confirmLabel: '确认跳过',
|
||
confirmVariant: 'btn-danger',
|
||
alert: promptCopy.alert,
|
||
});
|
||
return { confirmed, addPhoneDetected: false };
|
||
}
|
||
|
||
function getHotmailAccounts(state = latestState) {
|
||
return Array.isArray(state?.hotmailAccounts) ? state.hotmailAccounts : [];
|
||
}
|
||
|
||
function getCurrentHotmailAccount(state = latestState) {
|
||
const currentId = state?.currentHotmailAccountId;
|
||
return getHotmailAccounts(state).find((account) => account.id === currentId) || null;
|
||
}
|
||
|
||
function getCurrentHotmailEmail(state = latestState) {
|
||
return String(getCurrentHotmailAccount(state)?.email || '').trim();
|
||
}
|
||
|
||
function getMail2925Accounts(state = latestState) {
|
||
return Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
|
||
}
|
||
|
||
function getCurrentMail2925Account(state = latestState) {
|
||
const currentId = state?.currentMail2925AccountId;
|
||
return getMail2925Accounts(state).find((account) => account.id === currentId) || null;
|
||
}
|
||
|
||
function getCurrentMail2925Email(state = latestState) {
|
||
return String(getCurrentMail2925Account(state)?.email || '').trim();
|
||
}
|
||
|
||
function getPayPalAccounts(state = latestState) {
|
||
return Array.isArray(state?.paypalAccounts) ? state.paypalAccounts : [];
|
||
}
|
||
|
||
function getCurrentPayPalAccount(state = latestState) {
|
||
const currentId = String(state?.currentPayPalAccountId || '').trim();
|
||
return getPayPalAccounts(state).find((account) => account.id === currentId) || null;
|
||
}
|
||
|
||
function syncMail2925BaseEmailFromCurrentAccount(state = latestState, options = {}) {
|
||
const { persist = false } = options;
|
||
if (!isMail2925AccountPoolEnabled(state)) {
|
||
return false;
|
||
}
|
||
|
||
const currentEmail = getCurrentMail2925Email(state);
|
||
if (!currentEmail || currentEmail === String(state?.mail2925BaseEmail || '').trim()) {
|
||
return false;
|
||
}
|
||
|
||
syncLatestState({ mail2925BaseEmail: currentEmail });
|
||
if (persist) {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function getCurrentLuckmailPurchase(state = latestState) {
|
||
return state?.currentLuckmailPurchase || null;
|
||
}
|
||
|
||
function getCurrentLuckmailEmail(state = latestState) {
|
||
return String(getCurrentLuckmailPurchase(state)?.email_address || '').trim();
|
||
}
|
||
|
||
function getLuckmailUsedPurchases(state = latestState) {
|
||
const rawValue = state?.luckmailUsedPurchases;
|
||
if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
|
||
return {};
|
||
}
|
||
|
||
return Object.entries(rawValue).reduce((result, [key, value]) => {
|
||
const numeric = Number(key);
|
||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||
return result;
|
||
}
|
||
result[String(Math.floor(numeric))] = Boolean(value);
|
||
return result;
|
||
}, {});
|
||
}
|
||
|
||
function normalizeLuckmailProjectName(value = '') {
|
||
return String(value || '').trim().toLowerCase();
|
||
}
|
||
|
||
function getLuckmailPreserveTagName(state = latestState) {
|
||
return String(state?.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME;
|
||
}
|
||
|
||
function formatLuckmailDateTime(value) {
|
||
const timestamp = normalizeLuckmailTimestampValue(value);
|
||
if (!timestamp) {
|
||
return String(value || '').trim() || '未知';
|
||
}
|
||
return new Date(timestamp).toLocaleString('zh-CN', {
|
||
hour12: false,
|
||
timeZone: DISPLAY_TIMEZONE,
|
||
});
|
||
}
|
||
|
||
function getMailProviderLoginConfig(provider = selectMailProvider.value) {
|
||
return MAIL_PROVIDER_LOGIN_CONFIGS[String(provider || '').trim()] || null;
|
||
}
|
||
|
||
function getSelectedIcloudHostPreference() {
|
||
return normalizeIcloudHost(selectIcloudHostPreference?.value || latestState?.icloudHostPreference || '')
|
||
|| normalizeIcloudHost(latestState?.preferredIcloudHost)
|
||
|| 'icloud.com';
|
||
}
|
||
|
||
function getMailProviderLoginUrl(provider = selectMailProvider.value) {
|
||
const config = getMailProviderLoginConfig(provider);
|
||
if (String(provider || '').trim() === ICLOUD_PROVIDER) {
|
||
return getIcloudLoginUrlForHost(getSelectedIcloudHostPreference());
|
||
}
|
||
const url = String(config?.url || '').trim();
|
||
return url ? url : '';
|
||
}
|
||
|
||
function getIpProxyServiceLoginConfig(service = selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE) {
|
||
return IP_PROXY_SERVICE_LOGIN_CONFIGS[String(service || '').trim()] || null;
|
||
}
|
||
|
||
function getIpProxyServiceLoginUrl(service = selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE) {
|
||
const config = getIpProxyServiceLoginConfig(service);
|
||
const url = String(config?.url || '').trim();
|
||
return url ? url : '';
|
||
}
|
||
|
||
function isCurrentEmailManagedByHotmail(state = latestState) {
|
||
const hotmailEmail = getCurrentHotmailEmail(state);
|
||
if (!hotmailEmail) {
|
||
return false;
|
||
}
|
||
|
||
const inputEmailValue = String(inputEmail.value || '').trim();
|
||
const stateEmailValue = String(state?.email || '').trim();
|
||
return inputEmailValue === hotmailEmail || stateEmailValue === hotmailEmail;
|
||
}
|
||
|
||
function isCurrentEmailManagedByLuckmail(state = latestState) {
|
||
const luckmailEmail = getCurrentLuckmailEmail(state);
|
||
if (!luckmailEmail) {
|
||
return false;
|
||
}
|
||
|
||
const inputEmailValue = String(inputEmail.value || '').trim();
|
||
const stateEmailValue = String(state?.email || '').trim();
|
||
return inputEmailValue === luckmailEmail || stateEmailValue === luckmailEmail;
|
||
}
|
||
|
||
function isCurrentEmailManagedByGeneratedAlias(
|
||
provider = latestState?.mailProvider,
|
||
state = latestState,
|
||
mail2925Mode = latestState?.mail2925Mode
|
||
) {
|
||
const normalizedProvider = String(provider || '').trim();
|
||
if (!usesGeneratedAliasMailProvider(normalizedProvider, mail2925Mode)) {
|
||
return false;
|
||
}
|
||
|
||
const inputEmailValue = String(inputEmail.value || '').trim().toLowerCase();
|
||
const stateEmailValue = String(state?.email || '').trim().toLowerCase();
|
||
const baseEmail = getManagedAliasBaseEmailForProvider(normalizedProvider, state);
|
||
return isManagedAliasEmail(inputEmailValue, baseEmail, normalizedProvider)
|
||
|| isManagedAliasEmail(stateEmailValue, baseEmail, normalizedProvider);
|
||
}
|
||
|
||
async function maybeClearGeneratedAliasAfterEmailPrefixChange() {
|
||
const provider = selectMailProvider.value;
|
||
if (!usesGeneratedAliasMailProvider(provider, latestState?.mail2925Mode)) {
|
||
return;
|
||
}
|
||
|
||
const previousPrefix = getManagedAliasBaseEmailForProvider(provider, latestState);
|
||
const nextPrefix = inputEmailPrefix.value.trim();
|
||
if (previousPrefix === nextPrefix) {
|
||
return;
|
||
}
|
||
|
||
if (!previousPrefix) {
|
||
return;
|
||
}
|
||
|
||
if (!isCurrentEmailManagedByGeneratedAlias(provider, latestState, latestState?.mail2925Mode)) {
|
||
return;
|
||
}
|
||
|
||
await clearRegistrationEmail({ silent: true });
|
||
}
|
||
|
||
function updateMailLoginButtonState() {
|
||
if (!btnMailLogin) {
|
||
return;
|
||
}
|
||
|
||
const config = getMailProviderLoginConfig();
|
||
const loginUrl = getMailProviderLoginUrl();
|
||
btnMailLogin.disabled = !loginUrl;
|
||
btnMailLogin.textContent = config?.buttonLabel || '登录';
|
||
btnMailLogin.title = loginUrl ? `打开 ${config.label} 登录页` : '当前邮箱服务没有可跳转的登录页';
|
||
}
|
||
|
||
function updateIpProxyServiceLoginButtonState(options = {}) {
|
||
if (!btnIpProxyServiceLogin) {
|
||
return;
|
||
}
|
||
const service = normalizeIpProxyService(
|
||
options?.service
|
||
|| selectIpProxyService?.value
|
||
|| latestState?.ipProxyService
|
||
|| DEFAULT_IP_PROXY_SERVICE
|
||
);
|
||
const loginConfig = getIpProxyServiceLoginConfig(service);
|
||
const loginUrl = getIpProxyServiceLoginUrl(service);
|
||
const enabled = options?.enabled !== undefined
|
||
? Boolean(options.enabled)
|
||
: Boolean(getSelectedIpProxyEnabled());
|
||
btnIpProxyServiceLogin.disabled = !enabled || !loginUrl;
|
||
const buttonLabel = loginConfig?.buttonLabel || '登录';
|
||
btnIpProxyServiceLogin.textContent = buttonLabel;
|
||
btnIpProxyServiceLogin.title = loginUrl
|
||
? `打开 ${loginConfig?.label || service} ${buttonLabel}页`
|
||
: '当前代理服务没有可跳转的登录页';
|
||
}
|
||
|
||
function updateMailProviderUI() {
|
||
const normalizeIcloudHostValue = typeof normalizeIcloudHost === 'function'
|
||
? normalizeIcloudHost
|
||
: ((value) => {
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : '';
|
||
});
|
||
const icloudTargetMailboxTypeValue = typeof selectIcloudTargetMailboxType !== 'undefined'
|
||
? selectIcloudTargetMailboxType?.value
|
||
: latestState?.icloudTargetMailboxType;
|
||
const icloudForwardMailProviderValue = typeof selectIcloudForwardMailProvider !== 'undefined'
|
||
? selectIcloudForwardMailProvider?.value
|
||
: latestState?.icloudForwardMailProvider;
|
||
const icloudHostPreferenceValue = typeof selectIcloudHostPreference !== 'undefined'
|
||
? selectIcloudHostPreference?.value
|
||
: latestState?.icloudHostPreference;
|
||
const use2925 = selectMailProvider.value === '2925';
|
||
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
|
||
const useMail2925 = selectMailProvider.value === '2925';
|
||
const useMail2925AccountPool = useMail2925 && Boolean(inputMail2925UseAccountPool?.checked);
|
||
const mail2925Mode = getSelectedMail2925Mode();
|
||
const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string'
|
||
? GMAIL_ALIAS_GENERATOR
|
||
: 'gmail-alias';
|
||
const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
|
||
? CUSTOM_EMAIL_POOL_GENERATOR
|
||
: 'custom-pool';
|
||
const gmailOnlyGenerators = new Set([gmailAliasGenerator, customEmailPoolGenerator]);
|
||
Array.from(selectEmailGenerator?.options || []).forEach((option) => {
|
||
if (!option) return;
|
||
if (useGmail) {
|
||
option.hidden = !gmailOnlyGenerators.has(String(option.value || '').trim().toLowerCase());
|
||
return;
|
||
}
|
||
option.hidden = String(option.value || '').trim().toLowerCase() === gmailAliasGenerator;
|
||
});
|
||
if (useGmail && !gmailOnlyGenerators.has(String(selectEmailGenerator.value || '').trim().toLowerCase())) {
|
||
selectEmailGenerator.value = gmailAliasGenerator;
|
||
}
|
||
if (!useGmail && String(selectEmailGenerator.value || '').trim().toLowerCase() === gmailAliasGenerator) {
|
||
selectEmailGenerator.value = 'duck';
|
||
}
|
||
const selectedGenerator = getSelectedEmailGenerator();
|
||
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode, selectedGenerator);
|
||
const useInbucket = selectMailProvider.value === 'inbucket';
|
||
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
||
const useLuckmail = isLuckmailProvider();
|
||
const useCustomEmail = isCustomMailProvider();
|
||
const useCustomMailProviderPool = useCustomEmail && usesCustomMailProviderPool(selectMailProvider.value);
|
||
const useIcloudProvider = isIcloudMailProvider();
|
||
const useEmailGenerator = !useHotmail && !useLuckmail && !useCustomEmail && (!useGeneratedAlias || useGmail);
|
||
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
|
||
const aliasUiCopy = useGeneratedAlias
|
||
? getManagedAliasProviderUiCopy(selectMailProvider.value, mail2925Mode)
|
||
: null;
|
||
const uiCopy = getCurrentRegistrationEmailUiCopy();
|
||
updateMailLoginButtonState();
|
||
if (rowMail2925Mode) {
|
||
rowMail2925Mode.style.display = use2925 ? '' : 'none';
|
||
}
|
||
if (rowMail2925PoolSettings) {
|
||
rowMail2925PoolSettings.style.display = useMail2925 ? '' : 'none';
|
||
}
|
||
if (typeof rowCustomMailProviderPool !== 'undefined' && rowCustomMailProviderPool) {
|
||
rowCustomMailProviderPool.style.display = useCustomEmail ? '' : 'none';
|
||
}
|
||
rowEmailPrefix.style.display = useGeneratedAlias && !useMail2925AccountPool ? '' : 'none';
|
||
const hotmailServiceMode = getSelectedHotmailServiceMode();
|
||
rowInbucketHost.style.display = useInbucket ? '' : 'none';
|
||
rowInbucketMailbox.style.display = useInbucket ? '' : 'none';
|
||
const useCustomEmailPool = useEmailGenerator && selectedGenerator === customEmailPoolGenerator;
|
||
const useCloudflare = selectedGenerator === 'cloudflare';
|
||
const useIcloud = selectedGenerator === 'icloud';
|
||
const useCloudflareTempEmailGenerator = selectedGenerator === 'cloudflare-temp-email';
|
||
const showCloudflareDomain = useEmailGenerator && useCloudflare;
|
||
const showCloudflareTempEmailSettings = useCloudflareTempEmailProvider || (useEmailGenerator && useCloudflareTempEmailGenerator);
|
||
const showCloudflareTempEmailReceiveMailbox = useCloudflareTempEmailProvider && !useCloudflareTempEmailGenerator;
|
||
const selectedIcloudHost = typeof getSelectedIcloudHostPreference === 'function'
|
||
? getSelectedIcloudHostPreference()
|
||
: (normalizeIcloudHostValue(icloudHostPreferenceValue || latestState?.icloudHostPreference || '')
|
||
|| normalizeIcloudHostValue(latestState?.preferredIcloudHost)
|
||
|| 'icloud.com');
|
||
const icloudTargetMailboxType = normalizeIcloudTargetMailboxType(icloudTargetMailboxTypeValue);
|
||
const isIcloudComCnHost = selectedIcloudHost === 'icloud.com.cn';
|
||
const showIcloudTargetMailboxType = useIcloudProvider;
|
||
const showIcloudForwardMailProvider = useIcloudProvider && icloudTargetMailboxType === 'forward-mailbox';
|
||
const showCloudflareTempEmailRandomSubdomainToggle = useEmailGenerator && useCloudflareTempEmailGenerator;
|
||
const showCloudflareTempEmailDomain = useEmailGenerator && useCloudflareTempEmailGenerator;
|
||
if (rowEmailGenerator) {
|
||
rowEmailGenerator.style.display = useEmailGenerator ? '' : 'none';
|
||
}
|
||
if (typeof rowCustomEmailPool !== 'undefined' && rowCustomEmailPool) {
|
||
rowCustomEmailPool.style.display = useCustomEmailPool ? '' : 'none';
|
||
if (useCustomEmailPool) {
|
||
queueCustomEmailPoolRefresh();
|
||
} else {
|
||
resetCustomEmailPoolManager();
|
||
}
|
||
}
|
||
if (cloudflareTempEmailSection) {
|
||
cloudflareTempEmailSection.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||
}
|
||
if (icloudSection) {
|
||
const showIcloudSection = (useEmailGenerator && useIcloud) || useIcloudProvider;
|
||
icloudSection.style.display = showIcloudSection ? '' : 'none';
|
||
if (showIcloudSection) {
|
||
queueIcloudAliasRefresh();
|
||
}
|
||
if (!showIcloudSection) {
|
||
hideIcloudLoginHelp();
|
||
}
|
||
}
|
||
if (typeof rowIcloudTargetMailboxType !== 'undefined' && rowIcloudTargetMailboxType) {
|
||
rowIcloudTargetMailboxType.style.display = showIcloudTargetMailboxType ? '' : 'none';
|
||
}
|
||
if (typeof rowIcloudForwardMailProvider !== 'undefined' && rowIcloudForwardMailProvider) {
|
||
rowIcloudForwardMailProvider.style.display = showIcloudForwardMailProvider ? '' : 'none';
|
||
}
|
||
rowCfDomain.style.display = showCloudflareDomain ? '' : 'none';
|
||
const { domains } = getCloudflareDomainsFromState();
|
||
if (showCloudflareDomain) {
|
||
setCloudflareDomainEditMode(cloudflareDomainEditMode || domains.length === 0, { clearInput: false });
|
||
} else {
|
||
setCloudflareDomainEditMode(false, { clearInput: false });
|
||
}
|
||
rowTempEmailBaseUrl.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||
rowTempEmailAdminAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||
rowTempEmailCustomAuth.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||
rowTempEmailReceiveMailbox.style.display = showCloudflareTempEmailReceiveMailbox ? '' : 'none';
|
||
if (rowTempEmailRandomSubdomainToggle) {
|
||
rowTempEmailRandomSubdomainToggle.style.display = showCloudflareTempEmailRandomSubdomainToggle ? '' : 'none';
|
||
}
|
||
rowTempEmailDomain.style.display = showCloudflareTempEmailDomain ? '' : 'none';
|
||
const { domains: tempEmailDomains } = getCloudflareTempEmailDomainsFromState();
|
||
if (showCloudflareTempEmailDomain) {
|
||
setCloudflareTempEmailDomainEditMode(cloudflareTempEmailDomainEditMode || tempEmailDomains.length === 0, { clearInput: false });
|
||
} else {
|
||
setCloudflareTempEmailDomainEditMode(false, { clearInput: false });
|
||
}
|
||
|
||
if (hotmailSection) {
|
||
hotmailSection.style.display = useHotmail ? '' : 'none';
|
||
}
|
||
if (mail2925Section) {
|
||
mail2925Section.style.display = useMail2925AccountPool ? '' : 'none';
|
||
}
|
||
if (luckmailSection) {
|
||
luckmailSection.style.display = useLuckmail ? '' : 'none';
|
||
}
|
||
labelEmailPrefix.textContent = '邮箱前缀';
|
||
inputEmailPrefix.placeholder = '例如 abc';
|
||
if (labelMail2925UseAccountPool) {
|
||
labelMail2925UseAccountPool.style.display = useMail2925 ? '' : 'none';
|
||
}
|
||
syncMail2925PoolAccountOptions(latestState);
|
||
if (selectMail2925PoolAccount) {
|
||
selectMail2925PoolAccount.style.display = useMail2925AccountPool ? '' : 'none';
|
||
selectMail2925PoolAccount.disabled = !useMail2925AccountPool || getMail2925Accounts().length === 0;
|
||
}
|
||
inputEmailPrefix.style.display = '';
|
||
inputEmailPrefix.readOnly = false;
|
||
selectEmailGenerator.disabled = useHotmail || useLuckmail || useCustomEmail || (useGeneratedAlias && !useGmail);
|
||
if (useGmail) {
|
||
labelEmailPrefix.textContent = 'Gmail 原邮箱';
|
||
inputEmailPrefix.placeholder = '例如 yourname@gmail.com';
|
||
}
|
||
labelEmailPrefix.textContent = aliasUiCopy?.baseLabel || labelEmailPrefix.textContent;
|
||
inputEmailPrefix.placeholder = aliasUiCopy?.basePlaceholder || inputEmailPrefix.placeholder;
|
||
if (rowHotmailServiceMode) {
|
||
rowHotmailServiceMode.style.display = useHotmail ? '' : 'none';
|
||
}
|
||
if (rowHotmailRemoteBaseUrl) {
|
||
rowHotmailRemoteBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_REMOTE ? '' : 'none';
|
||
}
|
||
if (rowHotmailLocalBaseUrl) {
|
||
rowHotmailLocalBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_LOCAL ? '' : 'none';
|
||
}
|
||
btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail || useCustomEmailPool;
|
||
inputEmail.readOnly = useHotmail || useLuckmail;
|
||
inputEmail.placeholder = useHotmail
|
||
? '由 Hotmail 账号池自动分配'
|
||
: (useLuckmail
|
||
? '步骤 3 自动购买 LuckMail 邮箱并回填'
|
||
: (useGeneratedAlias ? '步骤 3 自动生成 2925 邮箱并回填' : uiCopy.placeholder));
|
||
if (useGmail && useGeneratedAlias) {
|
||
inputEmail.placeholder = '步骤 3 自动生成 Gmail +tag 邮箱并回填';
|
||
}
|
||
if (!useHotmail && !useLuckmail) {
|
||
inputEmail.placeholder = uiCopy.placeholder;
|
||
}
|
||
if (useCustomEmail && useCustomMailProviderPool) {
|
||
inputEmail.placeholder = '号池会按顺序自动回填当前轮邮箱,也可以手动覆盖';
|
||
}
|
||
btnFetchEmail.disabled = useLuckmail || useCustomEmail || useCustomEmailPool || isAutoRunLockedPhase();
|
||
if (!btnFetchEmail.disabled) {
|
||
btnFetchEmail.textContent = uiCopy.buttonLabel;
|
||
}
|
||
if (autoHintText) {
|
||
autoHintText.textContent = useHotmail
|
||
? '请先校验并选择一个 Hotmail 账号'
|
||
: (useLuckmail
|
||
? '步骤 3 会自动购买 LuckMail 邮箱并用于收码'
|
||
: (useGeneratedAlias
|
||
? '步骤 3 会自动生成邮箱,无需手动获取'
|
||
: (useCustomEmail ? '请先填写自定义注册邮箱,成功一轮后会自动清空' : `先自动获取${uiCopy.label},或手动粘贴邮箱后再继续`)));
|
||
}
|
||
if (autoHintText && useCustomEmailPool) {
|
||
autoHintText.textContent = getCustomEmailPoolSize() > 0
|
||
? `当前邮箱池共 ${getCustomEmailPoolSize()} 个邮箱,自动轮数会跟随数量;实际收码仍走当前邮箱服务`
|
||
: '请先在邮箱池里每行填写一个邮箱,自动轮数会跟随数量';
|
||
}
|
||
if (autoHintText && useCustomEmail && useCustomMailProviderPool) {
|
||
autoHintText.textContent = `当前自定义号池共 ${getCustomMailProviderPoolSize()} 个邮箱,自动轮数会跟随数量;第 4/8 步仍需手动输入验证码`;
|
||
}
|
||
if (autoHintText && useGmail && useGeneratedAlias) {
|
||
autoHintText.textContent = '请先填写 Gmail 原邮箱,步骤 3 会自动生成 Gmail +tag 地址';
|
||
}
|
||
if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
|
||
autoHintText.textContent = aliasUiCopy.hint;
|
||
}
|
||
if (autoHintText && useMail2925AccountPool && !useCustomEmailPool) {
|
||
autoHintText.textContent = getMail2925Accounts().length
|
||
? (useGeneratedAlias
|
||
? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址'
|
||
: '当前已启用 2925 号池模式,步骤 4 / 8 遇到登录页时会优先使用下拉框选中的账号自动登录')
|
||
: '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱';
|
||
}
|
||
if (autoHintText && showCloudflareTempEmailReceiveMailbox && !useCustomEmailPool) {
|
||
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
|
||
}
|
||
if (autoHintText && showCloudflareTempEmailRandomSubdomainToggle && inputTempEmailUseRandomSubdomain?.checked) {
|
||
autoHintText.textContent = '已启用随机子域名:扩展会按当前选中的 Temp 域名提交,并额外携带 enableRandomSubdomain;是否生效取决于后端 RANDOM_SUBDOMAIN_DOMAINS 配置。';
|
||
}
|
||
if (autoHintText && useIcloudProvider && showIcloudForwardMailProvider) {
|
||
const forwardProvider = normalizeIcloudForwardMailProvider(icloudForwardMailProviderValue);
|
||
const forwardProviderLabel = ICLOUD_FORWARD_MAIL_PROVIDER_LABELS[forwardProvider]
|
||
|| MAIL_PROVIDER_LOGIN_CONFIGS[forwardProvider]?.label
|
||
|| '目标邮箱';
|
||
autoHintText.textContent = `iCloud ${isIcloudComCnHost ? 'com.cn' : ''} 当前使用转发收码:第 4/8 步会从 ${forwardProviderLabel} 轮询验证码。`;
|
||
}
|
||
if (useHotmail) {
|
||
inputEmail.value = getCurrentHotmailEmail();
|
||
} else if (useLuckmail) {
|
||
inputEmail.value = getCurrentLuckmailEmail();
|
||
}
|
||
if (useCustomEmailPool) {
|
||
syncRunCountFromCustomEmailPool();
|
||
if (typeof queueCustomEmailPoolRefresh === 'function') {
|
||
queueCustomEmailPoolRefresh();
|
||
}
|
||
}
|
||
if (useCustomMailProviderPool) {
|
||
syncRunCountFromCustomMailProviderPool();
|
||
}
|
||
if (typeof inputRunCount !== 'undefined' && inputRunCount) {
|
||
inputRunCount.disabled = currentAutoRun.autoRunning || shouldLockRunCountToEmailPool();
|
||
}
|
||
renderHotmailAccounts();
|
||
if (useMail2925) {
|
||
renderMail2925Accounts();
|
||
}
|
||
if (useLuckmail) {
|
||
renderLuckmailPurchases();
|
||
}
|
||
}
|
||
|
||
async function saveCloudflareDomainSettings(domains, activeDomain, options = {}) {
|
||
const { silent = false } = options;
|
||
const normalizedDomains = normalizeCloudflareDomains(domains);
|
||
const normalizedActiveDomain = normalizeCloudflareDomainValue(activeDomain) || normalizedDomains[0] || '';
|
||
const payload = {
|
||
cloudflareDomain: normalizedActiveDomain,
|
||
cloudflareDomains: normalizedDomains,
|
||
};
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'SAVE_SETTING',
|
||
source: 'sidepanel',
|
||
payload,
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
|
||
syncLatestState({
|
||
...payload,
|
||
});
|
||
renderCloudflareDomainOptions(normalizedActiveDomain);
|
||
setCloudflareDomainEditMode(false, { clearInput: true });
|
||
markSettingsDirty(false);
|
||
updateMailProviderUI();
|
||
|
||
if (!silent) {
|
||
showToast('Cloudflare 域名已保存', 'success', 1800);
|
||
}
|
||
}
|
||
|
||
async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, options = {}) {
|
||
const { silent = false } = options;
|
||
const normalizedDomains = normalizeCloudflareTempEmailDomains(domains);
|
||
const normalizedActiveDomain = normalizeCloudflareTempEmailDomainValue(activeDomain) || normalizedDomains[0] || '';
|
||
const payload = {
|
||
cloudflareTempEmailDomain: normalizedActiveDomain,
|
||
cloudflareTempEmailDomains: normalizedDomains,
|
||
};
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'SAVE_SETTING',
|
||
source: 'sidepanel',
|
||
payload,
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
|
||
syncLatestState({
|
||
...payload,
|
||
});
|
||
renderCloudflareTempEmailDomainOptions(normalizedActiveDomain);
|
||
setCloudflareTempEmailDomainEditMode(false, { clearInput: true });
|
||
markSettingsDirty(false);
|
||
updateMailProviderUI();
|
||
|
||
if (!silent) {
|
||
showToast('Cloudflare Temp Email 域名已保存', 'success', 1800);
|
||
}
|
||
}
|
||
|
||
function updatePanelModeUI() {
|
||
const useSub2Api = selectPanelMode.value === 'sub2api';
|
||
const useCodex2Api = selectPanelMode.value === 'codex2api';
|
||
const useCpa = !useSub2Api && !useCodex2Api;
|
||
rowVpsUrl.style.display = useCpa ? '' : 'none';
|
||
rowVpsPassword.style.display = useCpa ? '' : 'none';
|
||
rowLocalCpaStep9Mode.style.display = useCpa ? '' : 'none';
|
||
rowSub2ApiUrl.style.display = useSub2Api ? '' : 'none';
|
||
rowSub2ApiEmail.style.display = useSub2Api ? '' : 'none';
|
||
rowSub2ApiPassword.style.display = useSub2Api ? '' : 'none';
|
||
rowSub2ApiGroup.style.display = useSub2Api ? '' : 'none';
|
||
rowSub2ApiDefaultProxy.style.display = useSub2Api ? '' : 'none';
|
||
rowCodex2ApiUrl.style.display = useCodex2Api ? '' : 'none';
|
||
rowCodex2ApiAdminKey.style.display = useCodex2Api ? '' : 'none';
|
||
|
||
const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]');
|
||
if (step9Btn) {
|
||
step9Btn.textContent = useSub2Api
|
||
? 'SUB2API 回调验证'
|
||
: (useCodex2Api ? 'Codex2API 回调验证' : 'CPA 回调验证');
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// UI Updates
|
||
// ============================================================
|
||
|
||
function updateStepUI(step, status) {
|
||
syncLatestState({
|
||
stepStatuses: {
|
||
...getStepStatuses(),
|
||
[step]: status,
|
||
},
|
||
});
|
||
|
||
renderSingleStepStatus(step, status);
|
||
updateButtonStates();
|
||
updateProgressCounter();
|
||
updateConfigMenuControls();
|
||
}
|
||
|
||
function renderSingleStepStatus(step, status) {
|
||
const normalizedStatus = status || 'pending';
|
||
const statusEl = document.querySelector(`.step-status[data-step="${step}"]`);
|
||
const row = document.querySelector(`.step-row[data-step="${step}"]`);
|
||
|
||
if (statusEl) statusEl.textContent = STATUS_ICONS[normalizedStatus] || '';
|
||
if (row) {
|
||
row.className = `step-row ${normalizedStatus}`;
|
||
}
|
||
}
|
||
|
||
function renderStepStatuses(state = latestState) {
|
||
const statuses = getStepStatuses(state);
|
||
for (const step of STEP_IDS) {
|
||
renderSingleStepStatus(step, statuses[step]);
|
||
}
|
||
updateProgressCounter();
|
||
}
|
||
|
||
function updateProgressCounter() {
|
||
const completed = Object.values(getStepStatuses()).filter(isDoneStatus).length;
|
||
stepsProgress.textContent = `${completed} / ${STEP_IDS.length}`;
|
||
}
|
||
|
||
function updateButtonStates() {
|
||
const statuses = getStepStatuses();
|
||
const anyRunning = Object.values(statuses).some(s => s === 'running');
|
||
const autoLocked = isAutoRunLockedPhase();
|
||
const autoScheduled = isAutoRunScheduledPhase();
|
||
const icloudTargetMailboxTypeValue = typeof selectIcloudTargetMailboxType !== 'undefined'
|
||
? selectIcloudTargetMailboxType?.value
|
||
: latestState?.icloudTargetMailboxType;
|
||
|
||
for (const step of STEP_IDS) {
|
||
const btn = document.querySelector(`.step-btn[data-step="${step}"]`);
|
||
if (!btn) continue;
|
||
|
||
if (anyRunning || autoLocked || autoScheduled) {
|
||
btn.disabled = true;
|
||
} else if (step === 1) {
|
||
btn.disabled = false;
|
||
} else {
|
||
const currentIndex = STEP_IDS.indexOf(step);
|
||
const prevStep = currentIndex > 0 ? STEP_IDS[currentIndex - 1] : null;
|
||
const prevStatus = prevStep === null ? 'completed' : statuses[prevStep];
|
||
const currentStatus = statuses[step];
|
||
btn.disabled = !(isDoneStatus(prevStatus) || currentStatus === 'failed' || isDoneStatus(currentStatus) || currentStatus === 'stopped');
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('.step-manual-btn').forEach((btn) => {
|
||
const step = Number(btn.dataset.step);
|
||
const currentStatus = statuses[step];
|
||
const currentIndex = STEP_IDS.indexOf(step);
|
||
const prevStep = currentIndex > 0 ? STEP_IDS[currentIndex - 1] : null;
|
||
const prevStatus = prevStep === null ? 'completed' : statuses[prevStep];
|
||
|
||
if (!SKIPPABLE_STEPS.has(step) || anyRunning || autoLocked || autoScheduled || currentStatus === 'running' || isDoneStatus(currentStatus)) {
|
||
btn.style.display = 'none';
|
||
btn.disabled = true;
|
||
btn.title = '当前不可跳过';
|
||
return;
|
||
}
|
||
|
||
if (prevStep !== null && !isDoneStatus(prevStatus)) {
|
||
btn.style.display = 'none';
|
||
btn.disabled = true;
|
||
btn.title = `请先完成步骤 ${prevStep}`;
|
||
return;
|
||
}
|
||
|
||
btn.style.display = '';
|
||
btn.disabled = false;
|
||
btn.title = `跳过步骤 ${step}`;
|
||
});
|
||
|
||
btnReset.disabled = anyRunning || autoScheduled || isAutoRunPausedPhase() || autoLocked;
|
||
const disableIcloudControls = anyRunning || autoScheduled || autoLocked;
|
||
if (btnIcloudRefresh) btnIcloudRefresh.disabled = disableIcloudControls;
|
||
if (btnIcloudDeleteUsed) btnIcloudDeleteUsed.disabled = disableIcloudControls || !hasDeletableUsedIcloudAliases();
|
||
if (selectIcloudHostPreference) selectIcloudHostPreference.disabled = disableIcloudControls;
|
||
if (typeof selectIcloudTargetMailboxType !== 'undefined' && selectIcloudTargetMailboxType) {
|
||
selectIcloudTargetMailboxType.disabled = disableIcloudControls;
|
||
}
|
||
if (typeof selectIcloudForwardMailProvider !== 'undefined' && selectIcloudForwardMailProvider) {
|
||
const normalizedIcloudTargetMailboxType = normalizeIcloudTargetMailboxType(icloudTargetMailboxTypeValue);
|
||
const allowIcloudForwardMailProvider = isIcloudMailProvider()
|
||
&& normalizedIcloudTargetMailboxType === 'forward-mailbox';
|
||
selectIcloudForwardMailProvider.disabled = disableIcloudControls || !allowIcloudForwardMailProvider;
|
||
}
|
||
if (selectIcloudFetchMode) {
|
||
const allowIcloudFetchMode = getSelectedEmailGenerator() === ICLOUD_PROVIDER
|
||
&& !isCustomMailProvider()
|
||
&& !isManagedAliasProvider();
|
||
selectIcloudFetchMode.disabled = disableIcloudControls || !allowIcloudFetchMode;
|
||
}
|
||
if (checkboxAutoDeleteIcloud) checkboxAutoDeleteIcloud.disabled = disableIcloudControls;
|
||
if (btnContributionMode) btnContributionMode.disabled = isContributionButtonLocked();
|
||
updateStopButtonState(anyRunning || autoScheduled || isAutoRunPausedPhase() || autoLocked);
|
||
renderContributionMode();
|
||
}
|
||
|
||
function updateStopButtonState(active) {
|
||
btnStop.disabled = !active;
|
||
}
|
||
|
||
function updateStatusDisplay(state) {
|
||
if (!state || !state.stepStatuses) return;
|
||
|
||
statusBar.className = 'status-bar';
|
||
|
||
const countdown = getActiveAutoRunCountdown();
|
||
if (countdown) {
|
||
const remainingMs = countdown.at - Date.now();
|
||
displayStatus.textContent = remainingMs > 0
|
||
? `${countdown.title},剩余 ${formatCountdown(remainingMs)}`
|
||
: `${countdown.title},即将结束...`;
|
||
statusBar.classList.add(countdown.tone === 'scheduled' ? 'scheduled' : 'running');
|
||
return;
|
||
}
|
||
|
||
if (isAutoRunScheduledPhase()) {
|
||
const remainingMs = Number.isFinite(currentAutoRun.scheduledAt)
|
||
? currentAutoRun.scheduledAt - Date.now()
|
||
: 0;
|
||
displayStatus.textContent = remainingMs > 0
|
||
? `自动计划中,剩余 ${formatCountdown(remainingMs)}`
|
||
: '倒计时即将结束,正在准备启动...';
|
||
statusBar.classList.add('scheduled');
|
||
return;
|
||
}
|
||
|
||
if (isAutoRunPausedPhase()) {
|
||
displayStatus.textContent = `自动已暂停${getAutoRunLabel()},等待邮箱后继续`;
|
||
statusBar.classList.add('paused');
|
||
return;
|
||
}
|
||
|
||
if (isAutoRunWaitingStepPhase()) {
|
||
const runningSteps = getRunningSteps(state);
|
||
displayStatus.textContent = runningSteps.length
|
||
? `自动等待步骤 ${runningSteps.join(', ')} 完成后继续${getAutoRunLabel()}`
|
||
: `自动正在按最新进度准备继续${getAutoRunLabel()}`;
|
||
statusBar.classList.add('running');
|
||
return;
|
||
}
|
||
|
||
const running = Object.entries(state.stepStatuses).find(([, s]) => s === 'running');
|
||
if (running) {
|
||
displayStatus.textContent = `步骤 ${running[0]} 运行中...`;
|
||
statusBar.classList.add('running');
|
||
return;
|
||
}
|
||
|
||
if (isAutoRunLockedPhase()) {
|
||
displayStatus.textContent = `${currentAutoRun.phase === 'retrying' ? '自动重试中' : '自动运行中'}${getAutoRunLabel()}`;
|
||
statusBar.classList.add('running');
|
||
return;
|
||
}
|
||
|
||
const failed = Object.entries(state.stepStatuses).find(([, s]) => s === 'failed');
|
||
if (failed) {
|
||
displayStatus.textContent = `步骤 ${failed[0]} 失败`;
|
||
statusBar.classList.add('failed');
|
||
return;
|
||
}
|
||
|
||
const stopped = Object.entries(state.stepStatuses).find(([, s]) => s === 'stopped');
|
||
if (stopped) {
|
||
displayStatus.textContent = `步骤 ${stopped[0]} 已停止`;
|
||
statusBar.classList.add('stopped');
|
||
return;
|
||
}
|
||
|
||
const lastCompleted = Object.entries(state.stepStatuses)
|
||
.filter(([, s]) => isDoneStatus(s))
|
||
.map(([k]) => Number(k))
|
||
.sort((a, b) => b - a)[0];
|
||
|
||
if (lastCompleted === STEP_IDS[STEP_IDS.length - 1]) {
|
||
displayStatus.textContent = (state.stepStatuses[lastCompleted] === 'manual_completed' || state.stepStatuses[lastCompleted] === 'skipped') ? '全部步骤已跳过/完成' : '全部步骤已完成';
|
||
statusBar.classList.add('completed');
|
||
} else if (lastCompleted) {
|
||
displayStatus.textContent = (state.stepStatuses[lastCompleted] === 'manual_completed' || state.stepStatuses[lastCompleted] === 'skipped')
|
||
? `步骤 ${lastCompleted} 已跳过`
|
||
: `步骤 ${lastCompleted} 已完成`;
|
||
} else {
|
||
displayStatus.textContent = '就绪';
|
||
}
|
||
}
|
||
|
||
function appendLog(entry) {
|
||
const time = new Date(entry.timestamp).toLocaleTimeString('zh-CN', {
|
||
hour12: false,
|
||
timeZone: DISPLAY_TIMEZONE,
|
||
});
|
||
const levelLabel = LOG_LEVEL_LABELS[entry.level] || entry.level;
|
||
const line = document.createElement('div');
|
||
line.className = `log-line log-${entry.level}`;
|
||
|
||
const normalizedStep = Math.floor(Number(entry.step) || 0);
|
||
const stepNum = normalizedStep > 0 ? String(normalizedStep) : null;
|
||
|
||
let html = `<span class="log-time">${time}</span> `;
|
||
html += `<span class="log-level log-level-${entry.level}">${levelLabel}</span> `;
|
||
if (stepNum) {
|
||
html += `<span class="log-step-tag step-${stepNum}">步${stepNum}</span>`;
|
||
}
|
||
html += `<span class="log-msg">${escapeHtml(entry.message)}</span>`;
|
||
|
||
line.innerHTML = html;
|
||
logArea.appendChild(line);
|
||
logArea.scrollTop = logArea.scrollHeight;
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
async function fetchGeneratedEmail(options = {}) {
|
||
const { showFailureToast = true } = options;
|
||
const uiCopy = getCurrentRegistrationEmailUiCopy();
|
||
if (isCustomMailProvider()) {
|
||
throw new Error('当前邮箱服务为自定义邮箱,请直接填写注册邮箱。');
|
||
}
|
||
const defaultLabel = uiCopy.buttonLabel;
|
||
btnFetchEmail.disabled = true;
|
||
btnFetchEmail.textContent = '...';
|
||
|
||
try {
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'FETCH_GENERATED_EMAIL',
|
||
source: 'sidepanel',
|
||
payload: {
|
||
generateNew: true,
|
||
generator: selectEmailGenerator.value,
|
||
mailProvider: selectMailProvider.value,
|
||
mail2925Mode: getSelectedMail2925Mode(),
|
||
...(getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR
|
||
? {
|
||
customEmailPool: getActiveCustomEmailPoolEmails(),
|
||
}
|
||
: {}),
|
||
...buildManagedAliasBaseEmailPayload(),
|
||
},
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
if (!response?.email) {
|
||
throw new Error('未返回可用邮箱。');
|
||
}
|
||
|
||
inputEmail.value = response.email;
|
||
if (getSelectedEmailGenerator() === 'icloud') {
|
||
queueIcloudAliasRefresh();
|
||
}
|
||
showToast(`已${uiCopy.successVerb} ${uiCopy.label}:${response.email}`, 'success', 2500);
|
||
return response.email;
|
||
} catch (err) {
|
||
if (showFailureToast) {
|
||
showToast(`${uiCopy.label}${uiCopy.successVerb}失败:${err.message}`, 'error');
|
||
}
|
||
throw err;
|
||
} finally {
|
||
btnFetchEmail.disabled = false;
|
||
btnFetchEmail.textContent = defaultLabel;
|
||
}
|
||
}
|
||
|
||
function syncToggleButtonLabel(button, input, labels) {
|
||
if (!button || !input) return;
|
||
|
||
const isHidden = input.type === 'password';
|
||
button.innerHTML = isHidden ? EYE_OPEN_ICON : EYE_CLOSED_ICON;
|
||
button.setAttribute('aria-label', isHidden ? labels.show : labels.hide);
|
||
button.title = isHidden ? labels.show : labels.hide;
|
||
}
|
||
|
||
function getPasswordToggleLabels(button) {
|
||
if (!button) {
|
||
return {
|
||
show: '\u663e\u793a\u5185\u5bb9',
|
||
hide: '\u9690\u85cf\u5185\u5bb9',
|
||
};
|
||
}
|
||
const show = button.dataset?.showLabel
|
||
|| button.getAttribute('aria-label')
|
||
|| button.title
|
||
|| '\u663e\u793a\u5185\u5bb9';
|
||
const hide = button.dataset?.hideLabel
|
||
|| String(show).replace(/^\u663e\u793a/, '\u9690\u85cf')
|
||
|| '\u9690\u85cf\u5185\u5bb9';
|
||
return { show, hide };
|
||
}
|
||
|
||
function syncPasswordVisibilityToggle(button) {
|
||
const targetId = String(button?.dataset?.passwordToggle || '').trim();
|
||
const input = targetId ? document.getElementById(targetId) : null;
|
||
if (!button || !input) return;
|
||
syncToggleButtonLabel(button, input, getPasswordToggleLabels(button));
|
||
}
|
||
|
||
function syncPasswordVisibilityToggles(root = document) {
|
||
root.querySelectorAll?.('[data-password-toggle]').forEach(syncPasswordVisibilityToggle);
|
||
}
|
||
|
||
function bindPasswordVisibilityToggles(root = document) {
|
||
root.querySelectorAll?.('[data-password-toggle]').forEach((button) => {
|
||
if (button.dataset?.passwordToggleBound === 'true') {
|
||
syncPasswordVisibilityToggle(button);
|
||
return;
|
||
}
|
||
if (button.dataset) {
|
||
button.dataset.passwordToggleBound = 'true';
|
||
}
|
||
syncPasswordVisibilityToggle(button);
|
||
button.addEventListener('click', () => {
|
||
const targetId = String(button.dataset?.passwordToggle || '').trim();
|
||
const input = targetId ? document.getElementById(targetId) : null;
|
||
if (!input) return;
|
||
input.type = input.type === 'password' ? 'text' : 'password';
|
||
syncPasswordVisibilityToggle(button);
|
||
});
|
||
});
|
||
}
|
||
|
||
async function copyTextToClipboard(text) {
|
||
const value = String(text || '').trim();
|
||
if (!value) {
|
||
throw new Error('没有可复制的内容。');
|
||
}
|
||
if (!navigator.clipboard?.writeText) {
|
||
throw new Error('当前环境不支持剪贴板复制。');
|
||
}
|
||
await navigator.clipboard.writeText(value);
|
||
}
|
||
|
||
const hotmailManager = window.SidepanelHotmailManager?.createHotmailManager({
|
||
state: {
|
||
getLatestState: () => latestState,
|
||
syncLatestState,
|
||
},
|
||
dom: {
|
||
btnAddHotmailAccount,
|
||
btnClearUsedHotmailAccounts,
|
||
btnDeleteAllHotmailAccounts,
|
||
btnHotmailUsageGuide,
|
||
btnImportHotmailAccounts,
|
||
btnToggleHotmailForm,
|
||
btnToggleHotmailList,
|
||
hotmailFormShell,
|
||
hotmailAccountsList,
|
||
hotmailListShell,
|
||
inputEmail,
|
||
inputHotmailClientId,
|
||
inputHotmailEmail,
|
||
inputHotmailImport,
|
||
inputHotmailPassword,
|
||
inputHotmailRefreshToken,
|
||
inputHotmailSearch,
|
||
selectHotmailFilter,
|
||
selectMailProvider,
|
||
},
|
||
helpers: {
|
||
copyTextToClipboard,
|
||
escapeHtml,
|
||
getCurrentHotmailEmail,
|
||
getHotmailAccounts,
|
||
openConfirmModal,
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
constants: {
|
||
copyIcon: COPY_ICON,
|
||
displayTimeZone: DISPLAY_TIMEZONE,
|
||
expandedStorageKey: 'multipage-hotmail-list-expanded',
|
||
},
|
||
hotmailUtils: {
|
||
filterHotmailAccountsByUsage,
|
||
getHotmailBulkActionLabel,
|
||
getHotmailListToggleLabel,
|
||
parseHotmailImportText,
|
||
shouldClearHotmailCurrentSelection,
|
||
upsertHotmailAccountInList,
|
||
},
|
||
});
|
||
const initHotmailListExpandedState = hotmailManager?.initHotmailListExpandedState
|
||
|| (() => { });
|
||
const renderHotmailAccounts = hotmailManager?.renderHotmailAccounts
|
||
|| (() => { });
|
||
const bindHotmailEvents = hotmailManager?.bindHotmailEvents
|
||
|| (() => { });
|
||
bindHotmailEvents();
|
||
|
||
const payPalManager = window.SidepanelPayPalManager?.createPayPalManager({
|
||
state: {
|
||
getLatestState: () => latestState,
|
||
syncLatestState,
|
||
},
|
||
dom: {
|
||
btnAddPayPalAccount,
|
||
selectPayPalAccount,
|
||
},
|
||
helpers: {
|
||
escapeHtml,
|
||
getPayPalAccounts,
|
||
openFormDialog: (options) => {
|
||
if (!sharedFormDialog?.open) {
|
||
throw new Error('表单弹窗能力未加载,请刷新扩展后重试。');
|
||
}
|
||
return sharedFormDialog.open(options);
|
||
},
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
paypalUtils: {
|
||
upsertPayPalAccountInList,
|
||
},
|
||
});
|
||
const renderPayPalAccounts = payPalManager?.renderPayPalAccounts
|
||
|| (() => { });
|
||
const bindPayPalEvents = payPalManager?.bindPayPalEvents
|
||
|| (() => { });
|
||
bindPayPalEvents();
|
||
|
||
const mail2925Manager = window.SidepanelMail2925Manager?.createMail2925Manager({
|
||
state: {
|
||
getLatestState: () => latestState,
|
||
syncLatestState,
|
||
},
|
||
dom: {
|
||
btnAddMail2925Account,
|
||
btnDeleteAllMail2925Accounts,
|
||
btnImportMail2925Accounts,
|
||
btnToggleMail2925Form,
|
||
btnToggleMail2925List,
|
||
inputMail2925Email,
|
||
inputMail2925Import,
|
||
inputMail2925Password,
|
||
inputMail2925Search,
|
||
selectMail2925Filter,
|
||
mail2925AccountsList,
|
||
mail2925FormShell,
|
||
mail2925ListShell,
|
||
},
|
||
helpers: {
|
||
copyTextToClipboard,
|
||
escapeHtml,
|
||
getMail2925Accounts,
|
||
openConfirmModal,
|
||
refreshManagedAliasBaseEmail: () => {
|
||
syncMail2925BaseEmailFromCurrentAccount(latestState, { persist: true });
|
||
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||
},
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
constants: {
|
||
copyIcon: COPY_ICON,
|
||
displayTimeZone: DISPLAY_TIMEZONE,
|
||
expandedStorageKey: 'multipage-mail2925-list-expanded',
|
||
},
|
||
mail2925Utils: window.Mail2925Utils || {},
|
||
});
|
||
const initMail2925ListExpandedState = mail2925Manager?.initMail2925ListExpandedState
|
||
|| (() => { });
|
||
const renderMail2925Accounts = mail2925Manager?.renderMail2925Accounts
|
||
|| (() => { });
|
||
const bindMail2925Events = mail2925Manager?.bindMail2925Events
|
||
|| (() => { });
|
||
bindMail2925Events();
|
||
|
||
const icloudManager = window.SidepanelIcloudManager?.createIcloudManager({
|
||
dom: {
|
||
btnIcloudBulkDelete,
|
||
btnIcloudBulkPreserve,
|
||
btnIcloudBulkUnpreserve,
|
||
btnIcloudBulkUnused,
|
||
btnIcloudBulkUsed,
|
||
btnIcloudDeleteUsed,
|
||
btnIcloudLoginDone,
|
||
btnIcloudRefresh,
|
||
checkboxIcloudSelectAll,
|
||
icloudList,
|
||
icloudLoginHelp,
|
||
icloudLoginHelpText,
|
||
icloudLoginHelpTitle,
|
||
icloudSection,
|
||
icloudSelectionSummary,
|
||
icloudSummary,
|
||
inputIcloudSearch,
|
||
selectIcloudFilter,
|
||
},
|
||
helpers: {
|
||
copyTextToClipboard,
|
||
escapeHtml,
|
||
openConfirmModal,
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
});
|
||
const hideIcloudLoginHelp = icloudManager?.hideIcloudLoginHelp
|
||
|| (() => { });
|
||
const hasDeletableUsedIcloudAliases = icloudManager?.hasDeletableUsedAliases
|
||
|| (() => false);
|
||
const queueIcloudAliasRefresh = icloudManager?.queueIcloudAliasRefresh
|
||
|| (() => { });
|
||
const refreshIcloudAliases = icloudManager?.refreshIcloudAliases
|
||
|| (async () => { });
|
||
const renderIcloudAliases = icloudManager?.renderIcloudAliases
|
||
|| (() => { });
|
||
const resetIcloudManager = icloudManager?.reset
|
||
|| (() => { });
|
||
const showIcloudLoginHelp = icloudManager?.showIcloudLoginHelp
|
||
|| (() => { });
|
||
const updateIcloudBulkUI = icloudManager?.updateIcloudBulkUI
|
||
|| (() => { });
|
||
const bindIcloudEvents = icloudManager?.bindIcloudEvents
|
||
|| (() => { });
|
||
bindIcloudEvents();
|
||
|
||
const luckmailManager = window.SidepanelLuckmailManager?.createLuckmailManager({
|
||
dom: {
|
||
btnLuckmailBulkDisable,
|
||
btnLuckmailBulkEnable,
|
||
btnLuckmailBulkPreserve,
|
||
btnLuckmailBulkUnpreserve,
|
||
btnLuckmailBulkUnused,
|
||
btnLuckmailBulkUsed,
|
||
btnLuckmailDisableUsed,
|
||
btnLuckmailRefresh,
|
||
checkboxLuckmailSelectAll,
|
||
inputEmail,
|
||
inputLuckmailSearch,
|
||
luckmailList,
|
||
luckmailSection,
|
||
luckmailSelectionSummary,
|
||
luckmailSummary,
|
||
selectLuckmailFilter,
|
||
},
|
||
helpers: {
|
||
copyTextToClipboard,
|
||
escapeHtml,
|
||
formatLuckmailDateTime,
|
||
getLuckmailPreserveTagName,
|
||
normalizeLuckmailProjectName,
|
||
openConfirmModal,
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
constants: {
|
||
copyIcon: COPY_ICON,
|
||
},
|
||
});
|
||
const queueLuckmailPurchaseRefresh = luckmailManager?.queueLuckmailPurchaseRefresh
|
||
|| (() => { });
|
||
const refreshLuckmailPurchases = luckmailManager?.refreshLuckmailPurchases
|
||
|| (async () => { });
|
||
const renderLuckmailPurchases = luckmailManager?.renderLuckmailPurchases
|
||
|| (() => { });
|
||
const resetLuckmailManager = luckmailManager?.reset
|
||
|| (() => { });
|
||
const bindLuckmailEvents = luckmailManager?.bindLuckmailEvents
|
||
|| (() => { });
|
||
bindLuckmailEvents();
|
||
|
||
const customEmailPoolManager = window.SidepanelCustomEmailPoolManager?.createCustomEmailPoolManager({
|
||
dom: {
|
||
btnCustomEmailPoolRefresh,
|
||
btnCustomEmailPoolClearUsed,
|
||
btnCustomEmailPoolDeleteAll,
|
||
inputCustomEmailPoolImport,
|
||
btnCustomEmailPoolImport,
|
||
customEmailPoolSummary,
|
||
inputCustomEmailPoolSearch,
|
||
selectCustomEmailPoolFilter,
|
||
checkboxCustomEmailPoolSelectAll,
|
||
customEmailPoolSelectionSummary,
|
||
btnCustomEmailPoolBulkUsed,
|
||
btnCustomEmailPoolBulkUnused,
|
||
btnCustomEmailPoolBulkEnable,
|
||
btnCustomEmailPoolBulkDisable,
|
||
btnCustomEmailPoolBulkDelete,
|
||
customEmailPoolList,
|
||
},
|
||
helpers: {
|
||
copyTextToClipboard,
|
||
escapeHtml,
|
||
openConfirmModal,
|
||
showToast,
|
||
},
|
||
state: {
|
||
getEntries: () => getNormalizedCustomEmailPoolEntriesState(),
|
||
setEntries: (entries) => {
|
||
setCustomEmailPoolEntriesState(entries);
|
||
},
|
||
getCurrentEmail: () => String(inputEmail?.value || latestState?.email || '').trim().toLowerCase(),
|
||
isVisible: () => Boolean(rowCustomEmailPool) && rowCustomEmailPool.style.display !== 'none',
|
||
},
|
||
actions: {
|
||
persistEntries: async () => {
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
await saveSettings({ silent: true });
|
||
},
|
||
setRuntimeEmail: async (email) => {
|
||
await setRuntimeEmailState(email);
|
||
syncLatestState({ email });
|
||
if (inputEmail) {
|
||
inputEmail.value = email || '';
|
||
}
|
||
},
|
||
},
|
||
constants: {
|
||
copyIcon: COPY_ICON,
|
||
},
|
||
});
|
||
const queueCustomEmailPoolRefresh = customEmailPoolManager?.queueCustomEmailPoolRefresh
|
||
|| (() => { });
|
||
const refreshCustomEmailPoolEntries = customEmailPoolManager?.refreshCustomEmailPoolEntries
|
||
|| (async () => { });
|
||
const renderCustomEmailPoolEntries = customEmailPoolManager?.renderCustomEmailPoolEntries
|
||
|| (() => { });
|
||
const resetCustomEmailPoolManager = customEmailPoolManager?.reset
|
||
|| (() => { });
|
||
const bindCustomEmailPoolEvents = customEmailPoolManager?.bindEvents
|
||
|| (() => { });
|
||
bindCustomEmailPoolEvents();
|
||
|
||
const accountRecordsManager = window.SidepanelAccountRecordsManager?.createAccountRecordsManager({
|
||
state: {
|
||
getLatestState: () => latestState,
|
||
syncLatestState,
|
||
},
|
||
dom: {
|
||
accountRecordsList,
|
||
accountRecordsMeta,
|
||
accountRecordsOverlay,
|
||
accountRecordsPageLabel,
|
||
accountRecordsStats,
|
||
btnAccountRecordsNext,
|
||
btnAccountRecordsPrev,
|
||
btnClearAccountRecords,
|
||
btnDeleteSelectedAccountRecords,
|
||
btnCloseAccountRecords,
|
||
btnOpenAccountRecords,
|
||
btnToggleAccountRecordsSelection,
|
||
},
|
||
helpers: {
|
||
escapeHtml,
|
||
openConfirmModal,
|
||
showToast,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
constants: {
|
||
displayTimeZone: DISPLAY_TIMEZONE,
|
||
pageSize: 10,
|
||
},
|
||
});
|
||
const renderAccountRecords = accountRecordsManager?.render
|
||
|| (() => { });
|
||
const bindAccountRecordEvents = accountRecordsManager?.bindEvents
|
||
|| (() => { });
|
||
const closeAccountRecordsPanel = accountRecordsManager?.closePanel
|
||
|| (() => { });
|
||
bindAccountRecordEvents();
|
||
const contributionModeManager = window.SidepanelContributionMode?.createContributionModeManager({
|
||
state: {
|
||
getLatestState: () => latestState,
|
||
},
|
||
dom: {
|
||
btnConfigMenu,
|
||
btnContributionMode,
|
||
inputContributionNickname,
|
||
inputContributionQq,
|
||
contributionCallbackStatus,
|
||
btnExitContributionMode,
|
||
btnOpenAccountRecords,
|
||
btnOpenContributionUpload,
|
||
btnStartContribution,
|
||
contributionModeBadge,
|
||
contributionModePanel,
|
||
contributionModeSummary,
|
||
contributionModeText,
|
||
contributionOauthStatus,
|
||
rowAccountRunHistoryHelperBaseUrl,
|
||
rowPhoneVerificationEnabled,
|
||
rowCustomPassword,
|
||
rowLocalCpaStep9Mode,
|
||
rowSub2ApiDefaultProxy,
|
||
rowSub2ApiEmail,
|
||
rowSub2ApiGroup,
|
||
rowSub2ApiPassword,
|
||
rowSub2ApiUrl,
|
||
rowVpsPassword,
|
||
rowVpsUrl,
|
||
selectPanelMode,
|
||
},
|
||
helpers: {
|
||
applySettingsState,
|
||
closeAccountRecordsPanel,
|
||
closeConfigMenu,
|
||
getContributionNickname: () => latestState?.email || '',
|
||
getContributionProfile: () => ({
|
||
nickname: String(inputContributionNickname?.value || '').trim(),
|
||
qq: String(inputContributionQq?.value || '').trim(),
|
||
}),
|
||
isModeSwitchBlocked: isContributionModeSwitchBlocked,
|
||
openConfirmModal,
|
||
openExternalUrl,
|
||
showToast,
|
||
startContributionAutoRun: () => startAutoRunFromCurrentSettings(),
|
||
updateAccountRunHistorySettingsUI,
|
||
updateConfigMenuControls,
|
||
updatePanelModeUI,
|
||
updateStatusDisplay,
|
||
},
|
||
runtime: {
|
||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||
},
|
||
constants: {
|
||
contributionOauthUrl: `${String(contributionContentService?.portalUrl || 'https://apikey.qzz.io').replace(/\/+$/, '')}/oauth/`,
|
||
contributionPortalUrl: String(contributionContentService?.portalUrl || 'https://apikey.qzz.io').replace(/\/+$/, ''),
|
||
contributionUploadUrl: `${String(contributionContentService?.portalUrl || 'https://apikey.qzz.io').replace(/\/+$/, '')}/upload`,
|
||
},
|
||
});
|
||
const baseRenderContributionMode = contributionModeManager?.render
|
||
|| (() => { });
|
||
const renderContributionMode = () => {
|
||
baseRenderContributionMode();
|
||
renderContributionUpdateHint();
|
||
updateSignupMethodUI({ notify: true });
|
||
};
|
||
const bindContributionModeEvents = contributionModeManager?.bindEvents
|
||
|| (() => { });
|
||
bindContributionModeEvents();
|
||
renderStepsList();
|
||
|
||
async function exportSettingsFile() {
|
||
closeConfigMenu();
|
||
configActionInFlight = true;
|
||
updateConfigMenuControls();
|
||
|
||
try {
|
||
await flushPendingSettingsBeforeExport();
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'EXPORT_SETTINGS',
|
||
source: 'sidepanel',
|
||
payload: {},
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
if (!response?.fileContent || !response?.fileName) {
|
||
throw new Error('\u672a\u751f\u6210\u53ef\u4e0b\u8f7d\u7684\u914d\u7f6e\u6587\u4ef6\u3002');
|
||
}
|
||
|
||
downloadTextFile(response.fileContent, response.fileName);
|
||
showToast('\u914d\u7f6e\u5df2\u5bfc\u51fa\uff1a' + response.fileName, 'success', 2200);
|
||
} catch (err) {
|
||
showToast('\u5bfc\u51fa\u914d\u7f6e\u5931\u8d25\uff1a' + err.message, 'error');
|
||
} finally {
|
||
configActionInFlight = false;
|
||
updateConfigMenuControls();
|
||
}
|
||
}
|
||
|
||
async function importSettingsFromFile(file) {
|
||
if (!file) return;
|
||
|
||
configActionInFlight = true;
|
||
closeConfigMenu();
|
||
updateConfigMenuControls();
|
||
|
||
try {
|
||
await settlePendingSettingsBeforeImport();
|
||
const rawText = await file.text();
|
||
|
||
let parsedConfig = null;
|
||
try {
|
||
parsedConfig = JSON.parse(rawText);
|
||
} catch {
|
||
throw new Error('\u914d\u7f6e\u6587\u4ef6\u4e0d\u662f\u6709\u6548\u7684 JSON\u3002');
|
||
}
|
||
|
||
const confirmed = await openConfirmModal({
|
||
title: '\u5bfc\u5165\u914d\u7f6e',
|
||
message: '\u786e\u8ba4\u5bfc\u5165\u914d\u7f6e\u6587\u4ef6 "' + file.name + '" \u5417\uff1f\u5bfc\u5165\u540e\u4f1a\u8986\u76d6\u5f53\u524d\u914d\u7f6e\u3002',
|
||
confirmLabel: '\u786e\u8ba4\u8986\u76d6\u5bfc\u5165',
|
||
confirmVariant: 'btn-danger',
|
||
});
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'IMPORT_SETTINGS',
|
||
source: 'sidepanel',
|
||
payload: {
|
||
config: parsedConfig,
|
||
},
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
if (!response?.state) {
|
||
throw new Error('\u5bfc\u5165\u540e\u672a\u8fd4\u56de\u6700\u65b0\u914d\u7f6e\u72b6\u6001\u3002');
|
||
}
|
||
|
||
applySettingsState(response.state);
|
||
updateStatusDisplay(latestState);
|
||
showToast('\u914d\u7f6e\u5df2\u5bfc\u5165\uff0c\u5f53\u524d\u914d\u7f6e\u5df2\u8986\u76d6\u3002', 'success', 2200);
|
||
} catch (err) {
|
||
showToast('\u5bfc\u5165\u914d\u7f6e\u5931\u8d25\uff1a' + err.message, 'error');
|
||
} finally {
|
||
configActionInFlight = false;
|
||
updateConfigMenuControls();
|
||
if (inputImportSettingsFile) {
|
||
inputImportSettingsFile.value = '';
|
||
}
|
||
}
|
||
}
|
||
|
||
function syncPasswordToggleLabel() {
|
||
syncToggleButtonLabel(btnTogglePassword, inputPassword, {
|
||
show: '显示密码',
|
||
hide: '隐藏密码',
|
||
});
|
||
}
|
||
|
||
function syncVpsUrlToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleVpsUrl, inputVpsUrl, {
|
||
show: '显示 CPA 地址',
|
||
hide: '隐藏 CPA 地址',
|
||
});
|
||
}
|
||
|
||
function syncVpsPasswordToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleVpsPassword, inputVpsPassword, {
|
||
show: '显示管理密钥',
|
||
hide: '隐藏管理密钥',
|
||
});
|
||
}
|
||
|
||
function syncIpProxyApiUrlToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleIpProxyApiUrl, inputIpProxyApiUrl, {
|
||
show: '显示代理 API',
|
||
hide: '隐藏代理 API',
|
||
});
|
||
}
|
||
|
||
function syncIpProxyUsernameToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleIpProxyUsername, inputIpProxyUsername, {
|
||
show: '显示代理账号',
|
||
hide: '隐藏代理账号',
|
||
});
|
||
}
|
||
|
||
function syncIpProxyPasswordToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleIpProxyPassword, inputIpProxyPassword, {
|
||
show: '显示代理密码',
|
||
hide: '隐藏代理密码',
|
||
});
|
||
}
|
||
|
||
function syncHeroSmsApiKeyToggleLabel() {
|
||
syncToggleButtonLabel(btnToggleHeroSmsApiKey, inputHeroSmsApiKey, {
|
||
show: '显示接码 API Key',
|
||
hide: '隐藏接码 API Key',
|
||
});
|
||
}
|
||
|
||
async function maybeTakeoverAutoRun(actionLabel) {
|
||
if (!isAutoRunPausedPhase()) {
|
||
return true;
|
||
}
|
||
|
||
const confirmed = await openConfirmModal({
|
||
title: '接管自动',
|
||
message: `当前自动流程已暂停。若继续${actionLabel},将停止自动流程并切换为手动控制。是否继续?`,
|
||
confirmLabel: '确认接管',
|
||
confirmVariant: 'btn-primary',
|
||
});
|
||
if (!confirmed) {
|
||
return false;
|
||
}
|
||
|
||
await chrome.runtime.sendMessage({ type: 'TAKEOVER_AUTO_RUN', source: 'sidepanel', payload: {} });
|
||
return true;
|
||
}
|
||
|
||
async function handleSkipStep(step) {
|
||
if (isAutoRunPausedPhase()) {
|
||
const takeoverResponse = await chrome.runtime.sendMessage({
|
||
type: 'TAKEOVER_AUTO_RUN',
|
||
source: 'sidepanel',
|
||
payload: {},
|
||
});
|
||
if (takeoverResponse?.error) {
|
||
throw new Error(takeoverResponse.error);
|
||
}
|
||
}
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: 'SKIP_STEP',
|
||
source: 'sidepanel',
|
||
payload: { step },
|
||
});
|
||
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
|
||
showToast(`步骤 ${step} 已跳过`, 'success', 2200);
|
||
}
|
||
|
||
// ============================================================
|
||
// Button Handlers
|
||
// ============================================================
|
||
|
||
stepsList?.addEventListener('click', async (event) => {
|
||
const btn = event.target.closest('.step-btn');
|
||
if (!btn) {
|
||
return;
|
||
}
|
||
try {
|
||
const step = Number(btn.dataset.step);
|
||
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
|
||
return;
|
||
}
|
||
await persistCurrentSettingsForAction();
|
||
if (step === 3) {
|
||
if (inputPassword.value !== (latestState?.customPassword || '')) {
|
||
await chrome.runtime.sendMessage({
|
||
type: 'SAVE_SETTING',
|
||
source: 'sidepanel',
|
||
payload: { customPassword: inputPassword.value },
|
||
});
|
||
syncLatestState({ customPassword: inputPassword.value });
|
||
}
|
||
let email = inputEmail.value.trim();
|
||
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
} else if (false && usesGeneratedAliasMailProvider(selectMailProvider.value)) {
|
||
const emailPrefix = inputEmailPrefix.value.trim();
|
||
if (!emailPrefix) {
|
||
showToast(selectMailProvider.value === GMAIL_PROVIDER ? '请先填写 Gmail 原邮箱。' : '请先填写 2925 邮箱前缀。', 'warn');
|
||
return;
|
||
}
|
||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, emailPrefix } });
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
} else {
|
||
let email = inputEmail.value.trim();
|
||
if (!email) {
|
||
if (isCustomMailProvider()) {
|
||
showToast('当前邮箱服务为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn');
|
||
return;
|
||
}
|
||
try {
|
||
email = await fetchGeneratedEmail({ showFailureToast: false });
|
||
} catch (err) {
|
||
showToast(`自动获取失败:${err.message},请手动粘贴邮箱后重试。`, 'warn');
|
||
return;
|
||
}
|
||
}
|
||
if (!validateCurrentRegistrationEmail(email, { showToastOnFailure: true })) {
|
||
return;
|
||
}
|
||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, email } });
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
}
|
||
} else {
|
||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
|
||
btnFetchEmail.addEventListener('click', async () => {
|
||
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider() || isCustomMailProvider()) {
|
||
return;
|
||
}
|
||
await fetchGeneratedEmail().catch(() => { });
|
||
});
|
||
|
||
btnTogglePassword.addEventListener('click', () => {
|
||
inputPassword.type = inputPassword.type === 'password' ? 'text' : 'password';
|
||
syncPasswordToggleLabel();
|
||
});
|
||
|
||
btnToggleVpsUrl.addEventListener('click', () => {
|
||
inputVpsUrl.type = inputVpsUrl.type === 'password' ? 'text' : 'password';
|
||
syncVpsUrlToggleLabel();
|
||
});
|
||
|
||
btnToggleVpsPassword.addEventListener('click', () => {
|
||
inputVpsPassword.type = inputVpsPassword.type === 'password' ? 'text' : 'password';
|
||
syncVpsPasswordToggleLabel();
|
||
});
|
||
|
||
btnToggleIpProxyApiUrl?.addEventListener('click', () => {
|
||
inputIpProxyApiUrl.type = inputIpProxyApiUrl.type === 'password' ? 'text' : 'password';
|
||
syncIpProxyApiUrlToggleLabel();
|
||
});
|
||
|
||
btnToggleIpProxyUsername?.addEventListener('click', () => {
|
||
inputIpProxyUsername.type = inputIpProxyUsername.type === 'password' ? 'text' : 'password';
|
||
syncIpProxyUsernameToggleLabel();
|
||
});
|
||
|
||
btnToggleIpProxyPassword?.addEventListener('click', () => {
|
||
inputIpProxyPassword.type = inputIpProxyPassword.type === 'password' ? 'text' : 'password';
|
||
syncIpProxyPasswordToggleLabel();
|
||
});
|
||
|
||
btnToggleIpProxySection?.addEventListener('click', () => {
|
||
if (typeof toggleIpProxySectionExpanded === 'function') {
|
||
toggleIpProxySectionExpanded();
|
||
}
|
||
});
|
||
|
||
btnTogglePhoneVerificationSection?.addEventListener('click', () => {
|
||
togglePhoneVerificationSectionExpanded();
|
||
});
|
||
|
||
btnMailLogin?.addEventListener('click', async () => {
|
||
const config = getMailProviderLoginConfig();
|
||
const loginUrl = getMailProviderLoginUrl();
|
||
if (!config || !loginUrl) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await chrome.tabs.create({ url: loginUrl, active: true });
|
||
} catch (err) {
|
||
showToast(`打开${config.label}失败:${err.message}`, 'error');
|
||
}
|
||
});
|
||
|
||
btnIpProxyServiceLogin?.addEventListener('click', () => {
|
||
const service = normalizeIpProxyService(
|
||
selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
|
||
);
|
||
const config = getIpProxyServiceLoginConfig(service);
|
||
const loginUrl = getIpProxyServiceLoginUrl(service);
|
||
if (!config || !loginUrl) {
|
||
showToast('当前代理服务没有可跳转的登录页。', 'warn', 1800);
|
||
return;
|
||
}
|
||
openExternalUrl(loginUrl);
|
||
});
|
||
|
||
localCpaStep9ModeButtons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
const nextMode = button.dataset.localCpaStep9Mode;
|
||
if (getSelectedLocalCpaStep9Mode() === normalizeLocalCpaStep9Mode(nextMode)) {
|
||
return;
|
||
}
|
||
setLocalCpaStep9Mode(nextMode);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
hotmailServiceModeButtons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
if (button.disabled) {
|
||
return;
|
||
}
|
||
const nextMode = button.dataset.hotmailServiceMode;
|
||
if (getSelectedHotmailServiceMode() === normalizeHotmailServiceMode(nextMode)) {
|
||
return;
|
||
}
|
||
setHotmailServiceMode(nextMode);
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
btnSaveSettings.addEventListener('click', async () => {
|
||
if (!settingsDirty) {
|
||
showToast('配置已是最新', 'info', 1400);
|
||
return;
|
||
}
|
||
await saveSettings({ silent: false }).catch(() => { });
|
||
});
|
||
|
||
btnStop.addEventListener('click', async () => {
|
||
btnStop.disabled = true;
|
||
await chrome.runtime.sendMessage({ type: 'STOP_FLOW', source: 'sidepanel', payload: {} });
|
||
showToast(isAutoRunScheduledPhase() ? '正在取消倒计时计划...' : '正在停止当前流程...', 'warn', 2000);
|
||
});
|
||
|
||
btnConfigMenu?.addEventListener('click', (event) => {
|
||
event.stopPropagation();
|
||
toggleConfigMenu();
|
||
});
|
||
|
||
btnRepoHome?.addEventListener('click', () => {
|
||
openRepositoryHomePage();
|
||
});
|
||
|
||
btnCloudflareTempEmailUsageGuide?.addEventListener('click', () => {
|
||
openCloudflareTempEmailUsageGuidePage();
|
||
});
|
||
|
||
btnCloudflareTempEmailGithub?.addEventListener('click', () => {
|
||
openCloudflareTempEmailRepositoryPage();
|
||
});
|
||
|
||
extensionUpdateStatus?.addEventListener('click', () => {
|
||
openReleaseListPage();
|
||
});
|
||
|
||
btnDismissContributionUpdateHint?.addEventListener('click', (event) => {
|
||
event.stopPropagation();
|
||
dismissContributionUpdateHint();
|
||
});
|
||
|
||
configMenu?.addEventListener('click', (event) => {
|
||
event.stopPropagation();
|
||
});
|
||
|
||
btnExportSettings?.addEventListener('click', async () => {
|
||
if (configActionInFlight || settingsSaveInFlight) {
|
||
return;
|
||
}
|
||
await exportSettingsFile();
|
||
});
|
||
|
||
btnImportSettings?.addEventListener('click', async () => {
|
||
if (configActionInFlight || settingsSaveInFlight) {
|
||
return;
|
||
}
|
||
closeConfigMenu();
|
||
if (inputImportSettingsFile) {
|
||
inputImportSettingsFile.value = '';
|
||
inputImportSettingsFile.click();
|
||
}
|
||
});
|
||
|
||
inputImportSettingsFile?.addEventListener('change', async () => {
|
||
const file = inputImportSettingsFile.files?.[0] || null;
|
||
await importSettingsFromFile(file);
|
||
});
|
||
|
||
autoStartModal?.addEventListener('click', (event) => {
|
||
if (event.target === autoStartModal) {
|
||
resolveModalChoice(null);
|
||
}
|
||
});
|
||
btnAutoStartClose?.addEventListener('click', () => resolveModalChoice(null));
|
||
|
||
async function startAutoRunFromCurrentSettings() {
|
||
try {
|
||
await refreshContributionContentHint();
|
||
} catch (error) {
|
||
console.warn('Failed to refresh contribution content hint before auto run:', error);
|
||
}
|
||
|
||
if (typeof persistCurrentSettingsForAction === 'function') {
|
||
await persistCurrentSettingsForAction();
|
||
}
|
||
|
||
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
|
||
&& usesCustomEmailPoolGenerator();
|
||
const lockedRunCount = typeof getLockedRunCountFromEmailPool === 'function'
|
||
? getLockedRunCountFromEmailPool()
|
||
: 0;
|
||
if (customEmailPoolEnabled && lockedRunCount <= 0) {
|
||
throw new Error('请先在邮箱池里至少填写 1 个邮箱。');
|
||
}
|
||
const totalRuns = lockedRunCount > 0 ? lockedRunCount : getRunCountValue();
|
||
if (lockedRunCount > 0) {
|
||
inputRunCount.value = String(lockedRunCount);
|
||
}
|
||
const plusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||
? Boolean(inputPlusModeEnabled.checked)
|
||
: Boolean(currentPlusModeEnabled || latestState?.plusModeEnabled);
|
||
let mode = 'restart';
|
||
const autoRunSkipFailures = inputAutoSkipFailures.checked;
|
||
const contributionNickname = String(inputContributionNickname?.value || '').trim();
|
||
const contributionQq = String(inputContributionQq?.value || '').trim();
|
||
const fallbackThreadIntervalMinutes = normalizeAutoRunThreadIntervalMinutes(
|
||
inputAutoSkipFailuresThreadIntervalMinutes.value
|
||
);
|
||
inputAutoSkipFailuresThreadIntervalMinutes.value = String(fallbackThreadIntervalMinutes);
|
||
|
||
if (shouldOfferAutoModeChoice()) {
|
||
const startStep = getFirstUnfinishedStep();
|
||
const runningStep = getRunningSteps()[0] ?? null;
|
||
const choice = await openAutoStartChoiceDialog(startStep, { runningStep });
|
||
if (!choice) {
|
||
return false;
|
||
}
|
||
mode = choice;
|
||
}
|
||
|
||
const confirmedPlusContributionPrompt = await maybeShowPlusContributionPromptBeforeAutoRun(plusModeEnabled);
|
||
if (!confirmedPlusContributionPrompt) {
|
||
return false;
|
||
}
|
||
|
||
if (shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures)
|
||
&& !isAutoRunFallbackRiskPromptDismissed()) {
|
||
const result = await openAutoRunFallbackRiskConfirmModal(totalRuns);
|
||
if (!result.confirmed) {
|
||
return false;
|
||
}
|
||
if (result.dismissPrompt) {
|
||
setAutoRunFallbackRiskPromptDismissed(true);
|
||
}
|
||
}
|
||
|
||
if (shouldWarnPlusAutoRunRisk(totalRuns, plusModeEnabled)
|
||
&& !isAutoRunPlusRiskPromptDismissed()) {
|
||
const result = await openPlusAutoRunRiskConfirmModal(totalRuns);
|
||
if (!result.confirmed) {
|
||
return false;
|
||
}
|
||
if (result.dismissPrompt) {
|
||
setAutoRunPlusRiskPromptDismissed(true);
|
||
}
|
||
}
|
||
|
||
btnAutoRun.disabled = true;
|
||
inputRunCount.disabled = true;
|
||
const delayEnabled = inputAutoDelayEnabled.checked;
|
||
const delayMinutes = normalizeAutoDelayMinutes(inputAutoDelayMinutes.value);
|
||
inputAutoDelayMinutes.value = String(delayMinutes);
|
||
btnAutoRun.innerHTML = delayEnabled
|
||
? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 璁″垝涓?..'
|
||
: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 杩愯涓?..';
|
||
const response = await chrome.runtime.sendMessage({
|
||
type: delayEnabled ? 'SCHEDULE_AUTO_RUN' : 'AUTO_RUN',
|
||
source: 'sidepanel',
|
||
payload: {
|
||
totalRuns,
|
||
delayMinutes,
|
||
autoRunSkipFailures,
|
||
contributionMode: Boolean(latestState?.contributionMode),
|
||
contributionNickname,
|
||
contributionQq,
|
||
mode,
|
||
},
|
||
});
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// Auto Run
|
||
btnAutoRun.addEventListener('click', async () => {
|
||
try {
|
||
await startAutoRunFromCurrentSettings();
|
||
} catch (err) {
|
||
setDefaultAutoRunButton();
|
||
inputRunCount.disabled = shouldLockRunCountToEmailPool();
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
|
||
btnAutoContinue.addEventListener('click', async () => {
|
||
const email = inputEmail.value.trim();
|
||
if (!email) {
|
||
showToast(
|
||
isCustomMailProvider() ? '请先填写自定义注册邮箱。' : '请先获取或粘贴邮箱。',
|
||
'warn'
|
||
);
|
||
return;
|
||
}
|
||
autoContinueBar.style.display = 'none';
|
||
await chrome.runtime.sendMessage({ type: 'RESUME_AUTO_RUN', source: 'sidepanel', payload: { email } });
|
||
});
|
||
|
||
btnAutoRunNow?.addEventListener('click', async () => {
|
||
try {
|
||
btnAutoRunNow.disabled = true;
|
||
const waitingInterval = currentAutoRun.phase === 'waiting_interval';
|
||
await chrome.runtime.sendMessage({
|
||
type: waitingInterval ? 'SKIP_AUTO_RUN_COUNTDOWN' : 'START_SCHEDULED_AUTO_RUN_NOW',
|
||
source: 'sidepanel',
|
||
payload: {},
|
||
});
|
||
if (waitingInterval) {
|
||
showToast('已跳过当前倒计时,自动流程将立即继续。', 'info', 1800);
|
||
}
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
} finally {
|
||
btnAutoRunNow.disabled = false;
|
||
}
|
||
});
|
||
|
||
btnAutoCancelSchedule?.addEventListener('click', async () => {
|
||
try {
|
||
btnAutoCancelSchedule.disabled = true;
|
||
await chrome.runtime.sendMessage({ type: 'CANCEL_SCHEDULED_AUTO_RUN', source: 'sidepanel', payload: {} });
|
||
showToast('已取消倒计时计划。', 'info', 1800);
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
} finally {
|
||
btnAutoCancelSchedule.disabled = false;
|
||
}
|
||
});
|
||
|
||
// Reset
|
||
btnReset.addEventListener('click', async () => {
|
||
const confirmed = await openConfirmModal({
|
||
title: '重置流程',
|
||
message: '确认重置全部步骤和数据吗?',
|
||
confirmLabel: '确认重置',
|
||
confirmVariant: 'btn-danger',
|
||
});
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
|
||
await chrome.runtime.sendMessage({ type: 'RESET', source: 'sidepanel' });
|
||
syncLatestState({
|
||
stepStatuses: STEP_DEFAULT_STATUSES,
|
||
currentHotmailAccountId: null,
|
||
currentLuckmailPurchase: null,
|
||
currentLuckmailMailCursor: null,
|
||
email: null,
|
||
});
|
||
syncAutoRunState({
|
||
autoRunning: false,
|
||
autoRunPhase: 'idle',
|
||
autoRunCurrentRun: 0,
|
||
autoRunTotalRuns: 1,
|
||
autoRunAttemptRun: 0,
|
||
scheduledAutoRunAt: null,
|
||
autoRunCountdownAt: null,
|
||
autoRunCountdownTitle: '',
|
||
autoRunCountdownNote: '',
|
||
});
|
||
displayOauthUrl.textContent = '等待中...';
|
||
displayOauthUrl.classList.remove('has-value');
|
||
displayLocalhostUrl.textContent = '等待中...';
|
||
displayLocalhostUrl.classList.remove('has-value');
|
||
inputEmail.value = '';
|
||
displayStatus.textContent = '就绪';
|
||
statusBar.className = 'status-bar';
|
||
logArea.innerHTML = '';
|
||
resetIcloudManager();
|
||
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
||
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
||
setDefaultAutoRunButton();
|
||
applyAutoRunStatus(currentAutoRun);
|
||
markSettingsDirty(false);
|
||
updateStopButtonState(false);
|
||
updateButtonStates();
|
||
updateProgressCounter();
|
||
renderHotmailAccounts();
|
||
resetLuckmailManager();
|
||
if (isLuckmailProvider()) {
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
});
|
||
|
||
// Clear log
|
||
btnClearLog.addEventListener('click', () => {
|
||
logArea.innerHTML = '';
|
||
});
|
||
|
||
// Save settings on change
|
||
inputEmail.addEventListener('change', async () => {
|
||
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
||
return;
|
||
}
|
||
const email = inputEmail.value.trim();
|
||
inputEmail.value = email;
|
||
try {
|
||
if (email) {
|
||
if (!validateCurrentRegistrationEmail(email, { showToastOnFailure: true })) {
|
||
return;
|
||
}
|
||
const response = await chrome.runtime.sendMessage({ type: 'SAVE_EMAIL', source: 'sidepanel', payload: { email } });
|
||
if (response?.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
} else {
|
||
await setRuntimeEmailState(null);
|
||
}
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
inputEmail.addEventListener('input', updateButtonStates);
|
||
inputVpsUrl.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputVpsUrl.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputVpsPassword.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputVpsPassword.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
[inputHotmailRemoteBaseUrl, inputHotmailLocalBaseUrl].forEach((input) => {
|
||
input?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
input?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
[inputLuckmailApiKey, inputLuckmailBaseUrl, inputLuckmailDomain].forEach((input) => {
|
||
input?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
input?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
selectLuckmailEmailType?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPassword.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
updateButtonStates();
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPassword.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPlusModeEnabled?.addEventListener('change', () => {
|
||
updatePlusModeUI();
|
||
updateSignupMethodUI({ notify: true });
|
||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled.checked), getSelectedPlusPaymentMethod(), { render: true });
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
|
||
updatePlusModeUI();
|
||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled?.checked), selectPlusPaymentMethod.value, { render: true });
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
||
updatePlusModeUI();
|
||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled?.checked), {
|
||
render: true,
|
||
plusPaymentMethod: selectPlusPaymentMethod.value,
|
||
});
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
[
|
||
selectGoPayCountryCode,
|
||
inputGoPayPhone,
|
||
inputGoPayOtp,
|
||
inputGoPayPin,
|
||
].forEach((input) => {
|
||
input?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
input?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
selectMailProvider.addEventListener('change', async () => {
|
||
const previousProvider = latestState?.mailProvider || '';
|
||
const previousMail2925Mode = latestState?.mail2925Mode;
|
||
const nextProvider = selectMailProvider.value;
|
||
syncManagedAliasBaseEmailDraftFromInput(previousProvider);
|
||
setManagedAliasBaseEmailInputForProvider(nextProvider, latestState);
|
||
updateMailProviderUI();
|
||
const leavingHotmail = previousProvider === 'hotmail-api'
|
||
&& nextProvider !== 'hotmail-api'
|
||
&& isCurrentEmailManagedByHotmail();
|
||
const leavingLuckmail = previousProvider === LUCKMAIL_PROVIDER
|
||
&& nextProvider !== LUCKMAIL_PROVIDER
|
||
&& isCurrentEmailManagedByLuckmail();
|
||
const leavingGeneratedAlias = (
|
||
previousProvider !== nextProvider
|
||
|| (previousProvider === '2925' && normalizeMail2925Mode(previousMail2925Mode) !== getSelectedMail2925Mode())
|
||
) && usesGeneratedAliasMailProvider(previousProvider, previousMail2925Mode)
|
||
&& isCurrentEmailManagedByGeneratedAlias(previousProvider, latestState, previousMail2925Mode);
|
||
if (leavingHotmail || leavingLuckmail || leavingGeneratedAlias) {
|
||
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
||
}
|
||
if (nextProvider === '2925' && Boolean(inputMail2925UseAccountPool?.checked)) {
|
||
syncMail2925PoolAccountOptions(latestState);
|
||
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
|
||
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
|
||
}
|
||
await syncSelectedMail2925PoolAccount({ silent: true }).catch(() => { });
|
||
}
|
||
if (nextProvider === LUCKMAIL_PROVIDER) {
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
mail2925ModeButtons.forEach((button) => {
|
||
button.addEventListener('click', async () => {
|
||
const nextMode = normalizeMail2925Mode(button.dataset.mail2925Mode);
|
||
const previousMode = normalizeMail2925Mode(latestState?.mail2925Mode);
|
||
if (nextMode === getSelectedMail2925Mode()) {
|
||
return;
|
||
}
|
||
|
||
setMail2925Mode(nextMode);
|
||
updateMailProviderUI();
|
||
|
||
const leavingGeneratedAlias = selectMailProvider.value === '2925'
|
||
&& previousMode === MAIL_2925_MODE_PROVIDE
|
||
&& nextMode !== MAIL_2925_MODE_PROVIDE
|
||
&& isCurrentEmailManagedByGeneratedAlias('2925', latestState, previousMode);
|
||
if (leavingGeneratedAlias) {
|
||
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
||
}
|
||
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
selectEmailGenerator.addEventListener('change', () => {
|
||
updateMailProviderUI();
|
||
clearRegistrationEmail({ silent: true }).catch(() => { });
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectIcloudHostPreference?.addEventListener('change', () => {
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
if (getSelectedEmailGenerator() === 'icloud') {
|
||
queueIcloudAliasRefresh();
|
||
}
|
||
});
|
||
|
||
selectIcloudTargetMailboxType?.addEventListener('change', () => {
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectIcloudForwardMailProvider?.addEventListener('change', () => {
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectIcloudFetchMode?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
checkboxAutoDeleteIcloud?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectPanelMode.addEventListener('change', () => {
|
||
updatePanelModeUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
function syncCurrentIpProxyServiceProfileToLatestState() {
|
||
const selectedService = normalizeIpProxyService(
|
||
selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
|
||
);
|
||
const normalizedProfiles = typeof buildIpProxyServiceProfilesPatch === 'function'
|
||
? buildIpProxyServiceProfilesPatch(selectedService, latestState || {})
|
||
: { ...(latestState?.ipProxyServiceProfiles || {}) };
|
||
const currentProfile = typeof getIpProxyServiceProfile === 'function'
|
||
? getIpProxyServiceProfile(selectedService, {
|
||
...(latestState || {}),
|
||
ipProxyService: selectedService,
|
||
ipProxyServiceProfiles: normalizedProfiles,
|
||
})
|
||
: {
|
||
mode: normalizeIpProxyMode(getSelectedIpProxyMode()),
|
||
apiUrl: String(inputIpProxyApiUrl?.value || '').trim(),
|
||
accountList: normalizeIpProxyAccountList(inputIpProxyAccountList?.value || ''),
|
||
accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(inputIpProxyAccountSessionPrefix?.value || ''),
|
||
accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(inputIpProxyAccountLifeMinutes?.value || ''),
|
||
poolTargetCount: normalizeIpProxyPoolTargetCount(inputIpProxyPoolTargetCount?.value || '', 20),
|
||
host: String(inputIpProxyHost?.value || '').trim(),
|
||
port: String(normalizeIpProxyPort(inputIpProxyPort?.value || '') || ''),
|
||
protocol: normalizeIpProxyProtocol(selectIpProxyProtocol?.value || ''),
|
||
username: String(inputIpProxyUsername?.value || '').trim(),
|
||
password: String(inputIpProxyPassword?.value || ''),
|
||
region: String(inputIpProxyRegion?.value || '').trim(),
|
||
};
|
||
syncLatestState({
|
||
ipProxyService: selectedService,
|
||
ipProxyServiceProfiles: normalizedProfiles,
|
||
...(typeof buildIpProxyStatePatchFromServiceProfile === 'function'
|
||
? buildIpProxyStatePatchFromServiceProfile(selectedService, currentProfile)
|
||
: {}),
|
||
});
|
||
}
|
||
|
||
function handleIpProxyEnabledToggle(nextEnabled) {
|
||
const enabled = Boolean(nextEnabled);
|
||
const previousEnabled = Boolean(latestState?.ipProxyEnabled);
|
||
if (previousEnabled === enabled) {
|
||
setIpProxyEnabled(enabled);
|
||
updateIpProxyUI(latestState);
|
||
return;
|
||
}
|
||
setIpProxyEnabled(enabled);
|
||
if (enabled && typeof setIpProxySectionExpanded === 'function') {
|
||
setIpProxySectionExpanded(true);
|
||
}
|
||
syncLatestState({ ipProxyEnabled: enabled });
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
}
|
||
|
||
if (inputIpProxyEnabled) {
|
||
inputIpProxyEnabled.addEventListener('change', () => {
|
||
handleIpProxyEnabledToggle(Boolean(inputIpProxyEnabled.checked));
|
||
});
|
||
} else {
|
||
ipProxyEnabledButtons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
const nextEnabled = String(button.dataset.ipProxyEnabled) === 'true';
|
||
handleIpProxyEnabledToggle(nextEnabled);
|
||
});
|
||
});
|
||
}
|
||
|
||
selectIpProxyService?.addEventListener('change', () => {
|
||
const previousService = normalizeIpProxyService(latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
|
||
const nextService = normalizeIpProxyService(selectIpProxyService.value);
|
||
const normalizedProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
||
? normalizeIpProxyServiceProfiles(latestState?.ipProxyServiceProfiles || {}, latestState || {})
|
||
: { ...(latestState?.ipProxyServiceProfiles || {}) };
|
||
|
||
if (typeof buildCurrentIpProxyServiceProfileFromInputs === 'function') {
|
||
normalizedProfiles[previousService] = buildCurrentIpProxyServiceProfileFromInputs();
|
||
}
|
||
|
||
const nextProfile = typeof getIpProxyServiceProfile === 'function'
|
||
? getIpProxyServiceProfile(nextService, {
|
||
...(latestState || {}),
|
||
ipProxyService: nextService,
|
||
ipProxyServiceProfiles: normalizedProfiles,
|
||
})
|
||
: {
|
||
mode: typeof normalizeIpProxyModeForCurrentRelease === 'function'
|
||
? normalizeIpProxyModeForCurrentRelease(latestState?.ipProxyMode)
|
||
: normalizeIpProxyMode(latestState?.ipProxyMode),
|
||
apiUrl: String(latestState?.ipProxyApiUrl || '').trim(),
|
||
accountList: normalizeIpProxyAccountList(latestState?.ipProxyAccountList || ''),
|
||
accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(latestState?.ipProxyAccountSessionPrefix || ''),
|
||
accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(latestState?.ipProxyAccountLifeMinutes || ''),
|
||
poolTargetCount: normalizeIpProxyPoolTargetCount(latestState?.ipProxyPoolTargetCount || '', 20),
|
||
host: String(latestState?.ipProxyHost || '').trim(),
|
||
port: String(normalizeIpProxyPort(latestState?.ipProxyPort || '') || ''),
|
||
protocol: normalizeIpProxyProtocol(latestState?.ipProxyProtocol),
|
||
username: String(latestState?.ipProxyUsername || '').trim(),
|
||
password: String(latestState?.ipProxyPassword || ''),
|
||
region: String(latestState?.ipProxyRegion || '').trim(),
|
||
};
|
||
|
||
if (typeof applyIpProxyServiceProfileToInputs === 'function') {
|
||
applyIpProxyServiceProfileToInputs(nextProfile);
|
||
} else {
|
||
setIpProxyMode(nextProfile.mode);
|
||
}
|
||
|
||
syncLatestState({
|
||
ipProxyService: nextService,
|
||
ipProxyServiceProfiles: normalizedProfiles,
|
||
...(typeof buildIpProxyStatePatchFromServiceProfile === 'function'
|
||
? buildIpProxyStatePatchFromServiceProfile(nextService, nextProfile)
|
||
: {}),
|
||
});
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
ipProxyModeButtons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
const nextMode = normalizeIpProxyMode(button.dataset.ipProxyMode || DEFAULT_IP_PROXY_MODE);
|
||
const apiModeAvailable = typeof isIpProxyApiModeAvailable === 'function'
|
||
? Boolean(isIpProxyApiModeAvailable())
|
||
: (typeof IP_PROXY_API_MODE_ENABLED !== 'undefined' ? Boolean(IP_PROXY_API_MODE_ENABLED) : false);
|
||
if (!apiModeAvailable && nextMode === 'api') {
|
||
setIpProxyMode('account');
|
||
updateIpProxyUI(latestState);
|
||
showToast('API 模式暂未开放,请先使用账号密码模式。', 'info', 1800);
|
||
return;
|
||
}
|
||
if (getSelectedIpProxyMode() === nextMode) {
|
||
return;
|
||
}
|
||
setIpProxyMode(nextMode);
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
});
|
||
|
||
selectIpProxyProtocol?.addEventListener('change', () => {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
btnIpProxyRefresh?.addEventListener('click', async () => {
|
||
try {
|
||
const result = typeof runIpProxyActionWithLock === 'function'
|
||
? await runIpProxyActionWithLock('refresh', async () => {
|
||
await saveSettings({ silent: true });
|
||
await refreshIpProxyPoolByApi();
|
||
})
|
||
: await (async () => {
|
||
await saveSettings({ silent: true });
|
||
await refreshIpProxyPoolByApi();
|
||
return { skipped: false };
|
||
})();
|
||
if (result?.skipped) {
|
||
return;
|
||
}
|
||
} catch (err) {
|
||
showToast(err?.message || String(err || '未知错误'), 'error');
|
||
}
|
||
});
|
||
|
||
btnIpProxyNext?.addEventListener('click', async () => {
|
||
try {
|
||
const result = typeof runIpProxyActionWithLock === 'function'
|
||
? await runIpProxyActionWithLock('next', async () => {
|
||
await saveSettings({ silent: true });
|
||
await switchIpProxyToNext();
|
||
})
|
||
: await (async () => {
|
||
await saveSettings({ silent: true });
|
||
await switchIpProxyToNext();
|
||
return { skipped: false };
|
||
})();
|
||
if (result?.skipped) {
|
||
return;
|
||
}
|
||
} catch (err) {
|
||
showToast(err?.message || String(err || '未知错误'), 'error');
|
||
}
|
||
});
|
||
|
||
btnIpProxyChange?.addEventListener('click', async () => {
|
||
try {
|
||
const result = typeof runIpProxyActionWithLock === 'function'
|
||
? await runIpProxyActionWithLock('change', async () => {
|
||
await saveSettings({ silent: true });
|
||
await changeIpProxyExitBySession();
|
||
})
|
||
: await (async () => {
|
||
await saveSettings({ silent: true });
|
||
await changeIpProxyExitBySession();
|
||
return { skipped: false };
|
||
})();
|
||
if (result?.skipped) {
|
||
return;
|
||
}
|
||
} catch (err) {
|
||
showToast(err?.message || String(err || '未知错误'), 'error');
|
||
}
|
||
});
|
||
|
||
btnIpProxyProbe?.addEventListener('click', async () => {
|
||
try {
|
||
const result = typeof runIpProxyActionWithLock === 'function'
|
||
? await runIpProxyActionWithLock('probe', async () => {
|
||
await saveSettings({ silent: true });
|
||
await probeIpProxyExit();
|
||
})
|
||
: await (async () => {
|
||
await saveSettings({ silent: true });
|
||
await probeIpProxyExit();
|
||
return { skipped: false };
|
||
})();
|
||
if (result?.skipped) {
|
||
return;
|
||
}
|
||
} catch (err) {
|
||
showToast(err?.message || String(err || '未知错误'), 'error');
|
||
}
|
||
});
|
||
|
||
btnIpProxyCheckIp?.addEventListener('click', async () => {
|
||
try {
|
||
await chrome.tabs.create({ url: 'https://ipinfo.io/what-is-my-ip' });
|
||
} catch (err) {
|
||
showToast(`打开 IP 检测页失败:${err?.message || String(err || '未知错误')}`, 'error');
|
||
}
|
||
});
|
||
|
||
selectCfDomain.addEventListener('change', () => {
|
||
if (selectCfDomain.disabled) {
|
||
return;
|
||
}
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectTempEmailDomain.addEventListener('change', () => {
|
||
if (selectTempEmailDomain.disabled) {
|
||
return;
|
||
}
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
btnCfDomainMode.addEventListener('click', async () => {
|
||
try {
|
||
if (!cloudflareDomainEditMode) {
|
||
setCloudflareDomainEditMode(true, { clearInput: true });
|
||
return;
|
||
}
|
||
|
||
const newDomain = normalizeCloudflareDomainValue(inputCfDomain.value);
|
||
if (!newDomain) {
|
||
showToast('请输入有效的 Cloudflare 域名。', 'warn');
|
||
inputCfDomain.focus();
|
||
return;
|
||
}
|
||
|
||
const { domains } = getCloudflareDomainsFromState();
|
||
await saveCloudflareDomainSettings([...domains, newDomain], newDomain);
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
|
||
btnTempEmailDomainMode.addEventListener('click', async () => {
|
||
try {
|
||
if (!cloudflareTempEmailDomainEditMode) {
|
||
setCloudflareTempEmailDomainEditMode(true, { clearInput: true });
|
||
return;
|
||
}
|
||
|
||
const newDomain = normalizeCloudflareTempEmailDomainValue(inputTempEmailDomain.value);
|
||
if (!newDomain) {
|
||
showToast('请输入有效的 Cloudflare Temp Email 域名。', 'warn');
|
||
inputTempEmailDomain.focus();
|
||
return;
|
||
}
|
||
|
||
const { domains } = getCloudflareTempEmailDomainsFromState();
|
||
await saveCloudflareTempEmailDomainSettings([...domains, newDomain], newDomain);
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
|
||
inputCfDomain.addEventListener('keydown', (event) => {
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
btnCfDomainMode.click();
|
||
}
|
||
});
|
||
|
||
inputTempEmailDomain.addEventListener('keydown', (event) => {
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
btnTempEmailDomainMode.click();
|
||
}
|
||
});
|
||
|
||
inputSub2ApiUrl.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputSub2ApiUrl.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputSub2ApiEmail.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputSub2ApiEmail.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputSub2ApiPassword.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputSub2ApiPassword.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputSub2ApiGroup.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputSub2ApiGroup.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputSub2ApiDefaultProxy.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputSub2ApiDefaultProxy.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputCodex2ApiUrl.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputCodex2ApiUrl.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputCodex2ApiAdminKey.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputCodex2ApiAdminKey.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
[
|
||
inputIpProxyApiUrl,
|
||
inputIpProxyAccountList,
|
||
inputIpProxyHost,
|
||
inputIpProxyUsername,
|
||
inputIpProxyPassword,
|
||
].forEach((input) => {
|
||
input?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
input?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
});
|
||
|
||
inputIpProxyUsername?.addEventListener('paste', () => {
|
||
setTimeout(() => {
|
||
let profileUpdated = false;
|
||
if (typeof sync711SessionFieldsFromUsernameForPanel !== 'function') {
|
||
profileUpdated = false;
|
||
} else {
|
||
const result = sync711SessionFieldsFromUsernameForPanel();
|
||
profileUpdated = profileUpdated || Boolean(result?.updated);
|
||
}
|
||
if (typeof sync711RegionFieldFromUsernameForPanel === 'function') {
|
||
const regionResult = sync711RegionFieldFromUsernameForPanel();
|
||
profileUpdated = profileUpdated || Boolean(regionResult?.updated);
|
||
}
|
||
if (typeof syncIpProxyRegionInputFromCredentials === 'function') {
|
||
const beforeRegion = String(inputIpProxyRegion?.value || '');
|
||
syncIpProxyRegionInputFromCredentials({ force: true });
|
||
const afterRegion = String(inputIpProxyRegion?.value || '');
|
||
profileUpdated = profileUpdated || (beforeRegion !== afterRegion);
|
||
}
|
||
if (!profileUpdated) return;
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
}, 0);
|
||
});
|
||
|
||
inputIpProxyHost?.addEventListener('blur', () => {
|
||
if (typeof syncIpProxyRegionInputFromCredentials === 'function') {
|
||
const beforeRegion = String(inputIpProxyRegion?.value || '');
|
||
syncIpProxyRegionInputFromCredentials({ force: true });
|
||
const afterRegion = String(inputIpProxyRegion?.value || '');
|
||
if (afterRegion !== beforeRegion) {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
}
|
||
}
|
||
});
|
||
|
||
inputIpProxyUsername?.addEventListener('blur', () => {
|
||
let profileUpdated = false;
|
||
if (typeof sync711SessionFieldsFromUsernameForPanel === 'function') {
|
||
const result = sync711SessionFieldsFromUsernameForPanel();
|
||
profileUpdated = profileUpdated || Boolean(result?.updated);
|
||
}
|
||
if (typeof sync711RegionFieldFromUsernameForPanel === 'function') {
|
||
const regionResult = sync711RegionFieldFromUsernameForPanel();
|
||
profileUpdated = profileUpdated || Boolean(regionResult?.updated);
|
||
}
|
||
|
||
if (typeof syncIpProxyRegionInputFromCredentials === 'function') {
|
||
const beforeRegion = String(inputIpProxyRegion?.value || '');
|
||
syncIpProxyRegionInputFromCredentials({ force: true });
|
||
const afterRegion = String(inputIpProxyRegion?.value || '');
|
||
profileUpdated = profileUpdated || (afterRegion !== beforeRegion);
|
||
}
|
||
|
||
if (profileUpdated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
}
|
||
});
|
||
|
||
inputIpProxyAccountSessionPrefix?.addEventListener('input', () => {
|
||
const syncResult = typeof sync711UsernameFromSessionFieldsForPanel === 'function'
|
||
? sync711UsernameFromSessionFieldsForPanel()
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputIpProxyAccountSessionPrefix?.addEventListener('blur', () => {
|
||
inputIpProxyAccountSessionPrefix.value = normalizeIpProxyAccountSessionPrefix(inputIpProxyAccountSessionPrefix.value || '');
|
||
const syncResult = typeof sync711UsernameFromSessionFieldsForPanel === 'function'
|
||
? sync711UsernameFromSessionFieldsForPanel({ removeWhenEmpty: true })
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
}
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyAccountLifeMinutes?.addEventListener('input', () => {
|
||
const syncResult = typeof sync711UsernameFromSessionFieldsForPanel === 'function'
|
||
? sync711UsernameFromSessionFieldsForPanel()
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputIpProxyAccountLifeMinutes?.addEventListener('blur', () => {
|
||
inputIpProxyAccountLifeMinutes.value = normalizeIpProxyAccountLifeMinutes(inputIpProxyAccountLifeMinutes.value || '');
|
||
const syncResult = typeof sync711UsernameFromSessionFieldsForPanel === 'function'
|
||
? sync711UsernameFromSessionFieldsForPanel({ removeWhenEmpty: true })
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
}
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyRegion?.addEventListener('input', () => {
|
||
const normalizedRegion = typeof normalize711RegionCodeForPanel === 'function'
|
||
? normalize711RegionCodeForPanel(inputIpProxyRegion.value || '')
|
||
: String(inputIpProxyRegion.value || '').trim().toUpperCase();
|
||
if (normalizedRegion) {
|
||
inputIpProxyRegion.value = normalizedRegion;
|
||
}
|
||
|
||
const syncResult = typeof sync711UsernameFromRegionForPanel === 'function'
|
||
? sync711UsernameFromRegionForPanel()
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputIpProxyRegion?.addEventListener('blur', () => {
|
||
const normalizedRegion = typeof normalize711RegionCodeForPanel === 'function'
|
||
? normalize711RegionCodeForPanel(inputIpProxyRegion.value || '')
|
||
: String(inputIpProxyRegion.value || '').trim().toUpperCase();
|
||
inputIpProxyRegion.value = normalizedRegion;
|
||
const syncResult = typeof sync711UsernameFromRegionForPanel === 'function'
|
||
? sync711UsernameFromRegionForPanel({ removeWhenEmpty: true })
|
||
: null;
|
||
if (syncResult?.updated) {
|
||
syncCurrentIpProxyServiceProfileToLatestState();
|
||
updateIpProxyUI(latestState);
|
||
markSettingsDirty(true);
|
||
}
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyPoolTargetCount?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputIpProxyPoolTargetCount?.addEventListener('blur', () => {
|
||
inputIpProxyPoolTargetCount.value = normalizeIpProxyPoolTargetCount(inputIpProxyPoolTargetCount.value || '', 20);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyAutoSyncEnabled?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
if (typeof updateIpProxyUI === 'function') {
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyAutoSyncIntervalMinutes?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
|
||
inputIpProxyAutoSyncIntervalMinutes?.addEventListener('blur', () => {
|
||
const numeric = Number.parseInt(String(inputIpProxyAutoSyncIntervalMinutes.value || '').trim(), 10);
|
||
const normalized = Number.isFinite(numeric)
|
||
? Math.max(1, Math.min(1440, numeric))
|
||
: 15;
|
||
inputIpProxyAutoSyncIntervalMinutes.value = String(normalized);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputIpProxyPort?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputIpProxyPort?.addEventListener('blur', () => {
|
||
const normalizedPort = normalizeIpProxyPort(inputIpProxyPort.value || '');
|
||
inputIpProxyPort.value = normalizedPort > 0 ? String(normalizedPort) : '';
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
});
|
||
|
||
inputEmailPrefix.addEventListener('input', () => {
|
||
maybeClearGeneratedAliasAfterEmailPrefixChange().catch(() => { });
|
||
syncManagedAliasBaseEmailDraftFromInput();
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputEmailPrefix.addEventListener('blur', () => {
|
||
maybeClearGeneratedAliasAfterEmailPrefixChange().catch(() => { });
|
||
syncManagedAliasBaseEmailDraftFromInput();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputCustomEmailPool?.addEventListener('input', () => {
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputCustomEmailPool?.addEventListener('blur', () => {
|
||
inputCustomEmailPool.value = normalizeCustomEmailPoolEntries(inputCustomEmailPool.value).join('\n');
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateMailProviderUI();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputCustomMailProviderPool?.addEventListener('input', () => {
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputCustomMailProviderPool?.addEventListener('blur', () => {
|
||
inputCustomMailProviderPool.value = normalizeCustomEmailPoolEntries(inputCustomMailProviderPool.value).join('\n');
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateMailProviderUI();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectMail2925PoolAccount?.addEventListener('change', async () => {
|
||
try {
|
||
await syncSelectedMail2925PoolAccount();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
|
||
inputMail2925UseAccountPool?.addEventListener('change', async () => {
|
||
const enabled = Boolean(inputMail2925UseAccountPool.checked);
|
||
syncLatestState({ mail2925UseAccountPool: enabled });
|
||
if (enabled) {
|
||
syncMail2925PoolAccountOptions(latestState);
|
||
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
|
||
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
|
||
}
|
||
try {
|
||
await syncSelectedMail2925PoolAccount({ silent: true });
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
}
|
||
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||
updateMailProviderUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputInbucketMailbox.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputInbucketMailbox.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputInbucketHost.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputInbucketHost.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputRunCount.addEventListener('input', () => {
|
||
updateFallbackThreadIntervalInputState();
|
||
});
|
||
inputRunCount.addEventListener('blur', () => {
|
||
if (shouldLockRunCountToEmailPool()) {
|
||
syncRunCountFromConfiguredEmailPool();
|
||
updateFallbackThreadIntervalInputState();
|
||
return;
|
||
}
|
||
inputRunCount.value = String(getRunCountValue());
|
||
updateFallbackThreadIntervalInputState();
|
||
});
|
||
|
||
inputAutoSkipFailures.addEventListener('change', async () => {
|
||
if (inputAutoSkipFailures.checked && !isAutoSkipFailuresPromptDismissed()) {
|
||
const result = await openAutoSkipFailuresConfirmModal();
|
||
if (!result.confirmed) {
|
||
inputAutoSkipFailures.checked = false;
|
||
updateFallbackThreadIntervalInputState();
|
||
return;
|
||
}
|
||
if (result.dismissPrompt) {
|
||
setAutoSkipFailuresPromptDismissed(true);
|
||
}
|
||
}
|
||
updateFallbackThreadIntervalInputState();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputTempEmailBaseUrl.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputTempEmailBaseUrl.addEventListener('blur', () => {
|
||
inputTempEmailBaseUrl.value = normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputTempEmailAdminAuth.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputTempEmailAdminAuth.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputTempEmailCustomAuth.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputTempEmailCustomAuth.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputTempEmailReceiveMailbox.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputTempEmailReceiveMailbox.addEventListener('blur', () => {
|
||
inputTempEmailReceiveMailbox.value = normalizeCloudflareTempEmailReceiveMailboxValue(inputTempEmailReceiveMailbox.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputTempEmailUseRandomSubdomain?.addEventListener('change', () => {
|
||
updateMailProviderUI();
|
||
clearRegistrationEmail({ silent: true }).catch(() => { });
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('blur', () => {
|
||
inputAutoSkipFailuresThreadIntervalMinutes.value = String(
|
||
normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputAutoDelayEnabled.addEventListener('change', () => {
|
||
updateAutoDelayInputState();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputAutoDelayMinutes.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputAutoDelayMinutes.addEventListener('blur', () => {
|
||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(inputAutoDelayMinutes.value));
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
|
||
|
||
function getPhoneSmsCountrySelectionForProvider(provider = getSelectedPhoneSmsProvider(), options = {}) {
|
||
const normalizedProvider = normalizePhoneSmsProvider(provider);
|
||
const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback;
|
||
const selectionLimit = Math.max(1, Math.floor(Number(options.maxSelection) || HERO_SMS_COUNTRY_SELECTION_MAX));
|
||
const ensureDefault = options.ensureDefault !== false;
|
||
const defaultCountry = normalizedProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? { id: DEFAULT_FIVE_SIM_COUNTRY_ID, label: DEFAULT_FIVE_SIM_COUNTRY_LABEL }
|
||
: { id: DEFAULT_HERO_SMS_COUNTRY_ID, label: DEFAULT_HERO_SMS_COUNTRY_LABEL };
|
||
|
||
if (!countrySelect) {
|
||
return ensureDefault ? [defaultCountry] : [];
|
||
}
|
||
|
||
const optionByValue = new Map(
|
||
Array.from(countrySelect.options || []).map((option) => [String(option.value), option])
|
||
);
|
||
const selectedIds = Array.from(countrySelect.options || [])
|
||
.filter((option) => option.selected)
|
||
.map((option) => normalizePhoneSmsCountryId(option.value, normalizedProvider))
|
||
.filter(Boolean);
|
||
|
||
if (!selectedIds.length && !countrySelect.multiple) {
|
||
const fallbackId = normalizePhoneSmsCountryId(countrySelect.value, normalizedProvider);
|
||
if (fallbackId) {
|
||
selectedIds.push(fallbackId);
|
||
}
|
||
}
|
||
|
||
const selectedSet = new Set(selectedIds.map((id) => String(id)));
|
||
let orderedIds = heroSmsCountrySelectionOrder
|
||
.map((id) => normalizePhoneSmsCountryId(id, normalizedProvider))
|
||
.filter((id) => id && selectedSet.has(String(id)));
|
||
|
||
selectedIds.forEach((id) => {
|
||
if (!orderedIds.some((existing) => String(existing) === String(id))) {
|
||
orderedIds.push(id);
|
||
}
|
||
});
|
||
|
||
if (!orderedIds.length && ensureDefault) {
|
||
orderedIds = [defaultCountry.id];
|
||
}
|
||
|
||
return orderedIds.slice(0, selectionLimit).map((id) => {
|
||
const option = optionByValue.get(String(id));
|
||
const optionLabel = String(option?.textContent || '').trim();
|
||
return {
|
||
id,
|
||
label: optionLabel || normalizePhoneSmsCountryLabel(defaultCountry.id === id ? defaultCountry.label : '', normalizedProvider),
|
||
};
|
||
});
|
||
}
|
||
|
||
async function switchPhoneSmsProvider(nextProvider) {
|
||
const previousProvider = getLastAppliedPhoneSmsProvider();
|
||
const normalizedNextProvider = normalizePhoneSmsProvider(nextProvider);
|
||
|
||
const currentApiKey = String(inputHeroSmsApiKey?.value || '');
|
||
const currentMaxPrice = normalizePhoneSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || '', previousProvider);
|
||
const currentSelection = typeof getPhoneSmsCountrySelectionForProvider === 'function'
|
||
? getPhoneSmsCountrySelectionForProvider(previousProvider, { ensureDefault: true })
|
||
: [];
|
||
const currentPrimary = currentSelection[0] || getSelectedHeroSmsCountryOption();
|
||
const currentFallback = currentSelection.slice(1);
|
||
|
||
const patch = {
|
||
phoneSmsProvider: normalizedNextProvider,
|
||
};
|
||
if (previousProvider === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
||
patch.fiveSimApiKey = currentApiKey;
|
||
patch.fiveSimMaxPrice = currentMaxPrice;
|
||
patch.fiveSimCountryId = currentPrimary.id;
|
||
patch.fiveSimCountryLabel = currentPrimary.label;
|
||
patch.fiveSimCountryFallback = currentFallback;
|
||
patch.fiveSimCountryOrder = [currentPrimary, ...currentFallback]
|
||
.map((country) => normalizeFiveSimCountryId(country?.id, ''))
|
||
.filter(Boolean);
|
||
patch.fiveSimOperator = normalizeFiveSimOperator(inputFiveSimOperator?.value || latestState?.fiveSimOperator);
|
||
} else {
|
||
patch.heroSmsApiKey = currentApiKey;
|
||
patch.heroSmsMaxPrice = currentMaxPrice;
|
||
patch.heroSmsCountryId = currentPrimary.id;
|
||
patch.heroSmsCountryLabel = currentPrimary.label;
|
||
patch.heroSmsCountryFallback = currentFallback;
|
||
}
|
||
|
||
syncLatestState(patch);
|
||
setPhoneSmsProviderSelectValue(normalizedNextProvider);
|
||
heroSmsCountrySelectionOrder = [];
|
||
if (inputHeroSmsApiKey) {
|
||
inputHeroSmsApiKey.value = normalizedNextProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? String(latestState?.fiveSimApiKey || '')
|
||
: String(latestState?.heroSmsApiKey || '');
|
||
}
|
||
if (inputHeroSmsMaxPrice) {
|
||
inputHeroSmsMaxPrice.value = normalizedNextProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimMaxPriceValue(latestState?.fiveSimMaxPrice || '')
|
||
: normalizeHeroSmsMaxPriceValue(latestState?.heroSmsMaxPrice || '');
|
||
}
|
||
if (inputFiveSimOperator) {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperator(latestState?.fiveSimOperator);
|
||
}
|
||
if (displayHeroSmsPriceTiers) displayHeroSmsPriceTiers.textContent = '未获取';
|
||
if (displayPhoneSmsBalance) displayPhoneSmsBalance.textContent = '余额未获取';
|
||
if (rowHeroSmsPriceTiers) rowHeroSmsPriceTiers.style.display = 'none';
|
||
|
||
await loadHeroSmsCountries();
|
||
const restoredPrimary = normalizedNextProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? {
|
||
id: normalizeFiveSimCountryId(latestState?.fiveSimCountryId),
|
||
label: normalizeFiveSimCountryLabel(latestState?.fiveSimCountryLabel),
|
||
}
|
||
: {
|
||
id: normalizeHeroSmsCountryId(latestState?.heroSmsCountryId),
|
||
label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel),
|
||
};
|
||
const restoredFallback = normalizedNextProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimCountryFallbackList(latestState?.fiveSimCountryFallback || [])
|
||
: normalizeHeroSmsCountryFallbackList(latestState?.heroSmsCountryFallback || []);
|
||
applyHeroSmsFallbackSelection([restoredPrimary, ...restoredFallback], { includePrimary: true });
|
||
updatePhoneVerificationSettingsUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => {});
|
||
}
|
||
|
||
selectPhoneSmsProvider?.addEventListener('change', () => {
|
||
switchPhoneSmsProvider(selectPhoneSmsProvider.value).catch((error) => {
|
||
showToast(`切换接码平台失败:${error?.message || error}`, 'warn', 2200);
|
||
});
|
||
});
|
||
|
||
inputPhoneVerificationEnabled?.addEventListener('change', () => {
|
||
if (inputPhoneVerificationEnabled.checked) {
|
||
setPhoneVerificationSectionExpanded(true);
|
||
} else {
|
||
setSignupMethod(SIGNUP_METHOD_EMAIL);
|
||
updatePhoneVerificationSettingsUI();
|
||
showToast('已切回邮箱注册', 'info', 1600);
|
||
}
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
signupMethodButtons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
if (button.disabled) {
|
||
return;
|
||
}
|
||
setSignupMethod(button.dataset.signupMethod);
|
||
updateSignupMethodUI();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
});
|
||
|
||
selectPhoneSmsProvider?.addEventListener('change', async () => {
|
||
if (selectPhoneSmsProvider) {
|
||
selectPhoneSmsProvider.value = normalizePhoneSmsProviderValue(selectPhoneSmsProvider.value);
|
||
}
|
||
if (selectPhoneSmsProvider?.value === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
||
await loadFiveSimCountries().catch(() => { });
|
||
applyFiveSimCountrySelection(
|
||
Array.isArray(latestState?.fiveSimCountryOrder) ? latestState.fiveSimCountryOrder : []
|
||
);
|
||
} else if (selectPhoneSmsProvider?.value === PHONE_SMS_PROVIDER_NEXSMS) {
|
||
await loadNexSmsCountries().catch(() => { });
|
||
applyNexSmsCountrySelection(
|
||
Array.isArray(latestState?.nexSmsCountryOrder) ? latestState.nexSmsCountryOrder : []
|
||
);
|
||
} else {
|
||
await loadHeroSmsCountries().catch(() => { });
|
||
const nextPrimaryCountryId = normalizeHeroSmsCountryId(latestState?.heroSmsCountryId, 0);
|
||
const nextPrimaryCountries = nextPrimaryCountryId > 0
|
||
? [{
|
||
id: nextPrimaryCountryId,
|
||
label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel),
|
||
}]
|
||
: [];
|
||
applyHeroSmsFallbackSelection(
|
||
[
|
||
...nextPrimaryCountries,
|
||
...normalizeHeroSmsCountryFallbackList(
|
||
Array.isArray(latestState?.heroSmsCountryFallback) ? latestState.heroSmsCountryFallback : []
|
||
),
|
||
],
|
||
{ includePrimary: true }
|
||
);
|
||
}
|
||
updateHeroSmsPlatformDisplay();
|
||
updatePhoneVerificationSettingsUI();
|
||
if (rowHeroSmsPriceTiers) {
|
||
rowHeroSmsPriceTiers.style.display = 'none';
|
||
}
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputAccountRunHistoryHelperBaseUrl?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
|
||
inputAccountRunHistoryHelperBaseUrl?.addEventListener('blur', () => {
|
||
inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
function syncAutoStepDelayInputs() {
|
||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(inputAutoStepDelaySeconds.value);
|
||
}
|
||
|
||
inputAutoStepDelaySeconds.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputAutoStepDelaySeconds.addEventListener('blur', () => {
|
||
syncAutoStepDelayInputs();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputVerificationResendCount?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputVerificationResendCount?.addEventListener('blur', () => {
|
||
inputVerificationResendCount.value = String(
|
||
normalizeVerificationResendCount(
|
||
inputVerificationResendCount.value,
|
||
DEFAULT_VERIFICATION_RESEND_COUNT
|
||
)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputHeroSmsApiKey?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputHeroSmsApiKey?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputFiveSimApiKey?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputFiveSimApiKey?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputFiveSimOperator?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputFiveSimOperator?.addEventListener('blur', () => {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperatorValue(inputFiveSimOperator.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputFiveSimProduct?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputFiveSimProduct?.addEventListener('blur', () => {
|
||
inputFiveSimProduct.value = normalizeFiveSimProductValue(inputFiveSimProduct.value);
|
||
updateHeroSmsPlatformDisplay();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputNexSmsApiKey?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputNexSmsApiKey?.addEventListener('blur', () => {
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputNexSmsServiceCode?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputNexSmsServiceCode?.addEventListener('blur', () => {
|
||
inputNexSmsServiceCode.value = normalizeNexSmsServiceCodeValue(inputNexSmsServiceCode.value);
|
||
updateHeroSmsPlatformDisplay();
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputHeroSmsReuseEnabled?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectHeroSmsAcquirePriority?.addEventListener('change', () => {
|
||
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(selectHeroSmsAcquirePriority.value);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
selectHeroSmsPreferredActivation?.addEventListener('change', () => {
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
inputHeroSmsMaxPrice?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
});
|
||
inputHeroSmsMaxPrice?.addEventListener('blur', () => {
|
||
inputHeroSmsMaxPrice.value = normalizePhoneSmsMaxPriceValue(inputHeroSmsMaxPrice.value, getSelectedPhoneSmsProvider());
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputFiveSimOperator?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputFiveSimOperator?.addEventListener('blur', () => {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperator(inputFiveSimOperator.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
inputHeroSmsPreferredPrice?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputHeroSmsPreferredPrice?.addEventListener('blur', () => {
|
||
inputHeroSmsPreferredPrice.value = normalizeHeroSmsMaxPriceValue(inputHeroSmsPreferredPrice.value);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectFiveSimCountry?.addEventListener('change', () => {
|
||
syncFiveSimCountrySelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: true,
|
||
});
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectNexSmsCountry?.addEventListener('change', () => {
|
||
syncNexSmsCountrySelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: true,
|
||
});
|
||
updateHeroSmsPlatformDisplay();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
btnHeroSmsPricePreview?.addEventListener('click', async () => {
|
||
try {
|
||
await previewHeroSmsPriceTiers();
|
||
if (typeof showToast === 'function') {
|
||
showToast('已刷新接码国家价格预览。', 'info', 1600);
|
||
}
|
||
} catch (error) {
|
||
if (typeof showToast === 'function') {
|
||
showToast(`价格预览失败:${error?.message || error}`, 'warn', 2200);
|
||
}
|
||
}
|
||
});
|
||
|
||
btnPhoneSmsBalance?.addEventListener('click', async () => {
|
||
try {
|
||
await previewPhoneSmsBalance();
|
||
if (typeof showToast === 'function') {
|
||
showToast('已刷新接码平台余额。', 'info', 1600);
|
||
}
|
||
} catch (error) {
|
||
if (typeof showToast === 'function') {
|
||
showToast(`余额查询失败:${error?.message || error}`, 'warn', 2200);
|
||
}
|
||
}
|
||
});
|
||
|
||
inputPhoneReplacementLimit?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPhoneReplacementLimit?.addEventListener('blur', () => {
|
||
inputPhoneReplacementLimit.value = String(
|
||
normalizePhoneVerificationReplacementLimit(
|
||
inputPhoneReplacementLimit.value,
|
||
DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT
|
||
)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPhoneCodeWaitSeconds?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPhoneCodeWaitSeconds?.addEventListener('blur', () => {
|
||
inputPhoneCodeWaitSeconds.value = String(
|
||
normalizePhoneCodeWaitSecondsValue(inputPhoneCodeWaitSeconds.value, DEFAULT_PHONE_CODE_WAIT_SECONDS)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPhoneCodeTimeoutWindows?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPhoneCodeTimeoutWindows?.addEventListener('blur', () => {
|
||
inputPhoneCodeTimeoutWindows.value = String(
|
||
normalizePhoneCodeTimeoutWindowsValue(
|
||
inputPhoneCodeTimeoutWindows.value,
|
||
DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS
|
||
)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPhoneCodePollIntervalSeconds?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPhoneCodePollIntervalSeconds?.addEventListener('blur', () => {
|
||
inputPhoneCodePollIntervalSeconds.value = String(
|
||
normalizePhoneCodePollIntervalSecondsValue(
|
||
inputPhoneCodePollIntervalSeconds.value,
|
||
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS
|
||
)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
inputPhoneCodePollMaxRounds?.addEventListener('input', () => {
|
||
markSettingsDirty(true);
|
||
scheduleSettingsAutoSave();
|
||
});
|
||
inputPhoneCodePollMaxRounds?.addEventListener('blur', () => {
|
||
inputPhoneCodePollMaxRounds.value = String(
|
||
normalizePhoneCodePollMaxRoundsValue(
|
||
inputPhoneCodePollMaxRounds.value,
|
||
DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS
|
||
)
|
||
);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
selectHeroSmsCountry?.addEventListener('change', () => {
|
||
syncHeroSmsFallbackSelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: true,
|
||
});
|
||
updateHeroSmsPlatformDisplay();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
selectHeroSmsCountryFallback?.addEventListener('change', () => {
|
||
syncHeroSmsFallbackSelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: true,
|
||
});
|
||
updateHeroSmsPlatformDisplay();
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
btnHeroSmsCountryMenu?.addEventListener('click', (event) => {
|
||
event.preventDefault();
|
||
const nextOpen = btnHeroSmsCountryMenu.getAttribute('aria-expanded') !== 'true';
|
||
setHeroSmsCountryMenuOpen(nextOpen);
|
||
});
|
||
|
||
btnHeroSmsCountryClear?.addEventListener('click', () => {
|
||
if (!selectHeroSmsCountry) {
|
||
return;
|
||
}
|
||
Array.from(selectHeroSmsCountry.options).forEach((option) => {
|
||
option.selected = false;
|
||
});
|
||
heroSmsCountryMenuSearchKeyword = '';
|
||
syncHeroSmsFallbackSelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: false,
|
||
});
|
||
updateHeroSmsPlatformDisplay();
|
||
setHeroSmsCountryMenuOpen(false);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
if (typeof showToast === 'function') {
|
||
showToast('已清空国家优先级。', 'info', 1800);
|
||
}
|
||
});
|
||
|
||
btnFiveSimCountryMenu?.addEventListener('click', (event) => {
|
||
event.preventDefault();
|
||
const nextOpen = btnFiveSimCountryMenu.getAttribute('aria-expanded') !== 'true';
|
||
setFiveSimCountryMenuOpen(nextOpen);
|
||
});
|
||
|
||
btnFiveSimCountryClear?.addEventListener('click', () => {
|
||
if (!selectFiveSimCountry) {
|
||
return;
|
||
}
|
||
Array.from(selectFiveSimCountry.options).forEach((option) => {
|
||
option.selected = false;
|
||
});
|
||
fiveSimCountryMenuSearchKeyword = '';
|
||
syncFiveSimCountrySelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: false,
|
||
});
|
||
setFiveSimCountryMenuOpen(false);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
if (typeof showToast === 'function') {
|
||
showToast('已清空国家优先级。', 'info', 1800);
|
||
}
|
||
});
|
||
|
||
btnNexSmsCountryMenu?.addEventListener('click', (event) => {
|
||
event.preventDefault();
|
||
const nextOpen = btnNexSmsCountryMenu.getAttribute('aria-expanded') !== 'true';
|
||
setNexSmsCountryMenuOpen(nextOpen);
|
||
});
|
||
|
||
btnNexSmsCountryClear?.addEventListener('click', () => {
|
||
if (!selectNexSmsCountry) {
|
||
return;
|
||
}
|
||
Array.from(selectNexSmsCountry.options).forEach((option) => {
|
||
option.selected = false;
|
||
});
|
||
nexSmsCountryMenuSearchKeyword = '';
|
||
syncNexSmsCountrySelectionOrderFromSelect({
|
||
enforceMax: true,
|
||
ensureDefault: false,
|
||
showLimitToast: false,
|
||
});
|
||
updateHeroSmsPlatformDisplay();
|
||
setNexSmsCountryMenuOpen(false);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
if (typeof showToast === 'function') {
|
||
showToast('已清空国家优先级。', 'info', 1800);
|
||
}
|
||
});
|
||
|
||
btnPhoneSmsProviderOrderMenu?.addEventListener('click', (event) => {
|
||
event.preventDefault();
|
||
const nextOpen = btnPhoneSmsProviderOrderMenu.getAttribute('aria-expanded') !== 'true';
|
||
setPhoneSmsProviderOrderMenuOpen(nextOpen);
|
||
});
|
||
|
||
selectPhoneSmsProviderOrder?.addEventListener('change', () => {
|
||
const nextOrder = syncPhoneSmsProviderOrderFromSelect({
|
||
ensureDefault: false,
|
||
enforceMax: true,
|
||
syncProvider: false,
|
||
showLimitToast: true,
|
||
});
|
||
updatePhoneVerificationSettingsUI();
|
||
updateHeroSmsPlatformDisplay();
|
||
updatePhoneSmsProviderOrderSummary(nextOrder);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
});
|
||
|
||
btnPhoneSmsProviderOrderReset?.addEventListener('click', () => {
|
||
const nextOrder = clearPhoneSmsProviderOrderSelection({
|
||
syncProvider: false,
|
||
});
|
||
setPhoneSmsProviderOrderMenuOpen(false);
|
||
updatePhoneVerificationSettingsUI();
|
||
updateHeroSmsPlatformDisplay();
|
||
updatePhoneSmsProviderOrderSummary(nextOrder);
|
||
markSettingsDirty(true);
|
||
saveSettings({ silent: true }).catch(() => { });
|
||
if (typeof showToast === 'function') {
|
||
showToast('已清空服务商顺序。', 'info', 1800);
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// Listen for Background broadcasts
|
||
// ============================================================
|
||
|
||
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||
switch (message.type) {
|
||
case 'REQUEST_CUSTOM_VERIFICATION_BYPASS_CONFIRMATION': {
|
||
(async () => {
|
||
const step = Number(message.payload?.step);
|
||
const result = await openCustomVerificationConfirmDialog(step);
|
||
sendResponse(result || { confirmed: false, addPhoneDetected: false });
|
||
})().catch((err) => {
|
||
sendResponse({ error: err.message });
|
||
});
|
||
return true;
|
||
}
|
||
|
||
case 'REQUEST_GOPAY_OTP_INPUT': {
|
||
(async () => {
|
||
const result = await openGoPayOtpInputDialog(message.payload || {});
|
||
sendResponse(result || { cancelled: true, code: '' });
|
||
})().catch((err) => {
|
||
sendResponse({ error: err.message });
|
||
});
|
||
return true;
|
||
}
|
||
|
||
case 'SECURITY_BLOCKED_ALERT': {
|
||
openConfirmModal({
|
||
title: message.payload?.title || '流程已完全停止',
|
||
message: message.payload?.message || '检测到安全风控,当前流程已完全停止。',
|
||
alert: message.payload?.alert || { text: '检测到 Cloudflare 风控,请暂停当前操作。', tone: 'danger' },
|
||
confirmLabel: '我知道了',
|
||
confirmVariant: 'btn-danger',
|
||
}).catch(() => { });
|
||
break;
|
||
}
|
||
|
||
case 'LOG_ENTRY':
|
||
appendLog(message.payload);
|
||
if (message.payload.level === 'error') {
|
||
showToast(message.payload.message, 'error');
|
||
}
|
||
break;
|
||
|
||
case 'STEP_STATUS_CHANGED': {
|
||
const { step, status } = message.payload;
|
||
updateStepUI(step, status);
|
||
chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(state => {
|
||
syncLatestState(state);
|
||
syncAutoRunState(state);
|
||
updateStatusDisplay(latestState);
|
||
updateButtonStates();
|
||
if (status === 'completed' || status === 'manual_completed' || status === 'skipped') {
|
||
syncPasswordField(state);
|
||
if (state.oauthUrl) {
|
||
displayOauthUrl.textContent = state.oauthUrl;
|
||
displayOauthUrl.classList.add('has-value');
|
||
}
|
||
if (state.localhostUrl) {
|
||
displayLocalhostUrl.textContent = state.localhostUrl;
|
||
displayLocalhostUrl.classList.add('has-value');
|
||
}
|
||
}
|
||
}
|
||
).catch(() => { });
|
||
break;
|
||
}
|
||
|
||
case 'AUTO_RUN_RESET': {
|
||
// Full UI reset for next run
|
||
syncLatestState({
|
||
oauthUrl: null,
|
||
localhostUrl: null,
|
||
email: null,
|
||
password: null,
|
||
stepStatuses: STEP_DEFAULT_STATUSES,
|
||
logs: [],
|
||
scheduledAutoRunAt: null,
|
||
autoRunCountdownAt: null,
|
||
autoRunCountdownTitle: '',
|
||
autoRunCountdownNote: '',
|
||
});
|
||
displayOauthUrl.textContent = '等待中...';
|
||
displayOauthUrl.classList.remove('has-value');
|
||
displayLocalhostUrl.textContent = '等待中...';
|
||
displayLocalhostUrl.classList.remove('has-value');
|
||
inputEmail.value = '';
|
||
displayStatus.textContent = '就绪';
|
||
statusBar.className = 'status-bar';
|
||
logArea.innerHTML = '';
|
||
resetIcloudManager();
|
||
resetLuckmailManager();
|
||
resetCustomEmailPoolManager();
|
||
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
||
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
||
syncAutoRunState({
|
||
autoRunning: false,
|
||
autoRunPhase: 'idle',
|
||
autoRunCurrentRun: 0,
|
||
autoRunTotalRuns: 1,
|
||
autoRunAttemptRun: 0,
|
||
scheduledAutoRunAt: null,
|
||
autoRunCountdownAt: null,
|
||
autoRunCountdownTitle: '',
|
||
autoRunCountdownNote: '',
|
||
});
|
||
applyAutoRunStatus(currentAutoRun);
|
||
updateProgressCounter();
|
||
updateButtonStates();
|
||
renderPayPalAccounts();
|
||
renderHotmailAccounts();
|
||
renderMail2925Accounts();
|
||
if (isLuckmailProvider()) {
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 'DATA_UPDATED': {
|
||
syncLatestState(message.payload);
|
||
if (message.payload.email !== undefined) {
|
||
inputEmail.value = message.payload.email || '';
|
||
queueCustomEmailPoolRefresh();
|
||
}
|
||
if (
|
||
message.payload.password !== undefined
|
||
|| message.payload.customPassword !== undefined
|
||
|| message.payload.contributionMode !== undefined
|
||
) {
|
||
syncPasswordField(latestState || {});
|
||
}
|
||
if (message.payload.localCpaStep9Mode !== undefined) {
|
||
setLocalCpaStep9Mode(message.payload.localCpaStep9Mode);
|
||
}
|
||
if (message.payload.panelMode !== undefined) {
|
||
selectPanelMode.value = message.payload.panelMode || 'cpa';
|
||
updatePanelModeUI();
|
||
}
|
||
if (
|
||
message.payload.ipProxyEnabled !== undefined
|
||
|| message.payload.ipProxyService !== undefined
|
||
|| message.payload.ipProxyServiceProfiles !== undefined
|
||
|| message.payload.ipProxyMode !== undefined
|
||
|| message.payload.ipProxyApiUrl !== undefined
|
||
|| message.payload.ipProxyAccountList !== undefined
|
||
|| message.payload.ipProxyAccountSessionPrefix !== undefined
|
||
|| message.payload.ipProxyAccountLifeMinutes !== undefined
|
||
|| message.payload.ipProxyPoolTargetCount !== undefined
|
||
|| message.payload.ipProxyHost !== undefined
|
||
|| message.payload.ipProxyPort !== undefined
|
||
|| message.payload.ipProxyProtocol !== undefined
|
||
|| message.payload.ipProxyUsername !== undefined
|
||
|| message.payload.ipProxyPassword !== undefined
|
||
|| message.payload.ipProxyRegion !== undefined
|
||
|| message.payload.ipProxyApiPool !== undefined
|
||
|| message.payload.ipProxyApiCurrentIndex !== undefined
|
||
|| message.payload.ipProxyApiCurrent !== undefined
|
||
|| message.payload.ipProxyAccountPool !== undefined
|
||
|| message.payload.ipProxyAccountCurrentIndex !== undefined
|
||
|| message.payload.ipProxyAccountCurrent !== undefined
|
||
|| message.payload.ipProxyCurrent !== undefined
|
||
|| message.payload.ipProxyCurrentIndex !== undefined
|
||
|| message.payload.ipProxyPool !== undefined
|
||
|| message.payload.ipProxyApplied !== undefined
|
||
|| message.payload.ipProxyAppliedReason !== undefined
|
||
|| message.payload.ipProxyAppliedHost !== undefined
|
||
|| message.payload.ipProxyAppliedPort !== undefined
|
||
|| message.payload.ipProxyAppliedRegion !== undefined
|
||
|| message.payload.ipProxyAppliedHasAuth !== undefined
|
||
|| message.payload.ipProxyAppliedWarning !== undefined
|
||
|| message.payload.ipProxyAppliedExitIp !== undefined
|
||
|| message.payload.ipProxyAppliedExitRegion !== undefined
|
||
|| message.payload.ipProxyAppliedExitDetecting !== undefined
|
||
|| message.payload.ipProxyAppliedExitError !== undefined
|
||
|| message.payload.ipProxyAppliedExitSource !== undefined
|
||
|| message.payload.ipProxyAutoSyncEnabled !== undefined
|
||
|| message.payload.ipProxyAutoSyncIntervalMinutes !== undefined
|
||
) {
|
||
const hasIpProxyConfigPayload = (
|
||
message.payload.ipProxyService !== undefined
|
||
|| message.payload.ipProxyServiceProfiles !== undefined
|
||
|| message.payload.ipProxyMode !== undefined
|
||
|| message.payload.ipProxyApiUrl !== undefined
|
||
|| message.payload.ipProxyAccountList !== undefined
|
||
|| message.payload.ipProxyAccountSessionPrefix !== undefined
|
||
|| message.payload.ipProxyAccountLifeMinutes !== undefined
|
||
|| message.payload.ipProxyPoolTargetCount !== undefined
|
||
|| message.payload.ipProxyHost !== undefined
|
||
|| message.payload.ipProxyPort !== undefined
|
||
|| message.payload.ipProxyProtocol !== undefined
|
||
|| message.payload.ipProxyUsername !== undefined
|
||
|| message.payload.ipProxyPassword !== undefined
|
||
|| message.payload.ipProxyRegion !== undefined
|
||
);
|
||
const selectedProxyService = normalizeIpProxyService(
|
||
message.payload.ipProxyService !== undefined
|
||
? message.payload.ipProxyService
|
||
: latestState?.ipProxyService
|
||
);
|
||
const mergedProxyState = {
|
||
...(latestState || {}),
|
||
...message.payload,
|
||
ipProxyService: selectedProxyService,
|
||
};
|
||
let normalizedProxyProfiles = (mergedProxyState?.ipProxyServiceProfiles || {});
|
||
if (typeof normalizeIpProxyServiceProfiles === 'function') {
|
||
normalizedProxyProfiles = normalizeIpProxyServiceProfiles(
|
||
mergedProxyState?.ipProxyServiceProfiles || {},
|
||
mergedProxyState
|
||
);
|
||
}
|
||
if (typeof buildIpProxyServiceProfileFromFlatState === 'function') {
|
||
normalizedProxyProfiles[selectedProxyService] = buildIpProxyServiceProfileFromFlatState(mergedProxyState);
|
||
}
|
||
if (selectIpProxyService) {
|
||
selectIpProxyService.value = selectedProxyService;
|
||
}
|
||
if (message.payload.ipProxyEnabled !== undefined) {
|
||
setIpProxyEnabled(Boolean(message.payload.ipProxyEnabled));
|
||
}
|
||
if (message.payload.ipProxyAutoSyncEnabled !== undefined && inputIpProxyAutoSyncEnabled) {
|
||
inputIpProxyAutoSyncEnabled.checked = Boolean(message.payload.ipProxyAutoSyncEnabled);
|
||
}
|
||
if (message.payload.ipProxyAutoSyncIntervalMinutes !== undefined && inputIpProxyAutoSyncIntervalMinutes) {
|
||
const numeric = Number.parseInt(String(message.payload.ipProxyAutoSyncIntervalMinutes ?? '').trim(), 10);
|
||
inputIpProxyAutoSyncIntervalMinutes.value = String(
|
||
Number.isFinite(numeric) ? Math.max(1, Math.min(1440, numeric)) : 15
|
||
);
|
||
}
|
||
if (message.payload.ipProxyApiUrl !== undefined && inputIpProxyApiUrl) {
|
||
inputIpProxyApiUrl.value = String(message.payload.ipProxyApiUrl || '').trim();
|
||
}
|
||
if (hasIpProxyConfigPayload) {
|
||
const activeProxyProfile = typeof getIpProxyServiceProfile === 'function'
|
||
? getIpProxyServiceProfile(selectedProxyService, {
|
||
...mergedProxyState,
|
||
ipProxyServiceProfiles: normalizedProxyProfiles,
|
||
})
|
||
: {
|
||
mode: typeof normalizeIpProxyModeForCurrentRelease === 'function'
|
||
? normalizeIpProxyModeForCurrentRelease(mergedProxyState?.ipProxyMode)
|
||
: normalizeIpProxyMode(mergedProxyState?.ipProxyMode),
|
||
apiUrl: String(mergedProxyState?.ipProxyApiUrl || '').trim(),
|
||
accountList: normalizeIpProxyAccountList(mergedProxyState?.ipProxyAccountList || ''),
|
||
accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(mergedProxyState?.ipProxyAccountSessionPrefix || ''),
|
||
accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(mergedProxyState?.ipProxyAccountLifeMinutes || ''),
|
||
poolTargetCount: normalizeIpProxyPoolTargetCount(mergedProxyState?.ipProxyPoolTargetCount || '', 20),
|
||
host: String(mergedProxyState?.ipProxyHost || '').trim(),
|
||
port: String(normalizeIpProxyPort(mergedProxyState?.ipProxyPort || '') || ''),
|
||
protocol: normalizeIpProxyProtocol(mergedProxyState?.ipProxyProtocol),
|
||
username: String(mergedProxyState?.ipProxyUsername || '').trim(),
|
||
password: String(mergedProxyState?.ipProxyPassword || ''),
|
||
region: String(mergedProxyState?.ipProxyRegion || '').trim(),
|
||
};
|
||
if (typeof applyIpProxyServiceProfileToInputs === 'function') {
|
||
applyIpProxyServiceProfileToInputs(activeProxyProfile);
|
||
} else {
|
||
setIpProxyMode(activeProxyProfile.mode);
|
||
if (inputIpProxyApiUrl) inputIpProxyApiUrl.value = String(activeProxyProfile.apiUrl || '').trim();
|
||
if (inputIpProxyAccountList) inputIpProxyAccountList.value = activeProxyProfile.accountList;
|
||
if (inputIpProxyAccountSessionPrefix) inputIpProxyAccountSessionPrefix.value = activeProxyProfile.accountSessionPrefix;
|
||
if (inputIpProxyAccountLifeMinutes) inputIpProxyAccountLifeMinutes.value = activeProxyProfile.accountLifeMinutes;
|
||
if (inputIpProxyPoolTargetCount) inputIpProxyPoolTargetCount.value = activeProxyProfile.poolTargetCount;
|
||
if (inputIpProxyHost) inputIpProxyHost.value = activeProxyProfile.host;
|
||
if (inputIpProxyPort) inputIpProxyPort.value = activeProxyProfile.port;
|
||
if (selectIpProxyProtocol) selectIpProxyProtocol.value = normalizeIpProxyProtocol(activeProxyProfile.protocol);
|
||
if (inputIpProxyUsername) inputIpProxyUsername.value = activeProxyProfile.username;
|
||
if (inputIpProxyPassword) inputIpProxyPassword.value = activeProxyProfile.password;
|
||
if (inputIpProxyRegion) inputIpProxyRegion.value = activeProxyProfile.region;
|
||
}
|
||
syncLatestState({
|
||
ipProxyService: selectedProxyService,
|
||
ipProxyServiceProfiles: normalizedProxyProfiles,
|
||
...(typeof buildIpProxyStatePatchFromServiceProfile === 'function'
|
||
? buildIpProxyStatePatchFromServiceProfile(selectedProxyService, activeProxyProfile)
|
||
: {}),
|
||
});
|
||
} else {
|
||
syncLatestState({
|
||
ipProxyService: selectedProxyService,
|
||
ipProxyServiceProfiles: normalizedProxyProfiles,
|
||
});
|
||
}
|
||
updateIpProxyUI(latestState);
|
||
}
|
||
if (message.payload.oauthUrl !== undefined) {
|
||
displayOauthUrl.textContent = message.payload.oauthUrl || '等待中...';
|
||
displayOauthUrl.classList.toggle('has-value', Boolean(message.payload.oauthUrl));
|
||
}
|
||
if (message.payload.localhostUrl !== undefined) {
|
||
displayLocalhostUrl.textContent = message.payload.localhostUrl || '等待中...';
|
||
displayLocalhostUrl.classList.toggle('has-value', Boolean(message.payload.localhostUrl));
|
||
}
|
||
if (message.payload.cloudflareTempEmailBaseUrl !== undefined) {
|
||
inputTempEmailBaseUrl.value = message.payload.cloudflareTempEmailBaseUrl || '';
|
||
}
|
||
if (message.payload.cloudflareTempEmailAdminAuth !== undefined) {
|
||
inputTempEmailAdminAuth.value = message.payload.cloudflareTempEmailAdminAuth || '';
|
||
}
|
||
if (message.payload.cloudflareTempEmailCustomAuth !== undefined) {
|
||
inputTempEmailCustomAuth.value = message.payload.cloudflareTempEmailCustomAuth || '';
|
||
}
|
||
if (message.payload.cloudflareTempEmailReceiveMailbox !== undefined) {
|
||
inputTempEmailReceiveMailbox.value = message.payload.cloudflareTempEmailReceiveMailbox || '';
|
||
}
|
||
if (message.payload.cloudflareTempEmailUseRandomSubdomain !== undefined && inputTempEmailUseRandomSubdomain) {
|
||
inputTempEmailUseRandomSubdomain.checked = Boolean(message.payload.cloudflareTempEmailUseRandomSubdomain);
|
||
}
|
||
if (message.payload.cloudflareTempEmailDomain !== undefined || message.payload.cloudflareTempEmailDomains !== undefined) {
|
||
renderCloudflareTempEmailDomainOptions(message.payload.cloudflareTempEmailDomain || latestState?.cloudflareTempEmailDomain || '');
|
||
}
|
||
if (
|
||
message.payload.cloudflareTempEmailUseRandomSubdomain !== undefined
|
||
|| message.payload.cloudflareTempEmailDomain !== undefined
|
||
|| message.payload.cloudflareTempEmailDomains !== undefined
|
||
) {
|
||
updateMailProviderUI();
|
||
}
|
||
if (message.payload.plusModeEnabled !== undefined && inputPlusModeEnabled) {
|
||
inputPlusModeEnabled.checked = Boolean(message.payload.plusModeEnabled);
|
||
}
|
||
if (message.payload.plusPaymentMethod !== undefined && selectPlusPaymentMethod) {
|
||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(message.payload.plusPaymentMethod);
|
||
}
|
||
if (message.payload.plusModeEnabled !== undefined || message.payload.plusPaymentMethod !== undefined) {
|
||
syncStepDefinitionsForMode(
|
||
Boolean(latestState?.plusModeEnabled),
|
||
latestState?.plusPaymentMethod,
|
||
{ render: true }
|
||
);
|
||
updatePlusModeUI();
|
||
updateSignupMethodUI({ notify: true });
|
||
}
|
||
if (
|
||
message.payload.plusManualConfirmationPending !== undefined
|
||
|| message.payload.plusManualConfirmationRequestId !== undefined
|
||
|| message.payload.plusManualConfirmationStep !== undefined
|
||
|| message.payload.plusManualConfirmationMethod !== undefined
|
||
|| message.payload.plusManualConfirmationTitle !== undefined
|
||
|| message.payload.plusManualConfirmationMessage !== undefined
|
||
) {
|
||
void syncPlusManualConfirmationDialog();
|
||
}
|
||
if (message.payload.currentHotmailAccountId !== undefined || message.payload.hotmailAccounts !== undefined) {
|
||
renderHotmailAccounts();
|
||
if (selectMailProvider.value === 'hotmail-api') {
|
||
inputEmail.value = getCurrentHotmailEmail();
|
||
}
|
||
}
|
||
if (message.payload.currentPayPalAccountId !== undefined || message.payload.paypalAccounts !== undefined) {
|
||
renderPayPalAccounts();
|
||
}
|
||
if (message.payload.currentMail2925AccountId !== undefined || message.payload.mail2925Accounts !== undefined) {
|
||
renderMail2925Accounts();
|
||
if (selectMailProvider.value === '2925') {
|
||
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||
}
|
||
}
|
||
if (message.payload.customEmailPoolEntries !== undefined || message.payload.customEmailPool !== undefined) {
|
||
setCustomEmailPoolEntriesState(restoreCustomEmailPoolEntriesFromState({
|
||
...latestState,
|
||
...message.payload,
|
||
}));
|
||
syncRunCountFromConfiguredEmailPool();
|
||
queueCustomEmailPoolRefresh();
|
||
}
|
||
if (message.payload.luckmailApiKey !== undefined) {
|
||
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
||
}
|
||
if (message.payload.luckmailBaseUrl !== undefined) {
|
||
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(message.payload.luckmailBaseUrl);
|
||
}
|
||
if (message.payload.luckmailEmailType !== undefined) {
|
||
selectLuckmailEmailType.value = normalizeLuckmailEmailType(message.payload.luckmailEmailType);
|
||
}
|
||
if (message.payload.luckmailDomain !== undefined) {
|
||
inputLuckmailDomain.value = message.payload.luckmailDomain || '';
|
||
}
|
||
if (message.payload.luckmailUsedPurchases !== undefined && isLuckmailProvider()) {
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
if (message.payload.currentLuckmailPurchase !== undefined && isLuckmailProvider()) {
|
||
inputEmail.value = getCurrentLuckmailEmail();
|
||
queueLuckmailPurchaseRefresh();
|
||
}
|
||
if (message.payload.autoDeleteUsedIcloudAlias !== undefined && checkboxAutoDeleteIcloud) {
|
||
checkboxAutoDeleteIcloud.checked = Boolean(message.payload.autoDeleteUsedIcloudAlias);
|
||
}
|
||
if (message.payload.accountRunHistoryHelperBaseUrl !== undefined && inputAccountRunHistoryHelperBaseUrl) {
|
||
inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(message.payload.accountRunHistoryHelperBaseUrl);
|
||
updateAccountRunHistorySettingsUI();
|
||
}
|
||
if (message.payload.icloudHostPreference !== undefined && selectIcloudHostPreference) {
|
||
const hostPreference = String(message.payload.icloudHostPreference || '').trim().toLowerCase();
|
||
selectIcloudHostPreference.value = hostPreference === 'icloud.com'
|
||
? 'icloud.com'
|
||
: (hostPreference === 'icloud.com.cn' ? 'icloud.com.cn' : 'auto');
|
||
updateMailProviderUI();
|
||
}
|
||
if (message.payload.icloudTargetMailboxType !== undefined && selectIcloudTargetMailboxType) {
|
||
selectIcloudTargetMailboxType.value = normalizeIcloudTargetMailboxType(message.payload.icloudTargetMailboxType);
|
||
updateMailProviderUI();
|
||
}
|
||
if (message.payload.icloudForwardMailProvider !== undefined && selectIcloudForwardMailProvider) {
|
||
selectIcloudForwardMailProvider.value = normalizeIcloudForwardMailProvider(message.payload.icloudForwardMailProvider);
|
||
updateMailProviderUI();
|
||
}
|
||
if (message.payload.icloudFetchMode !== undefined && selectIcloudFetchMode) {
|
||
selectIcloudFetchMode.value = normalizeIcloudFetchMode(message.payload.icloudFetchMode);
|
||
}
|
||
if (message.payload.autoRunSkipFailures !== undefined) {
|
||
inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
|
||
updateFallbackThreadIntervalInputState();
|
||
}
|
||
if (message.payload.autoRunDelayEnabled !== undefined) {
|
||
inputAutoDelayEnabled.checked = Boolean(message.payload.autoRunDelayEnabled);
|
||
updateAutoDelayInputState();
|
||
}
|
||
if (message.payload.autoRunDelayMinutes !== undefined) {
|
||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(message.payload.autoRunDelayMinutes));
|
||
}
|
||
if (message.payload.autoRunFallbackThreadIntervalMinutes !== undefined) {
|
||
inputAutoSkipFailuresThreadIntervalMinutes.value = String(
|
||
normalizeAutoRunThreadIntervalMinutes(message.payload.autoRunFallbackThreadIntervalMinutes)
|
||
);
|
||
updateFallbackThreadIntervalInputState();
|
||
}
|
||
if (message.payload.autoStepDelaySeconds !== undefined) {
|
||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
|
||
}
|
||
if (message.payload.oauthFlowTimeoutEnabled !== undefined && typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
|
||
inputOAuthFlowTimeoutEnabled.checked = Boolean(message.payload.oauthFlowTimeoutEnabled);
|
||
}
|
||
if (
|
||
(
|
||
message.payload.verificationResendCount !== undefined
|
||
|| message.payload.signupVerificationResendCount !== undefined
|
||
|| message.payload.loginVerificationResendCount !== undefined
|
||
)
|
||
&& inputVerificationResendCount
|
||
) {
|
||
const nextVerificationResendCount = message.payload.verificationResendCount !== undefined
|
||
? message.payload.verificationResendCount
|
||
: (message.payload.signupVerificationResendCount ?? message.payload.loginVerificationResendCount);
|
||
inputVerificationResendCount.value = String(
|
||
normalizeVerificationResendCount(
|
||
nextVerificationResendCount,
|
||
DEFAULT_VERIFICATION_RESEND_COUNT
|
||
)
|
||
);
|
||
}
|
||
if (message.payload.phoneSmsProvider !== undefined && selectPhoneSmsProvider) {
|
||
setPhoneSmsProviderSelectValue(message.payload.phoneSmsProvider);
|
||
}
|
||
if ((message.payload.heroSmsApiKey !== undefined || message.payload.fiveSimApiKey !== undefined) && inputHeroSmsApiKey) {
|
||
inputHeroSmsApiKey.value = getSelectedPhoneSmsProvider() === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? (message.payload.fiveSimApiKey !== undefined ? message.payload.fiveSimApiKey || '' : latestState?.fiveSimApiKey || '')
|
||
: (message.payload.heroSmsApiKey !== undefined ? message.payload.heroSmsApiKey || '' : latestState?.heroSmsApiKey || '');
|
||
}
|
||
if (message.payload.fiveSimApiKey !== undefined && inputFiveSimApiKey) {
|
||
inputFiveSimApiKey.value = String(message.payload.fiveSimApiKey || '').trim();
|
||
}
|
||
if (message.payload.nexSmsApiKey !== undefined && inputNexSmsApiKey) {
|
||
inputNexSmsApiKey.value = String(message.payload.nexSmsApiKey || '').trim();
|
||
}
|
||
if (message.payload.fiveSimCountryOrder !== undefined && typeof applyFiveSimCountrySelection === 'function') {
|
||
applyFiveSimCountrySelection(
|
||
Array.isArray(message.payload.fiveSimCountryOrder) ? message.payload.fiveSimCountryOrder : []
|
||
);
|
||
}
|
||
if (message.payload.nexSmsCountryOrder !== undefined && typeof applyNexSmsCountrySelection === 'function') {
|
||
applyNexSmsCountrySelection(
|
||
Array.isArray(message.payload.nexSmsCountryOrder) ? message.payload.nexSmsCountryOrder : []
|
||
);
|
||
}
|
||
if (message.payload.fiveSimOperator !== undefined && inputFiveSimOperator) {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperatorValue(message.payload.fiveSimOperator);
|
||
}
|
||
if (message.payload.fiveSimProduct !== undefined && inputFiveSimProduct) {
|
||
inputFiveSimProduct.value = normalizeFiveSimProductValue(message.payload.fiveSimProduct);
|
||
}
|
||
if (message.payload.nexSmsServiceCode !== undefined && inputNexSmsServiceCode) {
|
||
inputNexSmsServiceCode.value = normalizeNexSmsServiceCodeValue(message.payload.nexSmsServiceCode);
|
||
}
|
||
if (message.payload.heroSmsReuseEnabled !== undefined && inputHeroSmsReuseEnabled) {
|
||
inputHeroSmsReuseEnabled.checked = normalizeHeroSmsReuseEnabledValue(message.payload.heroSmsReuseEnabled);
|
||
}
|
||
if (message.payload.heroSmsAcquirePriority !== undefined && selectHeroSmsAcquirePriority) {
|
||
selectHeroSmsAcquirePriority.value = normalizeHeroSmsAcquirePriority(message.payload.heroSmsAcquirePriority);
|
||
}
|
||
if ((message.payload.heroSmsMaxPrice !== undefined || message.payload.fiveSimMaxPrice !== undefined) && inputHeroSmsMaxPrice) {
|
||
inputHeroSmsMaxPrice.value = getSelectedPhoneSmsProvider() === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimMaxPriceValue(message.payload.fiveSimMaxPrice !== undefined ? message.payload.fiveSimMaxPrice : latestState?.fiveSimMaxPrice)
|
||
: normalizeHeroSmsMaxPriceValue(message.payload.heroSmsMaxPrice !== undefined ? message.payload.heroSmsMaxPrice : latestState?.heroSmsMaxPrice);
|
||
}
|
||
if (message.payload.fiveSimOperator !== undefined && inputFiveSimOperator) {
|
||
inputFiveSimOperator.value = normalizeFiveSimOperator(message.payload.fiveSimOperator);
|
||
}
|
||
if (message.payload.heroSmsPreferredPrice !== undefined && typeof inputHeroSmsPreferredPrice !== 'undefined' && inputHeroSmsPreferredPrice) {
|
||
inputHeroSmsPreferredPrice.value = normalizeHeroSmsMaxPriceValue(message.payload.heroSmsPreferredPrice);
|
||
}
|
||
if (message.payload.phoneVerificationReplacementLimit !== undefined && typeof inputPhoneReplacementLimit !== 'undefined' && inputPhoneReplacementLimit) {
|
||
inputPhoneReplacementLimit.value = String(
|
||
normalizePhoneVerificationReplacementLimit(
|
||
message.payload.phoneVerificationReplacementLimit,
|
||
DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT
|
||
)
|
||
);
|
||
}
|
||
if (message.payload.phoneCodeWaitSeconds !== undefined && inputPhoneCodeWaitSeconds) {
|
||
inputPhoneCodeWaitSeconds.value = String(
|
||
normalizePhoneCodeWaitSecondsValue(message.payload.phoneCodeWaitSeconds, DEFAULT_PHONE_CODE_WAIT_SECONDS)
|
||
);
|
||
}
|
||
if (message.payload.phoneCodeTimeoutWindows !== undefined && inputPhoneCodeTimeoutWindows) {
|
||
inputPhoneCodeTimeoutWindows.value = String(
|
||
normalizePhoneCodeTimeoutWindowsValue(message.payload.phoneCodeTimeoutWindows, DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS)
|
||
);
|
||
}
|
||
if (message.payload.phoneCodePollIntervalSeconds !== undefined && inputPhoneCodePollIntervalSeconds) {
|
||
inputPhoneCodePollIntervalSeconds.value = String(
|
||
normalizePhoneCodePollIntervalSecondsValue(
|
||
message.payload.phoneCodePollIntervalSeconds,
|
||
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS
|
||
)
|
||
);
|
||
}
|
||
if (message.payload.phoneCodePollMaxRounds !== undefined && inputPhoneCodePollMaxRounds) {
|
||
inputPhoneCodePollMaxRounds.value = String(
|
||
normalizePhoneCodePollMaxRoundsValue(message.payload.phoneCodePollMaxRounds, DEFAULT_PHONE_CODE_POLL_MAX_ROUNDS)
|
||
);
|
||
}
|
||
if (
|
||
message.payload.phoneSmsProvider !== undefined
|
||
|| message.payload.phoneSmsProviderOrder !== undefined
|
||
) {
|
||
const nextOrder = applyPhoneSmsProviderOrderSelection(
|
||
message.payload.phoneSmsProviderOrder !== undefined
|
||
? message.payload.phoneSmsProviderOrder
|
||
: (Array.isArray(latestState?.phoneSmsProviderOrder)
|
||
? latestState.phoneSmsProviderOrder
|
||
: []),
|
||
{ ensureDefault: false, syncProvider: false }
|
||
);
|
||
if (selectPhoneSmsProvider) {
|
||
selectPhoneSmsProvider.value = normalizePhoneSmsProviderValue(
|
||
message.payload.phoneSmsProvider !== undefined
|
||
? message.payload.phoneSmsProvider
|
||
: latestState?.phoneSmsProvider
|
||
);
|
||
}
|
||
updatePhoneSmsProviderOrderSummary(nextOrder);
|
||
updatePhoneVerificationSettingsUI();
|
||
}
|
||
if (message.payload.phoneVerificationEnabled !== undefined && inputPhoneVerificationEnabled) {
|
||
inputPhoneVerificationEnabled.checked = Boolean(message.payload.phoneVerificationEnabled);
|
||
}
|
||
if (message.payload.signupMethod !== undefined) {
|
||
setSignupMethod(message.payload.signupMethod);
|
||
}
|
||
if (message.payload.phoneVerificationEnabled !== undefined || message.payload.signupMethod !== undefined) {
|
||
updatePhoneVerificationSettingsUI();
|
||
}
|
||
const activePhoneSmsProvider = normalizePhoneSmsProviderValue(
|
||
message.payload.phoneSmsProvider !== undefined
|
||
? message.payload.phoneSmsProvider
|
||
: latestState?.phoneSmsProvider
|
||
);
|
||
if (activePhoneSmsProvider === PHONE_SMS_PROVIDER_FIVE_SIM) {
|
||
if (
|
||
message.payload.fiveSimCountryOrder !== undefined
|
||
|| message.payload.phoneSmsProvider !== undefined
|
||
|| message.payload.phoneSmsProviderOrder !== undefined
|
||
) {
|
||
applyFiveSimCountrySelection(
|
||
message.payload.fiveSimCountryOrder !== undefined
|
||
? message.payload.fiveSimCountryOrder
|
||
: (Array.isArray(latestState?.fiveSimCountryOrder) ? latestState.fiveSimCountryOrder : [])
|
||
);
|
||
}
|
||
} else if (activePhoneSmsProvider === PHONE_SMS_PROVIDER_NEXSMS) {
|
||
if (
|
||
message.payload.nexSmsCountryOrder !== undefined
|
||
|| message.payload.phoneSmsProvider !== undefined
|
||
|| message.payload.phoneSmsProviderOrder !== undefined
|
||
) {
|
||
applyNexSmsCountrySelection(
|
||
message.payload.nexSmsCountryOrder !== undefined
|
||
? message.payload.nexSmsCountryOrder
|
||
: (Array.isArray(latestState?.nexSmsCountryOrder) ? latestState.nexSmsCountryOrder : [])
|
||
);
|
||
}
|
||
} else if (
|
||
message.payload.heroSmsCountryId !== undefined
|
||
|| message.payload.heroSmsCountryLabel !== undefined
|
||
|| message.payload.heroSmsCountryFallback !== undefined
|
||
|| message.payload.fiveSimCountryId !== undefined
|
||
|| message.payload.fiveSimCountryLabel !== undefined
|
||
|| message.payload.fiveSimCountryFallback !== undefined
|
||
|| message.payload.fiveSimCountryOrder !== undefined
|
||
) {
|
||
const activeProvider = getSelectedPhoneSmsProvider();
|
||
const nextPrimary = activeProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? {
|
||
id: normalizeFiveSimCountryId(
|
||
message.payload.fiveSimCountryId !== undefined
|
||
? message.payload.fiveSimCountryId
|
||
: latestState?.fiveSimCountryId
|
||
),
|
||
label: normalizeFiveSimCountryLabel(
|
||
message.payload.fiveSimCountryLabel !== undefined
|
||
? message.payload.fiveSimCountryLabel
|
||
: latestState?.fiveSimCountryLabel
|
||
),
|
||
}
|
||
: {
|
||
id: normalizeHeroSmsCountryId(
|
||
message.payload.heroSmsCountryId !== undefined
|
||
? message.payload.heroSmsCountryId
|
||
: latestState?.heroSmsCountryId
|
||
),
|
||
label: normalizeHeroSmsCountryLabel(
|
||
message.payload.heroSmsCountryLabel !== undefined
|
||
? message.payload.heroSmsCountryLabel
|
||
: latestState?.heroSmsCountryLabel
|
||
),
|
||
};
|
||
const nextFallback = activeProvider === PHONE_SMS_PROVIDER_FIVE_SIM
|
||
? normalizeFiveSimCountryFallbackList(
|
||
message.payload.fiveSimCountryFallback !== undefined
|
||
? message.payload.fiveSimCountryFallback
|
||
: message.payload.fiveSimCountryOrder !== undefined
|
||
? message.payload.fiveSimCountryOrder
|
||
: latestState?.fiveSimCountryFallback
|
||
)
|
||
: normalizeHeroSmsCountryFallbackList(
|
||
message.payload.heroSmsCountryFallback !== undefined
|
||
? message.payload.heroSmsCountryFallback
|
||
: latestState?.heroSmsCountryFallback
|
||
);
|
||
applyHeroSmsFallbackSelection(
|
||
[...nextPrimaryCountries, ...nextFallback],
|
||
{ includePrimary: true }
|
||
);
|
||
updateHeroSmsPlatformDisplay();
|
||
}
|
||
if (
|
||
message.payload.currentPhoneActivation !== undefined
|
||
|| message.payload.reusablePhoneActivation !== undefined
|
||
|| message.payload.phoneReusableActivationPool !== undefined
|
||
|| message.payload.phonePreferredActivation !== undefined
|
||
|| message.payload.currentPhoneVerificationCode !== undefined
|
||
|| message.payload.currentPhoneVerificationCountdownEndsAt !== undefined
|
||
|| message.payload.currentPhoneVerificationCountdownWindowIndex !== undefined
|
||
|| message.payload.currentPhoneVerificationCountdownWindowTotal !== undefined
|
||
|| message.payload.heroSmsLastPriceTiers !== undefined
|
||
|| message.payload.heroSmsLastPriceCountryId !== undefined
|
||
|| message.payload.heroSmsLastPriceCountryLabel !== undefined
|
||
|| message.payload.heroSmsLastPriceUserLimit !== undefined
|
||
|| message.payload.phoneSmsProvider !== undefined
|
||
|| message.payload.phoneSmsProviderOrder !== undefined
|
||
) {
|
||
updateHeroSmsRuntimeDisplay({
|
||
...latestState,
|
||
...message.payload,
|
||
});
|
||
}
|
||
updateAccountRunHistorySettingsUI();
|
||
renderContributionMode();
|
||
void syncPlusManualConfirmationDialog();
|
||
break;
|
||
}
|
||
|
||
case 'ICLOUD_LOGIN_REQUIRED': {
|
||
const loginMessage = '需要登录 iCloud,我已经为你打开登录页。';
|
||
showToast(loginMessage, 'warn', 5000);
|
||
if (icloudSummary) {
|
||
icloudSummary.textContent = loginMessage;
|
||
}
|
||
showIcloudLoginHelp(message.payload || {});
|
||
break;
|
||
}
|
||
|
||
case 'ICLOUD_ALIASES_CHANGED': {
|
||
queueIcloudAliasRefresh();
|
||
break;
|
||
}
|
||
|
||
case 'AUTO_RUN_STATUS': {
|
||
syncLatestState({
|
||
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(message.payload.phase),
|
||
autoRunPhase: message.payload.phase,
|
||
autoRunCurrentRun: message.payload.currentRun,
|
||
autoRunTotalRuns: message.payload.totalRuns,
|
||
autoRunAttemptRun: message.payload.attemptRun,
|
||
scheduledAutoRunAt: message.payload.scheduledAt ?? null,
|
||
autoRunCountdownAt: message.payload.countdownAt ?? null,
|
||
autoRunCountdownTitle: message.payload.countdownTitle ?? '',
|
||
autoRunCountdownNote: message.payload.countdownNote ?? '',
|
||
});
|
||
applyAutoRunStatus(message.payload);
|
||
updateStatusDisplay(latestState);
|
||
updateButtonStates();
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// Theme Toggle
|
||
// ============================================================
|
||
|
||
const btnTheme = document.getElementById('btn-theme');
|
||
|
||
function setTheme(theme) {
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
localStorage.setItem('multipage-theme', theme);
|
||
}
|
||
|
||
function initTheme() {
|
||
const saved = localStorage.getItem('multipage-theme');
|
||
if (saved) {
|
||
setTheme(saved);
|
||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||
setTheme('dark');
|
||
}
|
||
}
|
||
|
||
btnTheme.addEventListener('click', () => {
|
||
const current = document.documentElement.getAttribute('data-theme');
|
||
setTheme(current === 'dark' ? 'light' : 'dark');
|
||
});
|
||
|
||
document.addEventListener('click', (event) => {
|
||
const clickedInsideConfigMenu = Boolean(configMenuShell?.contains(event.target));
|
||
const clickedInsideCountryMenu = Boolean(heroSmsCountryMenuShell?.contains(event.target));
|
||
const clickedInsideFiveSimCountryMenu = Boolean(fiveSimCountryMenuShell?.contains(event.target));
|
||
const clickedInsideNexSmsCountryMenu = Boolean(nexSmsCountryMenuShell?.contains(event.target));
|
||
const clickedInsideProviderOrderMenu = Boolean(phoneSmsProviderOrderMenuShell?.contains(event.target));
|
||
|
||
if (configMenuOpen && !clickedInsideConfigMenu) {
|
||
closeConfigMenu();
|
||
}
|
||
|
||
const countryMenuOpen = btnHeroSmsCountryMenu?.getAttribute('aria-expanded') === 'true';
|
||
if (countryMenuOpen && !clickedInsideCountryMenu) {
|
||
setHeroSmsCountryMenuOpen(false);
|
||
}
|
||
const fiveSimCountryMenuOpen = btnFiveSimCountryMenu?.getAttribute('aria-expanded') === 'true';
|
||
if (fiveSimCountryMenuOpen && !clickedInsideFiveSimCountryMenu) {
|
||
setFiveSimCountryMenuOpen(false);
|
||
}
|
||
const nexSmsCountryMenuOpen = btnNexSmsCountryMenu?.getAttribute('aria-expanded') === 'true';
|
||
if (nexSmsCountryMenuOpen && !clickedInsideNexSmsCountryMenu) {
|
||
setNexSmsCountryMenuOpen(false);
|
||
}
|
||
const providerOrderMenuOpen = btnPhoneSmsProviderOrderMenu?.getAttribute('aria-expanded') === 'true';
|
||
if (providerOrderMenuOpen && !clickedInsideProviderOrderMenu) {
|
||
setPhoneSmsProviderOrderMenuOpen(false);
|
||
}
|
||
});
|
||
|
||
document.addEventListener('keydown', (event) => {
|
||
if (event.key !== 'Escape') {
|
||
return;
|
||
}
|
||
if (configMenuOpen) {
|
||
closeConfigMenu();
|
||
}
|
||
if (btnHeroSmsCountryMenu?.getAttribute('aria-expanded') === 'true') {
|
||
setHeroSmsCountryMenuOpen(false);
|
||
}
|
||
if (btnFiveSimCountryMenu?.getAttribute('aria-expanded') === 'true') {
|
||
setFiveSimCountryMenuOpen(false);
|
||
}
|
||
if (btnNexSmsCountryMenu?.getAttribute('aria-expanded') === 'true') {
|
||
setNexSmsCountryMenuOpen(false);
|
||
}
|
||
if (btnPhoneSmsProviderOrderMenu?.getAttribute('aria-expanded') === 'true') {
|
||
setPhoneSmsProviderOrderMenuOpen(false);
|
||
}
|
||
});
|
||
|
||
window.addEventListener('resize', () => {
|
||
positionContributionUpdateHint();
|
||
});
|
||
|
||
document.addEventListener('scroll', () => {
|
||
positionContributionUpdateHint();
|
||
}, true);
|
||
|
||
// ============================================================
|
||
// Init
|
||
// ============================================================
|
||
|
||
initializeManualStepActions();
|
||
bindPasswordVisibilityToggles();
|
||
initTheme();
|
||
initHotmailListExpandedState();
|
||
initMail2925ListExpandedState();
|
||
if (typeof initIpProxySectionExpandedState === 'function') {
|
||
initIpProxySectionExpandedState();
|
||
}
|
||
if (typeof initPhoneVerificationSectionExpandedState === 'function') {
|
||
initPhoneVerificationSectionExpandedState();
|
||
}
|
||
applyPhoneSmsProviderOrderSelection([], { ensureDefault: false, syncProvider: false });
|
||
updateSaveButtonState();
|
||
updateConfigMenuControls();
|
||
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
||
setMail2925Mode(DEFAULT_MAIL_2925_MODE);
|
||
initializeReleaseInfo().catch((err) => {
|
||
console.error('Failed to initialize release info:', err);
|
||
});
|
||
Promise.allSettled([
|
||
loadHeroSmsCountries(),
|
||
loadFiveSimCountries(),
|
||
loadNexSmsCountries({ silent: true }),
|
||
]).then((results) => {
|
||
const heroResult = results[0];
|
||
const fiveSimResult = results[1];
|
||
const nexSmsResult = results[2];
|
||
if (heroResult?.status === 'rejected') {
|
||
console.error('Failed to load HeroSMS countries:', heroResult.reason);
|
||
}
|
||
if (fiveSimResult?.status === 'rejected') {
|
||
console.error('Failed to load 5sim countries:', fiveSimResult.reason);
|
||
}
|
||
if (nexSmsResult?.status === 'rejected') {
|
||
console.error('Failed to load NexSMS countries:', nexSmsResult.reason);
|
||
}
|
||
return restoreState().then(() => {
|
||
syncPasswordToggleLabel();
|
||
syncVpsUrlToggleLabel();
|
||
syncVpsPasswordToggleLabel();
|
||
syncIpProxyApiUrlToggleLabel();
|
||
syncIpProxyUsernameToggleLabel();
|
||
syncIpProxyPasswordToggleLabel();
|
||
syncHeroSmsApiKeyToggleLabel();
|
||
syncPasswordVisibilityToggles();
|
||
syncHeroSmsApiKeyToggleLabel();
|
||
updatePanelModeUI();
|
||
updateButtonStates();
|
||
updateStatusDisplay(latestState);
|
||
return refreshContributionContentHint()
|
||
.catch((error) => {
|
||
console.warn('Failed to refresh contribution content hint during initialization:', error);
|
||
return null;
|
||
})
|
||
.then(() => maybeShowNewUserGuidePrompt());
|
||
}).catch((err) => {
|
||
console.error('Failed to initialize sidepanel state:', err);
|
||
});
|
||
});
|