// 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 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 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 selectPlusPaymentMethod = document.getElementById('select-plus-payment-method');
const rowPayPalAccount = document.getElementById('row-paypal-account');
const selectPayPalAccount = document.getElementById('select-paypal-account');
const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account');
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 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 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 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 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 rowHeroSmsRuntimePair = document.getElementById('row-hero-sms-runtime-pair');
const rowHeroSmsCurrentNumber = document.getElementById('row-hero-sms-current-number');
const rowHeroSmsPriceTiers = document.getElementById('row-hero-sms-price-tiers');
const rowHeroSmsCurrentCode = document.getElementById('row-hero-sms-current-code');
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 inputHeroSmsMaxPrice = document.getElementById('input-hero-sms-max-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 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 btnHeroSmsPricePreview = document.getElementById('btn-hero-sms-price-preview');
const displayHeroSmsPlatform = document.getElementById('display-hero-sms-platform');
const displayHeroSmsCurrentNumber = document.getElementById('display-hero-sms-current-number');
const displayHeroSmsPriceTiers = document.getElementById('display-hero-sms-price-tiers');
const displayHeroSmsCurrentCode = document.getElementById('display-hero-sms-current-code');
const displayHeroSmsCountryFallbackOrder = document.getElementById('display-hero-sms-country-fallback-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');
let currentPlusModeEnabled = false;
let currentPlusPaymentMethod = 'paypal';
let heroSmsCountrySelectionOrder = [];
let heroSmsCountryMenuSearchKeyword = '';
const heroSmsCountrySearchTextById = 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 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 DEFAULT_HERO_SMS_ACQUIRE_PRIORITY = HERO_SMS_ACQUIRE_PRIORITY_COUNTRY;
const HERO_SMS_FALLBACK_COUNTRY_ITEMS = Object.freeze([
{ 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' },
]);
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';
function normalizePlusPaymentMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal';
}
function getSelectedPlusPaymentMethod() {
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
return normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
}
return normalizePlusPaymentMethod(latestState?.plusPaymentMethod || currentPlusPaymentMethod);
}
function getStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethod = 'paypal') {
return (window.MultiPageStepDefinitions?.getSteps?.({
plusModeEnabled,
plusPaymentMethod: normalizePlusPaymentMethod(plusPaymentMethod),
}) || [])
.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, plusPaymentMethod = 'paypal') {
currentPlusModeEnabled = Boolean(plusModeEnabled);
currentPlusPaymentMethod = normalizePlusPaymentMethod(plusPaymentMethod);
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_IP_PROXY_SERVICE = '711proxy';
const SUPPORTED_IP_PROXY_SERVICES = ['711proxy', 'lumiproxy', 'iproyal', 'omegaproxy'];
const IP_PROXY_ENABLED_SERVICES = ['711proxy'];
const DEFAULT_IP_PROXY_MODE = 'account';
const SUPPORTED_IP_PROXY_MODES = ['api', 'account'];
const DEFAULT_IP_PROXY_PROTOCOL = 'http';
const SUPPORTED_IP_PROXY_PROTOCOLS = ['http', 'https', 'socks4', 'socks5'];
const IP_PROXY_API_MODE_ENABLED = false;
const IP_PROXY_ACCOUNT_LIST_ENABLED = false;
function getManagedAliasUtils() {
return window.MultiPageManagedAliasUtils || null;
}
function isManagedAliasProvider(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) {
const utils = getManagedAliasUtils();
if (utils?.usesManagedAliasGeneration) {
return utils.usesManagedAliasGeneration(provider, { mail2925Mode });
}
if (utils?.isManagedAliasProvider) {
const normalizedProvider = String(provider || '').trim().toLowerCase();
if (normalizedProvider === '2925') {
return utils.isManagedAliasProvider(provider)
&& normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
}
return utils.isManagedAliasProvider(provider);
}
const normalizedProvider = String(provider || '').trim().toLowerCase();
if (normalizedProvider === '2925') {
return normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
}
return normalizedProvider === GMAIL_PROVIDER;
}
function parseManagedAliasBaseEmail(rawValue, provider = selectMailProvider.value) {
const utils = getManagedAliasUtils();
if (utils?.parseManagedAliasBaseEmail) {
return utils.parseManagedAliasBaseEmail(rawValue, provider);
}
return null;
}
function isManagedAliasEmail(value, baseEmail = '', provider = selectMailProvider.value) {
const utils = getManagedAliasUtils();
if (utils?.isManagedAliasEmail) {
return utils.isManagedAliasEmail(value, provider, baseEmail);
}
return false;
}
function getManagedAliasProviderUiCopy(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) {
if (!isManagedAliasProvider(provider, mail2925Mode)) {
return null;
}
const utils = getManagedAliasUtils();
if (utils?.getManagedAliasProviderUiCopy) {
return utils.getManagedAliasProviderUiCopy(provider);
}
if (String(provider || '').trim().toLowerCase() === GMAIL_PROVIDER) {
return {
baseLabel: '基邮箱',
basePlaceholder: '例如 yourname@gmail.com',
buttonLabel: '生成',
successVerb: '生成',
label: 'Gmail +tag 邮箱',
placeholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱',
hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。',
};
}
if (String(provider || '').trim().toLowerCase() === '2925') {
return {
baseLabel: '基邮箱',
basePlaceholder: '例如 yourname@2925.com',
buttonLabel: '生成',
successVerb: '生成',
label: '2925 邮箱',
placeholder: '点击生成 2925 邮箱,或手动填写完整邮箱',
hint: '先填写基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。',
};
}
return null;
}
function getManagedAliasBaseEmailKey(provider = selectMailProvider.value) {
const normalizedProvider = String(provider || '').trim().toLowerCase();
if (normalizedProvider === GMAIL_PROVIDER) {
return 'gmailBaseEmail';
}
if (normalizedProvider === '2925') {
return 'mail2925BaseEmail';
}
return '';
}
function isMail2925AccountPoolEnabled(state = latestState) {
return Boolean(state?.mail2925UseAccountPool);
}
function getPreferredMail2925PoolAccountId(state = latestState) {
const currentId = String(state?.currentMail2925AccountId || '').trim();
if (currentId && getMail2925Accounts(state).some((account) => account.id === currentId)) {
return currentId;
}
return '';
}
function syncMail2925PoolAccountOptions(state = latestState) {
if (!selectMail2925PoolAccount) {
return;
}
const accounts = getMail2925Accounts(state);
const selectedId = getPreferredMail2925PoolAccountId(state);
const options = [''].concat(
accounts.map((account) => ``)
);
selectMail2925PoolAccount.innerHTML = options.join('');
selectMail2925PoolAccount.value = selectedId;
}
async function syncSelectedMail2925PoolAccount(options = {}) {
const { silent = false } = options;
if (!selectMail2925PoolAccount || !isMail2925AccountPoolEnabled(latestState)) {
return null;
}
const accountId = String(selectMail2925PoolAccount.value || '').trim();
if (!accountId) {
syncLatestState({ currentMail2925AccountId: null });
setManagedAliasBaseEmailInputForProvider('2925', latestState);
return null;
}
const response = await chrome.runtime.sendMessage({
type: 'SELECT_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) {
throw new Error(response.error);
}
syncLatestState({
currentMail2925AccountId: response.account?.id || accountId,
...(response.account?.email ? { mail2925BaseEmail: String(response.account.email).trim() } : {}),
});
setManagedAliasBaseEmailInputForProvider('2925', latestState);
if (!silent) {
showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800);
}
return response.account || null;
}
function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) {
if (String(provider || '').trim().toLowerCase() === '2925' && isMail2925AccountPoolEnabled(state)) {
const currentMail2925Email = getCurrentMail2925Email(state);
if (currentMail2925Email) {
return currentMail2925Email;
}
}
const key = getManagedAliasBaseEmailKey(provider);
if (!key) {
return '';
}
const providerValue = String(state?.[key] || '').trim();
if (providerValue) {
return providerValue;
}
const legacyEmailPrefix = String(state?.emailPrefix || '').trim();
return parseManagedAliasBaseEmail(legacyEmailPrefix, provider) ? legacyEmailPrefix : '';
}
function buildManagedAliasBaseEmailPayload(state = latestState) {
const payload = {
gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(),
mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(),
mail2925UseAccountPool: Boolean(state?.mail2925UseAccountPool),
emailPrefix: '',
};
const key = getManagedAliasBaseEmailKey();
if (key) {
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(state)) {
payload[key] = String(state?.mail2925BaseEmail || '').trim();
} else {
payload[key] = inputEmailPrefix.value.trim();
}
}
return payload;
}
function syncManagedAliasBaseEmailDraftFromInput(provider = selectMailProvider.value) {
const key = getManagedAliasBaseEmailKey(provider);
if (!key) {
return;
}
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(latestState)) {
return;
}
syncLatestState({ [key]: inputEmailPrefix.value.trim() });
}
function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) {
syncMail2925PoolAccountOptions(state);
inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state);
}
function getCurrentRegistrationEmailUiCopy() {
if (isCustomMailProvider()) {
return getCustomMailProviderUiCopy();
}
if (usesGeneratedAliasMailProvider()) {
return getManagedAliasProviderUiCopy();
}
return getEmailGeneratorUiCopy();
}
function isCurrentRegistrationEmailCompatible(email = inputEmail.value.trim(), provider = selectMailProvider.value, state = latestState) {
if (!usesGeneratedAliasMailProvider(provider, getSelectedMail2925Mode()) || !email) {
return true;
}
const baseEmail = getManagedAliasBaseEmailForProvider(provider, state);
return isManagedAliasEmail(email, baseEmail, provider);
}
function validateCurrentRegistrationEmail(email = inputEmail.value.trim(), options = {}) {
const { showToastOnFailure = false } = options;
if (isCurrentRegistrationEmailCompatible(email)) {
return true;
}
if (showToastOnFailure) {
const uiCopy = getManagedAliasProviderUiCopy();
const baseEmail = getManagedAliasBaseEmailForProvider();
showToast(
baseEmail
? `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,注册邮箱需与 ${uiCopy?.baseLabel || '基邮箱'} 对应。`
: `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,请直接填写完整邮箱,或先填写基邮箱后点击“生成”。`,
'warn'
);
}
return false;
}
let 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 phoneVerificationSectionExpanded = true;
function readPhoneVerificationSectionExpanded() {
try {
return globalThis.localStorage?.getItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY) !== '0';
} catch (err) {
return true;
}
}
function persistPhoneVerificationSectionExpanded(expanded) {
try {
globalThis.localStorage?.setItem(
PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY,
expanded ? '1' : '0'
);
} catch (err) {
// Ignore storage errors; in-memory state is sufficient for current session.
}
}
function setPhoneVerificationSectionExpanded(expanded) {
phoneVerificationSectionExpanded = Boolean(expanded);
persistPhoneVerificationSectionExpanded(phoneVerificationSectionExpanded);
updatePhoneVerificationSettingsUI();
}
function togglePhoneVerificationSectionExpanded() {
if (!inputPhoneVerificationEnabled?.checked) {
return;
}
setPhoneVerificationSectionExpanded(!phoneVerificationSectionExpanded);
}
function initPhoneVerificationSectionExpandedState() {
phoneVerificationSectionExpanded = readPhoneVerificationSectionExpanded();
updatePhoneVerificationSettingsUI();
}
const EYE_OPEN_ICON = '';
const EYE_CLOSED_ICON = '';
const COPY_ICON = '';
const parseHotmailImportText = window.HotmailUtils?.parseHotmailImportText;
const normalizeHotmailServiceModeFromUtils = window.HotmailUtils?.normalizeHotmailServiceMode;
const shouldClearHotmailCurrentSelection = window.HotmailUtils?.shouldClearHotmailCurrentSelection;
const upsertHotmailAccountInList = window.HotmailUtils?.upsertHotmailAccountInList;
const filterHotmailAccountsByUsage = window.HotmailUtils?.filterHotmailAccountsByUsage;
const getHotmailBulkActionLabel = window.HotmailUtils?.getHotmailBulkActionLabel;
const getHotmailListToggleLabel = window.HotmailUtils?.getHotmailListToggleLabel;
const upsertPayPalAccountInList = window.PayPalUtils?.upsertPayPalAccountInList;
const normalizeLuckmailTimestampValue = window.LuckMailUtils?.normalizeTimestamp
|| ((value) => {
const timestamp = Date.parse(String(value || ''));
return Number.isFinite(timestamp) ? timestamp : 0;
});
const sidepanelUpdateService = window.SidepanelUpdateService;
const contributionContentService = window.SidepanelContributionContentService;
const sharedFormDialog = window.SidepanelFormDialog?.createFormDialog?.({
overlay: sharedFormModal,
titleNode: sharedFormModalTitle,
closeButton: btnSharedFormModalClose,
messageNode: sharedFormModalMessage,
alertNode: sharedFormModalAlert,
fieldsContainer: sharedFormModalFields,
cancelButton: btnSharedFormModalCancel,
confirmButton: btnSharedFormModalConfirm,
});
const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = window.LuckMailUtils?.DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME || '保留';
const normalizeIcloudHost = window.IcloudUtils?.normalizeIcloudHost
|| ((value) => {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : '';
});
const normalizeIcloudFetchMode = (value) => {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'always_new' ? 'always_new' : 'reuse_existing';
};
const normalizeIcloudTargetMailboxType = window.MailProviderUtils?.normalizeIcloudTargetMailboxType
|| ((value) => String(value || '').trim().toLowerCase() === 'forward-mailbox'
? 'forward-mailbox'
: 'icloud-inbox');
const getIcloudForwardMailProviderOptions = window.MailProviderUtils?.getIcloudForwardMailProviderOptions
|| (() => Array.from(selectIcloudForwardMailProvider?.options || [])
.map((option) => ({
value: String(option?.value || '').trim().toLowerCase(),
label: String(option?.textContent || option?.label || option?.value || '').trim(),
}))
.filter((option) => option.value));
const normalizeIcloudForwardMailProvider = window.MailProviderUtils?.normalizeIcloudForwardMailProvider
|| ((value) => {
const normalized = String(value || '').trim().toLowerCase();
const options = getIcloudForwardMailProviderOptions();
return options.some((option) => option.value === normalized)
? normalized
: (options[0]?.value || 'qq');
});
const ICLOUD_FORWARD_MAIL_PROVIDER_LABELS = Object.fromEntries(
getIcloudForwardMailProviderOptions().map((option) => [option.value, option.label])
);
const getIcloudLoginUrlForHost = window.IcloudUtils?.getIcloudLoginUrlForHost
|| ((host) => host === 'icloud.com.cn' ? 'https://www.icloud.com.cn/' : (host === 'icloud.com' ? 'https://www.icloud.com/' : ''));
btnAutoCancelSchedule?.remove();
const MAIL_PROVIDER_LOGIN_CONFIGS = {
[ICLOUD_PROVIDER]: {
label: 'iCloud 邮箱',
buttonLabel: '登录',
},
[GMAIL_PROVIDER]: {
label: 'Gmail 邮箱',
url: 'https://mail.google.com/mail/u/0/#inbox',
buttonLabel: '登录',
},
'163': {
label: '163 邮箱',
url: 'https://mail.163.com/',
buttonLabel: '登录',
},
'163-vip': {
label: '163 VIP 邮箱',
url: 'https://webmail.vip.163.com/',
buttonLabel: '登录',
},
'126': {
label: '126 邮箱',
url: 'https://mail.126.com/',
buttonLabel: '登录',
},
qq: {
label: 'QQ 邮箱',
url: 'https://wx.mail.qq.com/',
buttonLabel: '登录',
},
'cloudflare-temp-email': {
label: 'Cloudflare Temp Email GitHub',
url: 'https://github.com/dreamhunter2333/cloudflare_temp_email',
buttonLabel: 'GitHub',
},
'2925': {
label: '2925 邮箱',
url: 'https://2925.com/#/mailList',
},
};
const IP_PROXY_SERVICE_LOGIN_CONFIGS = {
'711proxy': {
label: '711Proxy',
url: 'https://www.711proxy.com/signup?code=AD2497',
buttonLabel: '注册',
},
};
// ============================================================
// Toast Notifications
// ============================================================
const toastContainer = document.getElementById('toast-container');
const TOAST_ICONS = {
error: '',
warn: '',
success: '',
info: '',
};
const LOG_LEVEL_LABELS = {
info: '信息',
ok: '成功',
warn: '警告',
error: '错误',
};
const CLOUDFLARE_TEMP_EMAIL_REPOSITORY_URL = 'https://github.com/dreamhunter2333/cloudflare_temp_email';
function usesGeneratedAliasMailProvider(
provider,
mail2925Mode = getSelectedMail2925Mode(),
generator = undefined
) {
const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
: 'custom-pool';
const resolvedGenerator = generator !== undefined
? generator
: (typeof getSelectedEmailGenerator === 'function' ? getSelectedEmailGenerator() : '');
return resolvedGenerator !== customEmailPoolGenerator
&& isManagedAliasProvider(provider, mail2925Mode);
}
function parseGmailBaseEmail(rawValue = '') {
const value = String(rawValue || '').trim().toLowerCase();
const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i);
if (!match) return null;
return {
localPart: match[1],
domain: match[2].toLowerCase(),
};
}
function isManagedGmailAlias(value, baseEmail) {
const parsedBase = parseGmailBaseEmail(baseEmail);
if (!parsedBase) return false;
const match = String(value || '').trim().toLowerCase().match(/^([^@\s+]+)(?:\+[^@\s]+)?@((?:gmail|googlemail)\.com)$/i);
if (!match) return false;
return match[1] === parsedBase.localPart && match[2] === parsedBase.domain;
}
function showToast(message, type = 'error', duration = 4000) {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `${TOAST_ICONS[type] || ''}${escapeHtml(message)}`;
toast.querySelector('.toast-close').addEventListener('click', () => dismissToast(toast));
toastContainer.appendChild(toast);
if (duration > 0) {
setTimeout(() => dismissToast(toast), duration);
}
}
function dismissToast(toast) {
if (!toast.parentNode) return;
toast.classList.add('toast-exit');
toast.addEventListener('animationend', () => toast.remove());
}
function resetActionModalOption() {
if (!modalOptionRow || !modalOptionInput || !modalOptionText) {
return;
}
modalOptionRow.hidden = true;
modalOptionInput.checked = false;
modalOptionInput.disabled = false;
modalOptionText.textContent = '不再提示';
}
function resetActionModalAlert() {
if (!autoStartAlert) {
return;
}
autoStartAlert.hidden = true;
autoStartAlert.textContent = '';
autoStartAlert.className = 'modal-alert';
}
function setActionModalMessageContent({ text = '', html = '' } = {}) {
if (!autoStartMessage) {
return;
}
if (html) {
autoStartMessage.innerHTML = html;
return;
}
autoStartMessage.textContent = text;
}
function resetActionModalButtons() {
const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
buttons.forEach((button) => {
if (!button) return;
button.hidden = true;
button.disabled = false;
button.onclick = null;
});
currentModalActions = [];
}
function configureActionModalButton(button, action) {
if (!button) return;
if (!action) {
button.hidden = true;
button.onclick = null;
return;
}
button.hidden = false;
button.disabled = false;
button.textContent = action.label;
button.className = `btn ${action.variant || 'btn-outline'} btn-sm`;
button.onclick = () => resolveModalChoice(action.id);
}
function configureActionModalOption(option) {
if (!modalOptionRow || !modalOptionInput || !modalOptionText) {
return;
}
if (!option) {
resetActionModalOption();
return;
}
modalOptionRow.hidden = false;
modalOptionInput.checked = Boolean(option.checked);
modalOptionInput.disabled = Boolean(option.disabled);
modalOptionText.textContent = option.label || '不再提示';
}
function configureActionModalAlert(alert) {
if (!autoStartAlert) {
return;
}
if (!alert?.text) {
resetActionModalAlert();
return;
}
autoStartAlert.hidden = false;
autoStartAlert.textContent = alert.text;
autoStartAlert.className = `modal-alert${alert.tone === 'danger' ? ' is-danger' : ''}`;
}
function resolveModalChoice(choice) {
const optionChecked = Boolean(modalOptionInput?.checked);
const result = typeof modalResultBuilder === 'function'
? modalResultBuilder(choice, { optionChecked })
: choice;
if (modalChoiceResolver) {
modalChoiceResolver(result);
modalChoiceResolver = null;
}
modalResultBuilder = null;
resetActionModalButtons();
resetActionModalAlert();
resetActionModalOption();
if (autoStartModal) {
autoStartModal.hidden = true;
}
}
function openActionModal({ title, message, messageHtml, actions, option, alert, buildResult }) {
if (!autoStartModal) {
return Promise.resolve(null);
}
if (modalChoiceResolver) {
resolveModalChoice(null);
}
resetActionModalButtons();
autoStartTitle.textContent = title;
setActionModalMessageContent({ text: message, html: messageHtml });
currentModalActions = actions || [];
modalResultBuilder = typeof buildResult === 'function' ? buildResult : null;
const buttonSlots = currentModalActions.length <= 2
? [btnAutoStartCancel, btnAutoStartContinue]
: [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
buttonSlots.forEach((button, index) => {
configureActionModalButton(button, currentModalActions[index]);
});
configureActionModalAlert(alert);
configureActionModalOption(option);
autoStartModal.hidden = false;
return new Promise((resolve) => {
modalChoiceResolver = resolve;
});
}
function openAutoStartChoiceDialog(startStep, options = {}) {
const runningStep = Number.isInteger(options.runningStep) ? options.runningStep : null;
const continueMessage = runningStep
? `继续当前会先等待步骤 ${runningStep} 完成,再按最新进度自动执行。`
: `继续当前会从步骤 ${startStep} 开始自动执行。`;
return openActionModal({
title: '启动自动',
message: `检测到当前已有流程进度。${continueMessage}重新开始会清空当前流程进度并从步骤 1 新开一轮。`,
actions: [
{ id: null, label: '取消', variant: 'btn-ghost' },
{ id: 'restart', label: '重新开始', variant: 'btn-outline' },
{ id: 'continue', label: '继续当前', variant: 'btn-primary' },
],
});
}
async function openConfirmModal({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary', alert = null }) {
const choice = await openActionModal({
title,
message,
alert,
actions: [
{ id: null, label: '取消', variant: 'btn-ghost' },
{ id: 'confirm', label: confirmLabel, variant: confirmVariant },
],
});
return choice === 'confirm';
}
async function openConfirmModalWithOption({
title,
message,
confirmLabel = '确认',
confirmVariant = 'btn-primary',
alert = null,
optionLabel = '不再提示',
optionChecked = false,
optionDisabled = false,
}) {
const result = await openActionModal({
title,
message,
alert,
actions: [
{ id: null, label: '取消', variant: 'btn-ghost' },
{ id: 'confirm', label: confirmLabel, variant: confirmVariant },
],
option: {
label: optionLabel,
checked: optionChecked,
disabled: optionDisabled,
},
buildResult: (choice, meta) => ({
choice,
optionChecked: Boolean(meta?.optionChecked),
}),
});
return {
confirmed: result?.choice === 'confirm',
optionChecked: Boolean(result?.optionChecked),
};
}
// Override the initial GoPay confirmation helpers so the dialog can
// recover after an accidental close instead of silently leaving step 7 hanging.
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 [
'您觉得这个 Plus 功能怎么样?您的账户数量应该已经够个人使用啦。',
'可以打开贡献给作者贡献几个账号,以便于让作者开发更好的功能出来吗?或者打赏一下作者?',
``,
].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();
}
async function persistCurrentSettingsForAction() {
clearTimeout(settingsAutoSaveTimer);
await waitForSettingsSaveIdle();
await saveSettings({ silent: true, force: true });
}
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 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 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 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() {
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 = selectMailProvider.value) {
return getLockedRunCountFromEmailPool(provider) > 0;
}
function syncRunCountFromCustomEmailPool() {
if (!usesCustomEmailPoolGenerator()) {
return;
}
inputRunCount.value = String(getCustomEmailPoolSize());
}
function syncRunCountFromCustomMailProviderPool() {
if (!usesCustomMailProviderPool()) {
return;
}
inputRunCount.value = String(getCustomMailProviderPoolSize());
}
function syncRunCountFromConfiguredEmailPool(provider = selectMailProvider.value) {
const poolSize = getLockedRunCountFromEmailPool(provider);
if (poolSize > 0) {
inputRunCount.value = String(poolSize);
}
}
function getRunCountValue() {
const lockedRunCount = typeof getLockedRunCountFromEmailPool === 'function'
? getLockedRunCountFromEmailPool()
: 0;
if (lockedRunCount > 0) {
return lockedRunCount;
}
return Math.max(1, parseInt(inputRunCount.value, 10) || 1);
}
function updateFallbackThreadIntervalInputState() {
if (!inputAutoSkipFailuresThreadIntervalMinutes) {
return;
}
inputAutoSkipFailuresThreadIntervalMinutes.disabled = Boolean(inputAutoSkipFailures.disabled);
}
function updateAutoDelayInputState() {
const scheduled = isAutoRunScheduledPhase();
inputAutoDelayEnabled.disabled = scheduled;
inputAutoDelayMinutes.disabled = scheduled || !inputAutoDelayEnabled.checked;
}
function formatCountdown(remainingMs) {
const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
function formatScheduleTime(timestamp) {
return new Date(timestamp).toLocaleString('zh-CN', {
hour12: false,
timeZone: DISPLAY_TIMEZONE,
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
function stopScheduledCountdownTicker() {
clearInterval(scheduledCountdownTimer);
scheduledCountdownTimer = null;
}
function getActiveAutoRunCountdown() {
if (isAutoRunScheduledPhase() && Number.isFinite(currentAutoRun.scheduledAt)) {
return {
at: currentAutoRun.scheduledAt,
title: '已计划自动运行',
note: `计划于 ${formatScheduleTime(currentAutoRun.scheduledAt)} 开始`,
tone: 'scheduled',
};
}
if (currentAutoRun.phase !== 'waiting_interval') {
return null;
}
if (!Number.isFinite(currentAutoRun.countdownAt)) {
return null;
}
return {
at: currentAutoRun.countdownAt,
title: currentAutoRun.countdownTitle || '等待中',
note: currentAutoRun.countdownNote || '',
tone: 'running',
};
}
function renderScheduledAutoRunInfo() {
if (!autoScheduleBar) {
return;
}
const countdown = getActiveAutoRunCountdown();
if (!countdown) {
autoScheduleBar.style.display = 'none';
return;
}
const remainingMs = countdown.at - Date.now();
autoScheduleBar.style.display = 'flex';
if (btnAutoRunNow) {
btnAutoRunNow.hidden = false;
btnAutoRunNow.textContent = currentAutoRun.phase === 'waiting_interval' ? '立即继续' : '立即开始';
}
if (btnAutoCancelSchedule) {
btnAutoCancelSchedule.hidden = true;
}
autoScheduleTitle.textContent = countdown.title;
autoScheduleMeta.textContent = remainingMs > 0
? `${countdown.note ? `${countdown.note},` : ''}剩余 ${formatCountdown(remainingMs)}`
: '倒计时即将结束,正在准备继续...';
return;
}
function syncScheduledCountdownTicker() {
renderScheduledAutoRunInfo();
if (getActiveAutoRunCountdown()) {
if (scheduledCountdownTimer) {
return;
}
scheduledCountdownTimer = setInterval(() => {
renderScheduledAutoRunInfo();
updateStatusDisplay(latestState);
}, 1000);
return;
}
stopScheduledCountdownTicker();
return;
}
function setDefaultAutoRunButton() {
btnAutoRun.disabled = false;
inputRunCount.disabled = shouldLockRunCountToEmailPool();
btnAutoRun.innerHTML = ' 自动';
}
function normalizeCloudflareDomainValue(value = '') {
let normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';
normalized = normalized.replace(/^@+/, '');
normalized = normalized.replace(/^https?:\/\//, '');
normalized = normalized.replace(/\/.*$/, '');
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/.test(normalized)) {
return '';
}
return normalized;
}
function normalizeCloudflareDomains(values = []) {
const seen = new Set();
const domains = [];
for (const value of Array.isArray(values) ? values : []) {
const normalized = normalizeCloudflareDomainValue(value);
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
domains.push(normalized);
}
return domains;
}
function normalizeCloudflareTempEmailBaseUrlValue(value = '') {
const raw = String(value || '').trim();
if (!raw) return '';
const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(raw) ? raw : `https://${raw}`;
try {
const parsed = new URL(candidate);
parsed.hash = '';
parsed.search = '';
const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/, '');
return `${parsed.origin}${pathname}`;
} catch {
return '';
}
}
function normalizeCloudflareTempEmailReceiveMailboxValue(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized) ? normalized : '';
}
function normalizeCloudflareTempEmailDomainValue(value = '') {
return normalizeCloudflareDomainValue(value);
}
function normalizeCloudflareTempEmailDomains(values = []) {
const seen = new Set();
const domains = [];
for (const value of Array.isArray(values) ? values : []) {
const normalized = normalizeCloudflareTempEmailDomainValue(value);
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
domains.push(normalized);
}
return domains;
}
function 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 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 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 heroSmsApiKeyValue = typeof inputHeroSmsApiKey !== 'undefined' && inputHeroSmsApiKey
? (inputHeroSmsApiKey.value || '')
: '';
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 normalizeHeroSmsAcquirePriorityValue = typeof normalizeHeroSmsAcquirePriority === 'function'
? normalizeHeroSmsAcquirePriority
: (value) => (String(value || '').trim().toLowerCase() === 'price' ? 'price' : 'country');
const heroSmsAcquirePriorityValue = typeof selectHeroSmsAcquirePriority !== 'undefined' && selectHeroSmsAcquirePriority
? normalizeHeroSmsAcquirePriorityValue(selectHeroSmsAcquirePriority.value)
: normalizeHeroSmsAcquirePriorityValue(
typeof DEFAULT_HERO_SMS_ACQUIRE_PRIORITY !== 'undefined'
? DEFAULT_HERO_SMS_ACQUIRE_PRIORITY
: 'country'
);
const heroSmsMaxPriceValue = typeof inputHeroSmsMaxPrice !== 'undefined' && inputHeroSmsMaxPrice
? normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice.value)
: '';
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 heroSmsCountry = 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 heroSmsCountryFallback = typeof syncHeroSmsFallbackSelectionOrderFromSelect === 'function'
? syncHeroSmsFallbackSelectionOrderFromSelect()
.filter((country) => Number(country.id) !== Number(heroSmsCountry.id))
: [];
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 plusPaymentMethod = typeof getSelectedPlusPaymentMethod === 'function'
? getSelectedPlusPaymentMethod()
: (String(
(typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod
? selectPlusPaymentMethod.value
: latestState?.plusPaymentMethod) || ''
).trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal');
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,
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,
...(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: typeof normalizeCustomEmailPoolEntries === 'function'
? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
: [],
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: inputOAuthFlowTimeoutEnabled
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
: true,
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
verificationResendCount: normalizeVerificationResendCount(
inputVerificationResendCount?.value,
DEFAULT_VERIFICATION_RESEND_COUNT
),
heroSmsApiKey: heroSmsApiKeyValue,
heroSmsReuseEnabled: heroSmsReuseEnabledValue,
heroSmsAcquirePriority: heroSmsAcquirePriorityValue,
heroSmsMaxPrice: heroSmsMaxPriceValue,
phoneVerificationReplacementLimit: phoneVerificationReplacementLimitValue,
phoneCodeWaitSeconds: phoneCodeWaitSecondsValue,
phoneCodeTimeoutWindows: phoneCodeTimeoutWindowsValue,
phoneCodePollIntervalSeconds: phoneCodePollIntervalSecondsValue,
phoneCodePollMaxRounds: phoneCodePollMaxRoundsValue,
heroSmsCountryId: heroSmsCountry.id,
heroSmsCountryLabel: heroSmsCountry.label,
heroSmsCountryFallback,
};
}
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 normalizeHeroSmsCountryId(value) {
return Math.max(1, Math.floor(Number(value) || 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 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 = '') {
return String(value || '').trim().toLowerCase() === HERO_SMS_ACQUIRE_PRIORITY_PRICE
? HERO_SMS_ACQUIRE_PRIORITY_PRICE
: 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 price = Number(value);
if (!Number.isFinite(price) || price < 0) {
return null;
}
return price;
}
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);
if (cost !== null) {
const count = Number(payload.count);
const physicalCount = Number(payload.physicalCount);
const hasCount = Number.isFinite(count);
const hasPhysicalCount = Number.isFinite(physicalCount);
const stockCount = Math.max(hasCount ? count : 0, hasPhysicalCount ? physicalCount : 0);
const hasStockField = hasCount || hasPhysicalCount;
entries.push({
cost,
hasStockField,
stockCount: Number.isFinite(stockCount) ? stockCount : 0,
inStock: !hasStockField || stockCount > 0,
});
}
Object.values(payload).forEach((entry) => collectHeroSmsPriceEntriesForPreview(entry, entries));
return entries;
}
function collectHeroSmsPriceCandidatesForPreview(payload, candidates = []) {
collectHeroSmsPriceEntriesForPreview(payload, [])
.filter((entry) => entry.inStock)
.forEach((entry) => {
candidates.push(entry.cost);
});
return candidates;
}
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 getSelectedHeroSmsCountryOption() {
const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({
enforceMax: true,
ensureDefault: true,
showLimitToast: false,
});
if (selectedCountries.length) {
return selectedCountries[0];
}
return {
id: DEFAULT_HERO_SMS_COUNTRY_ID,
label: DEFAULT_HERO_SMS_COUNTRY_LABEL,
};
}
function updateHeroSmsPlatformDisplay() {
if (!displayHeroSmsPlatform) {
return;
}
displayHeroSmsPlatform.textContent = 'HeroSMS / OpenAI';
}
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 normalizeHeroSmsCountryLabel(matched?.textContent || '', `Country #${targetId}`);
}
function renderHeroSmsCountryFallbackOrder(countries = []) {
if (!displayHeroSmsCountryFallbackOrder) {
return;
}
const normalized = normalizeHeroSmsCountryFallbackList(countries);
if (!normalized.length) {
displayHeroSmsCountryFallbackOrder.textContent = '未设置';
return;
}
displayHeroSmsCountryFallbackOrder.textContent = normalized
.map((country) => `${country.label}(${country.id})`)
.join(' -> ');
}
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 = normalizeHeroSmsCountryFallbackList(selectedCountries);
if (!normalized.length) {
btnHeroSmsCountryMenu.textContent = `${DEFAULT_HERO_SMS_COUNTRY_LABEL} (1/${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;
const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({
enforceMax: true,
ensureDefault: true,
showLimitToast: true,
});
updateHeroSmsPlatformDisplay(selectedCountries[0]?.label || DEFAULT_HERO_SMS_COUNTRY_LABEL);
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 = {
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) => {
const parsedId = Math.floor(Number(option.value));
return Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0;
})
.filter((id) => id > 0);
if (!selectedIds.length && !countrySelect.multiple) {
const fallbackId = Math.floor(Number(countrySelect.value));
if (Number.isFinite(fallbackId) && fallbackId > 0) {
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 = normalizeHeroSmsCountryId(countrySelect.value || 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 sourceCountries = includePrimary
? countries
: [
getSelectedHeroSmsCountryOption(),
...normalizeHeroSmsCountryFallbackList(countries),
];
const normalized = normalizeHeroSmsCountryFallbackList(sourceCountries)
.slice(0, HERO_SMS_COUNTRY_SELECTION_MAX);
const selectedIds = normalized
.map((entry) => Number(entry.id))
.filter((id) => Number.isFinite(id) && id > 0);
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: true,
showLimitToast: false,
});
}
function updateHeroSmsRuntimeDisplay(state = {}) {
if (displayHeroSmsCurrentNumber) {
const activation = state?.currentPhoneActivation || null;
const phoneNumber = String(activation?.phoneNumber || '').trim();
const activationId = String(activation?.activationId || '').trim();
const countryLabel = normalizeHeroSmsCountryLabel(
activation?.countryLabel || getHeroSmsCountryLabelById(activation?.countryId || '')
);
displayHeroSmsCurrentNumber.textContent = phoneNumber
? `${phoneNumber}${activationId ? ` (#${activationId})` : ''}${countryLabel ? ` / ${countryLabel}` : ''}`
: '未分配';
}
if (displayHeroSmsCurrentCode) {
const code = String(state?.currentPhoneVerificationCode || '').trim();
displayHeroSmsCurrentCode.textContent = code || '未获取';
}
}
async function loadHeroSmsCountries() {
const countrySelect = selectHeroSmsCountry || selectHeroSmsCountryFallback;
if (!countrySelect) {
return;
}
const previousSelectionOrder = [...heroSmsCountrySelectionOrder];
const previousSelectedIds = previousSelectionOrder.length
? previousSelectionOrder
: Array.from(countrySelect.options)
.filter((option) => option.selected)
.map((option) => {
const parsedId = Math.floor(Number(option.value));
return Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0;
})
.filter((id) => id > 0);
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);
});
};
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) => Number(id));
heroSmsCountrySelectionOrder = normalizedSelectedIds;
const selectedSet = new Set(normalizedSelectedIds.map((id) => String(id)));
Array.from(countrySelect.options).forEach((option) => {
option.selected = selectedSet.has(String(option.value));
});
const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({
enforceMax: true,
ensureDefault: true,
showLimitToast: false,
});
updateHeroSmsPlatformDisplay(
selectedCountries[0]?.label || DEFAULT_HERO_SMS_COUNTRY_LABEL
);
}
async function previewHeroSmsPriceTiers() {
if (!displayHeroSmsPriceTiers) {
return;
}
const selectedCountries = syncHeroSmsFallbackSelectionOrderFromSelect({
enforceMax: true,
ensureDefault: true,
showLimitToast: false,
});
const candidates = selectedCountries.length
? selectedCountries
: [getSelectedHeroSmsCountryOption()];
const maxPriceText = normalizeHeroSmsMaxPriceValue(inputHeroSmsMaxPrice?.value || '');
const maxPrice = maxPriceText ? Number(maxPriceText) : null;
const apiKey = String(inputHeroSmsApiKey?.value || '').trim();
displayHeroSmsPriceTiers.textContent = '查询中...';
if (rowHeroSmsPriceTiers) {
rowHeroSmsPriceTiers.style.display = '';
}
const previews = [];
if (!apiKey) {
displayHeroSmsPriceTiers.textContent = '请先填写接码 API Key,再查询价格';
if (rowHeroSmsPriceTiers) {
rowHeroSmsPriceTiers.style.display = '';
}
return;
}
for (const country of candidates) {
const countryId = normalizeHeroSmsCountryId(country.id);
const countryLabel = normalizeHeroSmsCountryLabel(
country.label || getHeroSmsCountryLabelById(countryId),
`Country #${countryId}`
);
try {
const url = new URL('https://hero-sms.com/stubs/handler_api.php');
url.searchParams.set('action', 'getPrices');
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());
const rawText = await response.text();
let payload = rawText;
try {
payload = rawText ? JSON.parse(rawText) : '';
} catch {
payload = rawText;
}
if (!response.ok) {
previews.push(`${countryLabel}: ${summarizeHeroSmsPreviewError(payload, response.status)}`);
continue;
}
const priceEntries = collectHeroSmsPriceEntriesForPreview(payload, [])
.filter((entry) => Number.isFinite(Number(entry.cost)) && Number(entry.cost) > 0);
const inStockPrices = Array.from(new Set(
priceEntries
.filter((entry) => entry.inStock)
.map((entry) => Math.round(Number(entry.cost) * 10000) / 10000)
)).sort((left, right) => left - right);
const allPrices = Array.from(new Set(
priceEntries.map((entry) => Math.round(Number(entry.cost) * 10000) / 10000)
)).sort((left, right) => left - right);
if (!inStockPrices.length) {
if (allPrices.length) {
const lowestKnown = formatHeroSmsPriceForPreview(allPrices[0]) || String(allPrices[0]);
previews.push(`${countryLabel}: 最低 ${lowestKnown}(库存为 0,当前无可用号源)`);
continue;
}
const reason = summarizeHeroSmsPreviewError(payload, response.status);
previews.push(`${countryLabel}: ${reason || '无可用价格'}`);
continue;
}
const lowest = inStockPrices[0];
const lowestText = formatHeroSmsPriceForPreview(lowest) || String(lowest);
if (Number.isFinite(maxPrice) && maxPrice > 0 && lowest > maxPrice) {
previews.push(`${countryLabel}: 最低 ${lowestText}(高于上限 ${formatHeroSmsPriceForPreview(maxPrice) || maxPrice})`);
} else {
previews.push(`${countryLabel}: 最低 ${lowestText}`);
}
} catch (error) {
previews.push(`${countryLabel}: 查询失败(${normalizeHeroSmsFetchErrorMessage(error)})`);
}
}
displayHeroSmsPriceTiers.textContent = previews.join('\n') || '未获取';
}
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 updatePhoneVerificationSettingsUI() {
const enabled = Boolean(inputPhoneVerificationEnabled?.checked);
const showSettings = enabled && phoneVerificationSectionExpanded;
if (rowPhoneVerificationEnabled) {
rowPhoneVerificationEnabled.style.display = '';
}
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 rowHeroSmsPlatform !== 'undefined' ? rowHeroSmsPlatform : null,
typeof rowHeroSmsCountry !== 'undefined' ? rowHeroSmsCountry : null,
typeof rowHeroSmsCountryFallback !== 'undefined' ? rowHeroSmsCountryFallback : null,
typeof rowHeroSmsAcquirePriority !== 'undefined' ? rowHeroSmsAcquirePriority : null,
typeof rowHeroSmsApiKey !== 'undefined' ? rowHeroSmsApiKey : null,
typeof rowHeroSmsMaxPrice !== 'undefined' ? rowHeroSmsMaxPrice : null,
typeof rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null,
typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null,
typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : 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) {
return;
}
row.style.display = showSettings ? '' : 'none';
});
if (!showSettings && typeof rowHeroSmsPriceTiers !== 'undefined' && rowHeroSmsPriceTiers) {
rowHeroSmsPriceTiers.style.display = 'none';
}
}
function updatePlusModeUI() {
const enabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
? Boolean(inputPlusModeEnabled.checked)
: false;
const paymentMethod = getSelectedPlusPaymentMethod();
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
selectPlusPaymentMethod.value = paymentMethod;
selectPlusPaymentMethod.style.display = enabled ? '' : 'none';
}
[
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
].forEach((row) => {
if (!row) {
return;
}
row.style.display = enabled && paymentMethod === 'paypal' ? '' : 'none';
});
}
function setSettingsCardLocked(locked) {
if (!settingsCard) {
return;
}
settingsCard.classList.toggle('is-locked', locked);
settingsCard.toggleAttribute('inert', locked);
}
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 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();
}
}
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);
const lockedRunCount = getLockedRunCountFromEmailPool();
const shouldSyncAutoRunTotalRuns = currentAutoRun.autoRunning
|| locked
|| paused
|| scheduled;
inputRunCount.disabled = currentAutoRun.autoRunning || lockedRunCount > 0;
btnAutoRun.disabled = currentAutoRun.autoRunning;
btnFetchEmail.disabled = locked
|| isCustomMailProvider()
|| usesCustomEmailPoolGenerator();
inputEmail.disabled = locked;
inputAutoSkipFailures.disabled = scheduled;
if (lockedRunCount > 0) {
inputRunCount.value = String(lockedRunCount);
} else if (shouldSyncAutoRunTotalRuns && currentAutoRun.totalRuns > 0) {
inputRunCount.value = String(currentAutoRun.totalRuns);
}
switch (currentAutoRun.phase) {
case 'scheduled':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `已计划${runLabel}`;
break;
case 'waiting_step':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `等待中${runLabel}`;
break;
case 'waiting_email':
autoContinueBar.style.display = 'flex';
btnAutoRun.innerHTML = `已暂停${runLabel}`;
break;
case 'running':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `运行中${runLabel}`;
break;
case 'retrying':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `重试中${runLabel}`;
break;
case 'waiting_interval':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `等待中${runLabel}`;
break;
default:
autoContinueBar.style.display = 'none';
setDefaultAutoRunButton();
inputEmail.disabled = false;
if (!locked) {
btnFetchEmail.disabled = isCustomMailProvider() || usesCustomEmailPoolGenerator();
}
break;
}
updateAutoDelayInputState();
updateFallbackThreadIntervalInputState();
syncScheduledCountdownTicker();
updateStopButtonState(scheduled || paused || locked || Object.values(getStepStatuses()).some(status => status === 'running'));
updateConfigMenuControls();
renderContributionMode();
}
function initializeManualStepActions() {
document.querySelectorAll('.step-row').forEach((row) => {
if (row.querySelector('.step-actions')) {
return;
}
const step = Number(row.dataset.step);
const statusEl = row.querySelector('.step-status');
if (!statusEl) return;
const actions = document.createElement('div');
actions.className = 'step-actions';
const manualBtn = document.createElement('button');
manualBtn.type = 'button';
manualBtn.className = 'step-manual-btn';
manualBtn.dataset.step = String(step);
manualBtn.title = '跳过此步';
manualBtn.setAttribute('aria-label', `跳过步骤 ${step}`);
manualBtn.innerHTML = '';
manualBtn.addEventListener('click', async (event) => {
event.stopPropagation();
try {
await handleSkipStep(step);
} catch (err) {
showToast(err.message, 'error');
}
});
statusEl.parentNode.replaceChild(actions, statusEl);
actions.appendChild(manualBtn);
actions.appendChild(statusEl);
});
}
function renderStepsList() {
if (!stepsList) return;
stepsList.innerHTML = stepDefinitions.map((step) => `