Fab Helper

Fab Helper 优化版 - 自动领取免费商品,已拥有自动隐藏,后台多标签处理,智能限速处理

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

Advertisement:

// ==UserScript==
// @name         Fab Helper
// @name:zh-CN   Fab Helper
// @name:en      Fab Helper
// @namespace    https://www.fab.com/
// @version      3.5.7-20260709-1133
// @description  Fab Helper 优化版 - 自动领取免费商品,已拥有自动隐藏,后台多标签处理,智能限速处理
// @description:zh-CN  Fab Helper 优化版 - 自动领取免费商品,已拥有自动隐藏,后台多标签处理,智能限速处理
// @description:en  Fab Helper Optimized - Auto-claim free items, auto-hide owned items, background multi-tab processing, smart rate-limit handling
// @author       RunKing
// @match        https://www.fab.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=fab.com
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM_openInTab
// @connect      fab.com
// @connect      www.fab.com
// @run-at       document-start
// ==/UserScript==

(() => {
  var __defProp = Object.defineProperty;
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });

  // src/i18n/en.js
  var en = {
    // 基础UI
    hide: "Hide Done",
    show: "Show Done",
    sync: "Sync State",
    execute: "Start Tasks",
    executing: "Executing...",
    stopExecute: "Stop",
    retry_failed: "Retry Failed",
    added: "Done",
    failed: "Failed",
    todo: "To-Do",
    hidden: "Hidden",
    visible: "Visible",
    clearLog: "Clear Log",
    copyLog: "Copy Log",
    copied: "Copied!",
    tab_dashboard: "Dashboard",
    tab_settings: "Settings",
    tab_debug: "Debug",
    toast_all_tasks_completed: "\u{1F389} All tasks have been completed!",
    toast_reached_bottom: "\u{1F3C1} Reached the bottom of the list, all items scanned!",
    close: "Close",
    // 应用标题和标签
    app_title: "Fab Helper",
    free_label: "Free",
    operation_log: "\u{1F4DD} Operation Log",
    position_indicator: "\u{1F4CD} ",
    // 按钮文本
    clear_all_data: "\u{1F5D1}\uFE0F Clear All Data",
    debug_mode: "Debug Mode",
    page_diagnosis: "Page Diagnosis",
    copy_btn: "Copy",
    clear_btn: "Clear",
    copied_success: "Copied!",
    // 状态文本
    status_history: "Status Cycle History",
    script_startup: "Script Startup",
    normal_period: "Normal Operation",
    rate_limited_period: "Rate Limited",
    current_normal: "Current: Normal",
    current_rate_limited: "Current: Rate Limited",
    no_history: "No history records to display.",
    no_saved_position: "No saved position",
    // 状态历史详细信息
    time_label: "Time",
    info_label: "Info",
    ended_at: "Ended at",
    duration_label: "Duration",
    requests_label: "Requests",
    requests_unit: "times",
    unknown_duration: "Unknown",
    // 日志消息
    log_init: "Assistant is online!",
    log_db_loaded: "Reading archive...",
    log_exec_no_tasks: "To-Do list is empty.",
    log_verify_success: "Verified and added to library!",
    log_verify_fail: "Couldn't add. Will retry later.",
    log_429_error: "Request limit hit! Taking a 15s break...",
    log_no_failed_tasks: "No failed tasks to retry.",
    log_requeuing_tasks: "Re-queuing {0} failed tasks...",
    log_detail_page: "This is a detail or worker page. Halting main script execution.",
    log_copy_failed: "Failed to copy log:",
    log_auto_add_enabled: '"Auto add" is enabled. Will process all tasks in the current "To-Do" queue.',
    log_auto_add_toggle: "Infinite scroll auto add tasks {0}.",
    log_remember_pos_toggle: "Remember waterfall browsing position {0}.",
    log_auto_resume_toggle: "429 auto resume function {0}.",
    log_auto_resume_start: "\u{1F504} 429 auto resume activated! Will refresh page in {0} seconds to attempt recovery...",
    log_auto_resume_detect: "\u{1F504} Detected 429 error, will auto refresh page in {0} seconds to attempt recovery...",
    log_refresh_error: "Error during state synchronization:",
    log_unsettled_cards: "{0} cards are still loading. Will check again shortly.",
    // 调试日志消息
    debug_save_cursor: "Saving new recovery point: {0}",
    debug_prepare_hide: "Preparing to hide {0} cards, will use longer delay...",
    debug_unprocessed_cards: "Detected {0} unprocessed or inconsistent cards, re-executing hide logic",
    debug_new_content_loading: "Detected new content loading, waiting for API requests to complete...",
    debug_process_new_content: "Starting to process newly loaded content...",
    debug_unprocessed_cards_simple: "Detected unprocessed cards, re-executing hide logic",
    debug_hide_completed: "Completed hiding all {0} cards",
    debug_visible_after_hide: "\u{1F441}\uFE0F Actual visible items after hiding: {0}, hidden items: {1}",
    debug_filter_owned: "Filtered out {0} owned items and {1} items already in todo list.",
    debug_api_wait_complete: "API wait completed, starting to process {0} cards...",
    debug_api_stopped: "API activity stopped for {0}ms, continuing to process cards.",
    debug_wait_api_response: "Starting to wait for API response, will process {0} cards after API activity stops...",
    debug_api_wait_in_progress: "API wait process already in progress, adding current {0} cards to wait queue.",
    debug_cached_items: "Cached {0} item data",
    debug_no_cards_to_check: "No cards need to be checked",
    // Fab DOM Refresh 相关
    fab_dom_api_complete: "API query completed, confirmed {0} owned items.",
    fab_dom_checking_status: "Checking status of {0} items...",
    fab_dom_add_to_waitlist: "Added {0} item IDs to wait list, current wait list size: {0}",
    fab_dom_unknown_status: "{0} items have unknown status, waiting for native web requests to update",
    // 状态监控
    status_monitor_all_hidden: "Detected all items hidden in normal state ({0} items)",
    // 空搜索结果
    empty_search_initial: "Page just loaded, might be initial request, not triggering rate limit",
    // 游标相关
    cursor_patched_url: "Patched URL",
    cursor_injecting: "Injecting cursor. Original",
    page_patcher_match: "-> \u2705 MATCH! URL will be patched",
    // 自动刷新相关
    auto_refresh_countdown: "\u23F1\uFE0F Auto refresh countdown: {0} seconds...",
    rate_limit_success_request: "Successful request during rate limit +1, current consecutive: {0}/{1}, source: {2}",
    rate_limit_no_visible_continue: "\u{1F504} No visible items on page and in rate limit state, will continue auto refresh.",
    rate_limit_no_visible_suggest: "\u{1F504} In rate limit state with no visible items, suggest refreshing page",
    status_check_summary: "\u{1F4CA} Status check - Actually visible: {0}, Total cards: {1}, Hidden items: {2}",
    refresh_plan_exists: "Refresh plan already in progress, not scheduling new refresh (429 auto recovery)",
    page_content_rate_limit_detected: "[Page Content Detection] Detected page showing rate limit error message!",
    last_moment_check_cancelled: "\u26A0\uFE0F Last moment check: refresh conditions not met, auto refresh cancelled.",
    refresh_cancelled_visible_items: "\u23F9\uFE0F Detected {0} visible items on page before refresh, auto refresh cancelled.",
    log_all_hidden_rate_limited: "Rate limited with no visible items; scheduling one recovery refresh.",
    log_refresh_cancelled_tasks: "Detected {0} to-do tasks and {1} active workers; auto refresh cancelled.",
    log_refresh_cancelled_visible: "Detected {0} visible items before refresh; auto refresh cancelled.",
    log_refreshing: "No visible items and still rate limited; refreshing page.",
    rate_limit_no_visible_reason: "rate limited with no visible items",
    // 限速检测来源
    rate_limit_source_page_content: "Page Content Detection",
    rate_limit_source_global_call: "Global Call",
    // 日志标签
    log_tag_auto_add: "Auto Add",
    // 自动添加相关消息
    auto_add_api_timeout: "API wait timeout, waited {0}ms, will continue processing cards.",
    auto_add_api_error: "Error while waiting for API: {0}",
    auto_add_new_tasks: "Added {0} new tasks to queue.",
    auto_scroll_attempt: "[Auto Scroll] Queue empty, attempting to scroll down to load more... (Attempt {0}/{1})",
    auto_scroll_success: "[Auto Scroll] Successfully loaded and identified {0} new tasks, continuing...",
    auto_scroll_cards_loaded: "[Auto Scroll] Detected {0} new cards, continuing hide/scan and scroll...",
    auto_scroll_reached_bottom: "[Auto Scroll] Reached page bottom, stopping scroll.",
    auto_scroll_no_new_items: "[Auto Scroll] No new eligible items found after {0} consecutive scrolls, stopping auto scroll.",
    auto_scroll_waiting: "[Auto Scroll] No new eligible items found, waiting for next scroll attempt...",
    // HTTP状态检测
    http_status_check_performance_api: "Using Performance API check, no longer sending HEAD requests",
    // 页面状态检测
    page_status_hidden_no_visible: "\u{1F441}\uFE0F Detected {0} hidden items on page, but no visible items",
    page_status_suggest_refresh: "\u{1F504} Detected {0} hidden items on page, but no visible items, suggest refreshing page",
    // 限速状态相关
    rate_limit_already_active: "Already in rate limit state, source: {0}, ignoring new rate limit trigger: {1}",
    xhr_detected_429: "[XHR] Detected 429 status code: {0}",
    // 状态历史消息
    history_cleared_new_session: "History cleared, new session started",
    status_history_cleared: "Status history cleared.",
    duplicate_normal_status_detected: "Detected duplicate normal status record, source: {0}",
    execution_status_changed: "Detected execution status change: {0}",
    status_executing: "Executing",
    status_stopped: "Stopped",
    // 状态历史UI文本
    status_duration_label: "Duration: ",
    status_requests_label: "Requests: ",
    status_ended_at_label: "Ended at: ",
    status_started_at_label: "Started at: ",
    status_ongoing_label: "Ongoing: ",
    status_unknown_time: "Unknown time",
    status_unknown_duration: "Unknown",
    // 启动时状态检测
    startup_rate_limited: "Script started in rate limited state. Rate limit has lasted at least {0}s, source: {1}",
    status_unknown_source: "Unknown",
    // 请求成功来源
    request_source_search_response: "Search Response Success",
    request_source_xhr_search: "XHR Search Success",
    request_source_xhr_item: "XHR Item Request",
    consecutive_success_exit: "Consecutive {0} successful requests ({1})",
    search_response_parse_failed: "Search response parsing failed: {0}",
    // 缓存清理和Fab DOM相关
    cache_cleanup_complete: "[Cache] Cleanup complete, current cache size: items={0}, owned status={1}, prices={2}",
    fab_dom_no_new_owned: "[Fab DOM Refresh] API query completed, no new owned items found.",
    // 状态报告UI标签
    status_time_label: "Time",
    status_info_label: "Info",
    // 隐性限速检测和API监控
    implicit_rate_limit_detection: "[Implicit Rate Limit Detection]",
    scroll_api_monitoring: "[Scroll API Monitoring]",
    task_execution_time: "Task execution time: {0} seconds",
    detected_rate_limit_error: "Detected rate limit error info: {0}",
    detected_possible_rate_limit_empty: "Detected possible rate limit situation (empty result): {0}",
    detected_possible_rate_limit_scroll: "Detected possible rate limit situation: no card count increase after {0} consecutive scrolls.",
    detected_api_429_status: "Detected API request status code 429: {0}",
    detected_api_rate_limit_content: "Detected API response content contains rate limit info: {0}",
    // 限速来源标识
    source_implicit_rate_limit: "Implicit Rate Limit Detection",
    source_scroll_api_monitoring: "Scroll API Monitoring",
    // 设置项
    setting_auto_refresh: "Auto refresh when no items visible",
    setting_auto_add_scroll: "Auto add tasks on infinite scroll",
    setting_remember_position: "Remember waterfall browsing position",
    setting_auto_resume_429: "Auto resume after 429 errors",
    setting_hide_discounted: "Hide discounted paid items",
    setting_hide_paid: "Hide all paid items",
    setting_block_large_resources: "Disable images and media",
    setting_debug_tooltip: "Enable detailed logging for troubleshooting",
    // 状态文本
    status_enabled: "enabled",
    status_disabled: "disabled",
    // 确认对话框
    confirm_clear_data: "Are you sure you want to clear all locally stored script data (completed, failed, to-do lists)? This action cannot be undone!",
    confirm_open_failed: "Are you sure you want to open {0} failed items in new tabs?",
    confirm_clear_history: "Are you sure you want to clear all status history records?",
    // 错误提示
    error_api_refresh: "API refresh failed. Please check console for error details and confirm you are logged in.",
    // 工具提示
    tooltip_open_failed: "Click to open all failed items",
    tooltip_executing_progress: "Executing: {0}/{1} ({2}%)",
    tooltip_executing: "Executing",
    tooltip_start_tasks: "Click to start executing tasks",
    // 其他
    goto_page_label: "Page:",
    goto_page_btn: "Go",
    page_reset: "Page: 1",
    untitled: "Untitled",
    cursor_mode: "Cursor Mode",
    using_native_requests: "Using native web requests, waiting: {0}",
    worker_closed: "Worker tab closed before completion",
    worker_captcha: "CAPTCHA / human verification detected (manual action needed)",
    log_keepalive_on: "Background keep-alive started (Worker heartbeat + freeze guard)",
    log_keepalive_failed: "Failed to start keep-alive: {0}",
    log_auto_retry: "\u21BB Auto-retry ({0}/{1}): {2}",
    keepalive_label: "KeepAlive",
    keepalive_dead: "\u26A0\uFE0Foff (CSP?)",
    // 脚本启动和初始化
    log_script_starting: "Script starting...",
    log_network_filter_deprecated: "NetworkFilter module deprecated, functionality handled by PagePatcher.",
    // 限速状态检查
    log_rate_limit_check_active: "Rate limit check already in progress, skipping",
    log_rate_limit_check_start: "Starting rate limit status check...",
    log_page_content_rate_limit: "Page content contains rate limit info, confirming still rate limited",
    log_use_performance_api: "Using Performance API to check recent requests, no longer sending active requests",
    log_detected_429_in_10s: "Detected 429 status in recent 10s, judging as rate limited",
    log_detected_success_in_10s: "Detected successful request in recent 10s, judging as normal",
    log_insufficient_info_status: "Insufficient info to judge rate limit status, maintaining current state",
    log_rate_limit_check_failed: "Rate limit status check failed: {0}",
    // 游标和位置
    log_cursor_initialized_with: "[Cursor] Initialized. Loaded saved cursor: {0}...",
    log_cursor_initialized_empty: "[Cursor] Initialized. No saved cursor found.",
    log_cursor_restore_failed: "[Cursor] Failed to restore cursor state:",
    log_cursor_interceptors_applied: "[Cursor] Network interceptors applied.",
    log_cursor_skip_known_position: "[Cursor] Skipping known position save: {0}",
    log_cursor_skip_backtrack: "[Cursor] Skipping backtrack position: {0} (current: {1}), sort: {2}",
    log_cursor_save_error: "[Cursor] Error saving cursor:",
    log_url_sort_changed: 'Detected URL sort parameter change from "{0}" to "{1}"',
    log_sort_changed_position_cleared: "Cleared saved position due to sort method change",
    log_sort_check_error: "Error checking URL sort parameter: {0}",
    log_position_cleared: "Cleared saved browsing position.",
    clear_position_tooltip: "Reset position and refresh",
    confirm_reset_position: "Are you sure you want to clear the saved browsing position and refresh the page?",
    no_position_to_reset: "No saved browsing position.",
    log_sort_ascending: "Ascending",
    log_sort_descending: "Descending",
    // XHR/Fetch 限速检测
    log_xhr_rate_limit_detect: "[XHR Rate Limit] Detected rate limit, response: {0}",
    log_list_end_normal: "[List End] Reached end of list, normal situation: {0}...",
    log_empty_search_with_filters: "[Empty Search] Empty result but has filters, may be normal: {0}...",
    log_empty_search_already_limited: "[Empty Search] Already rate limited, not triggering again: {0}...",
    log_empty_search_page_loading: "[Empty Search] Page still loading, may be initial request: {0}...",
    log_debounce_intercept: "[Debounce] \u{1F6A6} Intercepted scroll request. Applying {0}ms delay...",
    log_debounce_discard: "[Debounce] \u{1F5D1}\uFE0F Discarded previous pending request.",
    log_debounce_sending: "[Debounce] \u25B6\uFE0F Sending latest scroll request: {0}",
    log_fetch_detected_429: "[Fetch] Detected 429 status code: {0}",
    log_fetch_rate_limit_detect: "[Fetch Rate Limit] Detected rate limit, response: {0}...",
    log_fetch_list_end: "[Fetch List End] Reached end, normal: {0}...",
    log_fetch_empty_with_filters: "[Fetch Empty] Empty with filters, may be normal: {0}...",
    log_fetch_empty_already_limited: "[Fetch Empty] Already limited, not triggering: {0}...",
    log_fetch_empty_page_loading: "[Fetch Empty] Page loading, may be initial: {0}...",
    log_fetch_implicit_rate_limit: "[Fetch Implicit] Possible rate limit (empty): {0}...",
    log_json_parse_error: "JSON parse error: {0}",
    log_response_length: "Response length: {0}, first 100 chars: {1}",
    log_handling_rate_limit_error: "Error handling rate limit: {0}",
    // 执行控制
    log_execution_stopped_manually: "Execution manually stopped by user.",
    log_todo_cleared_scan: "To-do list cleared. Will scan and add only visible items.",
    log_scanning_loaded_items: "Scanning loaded items...",
    log_executor_running_queued: "Executor running, new tasks queued for processing.",
    log_todo_empty_scanning: "To-do list empty, scanning current page...",
    log_request_no_results_not_counted: "Request successful but no valid results, not counting. Source: {0}",
    log_not_rate_limited_ignore_exit: "Not rate limited, ignoring exit request: {0}",
    log_found_todo_auto_resume: "Found {0} to-do tasks, auto-resuming execution...",
    log_dispatching_wait: "Dispatching tasks, please wait...",
    log_rate_limited_continue_todo: "In rate limited state, but will continue executing to-do tasks...",
    log_detected_todo_no_workers: "Detected to-do tasks but no active workers, attempting retry...",
    // 数据库和同步
    log_db_sync_cleared_failed: "[Fab DB Sync] Cleared {0} manually completed items from failed list.",
    log_no_unowned_in_batch: "No unowned items found in this batch.",
    log_no_truly_free_after_verify: "Found unowned items, but none truly free after price verification.",
    log_429_scan_paused: "Detected 429 error, requesting too frequently. Will pause scanning.",
    // 工作线程
    log_worker_tabs_cleared: "Cleared all worker tab states.",
    log_worker_task_cleared_closing: "Task data cleared, worker tab will close.",
    log_worker_instance_cooperate: "Detected active instance [{0}], worker tab will cooperate.",
    log_other_instance_report_ignore: "Received report from other instance [{0}], current [{1}] will ignore.",
    // 失败和重试
    log_failed_list_empty: "Failed list empty, no action needed.",
    log_cleared_from_failed: "Cleared {0} actually-owned items from the failed list.",
    log_failed_check_start: "Checking status of failed items (verifying local records and server status)...",
    log_failed_check_local: "[Local Check] Found {0} items already marked done locally, removing from failed list...",
    log_failed_check_network: "[Server Check] Querying server for acquisition status of remaining {0} items...",
    log_failed_check_network_confirmed: "[Server Check] Confirmed {0} items actually acquired, updating database...",
    log_failed_check_network_none: "[Server Check] No other acquired items detected.",
    log_failed_check_complete: "\u2728 Check complete! Removed {0} successfully acquired items from the failed list.",
    log_failed_check_all_done: "\u{1F389} All failed items are verified as acquired! Failed list is now empty.",
    confirm_open_failed_remaining: "Status check complete. {0} items are still not in library.\nDo you want to open these {0} failed items in new tabs for manual processing?",
    alert_failed_all_done: "All failed items are verified as acquired. No manual action needed!",
    // Wake/visibility recovery
    log_wake_recovery: "\u{1F514} Tab re-activated (lock screen / tab switch recovery). Checking execution state...",
    log_wake_cleanup_stale: "\u{1F9F9} Cleaned up {0} stale workers frozen by lock screen. Restarting execution.",
    log_wake_restarting: "\u25B6\uFE0F Re-activating execution ({0} pending tasks).",
    // 调试模式
    log_debug_mode_toggled: "Debug mode {0}. {1}",
    log_debug_mode_detail_info: "Will display detailed log information",
    log_no_history_to_copy: "No history to copy.",
    // 启动和恢复
    log_execution_state_inconsistent: "Execution state inconsistent, restoring from storage: {0}",
    log_invalid_worker_report: "Received invalid worker report. Missing workerId or task.",
    log_all_tasks_completed: "All tasks completed.",
    log_all_tasks_completed_rate_limited: "All tasks completed and rate limited, will refresh to recover...",
    log_recovery_probe_failed: "Recovery probe failed. Still rate limited, will continue refresh...",
    // 实例管理
    log_not_active_instance: "Current instance not active, not executing tasks.",
    log_no_active_instance_activating: "No active instance detected, instance [{0}] activated.",
    log_inactive_instance_taking_over: "Previous instance [{0}] inactive, taking over.",
    log_is_search_page_activated: "This is search page, instance [{0}] activated.",
    // 可见性和刷新
    log_no_visible_items_todo_workers: "Rate limited with {0} to-do and {1} workers, not auto-refreshing.",
    log_visible_items_detected_skipping: "\u23F9\uFE0F Detected {0} visible items, not refreshing to avoid interruption.",
    log_please_complete_tasks_first: "Please complete or cancel these tasks before refreshing.",
    log_display_mode_switched: "\u{1F441}\uFE0F Display mode switched, current page has {0} visible items",
    position_label: "Location",
    log_entering_rate_limit_from: "\u{1F6A8} Entering RATE LIMIT state from [{0}]! Normal period lasted {1}s with {2} requests.",
    log_entering_rate_limit_from_v2: "\u{1F6A8} RATE LIMIT DETECTED from [{0}]! Normal operation lasted {1}s with {2} successful search requests.",
    rate_limit_recovery_success: "\u2705 Rate limit appears to be lifted from [{0}]. The 429 period lasted {1}s.",
    fab_dom_refresh_complete: "[Fab DOM Refresh] Complete. Updated {0} visible card states.",
    auto_refresh_disabled_rate_limit: "\u26A0\uFE0F In rate limit state, auto refresh is disabled. Please manually refresh the page if needed.",
    // 页面诊断
    log_diagnosis_complete: "Page diagnosis complete, check console output",
    log_diagnosis_failed: "Page diagnosis failed: {0}",
    // Auto resume
    log_auto_resume_page_loading: "[Auto-Resume] Page loaded in rate limited state. Running recovery probe...",
    log_recovery_probe_success: "\u2705 Recovery probe succeeded! Rate limit lifted, continuing normal operations.",
    log_tasks_still_running: "Still have {0} tasks running, waiting for them to finish before refresh...",
    log_todo_tasks_waiting: "{0} to-do tasks waiting to execute, will try to continue execution...",
    countdown_refresh_source: "Recovery probe failed",
    failed_list_empty: "Failed list is empty, no action needed.",
    opening_failed_items: "Opening {0} failed items...",
    // 自动滚动挂起恢复
    log_auto_scroll_stuck_refresh: "\u26A0\uFE0F Auto-scroll failed to find new items consecutively while executing tasks, triggering auto page refresh to recover...",
    log_auto_scroll_stuck_96_refresh: "\u26A0\uFE0F Detected special scenario ({0} items hidden and no visible items, pagination stuck), saving temporary cursor, refreshing page to recover...",
    log_auto_scroll_stuck_recovery_failed: "Already tried to recover using the current cursor but still no new items. Assuming end of list has been reached, stopping refresh.",
    // 账号验证
    auth_error: "Session expired: CSRF token not found, please log in again",
    auth_error_alert: "Session expired: Please log in again before using the script",
    auth_session_invalid: "Login session is invalid. Please log in again before starting a run.",
    auth_worker_aborted: "Worker tab detected no active login; task processing aborted.",
    auth_scan_blocked: "Not logged in (or session expired). Scan skipped to avoid treating every paid item as free."
  };

  // src/i18n/zh.js
  var zh = {
    // 基础UI
    hide: "\u9690\u85CF\u5DF2\u5F97",
    show: "\u663E\u793A\u5DF2\u5F97",
    sync: "\u540C\u6B65\u72B6\u6001",
    execute: "\u4E00\u952E\u5F00\u5237",
    executing: "\u6267\u884C\u4E2D...",
    stopExecute: "\u505C\u6B62",
    retry_failed: "\u91CD\u8BD5\u5931\u8D25",
    hideDiscounted: "\u9690\u85CF\u6253\u6298",
    showDiscounted: "\u663E\u793A\u6253\u6298",
    hidePaid: "\u9690\u85CF\u4ED8\u8D39",
    showPaid: "\u663E\u793A\u4ED8\u8D39",
    added: "\u5DF2\u5165\u5E93",
    failed: "\u5931\u8D25",
    todo: "\u5F85\u529E",
    hidden: "\u5DF2\u9690\u85CF",
    visible: "\u53EF\u89C1",
    clearLog: "\u6E05\u7A7A\u65E5\u5FD7",
    copyLog: "\u590D\u5236\u65E5\u5FD7",
    copied: "\u5DF2\u590D\u5236!",
    tab_dashboard: "\u4EEA\u8868\u76D8",
    tab_settings: "\u8BBE\u5B9A",
    tab_debug: "\u8C03\u8BD5",
    toast_all_tasks_completed: "\u{1F389} \u6240\u6709\u4EFB\u52A1\u5DF2\u5904\u7406\u5B8C\u6210\uFF01",
    toast_reached_bottom: "\u{1F3C1} \u5DF2\u5230\u8FBE\u5546\u54C1\u5217\u8868\u5E95\u90E8\uFF0C\u5168\u90E8\u5546\u54C1\u626B\u63CF\u5B8C\u6BD5\uFF01",
    close: "\u5173\u95ED",
    // 应用标题和标签
    app_title: "Fab Helper",
    free_label: "\u514D\u8D39",
    operation_log: "\u{1F4DD} \u64CD\u4F5C\u65E5\u5FD7",
    position_indicator: "\u{1F4CD} ",
    // 按钮文本
    clear_all_data: "\u{1F5D1}\uFE0F \u6E05\u7A7A\u6240\u6709\u5B58\u6863",
    debug_mode: "\u8C03\u8BD5\u6A21\u5F0F",
    page_diagnosis: "\u9875\u9762\u8BCA\u65AD",
    copy_btn: "\u590D\u5236",
    clear_btn: "\u6E05\u7A7A",
    copied_success: "\u5DF2\u590D\u5236!",
    // 状态文本
    status_history: "\u72B6\u6001\u5468\u671F\u5386\u53F2\u8BB0\u5F55",
    script_startup: "\u811A\u672C\u542F\u52A8",
    normal_period: "\u6B63\u5E38\u8FD0\u884C\u671F",
    rate_limited_period: "\u9650\u901F\u671F",
    current_normal: "\u5F53\u524D: \u6B63\u5E38\u8FD0\u884C",
    current_rate_limited: "\u5F53\u524D: \u9650\u901F\u4E2D",
    no_history: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u5386\u53F2\u8BB0\u5F55\u3002",
    no_saved_position: "\u65E0\u4FDD\u5B58\u4F4D\u7F6E",
    // 状态历史详细信息
    time_label: "\u65F6\u95F4",
    info_label: "\u4FE1\u606F",
    ended_at: "\u7ED3\u675F\u4E8E",
    duration_label: "\u6301\u7EED",
    requests_label: "\u8BF7\u6C42",
    requests_unit: "\u6B21",
    unknown_duration: "\u672A\u77E5",
    // 日志消息
    log_init: "\u52A9\u624B\u5DF2\u4E0A\u7EBF\uFF01",
    log_db_loaded: "\u6B63\u5728\u8BFB\u53D6\u5B58\u6863...",
    log_exec_no_tasks: '"\u5F85\u529E"\u6E05\u5355\u662F\u7A7A\u7684\u3002',
    log_verify_success: "\u641E\u5B9A\uFF01\u5DF2\u6210\u529F\u5165\u5E93\u3002",
    log_verify_fail: "\u54CE\u5440\uFF0C\u8FD9\u4E2A\u6CA1\u52A0\u4E0A\u3002\u7A0D\u540E\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF01",
    log_429_error: "\u8BF7\u6C42\u592A\u5FEB\u88AB\u670D\u52A1\u5668\u9650\u901F\u4E86\uFF01\u4F11\u606F15\u79D2\u540E\u81EA\u52A8\u91CD\u8BD5...",
    log_no_failed_tasks: "\u6CA1\u6709\u5931\u8D25\u7684\u4EFB\u52A1\u9700\u8981\u91CD\u8BD5\u3002",
    log_requeuing_tasks: "\u6B63\u5728\u91CD\u65B0\u6392\u961F {0} \u4E2A\u5931\u8D25\u4EFB\u52A1...",
    log_detail_page: "\u8FD9\u662F\u8BE6\u60C5\u9875\u6216\u5DE5\u4F5C\u6807\u7B7E\u9875\u3002\u505C\u6B62\u4E3B\u811A\u672C\u6267\u884C\u3002",
    log_copy_failed: "\u590D\u5236\u65E5\u5FD7\u5931\u8D25:",
    log_auto_add_enabled: '"\u81EA\u52A8\u6DFB\u52A0"\u5DF2\u5F00\u542F\u3002\u5C06\u76F4\u63A5\u5904\u7406\u5F53\u524D"\u5F85\u529E"\u961F\u5217\u4E2D\u7684\u6240\u6709\u4EFB\u52A1\u3002',
    log_auto_add_toggle: "\u65E0\u9650\u6EDA\u52A8\u81EA\u52A8\u6DFB\u52A0\u4EFB\u52A1\u5DF2{0}\u3002",
    log_remember_pos_toggle: "\u8BB0\u4F4F\u7011\u5E03\u6D41\u6D4F\u89C8\u4F4D\u7F6E\u529F\u80FD\u5DF2{0}\u3002",
    log_auto_resume_toggle: "429\u540E\u81EA\u52A8\u6062\u590D\u529F\u80FD\u5DF2{0}\u3002",
    log_auto_resume_start: "\u{1F504} 429\u81EA\u52A8\u6062\u590D\u542F\u52A8\uFF01\u5C06\u5728{0}\u79D2\u540E\u5237\u65B0\u9875\u9762\u5C1D\u8BD5\u6062\u590D...",
    log_auto_resume_detect: "\u{1F504} \u68C0\u6D4B\u5230429\u9519\u8BEF\uFF0C\u5C06\u5728{0}\u79D2\u540E\u81EA\u52A8\u5237\u65B0\u9875\u9762\u5C1D\u8BD5\u6062\u590D...",
    log_refresh_error: "\u72B6\u6001\u540C\u6B65\u8FC7\u7A0B\u4E2D\u51FA\u9519:",
    log_unsettled_cards: "\u8FD8\u6709 {0} \u5F20\u5361\u7247\u6B63\u5728\u52A0\u8F7D\uFF0C\u7A0D\u540E\u518D\u68C0\u67E5\u3002",
    // 调试日志消息
    debug_save_cursor: "\u4FDD\u5B58\u65B0\u7684\u6062\u590D\u70B9: {0}",
    debug_prepare_hide: "\u51C6\u5907\u9690\u85CF {0} \u5F20\u5361\u7247\uFF0C\u5C06\u4F7F\u7528\u66F4\u957F\u7684\u5EF6\u8FDF...",
    debug_unprocessed_cards: "\u68C0\u6D4B\u5230 {0} \u4E2A\u672A\u5904\u7406\u6216\u72B6\u6001\u4E0D\u4E00\u81F4\u7684\u5361\u7247\uFF0C\u91CD\u65B0\u6267\u884C\u9690\u85CF\u903B\u8F91",
    debug_new_content_loading: "\u68C0\u6D4B\u5230\u65B0\u5185\u5BB9\u52A0\u8F7D\uFF0C\u7B49\u5F85API\u8BF7\u6C42\u5B8C\u6210...",
    debug_process_new_content: "\u5F00\u59CB\u5904\u7406\u65B0\u52A0\u8F7D\u7684\u5185\u5BB9...",
    debug_unprocessed_cards_simple: "\u68C0\u6D4B\u5230\u672A\u5904\u7406\u7684\u5361\u7247\uFF0C\u91CD\u65B0\u6267\u884C\u9690\u85CF\u903B\u8F91",
    debug_hide_completed: "\u5DF2\u5B8C\u6210\u6240\u6709 {0} \u5F20\u5361\u7247\u7684\u9690\u85CF",
    debug_visible_after_hide: "\u{1F441}\uFE0F \u9690\u85CF\u540E\u5B9E\u9645\u53EF\u89C1\u5546\u54C1\u6570: {0}\uFF0C\u9690\u85CF\u5546\u54C1\u6570: {1}",
    debug_filter_owned: "\u8FC7\u6EE4\u6389 {0} \u4E2A\u5DF2\u5165\u5E93\u5546\u54C1\u548C {1} \u4E2A\u5DF2\u5728\u5F85\u529E\u5217\u8868\u4E2D\u7684\u5546\u54C1\u3002",
    debug_api_wait_complete: "API\u7B49\u5F85\u5B8C\u6210\uFF0C\u5F00\u59CB\u5904\u7406 {0} \u5F20\u5361\u7247...",
    debug_api_stopped: "API\u6D3B\u52A8\u5DF2\u505C\u6B62 {0}ms\uFF0C\u7EE7\u7EED\u5904\u7406\u5361\u7247\u3002",
    debug_wait_api_response: "\u5F00\u59CB\u7B49\u5F85API\u54CD\u5E94\uFF0C\u5C06\u5728API\u6D3B\u52A8\u505C\u6B62\u540E\u5904\u7406 {0} \u5F20\u5361\u7247...",
    debug_api_wait_in_progress: "\u5DF2\u6709API\u7B49\u5F85\u8FC7\u7A0B\u5728\u8FDB\u884C\uFF0C\u5C06\u5F53\u524D {0} \u5F20\u5361\u7247\u52A0\u5165\u7B49\u5F85\u961F\u5217\u3002",
    debug_cached_items: "\u5DF2\u7F13\u5B58 {0} \u4E2A\u5546\u54C1\u6570\u636E",
    debug_no_cards_to_check: "\u6CA1\u6709\u9700\u8981\u68C0\u67E5\u7684\u5361\u7247",
    // Fab DOM Refresh 相关
    fab_dom_api_complete: "API\u67E5\u8BE2\u5B8C\u6210\uFF0C\u5171\u786E\u8BA4 {0} \u4E2A\u5DF2\u62E5\u6709\u7684\u9879\u76EE\u3002",
    fab_dom_checking_status: "\u6B63\u5728\u68C0\u67E5 {0} \u4E2A\u9879\u76EE\u7684\u72B6\u6001...",
    fab_dom_add_to_waitlist: "\u6DFB\u52A0 {0} \u4E2A\u5546\u54C1ID\u5230\u7B49\u5F85\u5217\u8868\uFF0C\u5F53\u524D\u7B49\u5F85\u5217\u8868\u5927\u5C0F: {0}",
    fab_dom_unknown_status: "\u6709 {0} \u4E2A\u5546\u54C1\u72B6\u6001\u672A\u77E5\uFF0C\u7B49\u5F85\u7F51\u9875\u539F\u751F\u8BF7\u6C42\u66F4\u65B0",
    // 状态监控
    status_monitor_all_hidden: "\u68C0\u6D4B\u5230\u6B63\u5E38\u72B6\u6001\u4E0B\u6240\u6709\u5546\u54C1\u90FD\u88AB\u9690\u85CF ({0}\u4E2A)",
    // 空搜索结果
    empty_search_initial: "\u9875\u9762\u521A\u521A\u52A0\u8F7D\uFF0C\u53EF\u80FD\u662F\u521D\u59CB\u8BF7\u6C42\uFF0C\u4E0D\u89E6\u53D1\u9650\u901F",
    // 游标相关
    cursor_patched_url: "Patched URL",
    cursor_injecting: "Injecting cursor. Original",
    page_patcher_match: "-> \u2705 MATCH! URL will be patched",
    // 自动刷新相关
    auto_refresh_countdown: "\u23F1\uFE0F \u81EA\u52A8\u5237\u65B0\u5012\u8BA1\u65F6: {0} \u79D2...",
    rate_limit_success_request: "\u9650\u901F\u72B6\u6001\u4E0B\u6210\u529F\u8BF7\u6C42 +1\uFF0C\u5F53\u524D\u8FDE\u7EED\u6210\u529F: {0}/{1}\uFF0C\u6765\u6E90: {2}",
    rate_limit_no_visible_continue: "\u{1F504} \u9875\u9762\u4E0A\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\u4E14\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u5C06\u7EE7\u7EED\u81EA\u52A8\u5237\u65B0\u3002",
    rate_limit_no_visible_suggest: "\u{1F504} \u5904\u4E8E\u9650\u901F\u72B6\u6001\u4E14\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\uFF0C\u5EFA\u8BAE\u5237\u65B0\u9875\u9762",
    status_check_summary: "\u{1F4CA} \u72B6\u6001\u68C0\u67E5 - \u5B9E\u9645\u53EF\u89C1: {0}, \u603B\u5361\u7247: {1}, \u9690\u85CF\u5546\u54C1\u6570: {2}",
    refresh_plan_exists: "\u5DF2\u6709\u5237\u65B0\u8BA1\u5212\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u4E0D\u518D\u5B89\u6392\u65B0\u7684\u5237\u65B0 (429\u81EA\u52A8\u6062\u590D)",
    page_content_rate_limit_detected: "[\u9875\u9762\u5185\u5BB9\u68C0\u6D4B] \u68C0\u6D4B\u5230\u9875\u9762\u663E\u793A\u9650\u901F\u9519\u8BEF\u4FE1\u606F\uFF01",
    last_moment_check_cancelled: "\u26A0\uFE0F \u6700\u540E\u4E00\u523B\u68C0\u67E5\uFF1A\u5237\u65B0\u6761\u4EF6\u4E0D\u6EE1\u8DB3\uFF0C\u81EA\u52A8\u5237\u65B0\u5DF2\u53D6\u6D88\u3002",
    refresh_cancelled_visible_items: "\u23F9\uFE0F \u5237\u65B0\u524D\u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 {0} \u4E2A\u53EF\u89C1\u5546\u54C1\uFF0C\u5DF2\u53D6\u6D88\u81EA\u52A8\u5237\u65B0\u3002",
    log_all_hidden_rate_limited: "\u9650\u901F\u72B6\u6001\u4E0B\u5F53\u524D\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\uFF0C\u51C6\u5907\u5B89\u6392\u4E00\u6B21\u6062\u590D\u5237\u65B0\u3002",
    log_refresh_cancelled_tasks: "\u68C0\u6D4B\u5230\u6709 {0} \u4E2A\u5F85\u529E\u4EFB\u52A1\u548C {1} \u4E2A\u6D3B\u52A8\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u5DF2\u53D6\u6D88\u81EA\u52A8\u5237\u65B0\u3002",
    log_refresh_cancelled_visible: "\u5237\u65B0\u524D\u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 {0} \u4E2A\u53EF\u89C1\u5546\u54C1\uFF0C\u5DF2\u53D6\u6D88\u81EA\u52A8\u5237\u65B0\u3002",
    log_refreshing: "\u9875\u9762\u4E0A\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\u4E14\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u6B63\u5728\u5237\u65B0\u9875\u9762\u3002",
    rate_limit_no_visible_reason: "\u9650\u901F\u72B6\u6001\u65E0\u53EF\u89C1\u5546\u54C1",
    // 限速检测来源
    rate_limit_source_page_content: "\u9875\u9762\u5185\u5BB9\u68C0\u6D4B",
    rate_limit_source_global_call: "\u5168\u5C40\u8C03\u7528",
    // 日志标签
    log_tag_auto_add: "\u81EA\u52A8\u6DFB\u52A0",
    // 自动添加相关消息
    auto_add_api_timeout: "API\u7B49\u5F85\u8D85\u65F6\uFF0C\u5DF2\u7B49\u5F85 {0}ms\uFF0C\u5C06\u7EE7\u7EED\u5904\u7406\u5361\u7247\u3002",
    auto_add_api_error: "\u7B49\u5F85API\u65F6\u51FA\u9519: {0}",
    auto_add_new_tasks: "\u65B0\u589E {0} \u4E2A\u4EFB\u52A1\u5230\u961F\u5217\u3002",
    auto_scroll_attempt: "[\u81EA\u52A8\u6EDA\u52A8] \u961F\u5217\u5DF2\u7A7A\uFF0C\u5C1D\u8BD5\u5411\u4E0B\u6EDA\u52A8\u52A0\u8F7D\u66F4\u591A\u5546\u54C1... (\u5C1D\u8BD5 {0}/{1})",
    auto_scroll_success: "[\u81EA\u52A8\u6EDA\u52A8] \u6210\u529F\u52A0\u8F7D\u5E76\u8BC6\u522B\u5230 {0} \u4E2A\u65B0\u4EFB\u52A1\uFF0C\u7EE7\u7EED\u6267\u884C...",
    auto_scroll_cards_loaded: "[\u81EA\u52A8\u6EDA\u52A8] \u68C0\u6D4B\u5230 {0} \u5F20\u65B0\u5361\u7247\uFF0C\u7EE7\u7EED\u9690\u85CF/\u626B\u63CF\u5E76\u6EDA\u52A8...",
    auto_scroll_reached_bottom: "[\u81EA\u52A8\u6EDA\u52A8] \u5DF2\u5230\u8FBE\u9875\u9762\u5E95\u90E8\uFF0C\u505C\u6B62\u6EDA\u52A8\u3002",
    auto_scroll_no_new_items: "[\u81EA\u52A8\u6EDA\u52A8] \u8FDE\u7EED {0} \u6B21\u6EDA\u52A8\u5747\u672A\u53D1\u73B0\u7B26\u5408\u6761\u4EF6\u7684\u65B0\u5546\u54C1\uFF0C\u505C\u6B62\u81EA\u52A8\u6EDA\u52A8\u3002",
    auto_scroll_waiting: "[\u81EA\u52A8\u6EDA\u52A8] \u672A\u53D1\u73B0\u7B26\u5408\u6761\u4EF6\u7684\u65B0\u5546\u54C1\uFF0C\u7B49\u5F85\u4E0B\u4E00\u6B21\u6EDA\u52A8\u5C1D\u8BD5...",
    // HTTP状态检测
    http_status_check_performance_api: "\u4F7F\u7528Performance API\u68C0\u67E5\uFF0C\u4E0D\u518D\u53D1\u9001HEAD\u8BF7\u6C42",
    // 页面状态检测
    page_status_hidden_no_visible: "\u{1F441}\uFE0F \u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 {0} \u4E2A\u9690\u85CF\u5546\u54C1\uFF0C\u4F46\u6CA1\u6709\u53EF\u89C1\u5546\u54C1",
    page_status_suggest_refresh: "\u{1F504} \u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 {0} \u4E2A\u9690\u85CF\u5546\u54C1\uFF0C\u4F46\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\uFF0C\u5EFA\u8BAE\u5237\u65B0\u9875\u9762",
    // 限速状态相关
    rate_limit_already_active: "\u5DF2\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u6765\u6E90: {0}\uFF0C\u5FFD\u7565\u65B0\u7684\u9650\u901F\u89E6\u53D1: {1}",
    xhr_detected_429: "[XHR] \u68C0\u6D4B\u5230429\u72B6\u6001\u7801: {0}",
    // 状态历史消息
    history_cleared_new_session: "\u5386\u53F2\u8BB0\u5F55\u5DF2\u6E05\u7A7A\uFF0C\u65B0\u4F1A\u8BDD\u5F00\u59CB",
    status_history_cleared: "\u72B6\u6001\u5386\u53F2\u8BB0\u5F55\u5DF2\u6E05\u7A7A\u3002",
    duplicate_normal_status_detected: "\u68C0\u6D4B\u5230\u91CD\u590D\u7684\u6B63\u5E38\u72B6\u6001\u8BB0\u5F55\uFF0C\u6765\u6E90: {0}",
    execution_status_changed: "\u68C0\u6D4B\u5230\u6267\u884C\u72B6\u6001\u53D8\u5316\uFF1A{0}",
    status_executing: "\u6267\u884C\u4E2D",
    status_stopped: "\u5DF2\u505C\u6B62",
    // 状态历史UI文本
    status_duration_label: "\u6301\u7EED\u65F6\u95F4: ",
    status_requests_label: "\u671F\u95F4\u8BF7\u6C42\u6570: ",
    status_ended_at_label: "\u7ED3\u675F\u4E8E: ",
    status_started_at_label: "\u5F00\u59CB\u4E8E: ",
    status_ongoing_label: "\u5DF2\u6301\u7EED: ",
    status_unknown_time: "\u672A\u77E5\u65F6\u95F4",
    status_unknown_duration: "\u672A\u77E5",
    // 启动时状态检测
    startup_rate_limited: "\u811A\u672C\u542F\u52A8\u65F6\u5904\u4E8E\u9650\u901F\u72B6\u6001\u3002\u9650\u901F\u5DF2\u6301\u7EED\u81F3\u5C11 {0}s\uFF0C\u6765\u6E90: {1}",
    status_unknown_source: "\u672A\u77E5",
    // 请求成功来源
    request_source_search_response: "\u641C\u7D22\u54CD\u5E94\u6210\u529F",
    request_source_xhr_search: "XHR\u641C\u7D22\u6210\u529F",
    request_source_xhr_item: "XHR\u5546\u54C1\u8BF7\u6C42",
    consecutive_success_exit: "\u8FDE\u7EED{0}\u6B21\u6210\u529F\u8BF7\u6C42 ({1})",
    search_response_parse_failed: "\u641C\u7D22\u54CD\u5E94\u89E3\u6790\u5931\u8D25: {0}",
    // 缓存清理和Fab DOM相关
    cache_cleanup_complete: "[Cache] \u6E05\u7406\u5B8C\u6210\uFF0C\u5F53\u524D\u7F13\u5B58\u5927\u5C0F: \u5546\u54C1={0}, \u62E5\u6709\u72B6\u6001={1}, \u4EF7\u683C={2}",
    fab_dom_no_new_owned: "[Fab DOM Refresh] API\u67E5\u8BE2\u5B8C\u6210\uFF0C\u6CA1\u6709\u53D1\u73B0\u65B0\u7684\u5DF2\u62E5\u6709\u9879\u76EE\u3002",
    // 状态报告UI标签
    status_time_label: "\u65F6\u95F4",
    status_info_label: "\u4FE1\u606F",
    // 隐性限速检测和API监控
    implicit_rate_limit_detection: "[\u9690\u6027\u9650\u901F\u68C0\u6D4B]",
    scroll_api_monitoring: "[\u6EDA\u52A8API\u76D1\u63A7]",
    task_execution_time: "\u4EFB\u52A1\u6267\u884C\u65F6\u95F4: {0}\u79D2",
    detected_rate_limit_error: "\u68C0\u6D4B\u5230\u9650\u901F\u9519\u8BEF\u4FE1\u606F: {0}",
    detected_possible_rate_limit_empty: "\u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9650\u901F\u60C5\u51B5(\u7A7A\u7ED3\u679C): {0}",
    detected_possible_rate_limit_scroll: "\u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9650\u901F\u60C5\u51B5\uFF1A\u8FDE\u7EED{0}\u6B21\u6EDA\u52A8\u540E\u5361\u7247\u6570\u91CF\u672A\u589E\u52A0\u3002",
    detected_api_429_status: "\u68C0\u6D4B\u5230API\u8BF7\u6C42\u72B6\u6001\u7801\u4E3A429: {0}",
    detected_api_rate_limit_content: "\u68C0\u6D4B\u5230API\u54CD\u5E94\u5185\u5BB9\u5305\u542B\u9650\u901F\u4FE1\u606F: {0}",
    // 限速来源标识
    source_implicit_rate_limit: "\u9690\u6027\u9650\u901F\u68C0\u6D4B",
    source_scroll_api_monitoring: "\u6EDA\u52A8API\u76D1\u63A7",
    // 设置项
    setting_auto_refresh: "\u65E0\u5546\u54C1\u53EF\u89C1\u65F6\u81EA\u52A8\u5237\u65B0",
    setting_auto_add_scroll: "\u65E0\u9650\u6EDA\u52A8\u65F6\u81EA\u52A8\u6DFB\u52A0\u4EFB\u52A1",
    setting_remember_position: "\u8BB0\u4F4F\u7011\u5E03\u6D41\u6D4F\u89C8\u4F4D\u7F6E",
    setting_auto_resume_429: "429\u540E\u81EA\u52A8\u6062\u590D\u5E76\u7EE7\u7EED",
    setting_hide_discounted: "\u9690\u85CF\u6253\u6298\u7684\u4ED8\u8D39\u5546\u54C1",
    setting_hide_paid: "\u9690\u85CF\u6240\u6709\u4ED8\u8D39\u5546\u54C1",
    setting_block_large_resources: "\u7981\u7528\u56FE\u7247/\u5A92\u4F53\u7B49\u5927\u8D44\u6E90",
    setting_debug_tooltip: "\u542F\u7528\u8BE6\u7EC6\u65E5\u5FD7\u8BB0\u5F55\uFF0C\u7528\u4E8E\u6392\u67E5\u95EE\u9898",
    // 状态文本
    status_enabled: "\u5F00\u542F",
    status_disabled: "\u5173\u95ED",
    // 确认对话框
    confirm_clear_data: "\u60A8\u786E\u5B9A\u8981\u6E05\u7A7A\u6240\u6709\u672C\u5730\u5B58\u50A8\u7684\u811A\u672C\u6570\u636E\uFF08\u5DF2\u5B8C\u6210\u3001\u5931\u8D25\u3001\u5F85\u529E\u5217\u8868\uFF09\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u9006\uFF01",
    confirm_open_failed: "\u60A8\u786E\u5B9A\u8981\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 {0} \u4E2A\u5931\u8D25\u7684\u9879\u76EE\u5417\uFF1F",
    confirm_clear_history: "\u60A8\u786E\u5B9A\u8981\u6E05\u7A7A\u6240\u6709\u72B6\u6001\u5386\u53F2\u8BB0\u5F55\u5417\uFF1F",
    // 错误提示
    error_api_refresh: "API \u5237\u65B0\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u63A7\u5236\u53F0\u4E2D\u7684\u9519\u8BEF\u4FE1\u606F\uFF0C\u5E76\u786E\u8BA4\u60A8\u5DF2\u767B\u5F55\u3002",
    // 工具提示
    tooltip_open_failed: "\u70B9\u51FB\u6253\u5F00\u6240\u6709\u5931\u8D25\u7684\u9879\u76EE",
    tooltip_executing_progress: "\u6267\u884C\u4E2D: {0}/{1} ({2}%)",
    tooltip_executing: "\u6267\u884C\u4E2D",
    tooltip_start_tasks: "\u70B9\u51FB\u5F00\u59CB\u6267\u884C\u4EFB\u52A1",
    // 其他
    goto_page_label: "\u9875\u7801:",
    goto_page_btn: "\u8DF3\u8F6C",
    page_reset: "Page: 1",
    untitled: "Untitled",
    cursor_mode: "Cursor Mode",
    using_native_requests: "\u4F7F\u7528\u7F51\u9875\u539F\u751F\u8BF7\u6C42\uFF0C\u7B49\u5F85\u4E2D: {0}",
    worker_closed: "\u5DE5\u4F5C\u6807\u7B7E\u9875\u5728\u5B8C\u6210\u524D\u5173\u95ED",
    worker_captcha: "\u68C0\u6D4B\u5230\u4EBA\u673A\u9A8C\u8BC1 / \u9700\u8981\u4EBA\u5DE5\u5904\u7406",
    log_keepalive_on: "\u540E\u53F0\u4FDD\u6D3B\u5DF2\u542F\u52A8\uFF08Worker \u5FC3\u8DF3 + \u9632\u51BB\u7ED3\uFF09",
    log_keepalive_failed: "\u540E\u53F0\u4FDD\u6D3B\u542F\u52A8\u5931\u8D25: {0}",
    log_auto_retry: "\u21BB \u81EA\u52A8\u91CD\u8BD5 ({0}/{1}): {2}",
    keepalive_label: "\u4FDD\u6D3B",
    keepalive_dead: "\u26A0\uFE0F\u672A\u542F\u52A8(\u7591\u88ABCSP\u62E6)",
    // 脚本启动和初始化
    log_script_starting: "\u811A\u672C\u5F00\u59CB\u8FD0\u884C...",
    log_network_filter_deprecated: "\u7F51\u7EDC\u8FC7\u6EE4\u5668(NetworkFilter)\u6A21\u5757\u5DF2\u5F03\u7528\uFF0C\u529F\u80FD\u7531\u8865\u4E01\u7A0B\u5E8F(PagePatcher)\u5904\u7406\u3002",
    // 限速状态检查
    log_rate_limit_check_active: "\u5DF2\u6709\u9650\u901F\u72B6\u6001\u68C0\u67E5\u6B63\u5728\u8FDB\u884C\uFF0C\u8DF3\u8FC7\u672C\u6B21\u68C0\u67E5",
    log_rate_limit_check_start: "\u5F00\u59CB\u68C0\u67E5\u9650\u901F\u72B6\u6001...",
    log_page_content_rate_limit: "\u9875\u9762\u5185\u5BB9\u5305\u542B\u9650\u901F\u4FE1\u606F\uFF0C\u786E\u8BA4\u4ECD\u5904\u4E8E\u9650\u901F\u72B6\u6001",
    log_use_performance_api: "\u4F7F\u7528Performance API\u68C0\u67E5\u6700\u8FD1\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0C\u4E0D\u518D\u4E3B\u52A8\u53D1\u9001API\u8BF7\u6C42",
    log_detected_429_in_10s: "\u68C0\u6D4B\u5230\u6700\u8FD110\u79D2\u5185\u6709429\u72B6\u6001\u7801\u7684\u8BF7\u6C42\uFF0C\u5224\u65AD\u4E3A\u9650\u901F\u72B6\u6001",
    log_detected_success_in_10s: "\u68C0\u6D4B\u5230\u6700\u8FD110\u79D2\u5185\u6709\u6210\u529F\u7684API\u8BF7\u6C42\uFF0C\u5224\u65AD\u4E3A\u6B63\u5E38\u72B6\u6001",
    log_insufficient_info_status: "\u6CA1\u6709\u8DB3\u591F\u7684\u4FE1\u606F\u5224\u65AD\u9650\u901F\u72B6\u6001\uFF0C\u4FDD\u6301\u5F53\u524D\u72B6\u6001",
    log_rate_limit_check_failed: "\u9650\u901F\u72B6\u6001\u68C0\u67E5\u5931\u8D25: {0}",
    // 游标和位置
    log_cursor_initialized_with: "[Cursor] \u521D\u59CB\u5316\u5B8C\u6210\u3002\u52A0\u8F7D\u5DF2\u4FDD\u5B58\u7684cursor: {0}...",
    log_cursor_initialized_empty: "[Cursor] \u521D\u59CB\u5316\u5B8C\u6210\u3002\u672A\u627E\u5230\u5DF2\u4FDD\u5B58\u7684cursor\u3002",
    log_cursor_restore_failed: "[Cursor] \u6062\u590Dcursor\u72B6\u6001\u5931\u8D25:",
    log_cursor_interceptors_applied: "[Cursor] \u7F51\u7EDC\u62E6\u622A\u5668\u5DF2\u5E94\u7528\u3002",
    log_cursor_skip_known_position: "[Cursor] \u8DF3\u8FC7\u5DF2\u77E5\u4F4D\u7F6E\u7684\u4FDD\u5B58: {0}",
    log_cursor_skip_backtrack: "[Cursor] \u8DF3\u8FC7\u56DE\u9000\u4F4D\u7F6E: {0} (\u5F53\u524D\u4F4D\u7F6E: {1}), \u6392\u5E8F: {2}",
    log_cursor_save_error: "[Cursor] \u4FDD\u5B58cursor\u65F6\u51FA\u9519:",
    log_url_sort_changed: '\u68C0\u6D4B\u5230URL\u6392\u5E8F\u53C2\u6570\u53D8\u66F4\uFF0C\u6392\u5E8F\u65B9\u5F0F\u5DF2\u4ECE"{0}"\u66F4\u6539\u4E3A"{1}"',
    log_sort_changed_position_cleared: "\u7531\u4E8E\u6392\u5E8F\u65B9\u5F0F\u53D8\u66F4\uFF0C\u5DF2\u6E05\u9664\u4FDD\u5B58\u7684\u6D4F\u89C8\u4F4D\u7F6E",
    log_sort_check_error: "\u68C0\u67E5URL\u6392\u5E8F\u53C2\u6570\u65F6\u51FA\u9519: {0}",
    log_position_cleared: "\u5DF2\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u6D4F\u89C8\u4F4D\u7F6E\u3002",
    clear_position_tooltip: "\u91CD\u7F6E\u6D4F\u89C8\u4F4D\u7F6E\u5E76\u5237\u65B0",
    confirm_reset_position: "\u786E\u5B9A\u8981\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u6D4F\u89C8\u4F4D\u7F6E\u5E76\u5237\u65B0\u9875\u9762\u5417\uFF1F",
    no_position_to_reset: "\u5F53\u524D\u6CA1\u6709\u4FDD\u5B58\u7684\u6D4F\u89C8\u4F4D\u7F6E\u3002",
    log_sort_ascending: "\u5347\u5E8F",
    log_sort_descending: "\u964D\u5E8F",
    // XHR/Fetch 限速检测
    log_xhr_rate_limit_detect: "[XHR\u9650\u901F\u68C0\u6D4B] \u68C0\u6D4B\u5230\u9650\u901F\u60C5\u51B5\uFF0C\u539F\u59CB\u54CD\u5E94: {0}",
    log_list_end_normal: "[\u5217\u8868\u672B\u5C3E] \u68C0\u6D4B\u5230\u5DF2\u5230\u8FBE\u5217\u8868\u672B\u5C3E\uFF0C\u8FD9\u662F\u6B63\u5E38\u60C5\u51B5\uFF0C\u4E0D\u89E6\u53D1\u9650\u901F: {0}...",
    log_empty_search_with_filters: "[\u7A7A\u641C\u7D22\u7ED3\u679C] \u68C0\u6D4B\u5230\u641C\u7D22\u7ED3\u679C\u4E3A\u7A7A\uFF0C\u4F46\u5305\u542B\u7279\u6B8A\u8FC7\u6EE4\u6761\u4EF6\uFF0C\u8FD9\u53EF\u80FD\u662F\u6B63\u5E38\u60C5\u51B5: {0}...",
    log_empty_search_already_limited: "[\u7A7A\u641C\u7D22\u7ED3\u679C] \u5DF2\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u4E0D\u91CD\u590D\u89E6\u53D1: {0}...",
    log_empty_search_page_loading: "[\u7A7A\u641C\u7D22\u7ED3\u679C] \u9875\u9762\u5C1A\u672A\u5B8C\u5168\u52A0\u8F7D\uFF0C\u53EF\u80FD\u662F\u521D\u59CB\u8BF7\u6C42\uFF0C\u4E0D\u89E6\u53D1\u9650\u901F: {0}...",
    log_debounce_intercept: "[Debounce] \u{1F6A6} \u62E6\u622A\u6EDA\u52A8\u8BF7\u6C42\u3002\u5E94\u7528{0}ms\u5EF6\u8FDF...",
    log_debounce_discard: "[Debounce] \u{1F5D1}\uFE0F \u4E22\u5F03\u4E4B\u524D\u7684\u6302\u8D77\u8BF7\u6C42\u3002",
    log_debounce_sending: "[Debounce] \u25B6\uFE0F \u53D1\u9001\u6700\u65B0\u6EDA\u52A8\u8BF7\u6C42: {0}",
    log_fetch_detected_429: "[Fetch] \u68C0\u6D4B\u5230429\u72B6\u6001\u7801: {0}",
    log_fetch_rate_limit_detect: "[Fetch\u9650\u901F\u68C0\u6D4B] \u68C0\u6D4B\u5230\u9650\u901F\u60C5\u51B5\uFF0C\u539F\u59CB\u54CD\u5E94: {0}...",
    log_fetch_list_end: "[Fetch\u5217\u8868\u672B\u5C3E] \u68C0\u6D4B\u5230\u5DF2\u5230\u8FBE\u5217\u8868\u672B\u5C3E\uFF0C\u8FD9\u662F\u6B63\u5E38\u60C5\u51B5\uFF0C\u4E0D\u89E6\u53D1\u9650\u901F: {0}...",
    log_fetch_empty_with_filters: "[Fetch\u7A7A\u641C\u7D22\u7ED3\u679C] \u68C0\u6D4B\u5230\u641C\u7D22\u7ED3\u679C\u4E3A\u7A7A\uFF0C\u4F46\u5305\u542B\u7279\u6B8A\u8FC7\u6EE4\u6761\u4EF6\uFF0C\u8FD9\u53EF\u80FD\u662F\u6B63\u5E38\u60C5\u51B5: {0}...",
    log_fetch_empty_already_limited: "[Fetch\u7A7A\u641C\u7D22\u7ED3\u679C] \u5DF2\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u4E0D\u91CD\u590D\u89E6\u53D1: {0}...",
    log_fetch_empty_page_loading: "[Fetch\u7A7A\u641C\u7D22\u7ED3\u679C] \u9875\u9762\u5C1A\u672A\u5B8C\u5168\u52A0\u8F7D\uFF0C\u53EF\u80FD\u662F\u521D\u59CB\u8BF7\u6C42\uFF0C\u4E0D\u89E6\u53D1\u9650\u901F: {0}...",
    log_fetch_implicit_rate_limit: "[Fetch\u9690\u6027\u9650\u901F] \u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9650\u901F\u60C5\u51B5(\u7A7A\u7ED3\u679C): {0}...",
    log_json_parse_error: "JSON\u89E3\u6790\u9519\u8BEF: {0}",
    log_response_length: "\u54CD\u5E94\u957F\u5EA6: {0}, \u524D100\u4E2A\u5B57\u7B26: {1}",
    log_handling_rate_limit_error: "\u5904\u7406\u9650\u901F\u65F6\u51FA\u9519: {0}",
    // 执行控制
    log_execution_stopped_manually: "\u6267\u884C\u5DF2\u7531\u7528\u6237\u624B\u52A8\u505C\u6B62\u3002",
    log_todo_cleared_scan: "\u5F85\u529E\u5217\u8868\u5DF2\u6E05\u7A7A\u3002\u73B0\u5728\u5C06\u626B\u63CF\u5E76\u4EC5\u6DFB\u52A0\u5F53\u524D\u53EF\u89C1\u7684\u9879\u76EE\u3002",
    log_scanning_loaded_items: "\u6B63\u5728\u626B\u63CF\u5DF2\u52A0\u8F7D\u5B8C\u6210\u7684\u5546\u54C1...",
    log_executor_running_queued: "\u6267\u884C\u5668\u5DF2\u5728\u8FD0\u884C\u4E2D\uFF0C\u65B0\u4EFB\u52A1\u5DF2\u52A0\u5165\u961F\u5217\u7B49\u5F85\u5904\u7406\u3002",
    log_todo_empty_scanning: "\u5F85\u529E\u6E05\u5355\u4E3A\u7A7A\uFF0C\u6B63\u5728\u626B\u63CF\u5F53\u524D\u9875\u9762...",
    log_request_no_results_not_counted: "\u8BF7\u6C42\u6210\u529F\u4F46\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7ED3\u679C\uFF0C\u4E0D\u8BA1\u5165\u8FDE\u7EED\u6210\u529F\u8BA1\u6570\u3002\u6765\u6E90: {0}",
    log_not_rate_limited_ignore_exit: "\u5F53\u524D\u4E0D\u662F\u9650\u901F\u72B6\u6001\uFF0C\u5FFD\u7565\u9000\u51FA\u9650\u901F\u8BF7\u6C42: {0}",
    log_found_todo_auto_resume: "\u53D1\u73B0 {0} \u4E2A\u5F85\u529E\u4EFB\u52A1\uFF0C\u81EA\u52A8\u6062\u590D\u6267\u884C...",
    log_dispatching_wait: "\u6B63\u5728\u6D3E\u53D1\u4EFB\u52A1\u4E2D\uFF0C\u8BF7\u7A0D\u5019...",
    log_rate_limited_continue_todo: "\u5F53\u524D\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u4F46\u4ECD\u5C06\u7EE7\u7EED\u6267\u884C\u5F85\u529E\u4EFB\u52A1...",
    log_detected_todo_no_workers: "\u68C0\u6D4B\u5230\u6709\u5F85\u529E\u4EFB\u52A1\u4F46\u6CA1\u6709\u6D3B\u52A8\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u5C1D\u8BD5\u91CD\u65B0\u6267\u884C...",
    // 数据库和同步
    log_db_sync_cleared_failed: '[Fab DB Sync] \u4ECE"\u5931\u8D25"\u5217\u8868\u4E2D\u6E05\u9664\u4E86 {0} \u4E2A\u5DF2\u624B\u52A8\u5B8C\u6210\u7684\u5546\u54C1\u3002',
    log_no_unowned_in_batch: "\u672C\u6279\u6B21\u4E2D\u6CA1\u6709\u53D1\u73B0\u672A\u62E5\u6709\u7684\u5546\u54C1\u3002",
    log_no_truly_free_after_verify: "\u627E\u5230\u672A\u62E5\u6709\u7684\u5546\u54C1\uFF0C\u4F46\u4EF7\u683C\u9A8C\u8BC1\u540E\u6CA1\u6709\u771F\u6B63\u514D\u8D39\u7684\u5546\u54C1\u3002",
    log_429_scan_paused: "\u68C0\u6D4B\u5230429\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\u3002\u5C06\u6682\u505C\u626B\u63CF\u3002",
    // 工作线程
    log_worker_tabs_cleared: "\u5DF2\u6E05\u7406\u6240\u6709\u5DE5\u4F5C\u6807\u7B7E\u9875\u7684\u72B6\u6001\u3002",
    log_worker_task_cleared_closing: "\u4EFB\u52A1\u6570\u636E\u5DF2\u88AB\u6E05\u7406\uFF0C\u5DE5\u4F5C\u6807\u7B7E\u9875\u5C06\u5173\u95ED\u3002",
    log_worker_instance_cooperate: "\u68C0\u6D4B\u5230\u6D3B\u8DC3\u7684\u811A\u672C\u5B9E\u4F8B [{0}]\uFF0C\u5F53\u524D\u5DE5\u4F5C\u6807\u7B7E\u9875\u5C06\u4E0E\u4E4B\u534F\u4F5C\u3002",
    log_other_instance_report_ignore: "\u6536\u5230\u6765\u81EA\u5176\u4ED6\u5B9E\u4F8B [{0}] \u7684\u5DE5\u4F5C\u62A5\u544A\uFF0C\u5F53\u524D\u5B9E\u4F8B [{1}] \u5C06\u5FFD\u7565\u3002",
    // 失败和重试
    log_failed_list_empty: "\u5931\u8D25\u5217\u8868\u4E3A\u7A7A\uFF0C\u65E0\u9700\u64CD\u4F5C\u3002",
    log_cleared_from_failed: "\u5DF2\u4ECE\u5931\u8D25\u5217\u8868\u4E2D\u6E05\u9664 {0} \u4E2A\u5B9E\u9645\u5DF2\u5165\u5E93\u7684\u5546\u54C1\u3002",
    log_failed_check_start: "\u6B63\u5728\u5BF9\u5931\u8D25\u9879\u76EE\u8FDB\u884C\u72B6\u6001\u68C0\u6D4B\uFF08\u6BD4\u5BF9\u672C\u5730\u8BB0\u5F55\u4E0E\u8054\u7F51\u72B6\u6001\uFF09...",
    log_failed_check_local: "[\u672C\u5730\u68C0\u6D4B] \u53D1\u73B0 {0} \u4E2A\u9879\u76EE\u5728\u672C\u5730\u5DF2\u6807\u8BB0\u5B8C\u6210\uFF0C\u6B63\u5728\u4ECE\u5931\u8D25\u5217\u8868\u4E2D\u79FB\u9664...",
    log_failed_check_network: "[\u8054\u7F51\u68C0\u6D4B] \u6B63\u5728\u5411\u670D\u52A1\u5668\u67E5\u8BE2\u5269\u4F59 {0} \u4E2A\u9879\u76EE\u7684\u5165\u5E93\u72B6\u6001...",
    log_failed_check_network_confirmed: "[\u8054\u7F51\u68C0\u6D4B] \u786E\u8BA4\u6709 {0} \u4E2A\u9879\u76EE\u5B9E\u9645\u5DF2\u88AB\u52A0\u5E93\uFF0C\u6B63\u5728\u66F4\u65B0\u6570\u636E\u5E93...",
    log_failed_check_network_none: "[\u8054\u7F51\u68C0\u6D4B] \u672A\u68C0\u6D4B\u5230\u5176\u4ED6\u5DF2\u52A0\u5E93\u9879\u76EE\u3002",
    log_failed_check_complete: "\u2728 \u68C0\u6D4B\u5B8C\u6210\uFF01\u5171\u5254\u9664\u4E86 {0} \u4E2A\u5DF2\u6210\u529F\u5165\u5E93\u7684\u9879\u76EE\u3002",
    log_failed_check_all_done: "\u{1F389} \u6240\u6709\u5931\u8D25\u9879\u76EE\u68C0\u6D4B\u5747\u5DF2\u786E\u8BA4\u5165\u5E93\uFF0C\u5931\u8D25\u5217\u8868\u5DF2\u6E05\u7A7A\uFF01",
    confirm_open_failed_remaining: "\u72B6\u6001\u6838\u5BF9\u5B8C\u6210\uFF0C\u4ECD\u6709 {0} \u4E2A\u5546\u54C1\u672A\u5165\u5E93\u3002\n\u662F\u5426\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00\u8FD9 {0} \u4E2A\u5931\u8D25\u5546\u54C1\u4EE5\u4F9B\u624B\u52A8\u5904\u7406\uFF1F",
    alert_failed_all_done: "\u6240\u6709\u5931\u8D25\u9879\u76EE\u68C0\u6D4B\u5747\u5DF2\u786E\u8BA4\u5165\u5E93\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\uFF01",
    // 锁屏/后台恢复
    log_wake_recovery: "\u{1F514} \u68C0\u6D4B\u5230\u6807\u7B7E\u9875\u91CD\u65B0\u6FC0\u6D3B\uFF08\u9501\u5C4F/\u5207\u6362\u540E\u6062\u590D\uFF09\uFF0C\u6B63\u5728\u68C0\u67E5\u6267\u884C\u72B6\u6001...",
    log_wake_cleanup_stale: "\u{1F9F9} \u6E05\u7406 {0} \u4E2A\u56E0\u9501\u5C4F\u51BB\u7ED3\u8D85\u65F6\u7684\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u91CD\u65B0\u542F\u52A8\u6267\u884C\u3002",
    log_wake_restarting: "\u25B6\uFE0F \u91CD\u65B0\u6FC0\u6D3B\u6267\u884C\uFF08\u5171 {0} \u4E2A\u5F85\u529E\u4EFB\u52A1\uFF09\u3002",
    // 调试模式
    log_debug_mode_toggled: "\u8C03\u8BD5\u6A21\u5F0F\u5DF2{0}\u3002{1}",
    log_debug_mode_detail_info: "\u5C06\u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\u4FE1\u606F",
    log_no_history_to_copy: "\u6CA1\u6709\u5386\u53F2\u8BB0\u5F55\u53EF\u4F9B\u590D\u5236\u3002",
    // 启动和恢复
    log_execution_state_inconsistent: "\u6267\u884C\u72B6\u6001\u4E0D\u4E00\u81F4\uFF0C\u4ECE\u5B58\u50A8\u4E2D\u6062\u590D\uFF1A{0}",
    log_invalid_worker_report: "\u6536\u5230\u65E0\u6548\u7684\u5DE5\u4F5C\u62A5\u544A\u3002\u7F3A\u5C11workerId\u6216task\u3002",
    log_all_tasks_completed: "\u6240\u6709\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002",
    log_all_tasks_completed_rate_limited: "\u6240\u6709\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u4E14\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u5C06\u5237\u65B0\u9875\u9762\u5C1D\u8BD5\u6062\u590D...",
    log_recovery_probe_failed: "\u6062\u590D\u63A2\u6D4B\u5931\u8D25\u3002\u4ECD\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u5C06\u7EE7\u7EED\u968F\u673A\u5237\u65B0...",
    // 实例管理
    log_not_active_instance: "\u5F53\u524D\u5B9E\u4F8B\u4E0D\u662F\u6D3B\u8DC3\u5B9E\u4F8B\uFF0C\u4E0D\u6267\u884C\u4EFB\u52A1\u3002",
    log_no_active_instance_activating: "\u6CA1\u6709\u68C0\u6D4B\u5230\u6D3B\u8DC3\u5B9E\u4F8B\uFF0C\u5F53\u524D\u5B9E\u4F8B [{0}] \u5DF2\u6FC0\u6D3B\u3002",
    log_inactive_instance_taking_over: "\u524D\u4E00\u4E2A\u5B9E\u4F8B [{0}] \u4E0D\u6D3B\u8DC3\uFF0C\u5F53\u524D\u5B9E\u4F8B\u63A5\u7BA1\u3002",
    log_is_search_page_activated: "\u5F53\u524D\u662F\u641C\u7D22\u9875\u9762\uFF0C\u5B9E\u4F8B [{0}] \u5DF2\u6FC0\u6D3B\u3002",
    // 可见性和刷新
    log_no_visible_items_todo_workers: "\u867D\u7136\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u4F46\u68C0\u6D4B\u5230\u6709 {0} \u4E2A\u5F85\u529E\u4EFB\u52A1\u548C {1} \u4E2A\u6D3B\u52A8\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u6682\u4E0D\u81EA\u52A8\u5237\u65B0\u9875\u9762\u3002",
    log_visible_items_detected_skipping: "\u23F9\uFE0F \u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 {0} \u4E2A\u53EF\u89C1\u5546\u54C1\uFF0C\u4E0D\u89E6\u53D1\u81EA\u52A8\u5237\u65B0\u4EE5\u907F\u514D\u4E2D\u65AD\u6D4F\u89C8\u3002",
    log_please_complete_tasks_first: "\u8BF7\u624B\u52A8\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u4E9B\u4EFB\u52A1\u540E\u518D\u5237\u65B0\u9875\u9762\u3002",
    log_display_mode_switched: "\u{1F441}\uFE0F \u663E\u793A\u6A21\u5F0F\u5DF2\u5207\u6362\uFF0C\u5F53\u524D\u9875\u9762\u6709 {0} \u4E2A\u53EF\u89C1\u5546\u54C1",
    position_label: "\u4F4D\u7F6E",
    log_entering_rate_limit_from: "\u{1F6A8} \u6765\u81EA [{0}] \u7684\u9650\u901F\u89E6\u53D1\uFF01\u6B63\u5E38\u8FD0\u884C\u671F\u6301\u7EED\u4E86 {1} \u79D2\uFF0C\u671F\u95F4\u6709 {2} \u6B21\u6210\u529F\u7684\u641C\u7D22\u8BF7\u6C42\u3002",
    log_entering_rate_limit_from_v2: "\u{1F6A8} \u4ECE [{0}] \u68C0\u6D4B\u5230\u9650\u901F\uFF01\u6B63\u5E38\u8FD0\u884C\u6301\u7EED\u4E86 {1} \u79D2\uFF0C\u5305\u542B {2} \u6B21\u6210\u529F\u641C\u7D22\u8BF7\u6C42\u3002",
    rate_limit_recovery_success: "\u2705 \u9650\u901F\u4F3C\u4E4E\u5DF2\u4ECE [{0}] \u89E3\u9664\u3002429 \u72B6\u6001\u6301\u7EED\u4E86 {1} \u79D2\u3002",
    fab_dom_refresh_complete: "[Fab DOM Refresh] \u5B8C\u6210\u3002\u66F4\u65B0\u4E86 {0} \u4E2A\u53EF\u89C1\u5361\u7247\u7684\u72B6\u6001\u3002",
    auto_refresh_disabled_rate_limit: "\u26A0\uFE0F \u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u81EA\u52A8\u5237\u65B0\u529F\u80FD\u5DF2\u5173\u95ED\uFF0C\u8BF7\u5728\u9700\u8981\u65F6\u624B\u52A8\u5237\u65B0\u9875\u9762\u3002",
    // 页面诊断
    log_diagnosis_complete: "\u9875\u9762\u8BCA\u65AD\u5B8C\u6210\uFF0C\u8BF7\u67E5\u770B\u63A7\u5236\u53F0\u8F93\u51FA",
    log_diagnosis_failed: "\u9875\u9762\u8BCA\u65AD\u5931\u8D25: {0}",
    // Auto resume
    log_auto_resume_page_loading: "[Auto-Resume] \u9875\u9762\u5728\u9650\u901F\u72B6\u6001\u4E0B\u52A0\u8F7D\u3002\u6B63\u5728\u8FDB\u884C\u6062\u590D\u63A2\u6D4B...",
    log_recovery_probe_success: "\u2705 \u6062\u590D\u63A2\u6D4B\u6210\u529F\uFF01\u9650\u901F\u5DF2\u89E3\u9664\uFF0C\u7EE7\u7EED\u6B63\u5E38\u64CD\u4F5C\u3002",
    log_tasks_still_running: "\u4ECD\u6709 {0} \u4E2A\u4EFB\u52A1\u5728\u6267\u884C\u4E2D\uFF0C\u7B49\u5F85\u5B83\u4EEC\u5B8C\u6210\u540E\u518D\u5237\u65B0...",
    log_todo_tasks_waiting: "\u6709 {0} \u4E2A\u5F85\u529E\u4EFB\u52A1\u7B49\u5F85\u6267\u884C\uFF0C\u5C06\u5C1D\u8BD5\u7EE7\u7EED\u6267\u884C...",
    countdown_refresh_source: "\u6062\u590D\u63A2\u6D4B\u5931\u8D25",
    failed_list_empty: "\u5931\u8D25\u5217\u8868\u4E3A\u7A7A\uFF0C\u65E0\u9700\u64CD\u4F5C\u3002",
    opening_failed_items: "\u6B63\u5728\u6253\u5F00 {0} \u4E2A\u5931\u8D25\u9879\u76EE...",
    // 自动滚动挂起恢复
    log_auto_scroll_stuck_refresh: "\u26A0\uFE0F \u81EA\u52A8\u6EDA\u52A8\u8FDE\u7EED\u672A\u53D1\u73B0\u65B0\u5546\u54C1\uFF0C\u4E14\u6B63\u5728\u6267\u884C\u4EFB\u52A1\u4E2D\uFF0C\u89E6\u53D1\u81EA\u52A8\u9875\u9762\u5237\u65B0\u4EE5\u5C1D\u8BD5\u6062\u590D...",
    log_auto_scroll_stuck_96_refresh: "\u26A0\uFE0F \u76D1\u6D4B\u5230\u7279\u6B8A\u60C5\u666F\uFF08\u5DF2\u9690\u85CF {0} \u4E2A\u5546\u54C1\u4E14\u65E0\u53EF\u89C1\u5546\u54C1\uFF0C\u7FFB\u9875\u6302\u8D77\uFF09\uFF0C\u4FDD\u5B58\u4E34\u65F6\u6E38\u6807\uFF0C\u6B63\u5728\u5237\u65B0\u9875\u9762\u4EE5\u5C1D\u8BD5\u6062\u590D...",
    log_auto_scroll_stuck_recovery_failed: "\u5DF2\u5C1D\u8BD5\u8FC7\u4F7F\u7528\u5F53\u524D\u6E38\u6807\u6062\u590D\u4F46\u4F9D\u7136\u65E0\u65B0\u5546\u54C1\uFF0C\u5224\u5B9A\u4E3A\u5DF2\u5230\u8FBE\u5217\u8868\u672B\u5C3E\uFF0C\u505C\u6B62\u5237\u65B0\u3002",
    // 账号验证
    auth_error: "\u8D26\u53F7\u5931\u6548\uFF1A\u672A\u627E\u5230 CSRF token\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55",
    auth_error_alert: "\u8D26\u53F7\u5931\u6548\uFF1A\u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u4F7F\u7528\u811A\u672C",
    auth_session_invalid: "\u68C0\u6D4B\u5230\u767B\u5F55\u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u542F\u52A8\u4EFB\u52A1\u3002",
    auth_worker_aborted: "\u5DE5\u4F5C\u6807\u7B7E\u9875\u68C0\u6D4B\u5230\u672A\u767B\u5F55\uFF0C\u5DF2\u4E2D\u6B62\u4EFB\u52A1\u5904\u7406\u3002",
    auth_scan_blocked: "\u672A\u767B\u5F55\u6216\u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u672C\u6B21\u626B\u63CF\u88AB\u8DF3\u8FC7\uFF08\u907F\u514D\u628A\u6240\u6709\u5546\u54C1\u8BEF\u5224\u4E3A\u514D\u8D39\u7A7A\u8DD1\uFF09\u3002"
  };

  // src/config.js
  var Config = {
    SCRIPT_NAME: "Fab Helper (\u4F18\u5316\u7248)",
    DB_VERSION: 3,
    DB_NAME: "fab_helper_db",
    MAX_CONCURRENT_WORKERS: 7,
    // 最大并发工作标签页数量
    WORKER_TIMEOUT: 9e4,
    // 工作标签页超时时间(watchdog 判定卡死)。领取一般 <1min,留足余量避免误杀慢任务
    KEEPALIVE_TICK_MS: 2e3,
    // 后台保活心跳间隔(Web Worker postMessage 频率)
    ENABLE_FREEZE_GUARD: true,
    // 是否启用 WebRTC 防整页冻结(锁屏/最小化场景需要)
    UI_CONTAINER_ID: "fab-helper-container",
    UI_LOG_ID: "fab-helper-log",
    DB_KEYS: {
      DONE: "fab_done_v8",
      FAILED: "fab_failed_v8",
      TODO: "fab_todo_v1",
      // 用于永久存储待办列表
      HIDE: "fab_hide_v8",
      AUTO_ADD: "fab_autoAdd_v8",
      // 自动添加设置键
      REMEMBER_POS: "fab_rememberPos_v8",
      LAST_CURSOR: "fab_lastCursor_v8",
      // Store only the cursor string
      WORKER_DONE: "fab_worker_done_v8",
      // This is the ONLY key workers use to report back.
      APP_STATUS: "fab_app_status_v1",
      // For tracking 429 rate limiting
      STATUS_HISTORY: "fab_status_history_v1",
      // 状态历史记录持久化
      AUTO_RESUME: "fab_auto_resume_v1",
      // 自动恢复功能设置
      IS_EXECUTING: "fab_is_executing_v1",
      // 执行状态保存
      AUTO_REFRESH_EMPTY: "fab_auto_refresh_empty_v1",
      // 无商品可见时自动刷新
      HIDE_DISCOUNTED: "fab_hideDiscounted_v8",
      // 隐藏打折的付费商品
      HIDE_PAID: "fab_hidePaid_v8",
      // 隐藏所有付费商品
      BLOCK_RESOURCES: "fab_block_resources_v1"
      // 禁用大资源设置键
      // 其他键值用于会话或主标签页持久化
    },
    SELECTORS: {
      card: "div.fabkit-Stack-root.nTa5u2sc, div.AssetCard-root",
      cardLink: 'a[href*="/listings/"]',
      addButton: 'button[aria-label*="Add to"], button[aria-label*="\u6DFB\u52A0\u81F3"], button[aria-label*="cart"]',
      rootElement: "#root",
      successBanner: 'div[class*="Toast-root"]',
      freeStatus: ".csZFzinF",
      ownedStatus: ".cUUvxo_s"
    },
    TEXTS: {
      en,
      zh
    },
    // Centralized keyword sets, based STRICTLY on the rules in FAB_HELPER_RULES.md
    OWNED_SUCCESS_CRITERIA: {
      // Check for an H2 tag with the specific success text.
      h2Text: ["\u5DF2\u4FDD\u5B58\u5728\u6211\u7684\u5E93\u4E2D", "Saved in My Library"],
      // Check for buttons/links with these texts.
      buttonTexts: ["\u5728\u6211\u7684\u5E93\u4E2D\u67E5\u770B", "View in My Library"],
      // Check for the temporary success popup (snackbar).
      snackbarText: ["\u4EA7\u54C1\u5DF2\u6DFB\u52A0\u81F3\u60A8\u7684\u5E93\u4E2D", "Product added to your library"]
    },
    ACQUISITION_TEXT_SET: /* @__PURE__ */ new Set([
      "\u6DFB\u52A0\u5230\u6211\u7684\u5E93",
      "Add to my library",
      "\u52A0\u5165\u8D2D\u7269\u8F66",
      "Add to cart",
      "\u7ED3\u8D26",
      "Checkout",
      "\u7ACB\u5373\u83B7\u53D6",
      "Get it",
      "\u514D\u8D39\u83B7\u53D6",
      "Get for free",
      "\u5B8C\u6210\u8BA2\u5355",
      "Complete order",
      "\u7ACB\u5373\u8D2D\u4E70",
      "Buy now",
      "\u83B7\u53D6\u8D44\u6E90",
      "Get asset",
      "Place order",
      "\u786E\u8BA4\u8BA2\u5355",
      "\u4E0B\u5355"
    ]),
    // Kept for backward compatibility with recon logic.
    SAVED_TEXT_SET: /* @__PURE__ */ new Set(["\u5DF2\u4FDD\u5B58\u5728\u6211\u7684\u5E93\u4E2D", "Saved in My Library", "\u5728\u6211\u7684\u5E93\u4E2D", "In My Library"]),
    FREE_TEXT_SET: /* @__PURE__ */ new Set(["\u514D\u8D39", "Free", "Free*", "0.00", "\u8D77\u59CB\u4EF7\u683C \u514D\u8D39", "Starting at Free"]),
    EXTERNAL_CTA_TEXT_SET: /* @__PURE__ */ new Set([
      "\u5728\u5916\u90E8\u7F51\u7AD9\u67E5\u770B",
      "View on external website"
    ]),
    // 添加一个实例ID,用于防止多实例运行
    INSTANCE_ID: "fab_instance_id_" + Math.random().toString(36).substring(2, 15),
    STATUS_CHECK_INTERVAL: 3e3
    // Status check interval in ms (throttled to reduce log spam)
  };

  // src/state.js
  var State = {
    db: {
      todo: [],
      // 待办任务列表
      done: [],
      // 已完成任务列表
      failed: []
      // 失败任务列表
    },
    hideSaved: false,
    // 是否隐藏已保存项目
    autoAddOnScroll: false,
    // 是否在滚动时自动添加任务
    isAutoScrolling: false,
    // 是否正在自动滚动中
    autoScrollAttempts: 0,
    // 连续自动滚动未发现新任务的次数
    rememberScrollPosition: false,
    // 是否记住滚动位置
    autoResumeAfter429: false,
    // 是否在429后自动恢复
    autoRefreshEmptyPage: true,
    // 新增:无商品可见时自动刷新(默认开启)
    hideDiscountedPaid: false,
    // 是否隐藏打折的付费商品
    hidePaid: false,
    // 是否隐藏所有付费商品
    blockLargeResources: true,
    // 新增:是否在工作标签页中禁用图片/媒体等大资源以节省流量和并发
    debugMode: false,
    // 是否启用调试模式
    lang: "zh",
    // 当前语言,默认中文,会在detectLanguage中更新
    isExecuting: false,
    // 是否正在执行任务
    isRefreshScheduled: false,
    // 新增:标记是否已经安排了页面刷新
    isWorkerTab: false,
    // 是否是工作标签页
    totalTasks: 0,
    // API扫描的总任务数
    completedTasks: 0,
    // API扫描的已完成任务数
    isDispatchingTasks: false,
    // 新增:标记是否正在派发任务
    isScanningTasks: false,
    // 新增:标记是否正在扫描任务,防止重复扫描
    processedCardUids: /* @__PURE__ */ new Set(),
    // 新增:已处理过的卡片UID,防止重复添加
    savedCursor: null,
    // Holds the loaded cursor for hijacking
    // --- NEW: State for 429 monitoring ---
    appStatus: "NORMAL",
    // 'NORMAL' or 'RATE_LIMITED'
    rateLimitStartTime: null,
    normalStartTime: Date.now(),
    successfulSearchCount: 0,
    statusHistory: [],
    // Holds the history of NORMAL/RATE_LIMITED periods
    // --- 限速恢复相关状态 ---
    consecutiveSuccessCount: 0,
    // 连续成功请求计数
    requiredSuccessCount: 3,
    // 退出限速需要的连续成功请求数
    lastLimitSource: "",
    // 最后一次限速的来源
    isCheckingRateLimit: false,
    // 是否正在检查限速状态
    // --- 登录态 ---
    isAuthenticated: false,
    // 当前是否检测到有效的登录态(fab_csrftoken cookie)
    // --- 恢复模式 ---
    isRecoveryMode: false,
    // 是否处于刷新恢复模式
    // --- End New State ---
    showAdvanced: false,
    activeWorkers: 0,
    runningWorkers: {},
    // NEW: To track active workers for the watchdog { workerId: { task, startTime } }
    lastKnownHref: null,
    // To detect SPA navigation
    hiddenThisPageCount: 0,
    cardCountCache: {
      total: 0,
      hidden: 0,
      visible: 0,
      dirty: true,
      documentRef: null,
      href: ""
    },
    lastHideModeKey: "",
    executionTotalTasks: 0,
    // For execution progress
    executionCompletedTasks: 0,
    // For execution progress
    executionFailedTasks: 0,
    // For execution progress
    watchdogTimer: null,
    // UI-related state
    uiExpanded: true,
    logs: [],
    valueChangeListeners: [],
    // For remembering scroll position
    knownCursors: /* @__PURE__ */ new Set(),
    lastSortMethod: null,
    // Session-level tracking (not persisted)
    sessionCompleted: /* @__PURE__ */ new Set(),
    sessionFailed: /* @__PURE__ */ new Set(),
    // 工作线程标签页任务ID
    workerTaskId: null,
    // 是否显示状态历史表格
    showStatusHistory: false,
    // Launcher flag
    hasRunDomPart: false,
    // Observer debounce timer
    observerDebounceTimer: null,
    // Pending retry for hide/show while card status labels are still loading
    hideRetryTimer: null,
    // Pending retry for auto-add while card status labels are still loading
    autoAddRetryTimer: null,
    // UI element references - populated by UI.create()
    UI: {
      container: null,
      tabs: {},
      tabContents: {},
      statusVisible: null,
      statusTodo: null,
      statusDone: null,
      statusFailed: null,
      statusHidden: null,
      execBtn: null,
      syncBtn: null,
      hideBtn: null,
      logPanel: null,
      savedPositionDisplay: null,
      debugContent: null,
      historyContainer: null
    }
  };

  // src/modules/utils.js
  var UI = null;
  var setUIReference = /* @__PURE__ */ __name((uiModule) => {
    UI = uiModule;
  }, "setUIReference");
  var Utils = {
    logger: /* @__PURE__ */ __name((type, ...args) => {
      if (State.isWorkerTab) {
        if (type === "error" || args.some((arg) => typeof arg === "string" && arg.includes("Worker"))) {
          console[type](`${Config.SCRIPT_NAME} [Worker]`, ...args);
        }
        return;
      }
      if (type === "debug") {
        if (!State.debugMode) {
          return;
        }
        console.log(`${Config.SCRIPT_NAME} [DEBUG]`, ...args);
      } else {
        console[type](`${Config.SCRIPT_NAME}`, ...args);
      }
      if (State.UI && State.UI.logPanel) {
        const messageText = args.join(" ");
        const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
        const debugPrefix = type === "debug" ? '<span style="color: #34c759;">[DEBUG]</span> ' : "";
        const firstChild = State.UI.logPanel.firstChild;
        if (firstChild && firstChild.dataset.rawText === messageText) {
          const count = parseInt(firstChild.dataset.count || "1") + 1;
          firstChild.dataset.count = count.toString();
          const timeSpan = firstChild.querySelector(".log-timestamp");
          if (timeSpan) timeSpan.textContent = `[${timestamp}]`;
          const contentSpan = firstChild.querySelector(".log-content");
          if (contentSpan) {
            contentSpan.innerHTML = `${debugPrefix}${messageText} <span style="color: #007aff; font-weight: bold; margin-left: 4px;">(x${count})</span>`;
          }
        } else {
          const logEntry = document.createElement("div");
          logEntry.style.cssText = "padding: 2px 4px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); font-size: 11px;";
          logEntry.dataset.rawText = messageText;
          logEntry.dataset.count = "1";
          if (type === "error") {
            logEntry.style.color = "#ff3b30";
            logEntry.style.fontWeight = "500";
          } else if (type === "warn") {
            logEntry.style.color = "#ff9500";
            logEntry.style.fontWeight = "500";
          } else if (type === "debug") {
            logEntry.style.color = "#8e8e93";
          } else {
            logEntry.style.color = "#f5f5f7";
          }
          logEntry.innerHTML = `<span class="log-timestamp" style="color: rgba(255, 255, 255, 0.4);">[${timestamp}]</span> <span class="log-content">${debugPrefix}${messageText}</span>`;
          State.UI.logPanel.prepend(logEntry);
        }
        while (State.UI.logPanel.children.length > 100) {
          State.UI.logPanel.removeChild(State.UI.logPanel.lastChild);
        }
      }
    }, "logger"),
    getText: /* @__PURE__ */ __name((key, ...args) => {
      let text = Config.TEXTS[State.lang]?.[key] || Config.TEXTS["en"]?.[key] || key;
      if (args.length > 0) {
        if (typeof args[0] === "object" && args[0] !== null) {
          const replacements = args[0];
          for (const placeholder in replacements) {
            text = text.replace(`%${placeholder}%`, replacements[placeholder]);
          }
        } else {
          args.forEach((arg, index) => {
            text = text.replace(new RegExp(`\\{${index}\\}`, "g"), arg);
          });
        }
      }
      return text;
    }, "getText"),
    detectLanguage: /* @__PURE__ */ __name(() => {
      const oldLang = State.lang;
      State.lang = window.location.href.includes("/zh-cn/") ? "zh" : navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
      Utils.logger("debug", `\u8BED\u8A00\u68C0\u6D4B: \u5730\u5740=${window.location.href}, \u68C0\u6D4B\u5230\u8BED\u8A00=${State.lang}${oldLang !== State.lang ? ` (\u4ECE${oldLang}\u5207\u6362)` : ""}`);
      if (oldLang !== State.lang && State.UI && State.UI.container && UI) {
        Utils.logger("info", `\u8BED\u8A00\u5DF2\u5207\u6362\u5230${State.lang}\uFF0C\u6B63\u5728\u66F4\u65B0\u754C\u9762...`);
        UI.update();
      }
    }, "detectLanguage"),
    waitForElement: /* @__PURE__ */ __name((selector, timeout = 5e3) => {
      return new Promise((resolve, reject) => {
        const interval = setInterval(() => {
          const element = document.querySelector(selector);
          if (element) {
            clearInterval(interval);
            resolve(element);
          }
        }, 100);
        setTimeout(() => {
          clearInterval(interval);
          reject(new Error(`Timeout waiting for selector: ${selector}`));
        }, timeout);
      });
    }, "waitForElement"),
    waitForButtonEnabled: /* @__PURE__ */ __name((button, timeout = 5e3) => {
      return new Promise((resolve, reject) => {
        const interval = setInterval(() => {
          if (button && !button.disabled) {
            clearInterval(interval);
            resolve();
          }
        }, 100);
        setTimeout(() => {
          clearInterval(interval);
          reject(new Error("Timeout waiting for button to be enabled."));
        }, timeout);
      });
    }, "waitForButtonEnabled"),
    // This function is now for UI display purposes only.
    getDisplayPageFromUrl: /* @__PURE__ */ __name((url) => {
      if (!url) return "1";
      try {
        const urlParams = new URLSearchParams(new URL(url).search);
        const cursor = urlParams.get("cursor");
        if (!cursor) return "1";
        if (cursor.startsWith("bz")) {
          const decoded = atob(cursor);
          const offsetMatch = decoded.match(/o=(\d+)/);
          if (offsetMatch && offsetMatch[1]) {
            const offset = parseInt(offsetMatch[1], 10);
            const pageSize = 24;
            const pageNum = Math.round(offset / pageSize + 1);
            return pageNum.toString();
          }
        }
        return "Cursor Mode";
      } catch (e) {
        return "...";
      }
    }, "getDisplayPageFromUrl"),
    getCookie: /* @__PURE__ */ __name((name) => {
      const value = `; ${document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) return parts.pop().split(";").shift();
      return null;
    }, "getCookie"),
    // Simulates a more forceful click by dispatching mouse events, which can succeed
    // where a simple .click() is ignored by a framework's event handling.
    deepClick: /* @__PURE__ */ __name((element) => {
      if (!element) return;
      try {
        element.focus();
      } catch (e) {
      }
      setTimeout(() => {
        const pageWindow = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
        Utils.logger("info", `Performing deep click on element: <${element.tagName.toLowerCase()} class="${element.className}">`);
        const eventOptions = { view: pageWindow, bubbles: true, cancelable: true, composed: true };
        element.dispatchEvent(new PointerEvent("pointerdown", eventOptions));
        element.dispatchEvent(new MouseEvent("mousedown", eventOptions));
        element.dispatchEvent(new PointerEvent("pointerup", eventOptions));
        element.dispatchEvent(new MouseEvent("mouseup", eventOptions));
        element.click();
      }, 50);
    }, "deepClick"),
    cleanup: /* @__PURE__ */ __name(() => {
      if (State.watchdogTimer) {
        clearInterval(State.watchdogTimer);
        State.watchdogTimer = null;
      }
      State.valueChangeListeners.forEach((id) => {
        try {
          GM_removeValueChangeListener(id);
        } catch (e) {
        }
      });
      State.valueChangeListeners = [];
    }, "cleanup"),
    // ... (existing helper methods) ...
    // 添加游标解码函数
    decodeCursor: /* @__PURE__ */ __name((cursor) => {
      if (!cursor) return Utils.getText("no_saved_position");
      try {
        const decoded = atob(cursor);
        let match;
        if (decoded.includes("&p=")) {
          match = decoded.match(/&p=([^&]+)/);
        } else if (decoded.startsWith("p=")) {
          match = decoded.match(/p=([^&]+)/);
        }
        if (match && match[1]) {
          let itemName = decodeURIComponent(match[1].replace(/\+/g, " "));
          if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(itemName)) {
            try {
              const date = new Date(itemName);
              const dateStr = date.toLocaleDateString();
              const timeStr = date.toLocaleTimeString([], { hour12: false });
              itemName = `${dateStr} ${timeStr}`;
            } catch (e) {
            }
          }
          return `${Utils.getText("position_label")}: "${itemName}"`;
        }
        return `${Utils.getText("position_label")}: (Unknown)`;
      } catch (e) {
        Utils.logger("error", `Cursor decode failed: ${e.message}`);
        return `${Utils.getText("position_label")}: (Invalid)`;
      }
    }, "decodeCursor"),
    // Helper to extract just the item name from cursor
    getCursorItemName: /* @__PURE__ */ __name((cursor) => {
      if (!cursor) return null;
      try {
        const decoded = atob(cursor);
        let match;
        if (decoded.includes("&p=")) {
          match = decoded.match(/&p=([^&]+)/);
        } else if (decoded.startsWith("p=")) {
          match = decoded.match(/p=([^&]+)/);
        }
        if (match && match[1]) {
          return decodeURIComponent(match[1].replace(/\+/g, " "));
        }
      } catch (e) {
      }
      return null;
    }, "getCursorItemName"),
    // 账号验证函数 - silent模式用于初始化时的检查,不弹出警告
    checkAuthentication: /* @__PURE__ */ __name((silent = false) => {
      const csrfToken = Utils.getCookie("fab_csrftoken");
      if (!csrfToken) {
        if (!silent) {
          Utils.logger("error", Utils.getText("auth_error"));
          if (State.isExecuting) {
            State.isExecuting = false;
            GM_setValue(Config.DB_KEYS.IS_EXECUTING, false);
          }
          if (State.UI && State.UI.execBtn) {
            State.UI.execBtn.textContent = Utils.getText("execute");
            State.UI.execBtn.disabled = true;
          }
          alert(Utils.getText("auth_error_alert"));
        }
        State.isAuthenticated = false;
        return false;
      }
      State.isAuthenticated = true;
      return true;
    }, "checkAuthentication"),
    // 从当前页面的 SSR 数据里同步读取登录态。fab.com 在每个页面都内嵌了:
    //   1. window._epicAccountId        — 已登录是真实 UUID,未登录是空字符串
    //   2. <script id="js-json-data-prefetched-data"> 里 "/i/users/me".isAnonymous
    //   3. 同一块 JSON 里的 result UUID — 全零(00000000-...)也代表匿名
    // 三者任一明确指向匿名 → 返回 false;任一明确指向已登录 → 返回 true;
    // 都拿不到 → 返回 null,留给调用方决定要不要走 API 兜底。
    detectLoginFromPage: /* @__PURE__ */ __name(() => {
      const ZERO_UUID = "00000000-0000-0000-0000-000000000000";
      try {
        if (typeof window !== "undefined" && Object.prototype.hasOwnProperty.call(window, "_epicAccountId")) {
          const id = window._epicAccountId;
          if (typeof id === "string") {
            if (id === "" || id === ZERO_UUID) return false;
            if (/^[0-9a-f-]{32,36}$/i.test(id)) return true;
          }
        }
      } catch (e) {
      }
      try {
        const tag = document && document.getElementById && document.getElementById("js-json-data-prefetched-data");
        if (tag && tag.textContent) {
          const data = JSON.parse(tag.textContent);
          const userInfo = data && data["/i/users/me"];
          if (userInfo) {
            if (userInfo.isAnonymous === true) return false;
            if (userInfo.isAnonymous === false) return true;
            if (typeof userInfo.result === "string") {
              if (userInfo.result === ZERO_UUID) return false;
              if (/^[0-9a-f-]{32,36}$/i.test(userInfo.result)) return true;
            }
          }
        }
      } catch (e) {
      }
      return null;
    }, "detectLoginFromPage"),
    // 校验登录态。优先用页面 SSR 数据(同步、零网络、零 Cloudflare 风险),
    // 拿不到再退回 /i/users/me API 探测。结果缓存到 State.isAuthenticated。
    verifyServerSession: /* @__PURE__ */ __name(() => {
      const csrfToken = Utils.getCookie("fab_csrftoken");
      if (!csrfToken) {
        State.isAuthenticated = false;
        return Promise.resolve(false);
      }
      const fromPage = Utils.detectLoginFromPage();
      if (fromPage === true) {
        State.isAuthenticated = true;
        return Promise.resolve(true);
      }
      if (fromPage === false) {
        State.isAuthenticated = false;
        return Promise.resolve(false);
      }
      return new Promise((resolve) => {
        try {
          GM_xmlhttpRequest({
            method: "GET",
            url: "https://www.fab.com/i/users/me",
            headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" },
            anonymous: false,
            timeout: 5e3,
            onload: /* @__PURE__ */ __name((response) => {
              if (response.status === 401 || response.status === 403) {
                State.isAuthenticated = false;
                resolve(false);
                return;
              }
              if (response.status >= 200 && response.status < 300) {
                try {
                  const data = JSON.parse(response.responseText);
                  if (data && (data.id || data.uid || data.email || data.username || data.isAnonymous === false)) {
                    State.isAuthenticated = true;
                    resolve(true);
                    return;
                  }
                } catch (e) {
                }
                State.isAuthenticated = false;
                resolve(false);
                return;
              }
              resolve(State.isAuthenticated);
            }, "onload"),
            onerror: /* @__PURE__ */ __name(() => resolve(State.isAuthenticated), "onerror"),
            ontimeout: /* @__PURE__ */ __name(() => resolve(State.isAuthenticated), "ontimeout")
          });
        } catch (e) {
          resolve(State.isAuthenticated);
        }
      });
    }, "verifyServerSession"),
    // 强校验:cookie + 服务端 session 一起验。返回 boolean。
    // notify=true 时对用户提示并停止当前执行。
    ensureAuthenticated: /* @__PURE__ */ __name(async (notify = true) => {
      if (!Utils.checkAuthentication(true)) {
        if (notify) Utils.notifyAuthFailure();
        return false;
      }
      const sessionOk = await Utils.verifyServerSession();
      if (!sessionOk) {
        if (notify) Utils.notifyAuthFailure();
        return false;
      }
      return true;
    }, "ensureAuthenticated"),
    notifyAuthFailure: /* @__PURE__ */ __name(() => {
      Utils.logger("error", Utils.getText("auth_error"));
      if (State.isExecuting) {
        State.isExecuting = false;
        try {
          GM_setValue(Config.DB_KEYS.IS_EXECUTING, false);
        } catch (e) {
        }
      }
      if (State.UI && State.UI.execBtn) {
        State.UI.execBtn.textContent = Utils.getText("execute");
        State.UI.execBtn.disabled = true;
      }
      try {
        alert(Utils.getText("auth_error_alert"));
      } catch (e) {
      }
    }, "notifyAuthFailure"),
    // 将所有空白字符(包括换行、多个空格)统一替换为单个空格
    normalizeWhitespace: /* @__PURE__ */ __name((text) => {
      if (!text) return "";
      return text.replace(/\s+/g, " ").trim();
    }, "normalizeWhitespace"),
    // Broadened to find more clickable elements including inputs and divs/spans that look like buttons.
    // Also traverses into same-origin iframes.
    findAllButtonsWithShadow: /* @__PURE__ */ __name((root = document) => {
      const interactables = [];
      const visitedIframes = /* @__PURE__ */ new WeakSet();
      const traverse = /* @__PURE__ */ __name((node) => {
        if (!node) return;
        if (node.nodeType === 1) {
          if (node.shadowRoot) {
            traverse(node.shadowRoot);
          }
          if (node.tagName === "IFRAME" && !visitedIframes.has(node)) {
            visitedIframes.add(node);
            try {
              const iframeDoc = node.contentDocument || node.contentWindow?.document;
              if (iframeDoc) {
                Utils.logger("debug", `Traversing iframe: ${node.src || "(inline)"}`);
                traverse(iframeDoc.body || iframeDoc);
              }
            } catch (e) {
              Utils.logger("debug", `Cannot access cross-origin iframe: ${node.src}`);
            }
          }
          const tagName = node.tagName;
          const role = node.getAttribute && node.getAttribute("role");
          const type = node.getAttribute && node.getAttribute("type");
          const className = node.className && typeof node.className === "string" ? node.className : "";
          if (tagName === "BUTTON") {
            interactables.push(node);
          } else if (tagName === "A" && (role === "button" || className.includes("btn") || className.includes("button"))) {
            interactables.push(node);
          } else if (tagName === "INPUT" && (type === "submit" || type === "button" || type === "reset")) {
            interactables.push(node);
          } else if (role === "button" || className.includes("payment-order-confirm__btn") || className.includes("place-order")) {
            interactables.push(node);
          }
        }
        let child = node.firstChild;
        while (child) {
          traverse(child);
          child = child.nextSibling;
        }
      }, "traverse");
      traverse(root);
      if (root === document) {
        try {
          const allIframes = document.querySelectorAll("iframe");
          allIframes.forEach((iframe) => {
            if (!visitedIframes.has(iframe)) {
              visitedIframes.add(iframe);
              try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
                if (iframeDoc) {
                  Utils.logger("debug", `Top-level iframe search: ${iframe.src || "(inline)"}`);
                  traverse(iframeDoc.body || iframeDoc);
                }
              } catch (e) {
              }
            }
          });
        } catch (e) {
        }
      }
      return interactables;
    }, "findAllButtonsWithShadow")
  };

  // src/modules/page-diagnostics.js
  var PageDiagnostics = {
    // 诊断商品详情页面状态
    diagnoseDetailPage: /* @__PURE__ */ __name(() => {
      const report = {
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        url: window.location.href,
        pageTitle: document.title,
        buttons: [],
        licenseOptions: [],
        priceInfo: {},
        ownedStatus: {},
        dynamicContent: {}
      };
      const buttons = document.querySelectorAll("button");
      buttons.forEach((btn, index) => {
        const text = btn.textContent?.trim();
        const isVisible = btn.offsetParent !== null;
        const isDisabled = btn.disabled;
        const classes = btn.className;
        if (text) {
          report.buttons.push({
            index,
            text,
            isVisible,
            isDisabled,
            classes,
            hasClickHandler: btn.onclick !== null
          });
        }
      });
      const licenseElements = document.querySelectorAll('[class*="license"], [class*="License"], [role="option"]');
      licenseElements.forEach((elem, index) => {
        const text = elem.textContent?.trim();
        const isVisible = elem.offsetParent !== null;
        if (text) {
          report.licenseOptions.push({
            index,
            text,
            isVisible,
            tagName: elem.tagName,
            classes: elem.className,
            role: elem.getAttribute("role")
          });
        }
      });
      const priceElements = document.querySelectorAll('[class*="price"], [class*="Price"]');
      priceElements.forEach((elem, index) => {
        const text = elem.textContent?.trim();
        if (text) {
          report.priceInfo[`price_${index}`] = {
            text,
            isVisible: elem.offsetParent !== null,
            classes: elem.className
          };
        }
      });
      const ownedElements = document.querySelectorAll('h2, [class*="owned"], [class*="library"]');
      ownedElements.forEach((elem, index) => {
        const text = elem.textContent?.trim();
        if (text && (text.includes("\u5E93") || text.includes("Library") || text.includes("\u62E5\u6709") || text.includes("Owned"))) {
          report.ownedStatus[`owned_${index}`] = {
            text,
            isVisible: elem.offsetParent !== null,
            tagName: elem.tagName,
            classes: elem.className
          };
        }
      });
      return report;
    }, "diagnoseDetailPage"),
    // 输出诊断报告到日志
    logDiagnosticReport: /* @__PURE__ */ __name((report) => {
      console.group("=== Fab Helper \u9875\u9762\u72B6\u6001\u8BCA\u65AD\u62A5\u544A ===");
      console.log(`\u9875\u9762: ${report.url}`);
      console.log(`\u6807\u9898: ${report.pageTitle}`);
      const visibleButtons = report.buttons.filter((b) => b.isVisible);
      if (visibleButtons.length > 0) {
        console.log(`--- \u53EF\u89C1\u6309\u94AE (${visibleButtons.length}) ---`);
        visibleButtons.forEach((btn) => {
          console.log(`  [${btn.index}] "${btn.text}" (\u7981\u7528: ${btn.isDisabled}, \u7C7B: ${btn.classes.split(" ").slice(0, 2).join(" ")}...)`);
        });
      }
      const visibleLicenses = report.licenseOptions.filter((l) => l.isVisible);
      if (visibleLicenses.length > 0) {
        console.log(`--- \u53EF\u89C1\u8BB8\u53EF\u9009\u9879 (${visibleLicenses.length}) ---`);
        visibleLicenses.forEach((opt) => {
          console.log(`  [${opt.index}] "${opt.text}" (\u6807\u7B7E: ${opt.tagName}, \u89D2\u8272: ${opt.role || "\u65E0"})`);
        });
      }
      const visiblePrices = Object.values(report.priceInfo).filter((p) => p.isVisible);
      if (visiblePrices.length > 0) {
        console.log(`--- \u4EF7\u683C\u4FE1\u606F (${visiblePrices.length}) ---`);
        visiblePrices.forEach((p) => console.log(`  \u4EF7\u683C: "${p.text}"`));
      }
      const visibleOwned = Object.values(report.ownedStatus).filter((s) => s.isVisible);
      if (visibleOwned.length > 0) {
        console.log(`--- \u62E5\u6709\u72B6\u6001 (${visibleOwned.length}) ---`);
        visibleOwned.forEach((s) => console.log(`  \u72B6\u6001: "${s.text}"`));
      }
      console.groupEnd();
    }, "logDiagnosticReport")
  };

  // src/modules/data-cache.js
  var DataCache = {
    // 商品数据缓存 - 键为商品ID,值为商品数据
    listings: /* @__PURE__ */ new Map(),
    // 拥有状态缓存 - 键为商品ID,值为拥有状态对象
    ownedStatus: /* @__PURE__ */ new Map(),
    // 价格缓存 - 键为报价ID,值为价格信息对象
    prices: /* @__PURE__ */ new Map(),
    // Maps Fab offer ids back to listing uids so done listings can skip price requests.
    offerListingUids: /* @__PURE__ */ new Map(),
    // 等待网页原生请求更新的UID列表
    waitingList: /* @__PURE__ */ new Set(),
    // 缓存时间戳 - 用于判断缓存是否过期
    timestamps: {
      listings: /* @__PURE__ */ new Map(),
      ownedStatus: /* @__PURE__ */ new Map(),
      prices: /* @__PURE__ */ new Map()
    },
    // 缓存有效期(毫秒)
    TTL: 5 * 60 * 1e3,
    // 5分钟
    // 检查缓存是否有效
    isValid: /* @__PURE__ */ __name(function(type, key) {
      const timestamp = this.timestamps[type].get(key);
      return timestamp && Date.now() - timestamp < this.TTL;
    }, "isValid"),
    // 保存商品数据到缓存
    saveListings: /* @__PURE__ */ __name(function(items) {
      if (!Array.isArray(items)) return;
      const now = Date.now();
      items.forEach((item) => {
        if (item && item.uid) {
          this.listings.set(item.uid, item);
          this.timestamps.listings.set(item.uid, now);
          this.extractOfferIds(item).forEach((offerId) => {
            this.offerListingUids.set(offerId, item.uid);
          });
        }
      });
    }, "saveListings"),
    extractOfferIds: /* @__PURE__ */ __name(function(item) {
      const offerIds = /* @__PURE__ */ new Set();
      const add = /* @__PURE__ */ __name((value) => {
        if (typeof value === "string" && value) offerIds.add(value);
      }, "add");
      add(item.offerId);
      add(item.offer_id);
      add(item.defaultOfferId);
      add(item.default_offer_id);
      add(item.offer?.offerId);
      add(item.offer?.offer_id);
      add(item.defaultOffer?.offerId);
      add(item.defaultOffer?.offer_id);
      add(item.startingPrice?.offerId);
      add(item.startingPrice?.offer_id);
      add(item.starting_price?.offerId);
      add(item.starting_price?.offer_id);
      add(item.price?.offerId);
      add(item.price?.offer_id);
      add(item.priceInfo?.offerId);
      add(item.priceInfo?.offer_id);
      if (Array.isArray(item.offers)) {
        item.offers.forEach((offer) => {
          add(offer?.offerId);
          add(offer?.offer_id);
        });
      }
      if (Array.isArray(item.licenses)) {
        item.licenses.forEach((license) => {
          add(license?.offerId);
          add(license?.offer_id);
          add(license?.offer?.offerId);
          add(license?.offer?.offer_id);
        });
      }
      return Array.from(offerIds);
    }, "extractOfferIds"),
    getListingUidForOffer: /* @__PURE__ */ __name(function(offerId) {
      return this.offerListingUids.get(offerId) || "";
    }, "getListingUidForOffer"),
    // 添加到等待列表
    addToWaitingList: /* @__PURE__ */ __name(function(uids) {
      if (!uids || !Array.isArray(uids)) return;
      uids.forEach((uid) => this.waitingList.add(uid));
      Utils.logger("debug", `[Cache] ${Utils.getText("fab_dom_add_to_waitlist", uids.length, this.waitingList.size)}`);
    }, "addToWaitingList"),
    // 检查并从等待列表中移除
    checkWaitingList: /* @__PURE__ */ __name(function() {
      if (this.waitingList.size === 0) return;
      let removedCount = 0;
      for (const uid of this.waitingList) {
        if (this.ownedStatus.has(uid)) {
          this.waitingList.delete(uid);
          removedCount++;
        }
      }
      if (removedCount > 0) {
        Utils.logger("info", `[Cache] \u4ECE\u7B49\u5F85\u5217\u8868\u4E2D\u79FB\u9664\u4E86 ${removedCount} \u4E2A\u5DF2\u66F4\u65B0\u7684\u5546\u54C1ID\uFF0C\u5269\u4F59: ${this.waitingList.size}`);
      }
    }, "checkWaitingList"),
    // 保存拥有状态到缓存
    saveOwnedStatus: /* @__PURE__ */ __name(function(states) {
      if (!Array.isArray(states)) return;
      const now = Date.now();
      states.forEach((state) => {
        if (state && state.uid) {
          this.ownedStatus.set(state.uid, {
            acquired: !!state.acquired,
            lastUpdatedAt: state.lastUpdatedAt || (/* @__PURE__ */ new Date()).toISOString(),
            uid: state.uid
          });
          this.timestamps.ownedStatus.set(state.uid, now);
          if (this.waitingList.has(state.uid)) {
            this.waitingList.delete(state.uid);
          }
        }
      });
      if (states.length > 0) {
        this.checkWaitingList();
      }
    }, "saveOwnedStatus"),
    // 保存价格信息到缓存
    savePrices: /* @__PURE__ */ __name(function(offers) {
      if (!Array.isArray(offers)) return;
      const now = Date.now();
      offers.forEach((offer) => {
        if (offer && offer.offerId) {
          this.prices.set(offer.offerId, {
            offerId: offer.offerId,
            price: offer.price || 0,
            currencyCode: offer.currencyCode || "USD"
          });
          this.timestamps.prices.set(offer.offerId, now);
        }
      });
    }, "savePrices"),
    // 获取商品数据,如果缓存有效则使用缓存
    getListings: /* @__PURE__ */ __name(function(uids) {
      const result = [];
      const missing = [];
      uids.forEach((uid) => {
        if (this.isValid("listings", uid)) {
          result.push(this.listings.get(uid));
        } else {
          missing.push(uid);
        }
      });
      return { result, missing };
    }, "getListings"),
    // 获取拥有状态,如果缓存有效则使用缓存
    getOwnedStatus: /* @__PURE__ */ __name(function(uids) {
      const result = [];
      const missing = [];
      uids.forEach((uid) => {
        if (this.isValid("ownedStatus", uid)) {
          result.push(this.ownedStatus.get(uid));
        } else {
          missing.push(uid);
        }
      });
      return { result, missing };
    }, "getOwnedStatus"),
    // 获取价格信息,如果缓存有效则使用缓存
    getPrices: /* @__PURE__ */ __name(function(offerIds) {
      const result = [];
      const missing = [];
      offerIds.forEach((offerId) => {
        if (this.isValid("prices", offerId)) {
          result.push(this.prices.get(offerId));
        } else {
          missing.push(offerId);
        }
      });
      return { result, missing };
    }, "getPrices"),
    // 清理过期缓存
    cleanupExpired: /* @__PURE__ */ __name(function() {
      try {
        const now = Date.now();
        const cacheTypes = ["listings", "ownedStatus", "prices"];
        for (const type of cacheTypes) {
          for (const [key, timestamp] of this.timestamps[type].entries()) {
            if (now - timestamp > this.TTL) {
              this[type].delete(key);
              this.timestamps[type].delete(key);
            }
          }
        }
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("cache_cleanup_complete", this.listings.size, this.ownedStatus.size, this.prices.size));
        }
      } catch (e) {
        Utils.logger("error", `\u7F13\u5B58\u6E05\u7406\u5931\u8D25: ${e.message}`);
      }
    }, "cleanupExpired")
  };

  // src/modules/api.js
  var API = {
    gmFetch: /* @__PURE__ */ __name((options) => {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          anonymous: false,
          // Default to false to ensure cookies are sent
          ...options,
          onload: /* @__PURE__ */ __name((response) => resolve(response), "onload"),
          onerror: /* @__PURE__ */ __name((error) => reject(new Error(`GM_xmlhttpRequest error: ${error.statusText || "Unknown Error"}`)), "onerror"),
          ontimeout: /* @__PURE__ */ __name(() => reject(new Error("Request timed out.")), "ontimeout"),
          onabort: /* @__PURE__ */ __name(() => reject(new Error("Request aborted.")), "onabort")
        });
      });
    }, "gmFetch"),
    // 接口响应数据提取函数
    extractStateData: /* @__PURE__ */ __name((rawData, source = "") => {
      const dataType = Array.isArray(rawData) ? "Array" : typeof rawData;
      if (State.debugMode) {
        Utils.logger("debug", `[${source}] \u63A5\u53E3\u8FD4\u56DE\u6570\u636E\u7C7B\u578B: ${dataType}`);
      }
      if (Array.isArray(rawData)) {
        return rawData;
      }
      if (rawData && typeof rawData === "object") {
        const keys = Object.keys(rawData);
        if (State.debugMode) {
          Utils.logger("debug", `[${source}] \u63A5\u53E3\u8FD4\u56DE\u5BF9\u8C61\u952E: ${keys.join(", ")}`);
        }
        const possibleArrayFields = ["data", "results", "items", "listings", "states"];
        for (const field of possibleArrayFields) {
          if (rawData[field] && Array.isArray(rawData[field])) {
            Utils.logger("info", `[${source}] \u5728\u5B57\u6BB5 "${field}" \u4E2D\u627E\u5230\u6570\u7EC4\u6570\u636E`);
            return rawData[field];
          }
        }
        for (const key of keys) {
          if (Array.isArray(rawData[key])) {
            Utils.logger("info", `[${source}] \u5728\u5B57\u6BB5 "${key}" \u4E2D\u627E\u5230\u6570\u7EC4\u6570\u636E`);
            return rawData[key];
          }
        }
        if (rawData.uid && "acquired" in rawData) {
          Utils.logger("info", `[${source}] \u8FD4\u56DE\u7684\u662F\u5355\u4E2A\u9879\u76EE\u6570\u636E\uFF0C\u8F6C\u6362\u4E3A\u6570\u7EC4`);
          return [rawData];
        }
      }
      Utils.logger("warn", `[${source}] \u65E0\u6CD5\u4ECEAPI\u54CD\u5E94\u4E2D\u63D0\u53D6\u6570\u7EC4\u6570\u636E`);
      if (State.debugMode) {
        try {
          const preview = JSON.stringify(rawData).substring(0, 200);
          Utils.logger("debug", `[${source}] API\u54CD\u5E94\u9884\u89C8: ${preview}...`);
        } catch (e) {
          Utils.logger("debug", `[${source}] \u65E0\u6CD5\u5E8F\u5217\u5316API\u54CD\u5E94: ${e.message}`);
        }
      }
      return [];
    }, "extractStateData"),
    // 优化后的商品拥有状态检查函数 - 只使用缓存和网页原生请求的数据
    checkItemsOwnership: /* @__PURE__ */ __name(async function(uids) {
      if (!uids || uids.length === 0) return [];
      try {
        const { result: cachedResults, missing: missingUids } = DataCache.getOwnedStatus(uids);
        if (missingUids.length > 0) {
          Utils.logger("debug", Utils.getText("fab_dom_unknown_status", missingUids.length));
          DataCache.addToWaitingList(missingUids);
        }
        return cachedResults;
      } catch (e) {
        Utils.logger("error", `\u68C0\u67E5\u62E5\u6709\u72B6\u6001\u5931\u8D25: ${e.message}`);
        return [];
      }
    }, "checkItemsOwnership"),
    // 优化后的价格验证函数
    checkItemsPrices: /* @__PURE__ */ __name(async function(offerIds) {
      if (!offerIds || offerIds.length === 0) return [];
      try {
        const { result: cachedResults, missing: missingOfferIds } = DataCache.getPrices(offerIds);
        if (missingOfferIds.length === 0) {
          if (State.debugMode) {
            Utils.logger("info", `\u4F7F\u7528\u7F13\u5B58\u7684\u4EF7\u683C\u6570\u636E\uFF0C\u907F\u514DAPI\u8BF7\u6C42`);
          }
          return cachedResults;
        }
        if (State.debugMode) {
          Utils.logger("info", `\u5BF9 ${missingOfferIds.length} \u4E2A\u7F3A\u5931\u7684\u62A5\u4EF7ID\u53D1\u9001API\u8BF7\u6C42`);
        }
        const csrfToken = Utils.getCookie("fab_csrftoken");
        if (!csrfToken) {
          Utils.checkAuthentication();
          throw new Error("CSRF token not found");
        }
        const pricesUrl = new URL("https://www.fab.com/i/listings/prices-infos");
        missingOfferIds.forEach((offerId) => pricesUrl.searchParams.append("offer_ids", offerId));
        const response = await this.gmFetch({
          method: "GET",
          url: pricesUrl.href,
          headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" }
        });
        try {
          const pricesData = JSON.parse(response.responseText);
          if (pricesData.offers && Array.isArray(pricesData.offers)) {
            DataCache.savePrices(pricesData.offers);
            return [...cachedResults, ...pricesData.offers];
          }
        } catch (e) {
          Utils.logger("error", `[\u4F18\u5316] \u89E3\u6790\u4EF7\u683CAPI\u54CD\u5E94\u5931\u8D25: ${e.message}`);
        }
        return cachedResults;
      } catch (e) {
        Utils.logger("error", `[\u4F18\u5316] \u83B7\u53D6\u4EF7\u683C\u4FE1\u606F\u5931\u8D25: ${e.message}`);
        return [];
      }
    }, "checkItemsPrices")
  };

  // src/modules/database.js
  var UI2 = null;
  var setUIReference2 = /* @__PURE__ */ __name((uiModule) => {
    UI2 = uiModule;
  }, "setUIReference");
  var Database = {
    normalizeListingUrl: /* @__PURE__ */ __name((url) => {
      if (!url) return "";
      const cleanUrl = String(url).split("?")[0].toLowerCase();
      const uidMatch = cleanUrl.match(/\/listings\/([^/?#]+)/i);
      if (uidMatch && uidMatch[1]) {
        return `https://www.fab.com/listings/${uidMatch[1].toLowerCase()}`;
      }
      return cleanUrl;
    }, "normalizeListingUrl"),
    getListingUid: /* @__PURE__ */ __name((url) => {
      if (!url) return "";
      const uidMatch = String(url).split("?")[0].match(/\/listings\/([^/?#]+)/i);
      return uidMatch && uidMatch[1] ? uidMatch[1].toLowerCase() : "";
    }, "getListingUid"),
    seedDoneOwnedStatusCache: /* @__PURE__ */ __name(() => {
      const states = State.db.done.map((url) => Database.getListingUid(url)).filter(Boolean).map((uid) => ({
        uid,
        acquired: true,
        lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
      }));
      DataCache.saveOwnedStatus(states);
      return states.length;
    }, "seedDoneOwnedStatusCache"),
    normalizeDoneList: /* @__PURE__ */ __name(() => {
      const normalized = [];
      const seen = /* @__PURE__ */ new Set();
      State.db.done.forEach((url) => {
        const cleanUrl = Database.normalizeListingUrl(url);
        if (!cleanUrl || seen.has(cleanUrl)) return;
        seen.add(cleanUrl);
        normalized.push(cleanUrl);
      });
      const changed = normalized.length !== State.db.done.length || normalized.some((url, index) => url !== State.db.done[index]);
      State.db.done = normalized;
      return changed;
    }, "normalizeDoneList"),
    addDoneUrl: /* @__PURE__ */ __name((url) => {
      const normalizedChanged = Database.normalizeDoneList();
      const cleanUrl = Database.normalizeListingUrl(url);
      if (!cleanUrl) return normalizedChanged;
      if (Database.isDone(cleanUrl)) return normalizedChanged;
      State.db.done.push(cleanUrl);
      Database.seedDoneOwnedStatusCache();
      return true;
    }, "addDoneUrl"),
    load: /* @__PURE__ */ __name(async () => {
      State.db.todo = await GM_getValue(Config.DB_KEYS.TODO, []);
      State.db.done = await GM_getValue(Config.DB_KEYS.DONE, []);
      State.db.failed = await GM_getValue(Config.DB_KEYS.FAILED, []);
      State.hideSaved = await GM_getValue(Config.DB_KEYS.HIDE, false);
      State.autoAddOnScroll = await GM_getValue(Config.DB_KEYS.AUTO_ADD, false);
      State.rememberScrollPosition = await GM_getValue(Config.DB_KEYS.REMEMBER_POS, false);
      State.autoResumeAfter429 = await GM_getValue(Config.DB_KEYS.AUTO_RESUME, false);
      State.autoRefreshEmptyPage = await GM_getValue(Config.DB_KEYS.AUTO_REFRESH_EMPTY, true);
      State.hideDiscountedPaid = await GM_getValue(Config.DB_KEYS.HIDE_DISCOUNTED, false);
      State.hidePaid = await GM_getValue(Config.DB_KEYS.HIDE_PAID, false);
      State.blockLargeResources = await GM_getValue(Config.DB_KEYS.BLOCK_RESOURCES, true);
      State.debugMode = await GM_getValue("fab_helper_debug_mode", false);
      State.currentSortOption = await GM_getValue("fab_helper_sort_option", "title_desc");
      State.isExecuting = await GM_getValue(Config.DB_KEYS.IS_EXECUTING, false);
      State.statusHistory = await GM_getValue(Config.DB_KEYS.STATUS_HISTORY, []);
      if (Database.normalizeDoneList()) {
        await Database.saveDone();
      }
      Database.seedDoneOwnedStatusCache();
      Utils.logger("info", Utils.getText("log_db_loaded"), `(Session) To-Do: ${State.db.todo.length}, Done: ${State.db.done.length}, Failed: ${State.db.failed.length}`);
    }, "load"),
    // 添加保存待办列表的方法
    saveTodo: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.TODO, State.db.todo), "saveTodo"),
    saveDone: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.DONE, State.db.done), "saveDone"),
    saveFailed: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.FAILED, State.db.failed), "saveFailed"),
    saveHidePref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.HIDE, State.hideSaved), "saveHidePref"),
    saveAutoAddPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.AUTO_ADD, State.autoAddOnScroll), "saveAutoAddPref"),
    // Save the setting
    saveRememberPosPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.REMEMBER_POS, State.rememberScrollPosition), "saveRememberPosPref"),
    saveAutoResumePref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.AUTO_RESUME, State.autoResumeAfter429), "saveAutoResumePref"),
    saveAutoRefreshEmptyPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.AUTO_REFRESH_EMPTY, State.autoRefreshEmptyPage), "saveAutoRefreshEmptyPref"),
    // 保存无商品自动刷新设置
    saveHideDiscountedPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.HIDE_DISCOUNTED, State.hideDiscountedPaid), "saveHideDiscountedPref"),
    // 保存隐藏打折付费设置
    saveHidePaidPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.HIDE_PAID, State.hidePaid), "saveHidePaidPref"),
    // 保存隐藏所有付费设置
    saveBlockResourcesPref: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.BLOCK_RESOURCES, State.blockLargeResources), "saveBlockResourcesPref"),
    // 保存禁用大资源设置
    saveExecutingState: /* @__PURE__ */ __name(() => GM_setValue(Config.DB_KEYS.IS_EXECUTING, State.isExecuting), "saveExecutingState"),
    // Save the execution state
    resetAllData: /* @__PURE__ */ __name(async () => {
      if (window.confirm(Utils.getText("confirm_clear_data"))) {
        await GM_deleteValue(Config.DB_KEYS.TODO);
        await GM_deleteValue(Config.DB_KEYS.DONE);
        await GM_deleteValue(Config.DB_KEYS.FAILED);
        await GM_deleteValue(Config.DB_KEYS.LAST_CURSOR);
        await GM_deleteValue(Config.DB_KEYS.HIDE_DISCOUNTED);
        await GM_deleteValue(Config.DB_KEYS.HIDE_PAID);
        await GM_deleteValue(Config.DB_KEYS.BLOCK_RESOURCES);
        State.db.todo = [];
        State.db.done = [];
        State.db.failed = [];
        State.savedCursor = null;
        State.blockLargeResources = true;
        Utils.logger("info", "\u6240\u6709\u811A\u672C\u6570\u636E\uFF08\u5305\u62EC\u6EDA\u52A8\u8BB0\u5FC6\u4E0E\u5927\u8D44\u6E90\u7981\u7528\u8BBE\u7F6E\uFF09\u5DF2\u91CD\u7F6E\u3002");
        if (UI2) {
          UI2.removeAllOverlays();
          UI2.update();
        }
      }
    }, "resetAllData"),
    isDone: /* @__PURE__ */ __name((url) => {
      if (!url) return false;
      const cleanUrl = Database.normalizeListingUrl(url);
      return State.db.done.some((doneUrl) => Database.normalizeListingUrl(doneUrl) === cleanUrl);
    }, "isDone"),
    isFailed: /* @__PURE__ */ __name((url) => {
      if (!url) return false;
      const cleanUrl = url.split("?")[0];
      return State.db.failed.some((task) => task.url === cleanUrl);
    }, "isFailed"),
    isTodo: /* @__PURE__ */ __name((url) => {
      if (!url) return false;
      const cleanUrl = url.split("?")[0];
      return State.db.todo.some((task) => task.url === cleanUrl);
    }, "isTodo"),
    markAsDone: /* @__PURE__ */ __name(async (task) => {
      if (!task || !task.uid) {
        Utils.logger("error", "\u6807\u8BB0\u4EFB\u52A1\u5B8C\u6210\u5931\u8D25\uFF0C\u6536\u5230\u65E0\u6548\u4EFB\u52A1:", JSON.stringify(task));
        return;
      }
      const initialTodoCount = State.db.todo.length;
      State.db.todo = State.db.todo.filter((t) => t.uid !== task.uid);
      if (State.db.todo.length !== initialTodoCount) {
        Database.saveTodo();
      }
      if (State.db.todo.length === initialTodoCount && initialTodoCount > 0) {
        Utils.logger("warn", "\u4EFB\u52A1\u672A\u80FD\u4ECE\u5F85\u529E\u5217\u8868\u4E2D\u79FB\u9664\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u5904\u7406");
      }
      let changed = false;
      const initialFailedCount = State.db.failed.length;
      State.db.failed = State.db.failed.filter((failedTask) => failedTask.uid !== task.uid);
      if (State.db.failed.length !== initialFailedCount) {
        changed = true;
        await Database.saveFailed();
      }
      if (Database.addDoneUrl(task.url)) {
        changed = true;
      }
      if (changed) {
        await Database.saveDone();
      }
    }, "markAsDone"),
    // 根据失败原因判定自动重试上限:
    //  - 人机验证/适配类:重试无意义且会招更多风控 → 0 次,直接进失败列表
    //  - 环境类(超时/被远程判死/唤醒清理):值得自动重试 → 3 次
    //  - 其他(可能只是页面没加载好):给 1 次机会
    getMaxRetry: /* @__PURE__ */ __name((reason) => {
      if (!reason) return 1;
      if (/人机验证|captcha|需要人工|verification/i.test(reason)) return 0;
      if (/超时|Watchdog|完成前关闭|closed before|Timeout|唤醒/i.test(reason)) return 3;
      return 1;
    }, "getMaxRetry"),
    // 返回 { retried: bool }。retried=true 表示已按归因自动放回待办重试,未计入最终失败;
    // 调用方据此决定是否累加 executionFailedTasks。
    markAsFailed: /* @__PURE__ */ __name(async (task, failureInfo = {}) => {
      if (!task || !task.uid) {
        Utils.logger("error", "\u6807\u8BB0\u4EFB\u52A1\u5931\u8D25\uFF0C\u6536\u5230\u65E0\u6548\u4EFB\u52A1:", JSON.stringify(task));
        return { retried: false };
      }
      const reason = failureInfo.reason || "\u672A\u77E5\u539F\u56E0";
      const nextRetry = (task.retryCount || 0) + 1;
      const maxRetry = Database.getMaxRetry(reason);
      State.db.todo = State.db.todo.filter((t) => t.uid !== task.uid);
      if (nextRetry <= maxRetry) {
        State.db.todo.push({ ...task, retryCount: nextRetry });
        Utils.logger("info", Utils.getText("log_auto_retry", nextRetry, maxRetry, task.name) + ` [${reason}]`);
        await Database.saveTodo();
        if (UI2) UI2.update();
        return { retried: true };
      }
      const failedTask = {
        ...task,
        failedAt: (/* @__PURE__ */ new Date()).toISOString(),
        failureReason: reason,
        errorDetails: failureInfo.details || null,
        workerLogs: failureInfo.logs || [],
        retryCount: nextRetry
      };
      Utils.logger("warn", `\u{1F4CB} \u4EFB\u52A1\u5931\u8D25\u8BE6\u60C5:`);
      Utils.logger("warn", `   - \u4EFB\u52A1\u540D\u79F0: ${task.name}`);
      Utils.logger("warn", `   - \u4EFB\u52A1UID: ${task.uid}`);
      Utils.logger("warn", `   - \u5931\u8D25\u539F\u56E0: ${failedTask.failureReason}`);
      Utils.logger("warn", `   - \u91CD\u8BD5\u6B21\u6570: ${failedTask.retryCount}`);
      if (failedTask.errorDetails) {
        Utils.logger("warn", `   - \u9519\u8BEF\u8BE6\u60C5: ${JSON.stringify(failedTask.errorDetails)}`);
      }
      if (failedTask.workerLogs && failedTask.workerLogs.length > 0) {
        Utils.logger("warn", `   - \u5DE5\u4F5C\u7EBF\u7A0B\u65E5\u5FD7 (${failedTask.workerLogs.length} \u6761):`);
        failedTask.workerLogs.slice(-5).forEach((log, i) => {
          Utils.logger("warn", `     ${i + 1}. ${log}`);
        });
      }
      const existingIndex = State.db.failed.findIndex((f) => f.uid === task.uid);
      if (existingIndex >= 0) {
        State.db.failed[existingIndex] = failedTask;
        Utils.logger("debug", `\u66F4\u65B0\u4E86\u5DF2\u5B58\u5728\u7684\u5931\u8D25\u8BB0\u5F55: ${task.name}`);
      } else {
        State.db.failed.push(failedTask);
      }
      await Database.saveTodo();
      await Database.saveFailed();
      return { retried: false };
    }, "markAsFailed")
  };

  // src/modules/rate-limit-manager.js
  var UI3 = null;
  var TaskRunner = null;
  var countdownRefresh = null;
  var setDependencies = /* @__PURE__ */ __name((deps) => {
    UI3 = deps.UI;
    TaskRunner = deps.TaskRunner;
    countdownRefresh = deps.countdownRefresh;
  }, "setDependencies");
  var RateLimitManager = {
    // 添加防止重复日志的变量
    _lastLogTime: 0,
    _lastLogType: null,
    _duplicateLogCount: 0,
    // 活跃心跳跟踪
    _lastActiveTime: 0,
    // 本实例上次写入活跃时间戳的时刻
    _trackingInterval: null,
    // 心跳 setInterval 句柄
    // 检查是否与最后一条记录重复
    isDuplicateRecord: /* @__PURE__ */ __name(function(newEntry) {
      if (State.statusHistory.length === 0) return false;
      const lastEntry = State.statusHistory[State.statusHistory.length - 1];
      if (lastEntry.type !== newEntry.type) return false;
      const lastTime = new Date(lastEntry.endTime).getTime();
      const newTime = new Date(newEntry.endTime).getTime();
      const timeDiff = Math.abs(newTime - lastTime);
      if (timeDiff < 1e4) {
        const durationDiff = Math.abs((lastEntry.duration || 0) - (newEntry.duration || 0));
        if (durationDiff < 5) {
          return true;
        }
      }
      return false;
    }, "isDuplicateRecord"),
    // 添加记录到历史,带去重检查
    addToHistory: /* @__PURE__ */ __name(async function(entry) {
      if (this.isDuplicateRecord(entry)) {
        Utils.logger("debug", `\u68C0\u6D4B\u5230\u91CD\u590D\u7684\u72B6\u6001\u8BB0\u5F55\uFF0C\u8DF3\u8FC7: ${entry.type} - ${entry.endTime}`);
        return false;
      }
      State.statusHistory.push(entry);
      if (State.statusHistory.length > 50) {
        State.statusHistory = State.statusHistory.slice(-50);
      }
      await GM_setValue(Config.DB_KEYS.STATUS_HISTORY, State.statusHistory);
      return true;
    }, "addToHistory"),
    // 进入限速状态
    enterRateLimitedState: /* @__PURE__ */ __name(async function(source = "\u672A\u77E5\u6765\u6E90") {
      if (State.appStatus === "RATE_LIMITED") {
        Utils.logger("info", Utils.getText("rate_limit_already_active", State.lastLimitSource, source));
        return false;
      }
      State.consecutiveSuccessCount = 0;
      State.lastLimitSource = source;
      const normalDuration = State.normalStartTime ? ((Date.now() - State.normalStartTime) / 1e3).toFixed(2) : "0.00";
      const logEntry = {
        type: "NORMAL",
        duration: parseFloat(normalDuration),
        requests: State.successfulSearchCount,
        endTime: (/* @__PURE__ */ new Date()).toISOString()
      };
      const wasAdded = await this.addToHistory(logEntry);
      if (wasAdded) {
        Utils.logger("error", Utils.getText("log_entering_rate_limit_from_v2", source, normalDuration, State.successfulSearchCount));
      } else {
        Utils.logger("debug", Utils.getText("duplicate_normal_status_detected", source));
      }
      State.appStatus = "RATE_LIMITED";
      State.rateLimitStartTime = Date.now();
      await GM_setValue(Config.DB_KEYS.APP_STATUS, {
        status: "RATE_LIMITED",
        startTime: State.rateLimitStartTime,
        source
      });
      if (UI3) {
        UI3.updateDebugTab();
        UI3.update();
      }
      const { visible: actualVisibleCards } = TaskRunner?.getCardCounts ? TaskRunner.getCardCounts() : { visible: document.querySelectorAll(Config.SELECTORS.card).length };
      const visibleCountElement = document.getElementById("fab-status-visible");
      if (visibleCountElement) {
        visibleCountElement.textContent = actualVisibleCards.toString();
      }
      if (State.db.todo.length > 0 || State.activeWorkers > 0 || actualVisibleCards > 0) {
        if (actualVisibleCards > 0) {
          Utils.logger("info", `\u68C0\u6D4B\u5230\u9875\u9762\u4E0A\u6709 ${actualVisibleCards} \u4E2A\u53EF\u89C1\u5546\u54C1\uFF0C\u6682\u4E0D\u81EA\u52A8\u5237\u65B0\u9875\u9762\u3002`);
          Utils.logger("info", "\u5F53\u4ECD\u6709\u53EF\u89C1\u5546\u54C1\u65F6\u4E0D\u89E6\u53D1\u81EA\u52A8\u5237\u65B0\uFF0C\u4EE5\u907F\u514D\u4E2D\u65AD\u6D4F\u89C8\u3002");
        } else {
          Utils.logger("info", `\u68C0\u6D4B\u5230\u6709 ${State.db.todo.length} \u4E2A\u5F85\u529E\u4EFB\u52A1\u548C ${State.activeWorkers} \u4E2A\u6D3B\u52A8\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u6682\u4E0D\u81EA\u52A8\u5237\u65B0\u9875\u9762\u3002`);
          Utils.logger("info", "\u8BF7\u624B\u52A8\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u4E9B\u4EFB\u52A1\u540E\u518D\u5237\u65B0\u9875\u9762\u3002");
        }
        Utils.logger("warn", "\u26A0\uFE0F \u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u4F46\u4E0D\u6EE1\u8DB3\u81EA\u52A8\u5237\u65B0\u6761\u4EF6\uFF0C\u8BF7\u5728\u9700\u8981\u65F6\u624B\u52A8\u5237\u65B0\u9875\u9762\u3002");
      } else if (State.autoRefreshEmptyPage) {
        const randomDelay = 45e3 + Math.random() * 15e3;
        if (State.autoResumeAfter429) {
          Utils.logger("info", Utils.getText("log_auto_resume_start", randomDelay ? (randomDelay / 1e3).toFixed(1) : "\u672A\u77E5"));
        } else {
          Utils.logger("info", Utils.getText("log_auto_resume_detect", randomDelay ? (randomDelay / 1e3).toFixed(1) : "\u672A\u77E5"));
        }
        if (countdownRefresh) {
          countdownRefresh(randomDelay, "429\u81EA\u52A8\u6062\u590D");
        }
      } else {
        Utils.logger("info", Utils.getText("auto_refresh_disabled_rate_limit"));
      }
      return true;
    }, "enterRateLimitedState"),
    // 记录成功请求
    recordSuccessfulRequest: /* @__PURE__ */ __name(async function(source = "\u672A\u77E5\u6765\u6E90", hasResults = true) {
      if (hasResults) {
        State.successfulSearchCount++;
        if (UI3) UI3.updateDebugTab();
      }
      if (State.appStatus !== "RATE_LIMITED") {
        return;
      }
      if (State.rateLimitStartTime) {
        const timeSinceRateLimit = Date.now() - State.rateLimitStartTime;
        if (timeSinceRateLimit < 4e4) {
          Utils.logger("debug", `\u5904\u4E8E\u9650\u901F\u5F3A\u5236\u51B7\u9759\u671F (${(timeSinceRateLimit / 1e3).toFixed(1)}s < 40s)\uFF0C\u5FFD\u7565\u6B64\u6B21\u6210\u529F\u8BF7\u6C42\u3002`);
          return;
        }
      }
      if (!hasResults) {
        Utils.logger("debug", `\u8BF7\u6C42\u6210\u529F\u4F46\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7ED3\u679C\uFF0C\u4E0D\u8BA1\u5165\u8FDE\u7EED\u6210\u529F\u8BA1\u6570\u3002\u6765\u6E90: ${source}`);
        State.consecutiveSuccessCount = 0;
        return;
      }
      State.consecutiveSuccessCount++;
      const isMilestone = State.consecutiveSuccessCount % 3 === 0 || State.consecutiveSuccessCount >= State.requiredSuccessCount;
      const logMsg = Utils.getText("rate_limit_success_request", State.consecutiveSuccessCount, State.requiredSuccessCount, source);
      Utils.logger(isMilestone ? "info" : "debug", logMsg);
      if (State.consecutiveSuccessCount >= State.requiredSuccessCount) {
        await this.exitRateLimitedState(Utils.getText("consecutive_success_exit", State.consecutiveSuccessCount, source));
      }
    }, "recordSuccessfulRequest"),
    // 退出限速状态
    exitRateLimitedState: /* @__PURE__ */ __name(async function(source = "\u672A\u77E5\u6765\u6E90") {
      if (State.appStatus !== "RATE_LIMITED") {
        Utils.logger("info", `\u5F53\u524D\u4E0D\u662F\u9650\u901F\u72B6\u6001\uFF0C\u5FFD\u7565\u9000\u51FA\u9650\u901F\u8BF7\u6C42: ${source}`);
        return false;
      }
      const rateLimitDuration = State.rateLimitStartTime ? ((Date.now() - State.rateLimitStartTime) / 1e3).toFixed(2) : "0.00";
      const logEntry = {
        type: "RATE_LIMITED",
        duration: parseFloat(rateLimitDuration),
        endTime: (/* @__PURE__ */ new Date()).toISOString(),
        source
      };
      const wasAdded = await this.addToHistory(logEntry);
      if (wasAdded) {
        Utils.logger("info", Utils.getText("rate_limit_recovery_success", source, rateLimitDuration));
      } else {
        Utils.logger("debug", `\u68C0\u6D4B\u5230\u91CD\u590D\u7684\u9650\u901F\u72B6\u6001\u8BB0\u5F55\uFF0C\u6765\u6E90: ${source}`);
      }
      State.appStatus = "NORMAL";
      State.rateLimitStartTime = null;
      State.normalStartTime = Date.now();
      State.consecutiveSuccessCount = 0;
      await GM_deleteValue(Config.DB_KEYS.APP_STATUS);
      if (UI3) {
        UI3.updateDebugTab();
        UI3.update();
      }
      if (State.db.todo.length > 0 && !State.isExecuting && TaskRunner) {
        Utils.logger("info", `\u53D1\u73B0 ${State.db.todo.length} \u4E2A\u5F85\u529E\u4EFB\u52A1\uFF0C\u81EA\u52A8\u6062\u590D\u6267\u884C...`);
        State.isExecuting = true;
        Database.saveExecutingState();
        TaskRunner.executeBatch();
      }
      return true;
    }, "exitRateLimitedState"),
    // 检查限速状态
    checkRateLimitStatus: /* @__PURE__ */ __name(async function() {
      if (State.isCheckingRateLimit) {
        Utils.logger("info", "\u5DF2\u6709\u9650\u901F\u72B6\u6001\u68C0\u67E5\u6B63\u5728\u8FDB\u884C\uFF0C\u8DF3\u8FC7\u672C\u6B21\u68C0\u67E5");
        return false;
      }
      State.isCheckingRateLimit = true;
      try {
        Utils.logger("debug", Utils.getText("log_rate_limit_check_start"));
        let pageText = "";
        const uiContainer = document.getElementById(Config.UI_CONTAINER_ID);
        if (uiContainer) {
          const originalDisplay = uiContainer.style.display;
          uiContainer.style.display = "none";
          pageText = document.body.innerText || "";
          uiContainer.style.display = originalDisplay;
        } else {
          pageText = document.body.innerText || "";
        }
        if (pageText.includes("Too many requests") || pageText.includes("rate limit") || pageText.match(/\{\s*"detail"\s*:\s*"Too many requests"\s*\}/i)) {
          Utils.logger("warn", "\u9875\u9762\u5185\u5BB9\u5305\u542B\u9650\u901F\u4FE1\u606F\uFF0C\u786E\u8BA4\u4ECD\u5904\u4E8E\u9650\u901F\u72B6\u6001");
          await this.enterRateLimitedState("\u9875\u9762\u5185\u5BB9\u68C0\u6D4B");
          return false;
        }
        Utils.logger("debug", "\u4F7F\u7528Performance API\u68C0\u67E5\u6700\u8FD1\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0C\u4E0D\u518D\u4E3B\u52A8\u53D1\u9001API\u8BF7\u6C42");
        if (window.performance && window.performance.getEntriesByType) {
          const recentRequests = window.performance.getEntriesByType("resource").filter((r) => r.name.includes("/i/listings/search") || r.name.includes("/i/users/me/listings-states")).filter((r) => Date.now() - r.startTime < 1e4);
          if (recentRequests.length > 0) {
            const has429 = recentRequests.some((r) => r.responseStatus === 429);
            if (has429) {
              Utils.logger("info", `\u68C0\u6D4B\u5230\u6700\u8FD110\u79D2\u5185\u6709429\u72B6\u6001\u7801\u7684\u8BF7\u6C42\uFF0C\u5224\u65AD\u4E3A\u9650\u901F\u72B6\u6001`);
              await this.enterRateLimitedState("Performance API\u68C0\u6D4B429");
              return false;
            }
            const hasSuccess = recentRequests.some((r) => r.responseStatus >= 200 && r.responseStatus < 300);
            if (hasSuccess) {
              Utils.logger("info", `\u68C0\u6D4B\u5230\u6700\u8FD110\u79D2\u5185\u6709\u6210\u529F\u7684API\u8BF7\u6C42\uFF0C\u5224\u65AD\u4E3A\u6B63\u5E38\u72B6\u6001`);
              await this.recordSuccessfulRequest("Performance API\u68C0\u6D4B\u6210\u529F", true);
              return true;
            }
          }
        }
        if (State.appStatus === "RATE_LIMITED") {
          const cooldownPassed = State.rateLimitStartTime ? Date.now() - State.rateLimitStartTime > 4e4 : true;
          if (cooldownPassed) {
            Utils.logger("info", "\u9650\u901F\u51B7\u9759\u671F\u5DF2\u8FC7\uFF0C\u4E3B\u52A8\u53D1\u9001\u8F7B\u91CF\u63A2\u6D4B\u8BF7\u6C42\u4EE5\u786E\u8BA4\u72B6\u6001...");
            try {
              const csrfToken = Utils.getCookie("fab_csrftoken");
              if (csrfToken) {
                const response = await API.gmFetch({
                  method: "GET",
                  url: "https://www.fab.com/i/users/context",
                  headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" }
                });
                if (response.status === 200) {
                  Utils.logger("info", "\u63A2\u6D4B\u8BF7\u6C42\u6210\u529F\uFF0CAPI\u9650\u901F\u5DF2\u89E3\u9664");
                  await this.exitRateLimitedState("\u63A2\u6D4B\u8BF7\u6C42\u6210\u529F");
                  return true;
                } else if (response.status === 429) {
                  Utils.logger("warn", "\u63A2\u6D4B\u8BF7\u6C42\u8FD4\u56DE429\uFF0C\u786E\u8BA4\u4ECD\u5904\u4E8E\u9650\u901F\u72B6\u6001");
                  State.rateLimitStartTime = Date.now();
                  await GM_setValue(Config.DB_KEYS.APP_STATUS, {
                    status: "RATE_LIMITED",
                    startTime: State.rateLimitStartTime,
                    source: "\u63A2\u6D4B\u8BF7\u6C42429"
                  });
                  return false;
                }
              }
            } catch (probeError) {
              Utils.logger("error", `\u63A2\u6D4B\u8BF7\u6C42\u53D1\u9001\u5931\u8D25: ${probeError.message}`);
            }
          }
        }
        Utils.logger("debug", Utils.getText("log_insufficient_info_status"));
        return State.appStatus === "NORMAL";
      } catch (e) {
        Utils.logger("error", `\u9650\u901F\u72B6\u6001\u68C0\u67E5\u5931\u8D25: ${e.message}`);
        return false;
      } finally {
        State.isCheckingRateLimit = false;
      }
    }, "checkRateLimitStatus"),
    /**
     * 检测不活动区间(浏览器关闭 / 标签页冻结),并修正限速期时长。
     * @param {boolean} isStartup - 是否为脚本启动时的首次检查
     */
    checkInactivity: /* @__PURE__ */ __name(async function(isStartup = false) {
      const GM_KEY = "fab_last_active_time";
      const now = Date.now();
      const lastActive = await GM_getValue(GM_KEY, 0);
      if (lastActive > 0) {
        const gap = now - lastActive;
        if (gap > 15e3 && State.appStatus === "RATE_LIMITED" && State.rateLimitStartTime) {
          if (gap > 12e4) {
            const activeDuration = lastActive > State.rateLimitStartTime ? ((lastActive - State.rateLimitStartTime) / 1e3).toFixed(2) : "0.00";
            Utils.logger(
              "warn",
              `[\u4F11\u7720\u68C0\u6D4B] \u68C0\u6D4B\u5230 ${(gap / 1e3).toFixed(0)}s \u4E0D\u6D3B\u52A8\u533A\u95F4\uFF08\u6D4F\u89C8\u5668\u5173\u95ED\u6216\u6807\u7B7E\u51BB\u7ED3\uFF09\uFF0C429 \u9650\u901F\u5DF2\u81EA\u7136\u6062\u590D\uFF0C\u6709\u6548\u9650\u901F\u65F6\u957F\u7EA6 ${activeDuration}s\uFF0C\u81EA\u52A8\u89E3\u9664\u9650\u901F\u72B6\u6001\u3002`
            );
            const logEntry = {
              type: "RATE_LIMITED",
              duration: parseFloat(activeDuration),
              endTime: new Date(lastActive).toISOString(),
              source: "\u4F11\u7720/\u5173\u95ED\u540E\u81EA\u52A8\u6062\u590D"
            };
            await this.addToHistory(logEntry);
            State.appStatus = "NORMAL";
            State.rateLimitStartTime = null;
            State.normalStartTime = now;
            State.consecutiveSuccessCount = 0;
            await GM_deleteValue(Config.DB_KEYS.APP_STATUS);
            if (UI3) {
              UI3.updateDebugTab();
              UI3.update();
            }
          } else {
            const oldStart = State.rateLimitStartTime;
            State.rateLimitStartTime += gap;
            Utils.logger(
              "debug",
              `[\u4F11\u7720\u68C0\u6D4B] \u68C0\u6D4B\u5230 ${(gap / 1e3).toFixed(0)}s \u4E0D\u6D3B\u52A8\u533A\u95F4\uFF0C\u9650\u901F\u5F00\u59CB\u65F6\u95F4\u7531 ${new Date(oldStart).toLocaleTimeString()} \u987A\u79FB\u81F3 ${new Date(State.rateLimitStartTime).toLocaleTimeString()}\u3002`
            );
            const persistedStatus = await GM_getValue(Config.DB_KEYS.APP_STATUS);
            if (persistedStatus) {
              persistedStatus.startTime = State.rateLimitStartTime;
              await GM_setValue(Config.DB_KEYS.APP_STATUS, persistedStatus);
            }
          }
        } else if (gap > 15e3 && State.appStatus === "NORMAL" && State.normalStartTime) {
          State.normalStartTime += gap;
          Utils.logger(
            "debug",
            `[\u4F11\u7720\u68C0\u6D4B] \u6B63\u5E38\u72B6\u6001\u4E0B\u68C0\u6D4B\u5230 ${(gap / 1e3).toFixed(0)}s \u4E0D\u6D3B\u52A8\u533A\u95F4\uFF0C\u6B63\u5E38\u8FD0\u884C\u8BA1\u65F6\u5DF2\u987A\u79FB\u3002`
          );
        }
      }
      this._lastActiveTime = now;
      await GM_setValue(GM_KEY, now);
    }, "checkInactivity"),
    /** 启动周期性活跃心跳(每 2 秒更新一次时间戳,用于检测不活动区间)。幂等。 */
    startActivityTracking: /* @__PURE__ */ __name(function() {
      if (this._trackingInterval) return;
      this._trackingInterval = setInterval(async () => {
        const now = Date.now();
        this._lastActiveTime = now;
        try {
          await GM_setValue("fab_last_active_time", now);
        } catch (e) {
        }
      }, 2e3);
      Utils.logger("debug", "[\u6D3B\u8DC3\u5FC3\u8DF3] \u5DF2\u542F\u52A8\uFF0C\u6BCF 2s \u66F4\u65B0\u6D3B\u8DC3\u65F6\u95F4\u6233\u3002");
    }, "startActivityTracking"),
    /** 脚本启动时调用:加载持久化限速状态、执行启动时不活动检测、启动活跃心跳。 */
    initStatus: /* @__PURE__ */ __name(async function() {
      const persistedStatus = await GM_getValue(Config.DB_KEYS.APP_STATUS);
      if (persistedStatus && persistedStatus.status === "RATE_LIMITED") {
        State.appStatus = "RATE_LIMITED";
        State.rateLimitStartTime = persistedStatus.startTime;
        const rawDuration = persistedStatus.startTime ? ((Date.now() - persistedStatus.startTime) / 1e3).toFixed(2) : "0.00";
        Utils.logger("warn", Utils.getText("startup_rate_limited", rawDuration, persistedStatus.source || Utils.getText("status_unknown_source")));
      }
      await this.checkInactivity(true);
      this.startActivityTracking();
    }, "initStatus")
  };

  // src/modules/page-patcher.js
  var PagePatcher = {
    _patchHasBeenApplied: false,
    _lastSeenCursor: null,
    _lastCheckedUrl: null,
    _bodyObserver: null,
    // State for request debouncing
    _debounceXhrTimer: null,
    _pendingXhr: null,
    _lastDebounceXhrFire: 0,
    async init() {
      try {
        const savedCursor = await GM_getValue(Config.DB_KEYS.LAST_CURSOR);
        if (savedCursor) {
          State.savedCursor = savedCursor;
          this._lastSeenCursor = savedCursor;
          Utils.logger("debug", `[Cursor] Initialized. Loaded saved cursor: ${savedCursor.substring(0, 30)}...`);
        } else {
          Utils.logger("debug", `[Cursor] Initialized. No saved cursor found.`);
        }
        if (typeof sessionStorage !== "undefined") {
          const recoveryCursor = sessionStorage.getItem("fab_helper_recovery_cursor");
          if (recoveryCursor) {
            State.savedCursor = recoveryCursor;
            this._lastSeenCursor = recoveryCursor;
            State.isRecoveryMode = true;
            Utils.logger("info", `[Recovery] \u68C0\u6D4B\u5230\u4E34\u65F6\u6062\u590D\u6E38\u6807\uFF0C\u5C06\u5728\u521D\u6B21\u52A0\u8F7D\u65F6\u5F3A\u5236\u6062\u590D\u4F4D\u7F6E`);
          }
        }
      } catch (e) {
        Utils.logger("warn", "[Cursor] Failed to restore cursor state:", e);
      }
      this.applyPatches();
      Utils.logger("debug", "[Cursor] Network interceptors applied.");
      this.setupSortMonitor();
    },
    // 添加监听URL变化的方法,检测排序方式和筛选条件变更
    setupSortMonitor() {
      this.checkUrlChanges();
      if (typeof MutationObserver !== "undefined") {
        const bodyObserver = new MutationObserver(() => {
          if (window.location.href !== this._lastCheckedUrl) {
            this._lastCheckedUrl = window.location.href;
            this.checkUrlChanges();
            Utils.detectLanguage();
          }
        });
        bodyObserver.observe(document.body, {
          childList: true,
          subtree: true
        });
        this._bodyObserver = bodyObserver;
      }
      window.addEventListener("popstate", () => {
        this.checkUrlChanges();
        Utils.detectLanguage();
      });
      window.addEventListener("hashchange", () => {
        this.checkUrlChanges();
        Utils.detectLanguage();
      });
      this._lastCheckedUrl = window.location.href;
    },
    // 检查URL变化(排序、搜索词、分类等)并重置状态
    checkUrlChanges() {
      try {
        const url = new URL(window.location.href);
        const isSearchOrList = url.pathname.includes("/search") || url.pathname === "/" || url.pathname === "/zh-cn/" || url.pathname === "/en/" || url.pathname.includes("/sellers/");
        if (!isSearchOrList) {
          return;
        }
        const currentSort = url.searchParams.get("sort_by");
        const currentQuery = url.searchParams.get("query") || url.searchParams.get("q");
        const currentCategory = url.searchParams.get("category");
        if (currentSort) {
          let matchedOption = null;
          if (State.sortOptions) {
            for (const [key, option] of Object.entries(State.sortOptions)) {
              if (option.value === currentSort) {
                matchedOption = key;
                break;
              }
            }
          }
          if (matchedOption && matchedOption !== State.currentSortOption) {
            const previousSort = State.currentSortOption;
            State.currentSortOption = matchedOption;
            GM_setValue("fab_helper_sort_option", State.currentSortOption);
            Utils.logger("debug", Utils.getText(
              "log_url_sort_changed",
              State.sortOptions?.[previousSort]?.name || previousSort,
              State.sortOptions?.[State.currentSortOption]?.name || State.currentSortOption
            ));
            this.clearSavedPosition("Sort change");
          }
        }
        if (this._lastParams) {
          if (this._lastParams.query !== currentQuery || this._lastParams.category !== currentCategory) {
            this.clearSavedPosition("Search/Filter change");
          }
        }
        this._lastParams = {
          query: currentQuery,
          category: currentCategory,
          sort: currentSort
        };
      } catch (e) {
        Utils.logger("warn", Utils.getText("log_sort_check_error", e.message));
      }
    },
    // 辅助方法:清除位置
    async clearSavedPosition(reason) {
      if (State.savedCursor) {
        State.savedCursor = null;
        await GM_deleteValue(Config.DB_KEYS.LAST_CURSOR);
        if (State.UI && State.UI.savedPositionDisplay) {
          State.UI.savedPositionDisplay.textContent = Utils.getText("no_saved_position");
        }
        Utils.logger("info", `${Utils.getText("log_sort_changed_position_cleared")} (${reason})`);
      }
      this._patchHasBeenApplied = false;
      this._lastSeenCursor = null;
    },
    async handleSearchResponse(request) {
      if (request.status === 429) {
        await RateLimitManager.enterRateLimitedState("\u641C\u7D22\u54CD\u5E94429");
      } else if (request.status >= 200 && request.status < 300) {
        try {
          const responseText = request.responseText;
          if (responseText) {
            const data = JSON.parse(responseText);
            const hasResults = data && data.results && data.results.length > 0;
            await RateLimitManager.recordSuccessfulRequest(Utils.getText("request_source_search_response"), hasResults);
          }
        } catch (e) {
          Utils.logger("warn", Utils.getText("search_response_parse_failed", e.message));
        }
      }
    },
    isDebounceableSearch(url) {
      if (typeof url !== "string" || !url.includes("/i/listings/search")) return false;
      if (url.includes("aggregate_on=") || url.includes("count=0")) return false;
      try {
        const urlObj = new URL(url, window.location.origin);
        if (urlObj.searchParams.has("cursor")) return false;
      } catch (e) {
        if (url.includes("cursor=")) return false;
      }
      return true;
    },
    shouldPatchUrl(url) {
      if (typeof url !== "string") return false;
      if (this._patchHasBeenApplied) return false;
      if (!url.includes("/i/listings/search")) return false;
      if (url.includes("aggregate_on=") || url.includes("count=0") || url.includes("in=wishlist")) return false;
      if (State.isRecoveryMode && State.savedCursor) {
        return true;
      }
      if (!State.rememberScrollPosition || !State.savedCursor) return false;
      Utils.logger("debug", Utils.getText("page_patcher_match") + ` URL: ${url}`);
      return true;
    },
    getPatchedUrl(originalUrl) {
      if (State.savedCursor) {
        const urlObj = new URL(originalUrl, window.location.origin);
        urlObj.searchParams.set("cursor", State.savedCursor);
        const modifiedUrl = urlObj.pathname + urlObj.search;
        Utils.logger("debug", `[Cursor] ${Utils.getText("cursor_injecting")}: ${originalUrl}`);
        Utils.logger("debug", `[Cursor] ${Utils.getText("cursor_patched_url")}: ${modifiedUrl}`);
        this._patchHasBeenApplied = true;
        if (State.isRecoveryMode) {
          State.isRecoveryMode = false;
          try {
            sessionStorage.removeItem("fab_helper_recovery_cursor");
            Utils.logger("debug", `[Recovery] \u4E34\u65F6\u6062\u590D\u6E38\u6807\u5DF2\u6D88\u8D39\u5E76\u6E05\u9664`);
          } catch (e) {
          }
        }
        if (State.UI && State.UI.savedPositionDisplay) {
          const container = State.UI.savedPositionDisplay.parentElement;
          if (container) {
            const originalBg = container.style.backgroundColor;
            const originalColor = container.style.color;
            container.style.transition = "background-color 0.5s, color 0.5s";
            container.style.backgroundColor = "rgba(52, 199, 89, 0.2)";
            container.style.color = "var(--green)";
            setTimeout(() => {
              container.style.backgroundColor = originalBg;
              container.style.color = originalColor;
            }, 5e3);
          }
        }
        return modifiedUrl;
      }
      return originalUrl;
    },
    saveLatestCursorFromUrl(url) {
      try {
        if (typeof url !== "string" || !url.includes("/i/listings/search") || !url.includes("cursor=")) return;
        const urlObj = new URL(url, window.location.origin);
        const newCursor = urlObj.searchParams.get("cursor");
        if (newCursor && newCursor !== this._lastSeenCursor) {
          this._lastSeenCursor = newCursor;
          State.savedCursor = newCursor;
          GM_setValue(Config.DB_KEYS.LAST_CURSOR, newCursor);
          if (State.debugMode) {
            Utils.logger("debug", Utils.getText("debug_save_cursor", newCursor.substring(0, 30) + "..."));
          }
          if (State.UI && State.UI.savedPositionDisplay) {
            State.UI.savedPositionDisplay.textContent = Utils.decodeCursor(newCursor);
          }
        }
      } catch (e) {
        Utils.logger("warn", Utils.getText("log_cursor_save_error"), e);
      }
    },
    applyPatches() {
      const self = this;
      const originalXhrOpen = XMLHttpRequest.prototype.open;
      const originalXhrSend = XMLHttpRequest.prototype.send;
      const DEBOUNCE_DELAY_MS = 350;
      const LEADING_EDGE_QUIET_MS = 1500;
      const listenerAwareSend = /* @__PURE__ */ __name(function(...args) {
        const request = this;
        const onLoad = /* @__PURE__ */ __name(() => {
          request.removeEventListener("load", onLoad);
          if (typeof window.recordNetworkActivity === "function") {
            window.recordNetworkActivity();
          }
          if (request.status >= 200 && request.status < 300 && request._url && self.isDebounceableSearch(request._url)) {
            if (typeof window.recordNetworkRequest === "function") {
              window.recordNetworkRequest(Utils.getText("request_source_xhr_item"), true);
            }
          }
          if (request.status === 429 || request.status === "429" || request.status.toString() === "429") {
            Utils.logger("warn", Utils.getText("xhr_detected_429", request.responseURL || request._url));
            RateLimitManager.enterRateLimitedState(request.responseURL || request._url || "XHR\u54CD\u5E94429");
            return;
          }
          if (request.status >= 200 && request.status < 300) {
            try {
              const responseText = request.responseText;
              if (responseText) {
                if (responseText.includes("Too many requests") || responseText.includes("rate limit") || responseText.match(/\{\s*"detail"\s*:\s*"Too many requests"\s*\}/i)) {
                  Utils.logger("warn", Utils.getText("log_xhr_rate_limit_detect", responseText));
                  RateLimitManager.enterRateLimitedState("XHR\u54CD\u5E94\u5185\u5BB9\u9650\u901F");
                  return;
                }
                try {
                  const data = JSON.parse(responseText);
                  if (data.detail && (data.detail.includes("Too many requests") || data.detail.includes("rate limit"))) {
                    Utils.logger("warn", Utils.getText("detected_rate_limit_error", JSON.stringify(data)));
                    RateLimitManager.enterRateLimitedState("XHR\u54CD\u5E94\u9650\u901F\u9519\u8BEF");
                    return;
                  }
                  if (data.results && data.results.length === 0 && self.isDebounceableSearch(request._url)) {
                    const isEndOfList = data.next === null && data.previous !== null && data.cursors && data.cursors.next === null && data.cursors.previous !== null;
                    const isEmptySearch = data.next === null && data.previous === null && data.cursors && data.cursors.next === null && data.cursors.previous === null;
                    const urlObj = new URL(request._url, window.location.origin);
                    const params = urlObj.searchParams;
                    const nonFilteringParams = /* @__PURE__ */ new Set(["cursor", "count", "limit", "offset", "workerId"]);
                    const hasSpecialFilters = Array.from(params.keys()).some((key) => !nonFilteringParams.has(key));
                    if (isEndOfList) {
                      Utils.logger("info", Utils.getText("log_list_end_normal", JSON.stringify(data).substring(0, 200)));
                      RateLimitManager.recordSuccessfulRequest("XHR\u5217\u8868\u672B\u5C3E", true);
                      return;
                    } else if (isEmptySearch && hasSpecialFilters) {
                      Utils.logger("info", Utils.getText("log_empty_search_with_filters", JSON.stringify(data).substring(0, 200)));
                      RateLimitManager.recordSuccessfulRequest("XHR\u7A7A\u641C\u7D22\u7ED3\u679C", true);
                      return;
                    } else if (isEmptySearch && State.appStatus === "RATE_LIMITED") {
                      Utils.logger("info", Utils.getText("log_empty_search_already_limited", JSON.stringify(data).substring(0, 200)));
                      return;
                    } else if (isEmptySearch && document.readyState !== "complete") {
                      Utils.logger("info", Utils.getText("log_empty_search_page_loading", JSON.stringify(data).substring(0, 200)));
                      return;
                    } else if (isEmptySearch && Date.now() - (window.pageLoadTime || 0) < 5e3) {
                      Utils.logger("info", Utils.getText("empty_search_initial"));
                      return;
                    } else {
                      Utils.logger("warn", Utils.getText("detected_possible_rate_limit_empty", JSON.stringify(data).substring(0, 200)));
                      RateLimitManager.enterRateLimitedState("XHR\u54CD\u5E94\u7A7A\u7ED3\u679C");
                      return;
                    }
                  }
                  if (self.isDebounceableSearch(request._url) && data.results && data.results.length > 0) {
                    RateLimitManager.recordSuccessfulRequest(Utils.getText("request_source_xhr_search"), true);
                  }
                } catch (jsonError) {
                }
              }
            } catch (e) {
            }
          }
          if (self.isDebounceableSearch(request._url)) {
            self.handleSearchResponse(request);
          }
        }, "onLoad");
        request.addEventListener("load", onLoad);
        return originalXhrSend.apply(request, args);
      }, "listenerAwareSend");
      XMLHttpRequest.prototype.open = function(method, url, ...args) {
        let modifiedUrl = url;
        if (self.shouldPatchUrl(url)) {
          modifiedUrl = self.getPatchedUrl(url);
          this._isDebouncedSearch = false;
        } else if (self.isDebounceableSearch(url)) {
          self.saveLatestCursorFromUrl(url);
          this._isDebouncedSearch = true;
        } else {
          self.saveLatestCursorFromUrl(url);
          this._isDebouncedSearch = false;
        }
        this._url = modifiedUrl;
        return originalXhrOpen.apply(this, [method, modifiedUrl, ...args]);
      };
      XMLHttpRequest.prototype.send = function(...args) {
        if (!this._isDebouncedSearch) {
          return listenerAwareSend.apply(this, args);
        }
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("log_debounce_intercept", DEBOUNCE_DELAY_MS));
        }
        if (self._pendingXhr) {
          self._pendingXhr.abort();
          Utils.logger("info", Utils.getText("log_debounce_discard"));
        }
        clearTimeout(self._debounceXhrTimer);
        self._pendingXhr = this;
        const now = Date.now();
        const sendArgs = args;
        const sendNow = /* @__PURE__ */ __name(() => {
          if (State.debugMode) {
            Utils.logger("debug", Utils.getText("log_debounce_sending", self._pendingXhr && self._pendingXhr._url));
          }
          self._lastDebounceXhrFire = Date.now();
          const xhr = self._pendingXhr;
          self._pendingXhr = null;
          listenerAwareSend.apply(xhr, sendArgs);
        }, "sendNow");
        if (now - self._lastDebounceXhrFire >= LEADING_EDGE_QUIET_MS) {
          sendNow();
        } else {
          self._debounceXhrTimer = setTimeout(sendNow, DEBOUNCE_DELAY_MS);
        }
      };
      const originalFetch = window.fetch;
      window.fetch = function(input, init) {
        let url = typeof input === "string" ? input : input.url;
        let modifiedInput = input;
        if (self.shouldPatchUrl(url)) {
          const modifiedUrl = self.getPatchedUrl(url);
          if (typeof input === "string") {
            modifiedInput = modifiedUrl;
          } else {
            modifiedInput = new Request(modifiedUrl, input);
          }
        } else {
          self.saveLatestCursorFromUrl(url);
        }
        return originalFetch.apply(this, [modifiedInput, init]).then((response) => {
          if (typeof window.recordNetworkActivity === "function") {
            window.recordNetworkActivity();
          }
          if (response.status >= 200 && response.status < 300 && typeof url === "string" && self.isDebounceableSearch(url)) {
            if (typeof window.recordNetworkRequest === "function") {
              window.recordNetworkRequest("Fetch\u5546\u54C1\u8BF7\u6C42", true);
            }
          }
          if (response.status === 429 || response.status === "429" || response.status.toString() === "429") {
            response.clone();
            Utils.logger("warn", Utils.getText("log_fetch_detected_429", response.url));
            RateLimitManager.enterRateLimitedState("Fetch\u54CD\u5E94429").catch(
              (e) => Utils.logger("error", Utils.getText("log_handling_rate_limit_error", e.message))
            );
          }
          if (response.status >= 200 && response.status < 300) {
            response.clone().text().then((text) => {
              if (text.includes("Too many requests") || text.includes("rate limit") || text.match(/\{\s*"detail"\s*:\s*"Too many requests"\s*\}/i)) {
                Utils.logger("warn", Utils.getText("log_fetch_rate_limit_detect", text.substring(0, 100)));
                RateLimitManager.enterRateLimitedState("Fetch\u54CD\u5E94\u5185\u5BB9\u9650\u901F").catch(
                  (e) => Utils.logger("error", Utils.getText("log_handling_rate_limit_error", e.message))
                );
                return response;
              }
              try {
                const data = JSON.parse(text);
                if (data.detail && (data.detail.includes("Too many requests") || data.detail.includes("rate limit"))) {
                  Utils.logger("warn", Utils.getText("detected_rate_limit_error", "API\u9650\u901F\u54CD\u5E94"));
                  RateLimitManager.enterRateLimitedState("API\u9650\u901F\u54CD\u5E94").catch(
                    (e) => Utils.logger("error", Utils.getText("log_handling_rate_limit_error", e.message))
                  );
                  return response;
                }
                const responseUrl = response.url || "";
                if (data.results && data.results.length === 0 && responseUrl.includes("/i/listings/search")) {
                  const isEndOfList = data.next === null && data.previous !== null && data.cursors && data.cursors.next === null && data.cursors.previous !== null;
                  const isEmptySearch = data.next === null && data.previous === null && data.cursors && data.cursors.next === null && data.cursors.previous === null;
                  const urlObj = new URL(responseUrl, window.location.origin);
                  const params = urlObj.searchParams;
                  const nonFilteringParams = /* @__PURE__ */ new Set(["cursor", "count", "limit", "offset", "workerId"]);
                  const hasSpecialFilters = Array.from(params.keys()).some((key) => !nonFilteringParams.has(key));
                  if (isEndOfList) {
                    Utils.logger("info", Utils.getText("log_fetch_list_end", JSON.stringify(data).substring(0, 200)));
                    RateLimitManager.recordSuccessfulRequest("Fetch\u5217\u8868\u672B\u5C3E", true);
                  } else if (isEmptySearch && hasSpecialFilters) {
                    Utils.logger("info", Utils.getText("log_fetch_empty_with_filters", JSON.stringify(data).substring(0, 200)));
                    RateLimitManager.recordSuccessfulRequest("Fetch\u7A7A\u641C\u7D22\u7ED3\u679C", true);
                  } else if (isEmptySearch && State.appStatus === "RATE_LIMITED") {
                    Utils.logger("info", Utils.getText("log_fetch_empty_already_limited", JSON.stringify(data).substring(0, 200)));
                  } else if (isEmptySearch && document.readyState !== "complete") {
                    Utils.logger("info", Utils.getText("log_fetch_empty_page_loading", JSON.stringify(data).substring(0, 200)));
                  } else if (isEmptySearch && Date.now() - (window.pageLoadTime || 0) < 5e3) {
                    Utils.logger("info", Utils.getText("empty_search_initial"));
                  } else {
                    Utils.logger("warn", Utils.getText("log_fetch_implicit_rate_limit", JSON.stringify(data).substring(0, 200)));
                    RateLimitManager.enterRateLimitedState("Fetch\u54CD\u5E94\u7A7A\u7ED3\u679C").catch(
                      (e) => Utils.logger("error", Utils.getText("log_handling_rate_limit_error", e.message))
                    );
                  }
                }
              } catch (jsonError) {
                Utils.logger("debug", Utils.getText("log_json_parse_error", jsonError.message));
              }
            }).catch(() => {
            });
          }
          return response;
        });
      };
    }
  };

  // src/modules/instance-manager.js
  var InstanceManager = {
    isActive: false,
    lastPingTime: 0,
    pingInterval: null,
    // 初始化实例管理
    init: /* @__PURE__ */ __name(async function() {
      try {
        if (new URLSearchParams(window.location.search).has("workerId")) {
          this.isActive = false;
          return true;
        }
        const isSearchPage = window.location.href.includes("/search") || window.location.pathname === "/" || window.location.pathname === "/zh-cn/" || window.location.pathname === "/en/";
        if (isSearchPage) {
          this.isActive = true;
          await this.registerAsActive();
          Utils.logger("info", Utils.getText("log_instance_activated", Config.INSTANCE_ID));
          this.pingInterval = setInterval(() => this.ping(), 3e3);
          return true;
        }
        const activeInstance = await GM_getValue("fab_active_instance", null);
        const currentTime = Date.now();
        if (activeInstance && currentTime - activeInstance.lastPing < 1e4) {
          Utils.logger("info", Utils.getText("log_instance_collaborating", activeInstance.id));
          this.isActive = false;
          return true;
        } else {
          this.isActive = true;
          await this.registerAsActive();
          Utils.logger("info", Utils.getText("log_instance_no_active", Config.INSTANCE_ID));
          this.pingInterval = setInterval(() => this.ping(), 3e3);
          return true;
        }
      } catch (error) {
        Utils.logger("error", Utils.getText("log_instance_init_failed", error.message));
        this.isActive = true;
        return true;
      }
    }, "init"),
    // 注册为活跃实例
    registerAsActive: /* @__PURE__ */ __name(async function() {
      await GM_setValue("fab_active_instance", {
        id: Config.INSTANCE_ID,
        lastPing: Date.now()
      });
    }, "registerAsActive"),
    // 定期更新活跃状态
    ping: /* @__PURE__ */ __name(async function() {
      if (!this.isActive) return;
      this.lastPingTime = Date.now();
      await this.registerAsActive();
    }, "ping"),
    // 检查是否可以接管
    checkTakeover: /* @__PURE__ */ __name(async function() {
      if (this.isActive) return;
      try {
        const activeInstance = await GM_getValue("fab_active_instance", null);
        const currentTime = Date.now();
        if (!activeInstance || currentTime - activeInstance.lastPing > 1e4) {
          this.isActive = true;
          await this.registerAsActive();
          Utils.logger("info", Utils.getText("log_instance_takeover", Config.INSTANCE_ID));
          this.pingInterval = setInterval(() => this.ping(), 3e3);
          location.reload();
        } else {
          setTimeout(() => this.checkTakeover(), 5e3);
        }
      } catch (error) {
        Utils.logger("error", Utils.getText("log_instance_takeover_failed", error.message));
        setTimeout(() => this.checkTakeover(), 5e3);
      }
    }, "checkTakeover"),
    // 清理实例
    cleanup: /* @__PURE__ */ __name(function() {
      if (this.pingInterval) {
        clearInterval(this.pingInterval);
        this.pingInterval = null;
      }
    }, "cleanup"),
    // 手动强制激活(用于用户手动开始任务时)
    activate: /* @__PURE__ */ __name(async function() {
      if (this.isActive) return;
      this.isActive = true;
      await this.registerAsActive();
      Utils.logger("info", Utils.getText("log_instance_activated", Config.INSTANCE_ID));
      if (!this.pingInterval) {
        this.pingInterval = setInterval(() => this.ping(), 3e3);
      }
    }, "activate")
  };

  // src/modules/keepalive.js
  var KeepAlive = {
    _worker: null,
    _workerUrl: null,
    _tickCb: null,
    _running: false,
    _lastTickAt: null,
    // WebRTC freeze-guard
    _pc1: null,
    _pc2: null,
    _dc: null,
    /** 注入心跳要执行的调度回调(由 index.js 提供,内含 ping/派发/watchdog) */
    setTick(cb) {
      this._tickCb = cb;
    },
    /** 启动后台保活(心跳 + 可选的防冻结)。幂等。 */
    start() {
      if (this._running) return;
      this._running = true;
      this._startHeartbeat();
      if (Config.ENABLE_FREEZE_GUARD) {
        this._startFreezeGuard();
      }
    },
    /** 停止后台保活,释放 Worker 与 RTC 资源。幂等。 */
    stop() {
      if (!this._running) return;
      this._running = false;
      this._stopHeartbeat();
      this._stopFreezeGuard();
    },
    _startHeartbeat() {
      if (this._worker) return;
      try {
        const intervalMs = Config.KEEPALIVE_TICK_MS || 2e3;
        const workerSrc = "let n=0;setInterval(function(){postMessage(++n);}," + intervalMs + ");";
        const blob = new Blob([workerSrc], { type: "application/javascript" });
        this._workerUrl = URL.createObjectURL(blob);
        this._worker = new Worker(this._workerUrl);
        this._worker.onmessage = () => {
          this._lastTickAt = Date.now();
          try {
            const r = this._tickCb && this._tickCb();
            if (r && typeof r.catch === "function") {
              r.catch((e) => console.error("[Fab Helper] keepalive tick error:", e));
            }
          } catch (e) {
            console.error("[Fab Helper] keepalive tick error:", e);
          }
        };
        Utils.logger("debug", Utils.getText("log_keepalive_on"));
      } catch (e) {
        Utils.logger("warn", Utils.getText("log_keepalive_failed", e.message));
      }
    },
    _stopHeartbeat() {
      if (this._worker) {
        try {
          this._worker.terminate();
        } catch (e) {
        }
        this._worker = null;
      }
      if (this._workerUrl) {
        try {
          URL.revokeObjectURL(this._workerUrl);
        } catch (e) {
        }
        this._workerUrl = null;
      }
    },
    async _startFreezeGuard() {
      if (this._pc1 || typeof RTCPeerConnection === "undefined") return;
      try {
        const pc1 = new RTCPeerConnection();
        const pc2 = new RTCPeerConnection();
        this._pc1 = pc1;
        this._pc2 = pc2;
        pc1.onicecandidate = (e) => {
          if (e.candidate) pc2.addIceCandidate(e.candidate).catch(() => {
          });
        };
        pc2.onicecandidate = (e) => {
          if (e.candidate) pc1.addIceCandidate(e.candidate).catch(() => {
          });
        };
        this._dc = pc1.createDataChannel("fab-keepalive");
        const offer = await pc1.createOffer();
        await pc1.setLocalDescription(offer);
        await pc2.setRemoteDescription(offer);
        const answer = await pc2.createAnswer();
        await pc2.setLocalDescription(answer);
        await pc1.setRemoteDescription(answer);
      } catch (e) {
        Utils.logger("warn", Utils.getText("log_keepalive_failed", e.message));
        this._stopFreezeGuard();
      }
    },
    _stopFreezeGuard() {
      try {
        if (this._dc) this._dc.close();
      } catch (e) {
      }
      try {
        if (this._pc1) this._pc1.close();
      } catch (e) {
      }
      try {
        if (this._pc2) this._pc2.close();
      } catch (e) {
      }
      this._dc = null;
      this._pc1 = null;
      this._pc2 = null;
    },
    /** 由心跳驱动,偶尔通过 data channel 发一下保持活跃 */
    poke() {
      try {
        if (this._dc && this._dc.readyState === "open") this._dc.send("p");
      } catch (e) {
      }
    },
    /** 返回保活运行状态,供 UI 展示、用于实测验证(尤其判断 Worker 是否被 CSP 拦) */
    getStatus() {
      return {
        running: this._running,
        heartbeatAlive: !!this._worker,
        lastTickAgoMs: this._lastTickAt ? Date.now() - this._lastTickAt : null,
        freezeGuard: this._dc ? this._dc.readyState : this._pc1 ? "connecting" : "off"
      };
    }
  };

  // src/modules/task-runner.js
  var UI4 = null;
  var countdownRefresh2 = null;
  function setUIReference3(uiModule) {
    UI4 = uiModule;
  }
  __name(setUIReference3, "setUIReference");
  function setDependencies2(deps = {}) {
    countdownRefresh2 = deps.countdownRefresh || countdownRefresh2;
  }
  __name(setDependencies2, "setDependencies");
  var TaskRunner2 = {
    findFreeLicenseOption: /* @__PURE__ */ __name((root) => {
      if (!root || typeof root.querySelectorAll !== "function") {
        return null;
      }
      const candidates = Array.from(root.querySelectorAll("span, div")).map((el) => {
        const text = Utils.normalizeWhitespace(el.textContent || "");
        const clickTarget = el.closest('[role="option"], button, label, input[type="radio"]');
        return { text, clickTarget };
      }).filter((candidate) => candidate.text && candidate.clickTarget);
      const hasExplicitFreeSignal = /* @__PURE__ */ __name((text) => {
        const cleanText = text.replace(/royalty\s*-?\s*free/gi, "");
        return [...Config.FREE_TEXT_SET].some((freeWord) => cleanText.includes(freeWord));
      }, "hasExplicitFreeSignal");
      const explicitFree = candidates.find((candidate) => hasExplicitFreeSignal(candidate.text));
      if (explicitFree) {
        return explicitFree.clickTarget;
      }
      const personalFree = candidates.find((candidate) => {
        const isPersonal = candidate.text.includes("\u4E2A\u4EBA") || candidate.text.includes("Personal");
        return isPersonal && hasExplicitFreeSignal(candidate.text);
      });
      return personalFree ? personalFree.clickTarget : null;
    }, "findFreeLicenseOption"),
    getExternalProductState: /* @__PURE__ */ __name((root = document) => {
      if (!root || typeof root.querySelectorAll !== "function") {
        return { handled: false };
      }
      const currentHref = typeof window !== "undefined" && window.location?.href ? window.location.href : "https://www.fab.com/";
      const currentHostname = typeof window !== "undefined" && window.location?.hostname ? window.location.hostname : "www.fab.com";
      const links = [...root.querySelectorAll("a[href]")];
      const externalLink = links.find((link) => {
        const text2 = Utils.normalizeWhitespace(link.textContent || "");
        if (!text2 || ![...Config.EXTERNAL_CTA_TEXT_SET].some((label) => text2.includes(label))) {
          return false;
        }
        const rect = typeof link.getBoundingClientRect === "function" ? link.getBoundingClientRect() : { width: 1, height: 1 };
        if (rect.width === 0 || rect.height === 0) {
          return false;
        }
        try {
          const href = link.href || link.getAttribute?.("href");
          if (!href) return false;
          const targetUrl = new URL(href, currentHref);
          return targetUrl.hostname !== currentHostname;
        } catch (error) {
          return false;
        }
      });
      if (!externalLink) {
        return { handled: false };
      }
      const text = Utils.normalizeWhitespace(externalLink.textContent || "");
      return {
        handled: true,
        reason: `External CTA "${text}"`,
        href: externalLink.href || externalLink.getAttribute?.("href") || ""
      };
    }, "getExternalProductState"),
    hasSavedLibraryText: /* @__PURE__ */ __name((card) => {
      const cardText = Utils.normalizeWhitespace(card.textContent || "");
      return [...Config.SAVED_TEXT_SET].some((savedText) => cardText.includes(savedText));
    }, "hasSavedLibraryText"),
    hasPositivePriceText: /* @__PURE__ */ __name((text) => {
      const priceMatches = Utils.normalizeWhitespace(text || "").match(/\$\s*(\d+(?:\.\d{2})?)/g);
      if (!priceMatches) return false;
      return priceMatches.some((priceStr) => {
        const numValue = parseFloat(priceStr.replace(/[^0-9.]/g, ""));
        return numValue > 0;
      });
    }, "hasPositivePriceText"),
    isCardSettled: /* @__PURE__ */ __name((card) => {
      const link = card.querySelector(Config.SELECTORS.cardLink);
      const url = link ? link.href.split("?")[0] : null;
      return card.querySelector(`${Config.SELECTORS.freeStatus}, ${Config.SELECTORS.ownedStatus}`) !== null || TaskRunner2.hasSavedLibraryText(card) || TaskRunner2.isFreeCard(card) || TaskRunner2.hasPositivePriceText(card.textContent || "") || url && (Database.isDone(url) || Database.isFailed(url) || State.sessionCompleted.has(Database.normalizeListingUrl(url)));
    }, "isCardSettled"),
    // Check if a card is finished (owned, done, or failed)
    isCardFinished: /* @__PURE__ */ __name((card) => {
      const link = card.querySelector(Config.SELECTORS.cardLink);
      const url = link ? link.href.split("?")[0] : null;
      const hasSavedText = TaskRunner2.hasSavedLibraryText(card);
      if (!link) {
        const icons = card.querySelectorAll("i.fabkit-Icon--intent-success, i.edsicon-check-circle-filled");
        if (icons.length > 0) return true;
        return hasSavedText;
      }
      const uidMatch = link.href.match(/listings\/([a-f0-9-]+)/);
      if (!uidMatch || !uidMatch[1]) return false;
      const uid = uidMatch[1];
      if (DataCache.ownedStatus.has(uid)) {
        const status = DataCache.ownedStatus.get(uid);
        if (status && status.acquired) return true;
      }
      if (card.querySelector(Config.SELECTORS.ownedStatus) !== null || hasSavedText) {
        if (uid) {
          DataCache.saveOwnedStatus([{
            uid,
            acquired: true,
            lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
          }]);
        }
        return true;
      }
      if (url) {
        if (Database.isDone(url)) return true;
        if (Database.isFailed(url)) return true;
        if (State.sessionCompleted.has(Database.normalizeListingUrl(url))) return true;
      }
      return false;
    }, "isCardFinished"),
    // Check if a card represents a free item
    isFreeCard: /* @__PURE__ */ __name((card) => {
      const rawText = card.textContent || "";
      const cardText = Utils.normalizeWhitespace(rawText);
      const cleanText = cardText.replace(/royalty\s*-?\s*free/gi, "");
      const hasFreeKeyword = [...Config.FREE_TEXT_SET].some((freeWord) => cleanText.includes(freeWord));
      const has100PercentDiscount = /-\s*100\s*%\s*(?:OFF|折扣)?/i.test(cleanText);
      const hasPositivePrice = TaskRunner2.hasPositivePriceText(cardText);
      if (hasPositivePrice) {
        if (!has100PercentDiscount && !hasFreeKeyword) {
          return false;
        }
      }
      return hasFreeKeyword || has100PercentDiscount;
    }, "isFreeCard"),
    // Check if a card is a discounted paid item
    isDiscountedPaidCard: /* @__PURE__ */ __name((card) => {
      if (TaskRunner2.isFreeCard(card)) return false;
      const rawText = card.textContent || "";
      const cardText = Utils.normalizeWhitespace(rawText);
      const hasDiscountTag = /-\d+%/.test(cardText) || cardText.includes("% off") || cardText.includes("% Off");
      if (!hasDiscountTag) return false;
      return TaskRunner2.hasPositivePriceText(cardText);
    }, "isDiscountedPaidCard"),
    // Toggle execution state
    toggleExecution: /* @__PURE__ */ __name(async () => {
      if (!Utils.checkAuthentication()) return;
      if (State.isExecuting) {
        State.isExecuting = false;
        Database.saveExecutingState();
        State.runningWorkers = {};
        State.activeWorkers = 0;
        State.executionTotalTasks = 0;
        State.executionCompletedTasks = 0;
        State.executionFailedTasks = 0;
        Utils.logger("info", Utils.getText("log_execution_stopped"));
        if (UI4) UI4.update();
        return;
      }
      const sessionOk = await Utils.verifyServerSession();
      if (!sessionOk) {
        Utils.notifyAuthFailure();
        return;
      }
      if (State.autoAddOnScroll) {
        Utils.logger("info", Utils.getText("log_auto_add_enabled"));
        Utils.logger("debug", "\u542F\u52A8\u4EFB\u52A1\u524D\u6B63\u5728\u786E\u8BA4\u5F53\u524D\u9875\u9762\u5546\u54C1\u8BC6\u522B\u72B6\u6001...");
        TaskRunner2.checkVisibleCardsStatus().then(() => {
          Utils.logger("debug", "\u6B63\u5728\u626B\u63CF\u5F53\u524D\u9875\u9762\u7B26\u5408\u6761\u4EF6\u7684\u5546\u54C1...");
          TaskRunner2.scanAndAddTasks(document.querySelectorAll(TaskRunner2.getVisibleCardSelector())).then(() => {
            TaskRunner2.startExecution();
          });
        });
        return;
      }
      State.db.todo = [];
      Utils.logger("info", Utils.getText("log_todo_cleared"));
      Utils.logger("debug", Utils.getText("log_scanning_items"));
      const cards = document.querySelectorAll(TaskRunner2.getVisibleCardSelector());
      const newlyAddedList = [];
      let alreadyInQueueCount = 0;
      let ownedCount = 0;
      let skippedCount = 0;
      cards.forEach((card) => {
        if (TaskRunner2.isCardHidden(card)) return;
        if (!TaskRunner2.isCardSettled(card)) {
          skippedCount++;
          return;
        }
        if (TaskRunner2.isCardFinished(card)) {
          ownedCount++;
          return;
        }
        const link = card.querySelector(Config.SELECTORS.cardLink);
        const url = link ? link.href.split("?")[0] : null;
        if (!url) return;
        if (Database.isTodo(url)) {
          alreadyInQueueCount++;
          return;
        }
        if (!TaskRunner2.isFreeCard(card)) return;
        const name = card.querySelector('a[aria-label*="\u521B\u4F5C\u7684"]')?.textContent.trim() || card.querySelector('a[href*="/listings/"]')?.textContent.trim() || Utils.getText("untitled");
        newlyAddedList.push({ name, url, type: "detail", uid: url.split("/").pop() });
      });
      if (skippedCount > 0) {
        Utils.logger("debug", Utils.getText("log_skipped_unsettled", skippedCount));
      }
      if (newlyAddedList.length > 0) {
        State.db.todo.push(...newlyAddedList);
        Utils.logger("info", Utils.getText("log_added_to_queue", newlyAddedList.length));
      }
      const actionableCount = State.db.todo.length;
      if (actionableCount > 0) {
        if (newlyAddedList.length === 0 && alreadyInQueueCount > 0) {
          Utils.logger("info", Utils.getText("log_all_in_queue", alreadyInQueueCount));
        }
        TaskRunner2.checkVisibleCardsStatus().then(() => {
          TaskRunner2.startExecution();
        });
      } else {
        Utils.logger("info", Utils.getText("log_no_new_items", ownedCount, skippedCount));
        if (UI4) UI4.update();
      }
    }, "toggleExecution"),
    // Start execution without scanning
    startExecution: /* @__PURE__ */ __name(() => {
      if (State.isExecuting) {
        const newTotal = State.db.todo.length;
        if (newTotal > State.executionTotalTasks) {
          Utils.logger("info", Utils.getText("log_new_tasks_added", newTotal));
          State.executionTotalTasks = newTotal;
          if (UI4) UI4.update();
        } else {
          Utils.logger("info", Utils.getText("log_executor_running"));
        }
        return;
      }
      if (State.db.todo.length === 0) {
        Utils.logger("debug", Utils.getText("log_exec_no_tasks"));
        return;
      }
      Utils.logger("info", Utils.getText("log_starting_execution", State.db.todo.length));
      if (typeof InstanceManager !== "undefined" && InstanceManager.activate) {
        InstanceManager.activate();
      }
      KeepAlive.start();
      State.isExecuting = true;
      Database.saveExecutingState();
      State.executionTotalTasks = State.db.todo.length;
      State.executionCompletedTasks = 0;
      State.executionFailedTasks = 0;
      State.autoScrollAttempts = 0;
      if (UI4) UI4.update();
      TaskRunner2.executeBatch();
    }, "startExecution"),
    // Toggle hide saved items
    toggleHideSaved: /* @__PURE__ */ __name(async () => {
      State.hideSaved = !State.hideSaved;
      await Database.saveHidePref();
      TaskRunner2.runHideOrShow();
      if (!State.hideSaved) {
        const { visible: actualVisibleCount } = TaskRunner2.getCardCounts(true);
        Utils.logger("info", Utils.getText("log_display_mode_switched", actualVisibleCount));
      }
      if (UI4) UI4.update();
    }, "toggleHideSaved"),
    toggleAutoAdd: /* @__PURE__ */ __name(async () => {
      if (State.isTogglingSetting) return;
      State.isTogglingSetting = true;
      State.autoAddOnScroll = !State.autoAddOnScroll;
      await Database.saveAutoAddPref();
      Utils.logger("info", Utils.getText("log_auto_add_toggle", State.autoAddOnScroll ? Utils.getText("status_enabled") : Utils.getText("status_disabled")));
      setTimeout(() => {
        State.isTogglingSetting = false;
      }, 200);
    }, "toggleAutoAdd"),
    toggleAutoResume: /* @__PURE__ */ __name(async () => {
      if (State.isTogglingSetting) return;
      State.isTogglingSetting = true;
      State.autoResumeAfter429 = !State.autoResumeAfter429;
      await Database.saveAutoResumePref();
      Utils.logger("info", Utils.getText("log_auto_resume_toggle", State.autoResumeAfter429 ? Utils.getText("status_enabled") : Utils.getText("status_disabled")));
      setTimeout(() => {
        State.isTogglingSetting = false;
      }, 200);
    }, "toggleAutoResume"),
    toggleRememberPosition: /* @__PURE__ */ __name(async () => {
      if (State.isTogglingSetting) return;
      State.isTogglingSetting = true;
      State.rememberScrollPosition = !State.rememberScrollPosition;
      await Database.saveRememberPosPref();
      Utils.logger("info", Utils.getText("log_remember_pos_toggle", State.rememberScrollPosition ? Utils.getText("status_enabled") : Utils.getText("status_disabled")));
      if (!State.rememberScrollPosition) {
        await GM_deleteValue(Config.DB_KEYS.LAST_CURSOR);
        PagePatcher._patchHasBeenApplied = false;
        PagePatcher._lastSeenCursor = null;
        State.savedCursor = null;
        Utils.logger("info", Utils.getText("log_position_cleared"));
        if (State.UI && State.UI.savedPositionDisplay) {
          State.UI.savedPositionDisplay.textContent = Utils.decodeCursor(null);
        }
      } else if (State.UI && State.UI.savedPositionDisplay) {
        State.UI.savedPositionDisplay.textContent = Utils.decodeCursor(State.savedCursor);
      }
      setTimeout(() => {
        State.isTogglingSetting = false;
      }, 200);
    }, "toggleRememberPosition"),
    toggleAutoRefreshEmpty: /* @__PURE__ */ __name(async () => {
      if (State.isTogglingSetting) return;
      State.isTogglingSetting = true;
      State.autoRefreshEmptyPage = !State.autoRefreshEmptyPage;
      await Database.saveAutoRefreshEmptyPref();
      Utils.logger("info", Utils.getText("log_auto_refresh_toggle", State.autoRefreshEmptyPage ? Utils.getText("status_enabled") : Utils.getText("status_disabled")));
      setTimeout(() => {
        State.isTogglingSetting = false;
      }, 200);
    }, "toggleAutoRefreshEmpty"),
    toggleHideDiscountedPaid: /* @__PURE__ */ __name(async () => {
      State.hideDiscountedPaid = !State.hideDiscountedPaid;
      await Database.saveHideDiscountedPref();
      TaskRunner2.runHideOrShow();
      if (State.hideDiscountedPaid) {
        Utils.logger("info", "\u5DF2\u5F00\u542F\u9690\u85CF\u6253\u6298\u4ED8\u8D39\u5546\u54C1");
      } else {
        Utils.logger("info", "\u5DF2\u5173\u95ED\u9690\u85CF\u6253\u6298\u4ED8\u8D39\u5546\u54C1");
      }
      if (UI4) UI4.update();
    }, "toggleHideDiscountedPaid"),
    toggleHidePaid: /* @__PURE__ */ __name(async () => {
      State.hidePaid = !State.hidePaid;
      await Database.saveHidePaidPref();
      TaskRunner2.runHideOrShow();
      if (State.hidePaid) {
        Utils.logger("info", "\u5DF2\u5F00\u542F\u9690\u85CF\u4ED8\u8D39\u5546\u54C1");
      } else {
        Utils.logger("info", "\u5DF2\u5173\u95ED\u9690\u85CF\u4ED8\u8D39\u5546\u54C1");
      }
      if (UI4) UI4.update();
    }, "toggleHidePaid"),
    toggleBlockResources: /* @__PURE__ */ __name(async () => {
      State.blockLargeResources = !State.blockLargeResources;
      await Database.saveBlockResourcesPref();
      if (State.blockLargeResources) {
        Utils.logger("info", "\u5DF2\u5F00\u542F\u5DE5\u4F5C\u6807\u7B7E\u9875\u5927\u8D44\u6E90\u8FC7\u6EE4");
      } else {
        Utils.logger("info", "\u5DF2\u5173\u95ED\u5DE5\u4F5C\u6807\u7B7E\u9875\u5927\u8D44\u6E90\u8FC7\u6EE4");
      }
      if (UI4) UI4.update();
    }, "toggleBlockResources"),
    stop: /* @__PURE__ */ __name(() => {
      if (!State.isExecuting) return;
      State.isExecuting = false;
      Database.saveExecutingState();
      Database.saveTodo();
      State.runningWorkers = {};
      State.activeWorkers = 0;
      State.executionTotalTasks = 0;
      State.executionCompletedTasks = 0;
      State.executionFailedTasks = 0;
      Utils.logger("info", Utils.getText("log_execution_stopped"));
      if (UI4) UI4.update();
    }, "stop"),
    runRecoveryProbe: /* @__PURE__ */ __name(async () => {
      const randomDelay = Math.floor(Math.random() * (3e4 - 15e3 + 1) + 15e3);
      Utils.logger("info", Utils.getText("log_recovery_probe", (randomDelay / 1e3).toFixed(1)));
      setTimeout(async () => {
        Utils.logger("info", Utils.getText("log_probing_connection"));
        try {
          const csrfToken = Utils.getCookie("fab_csrftoken");
          if (!csrfToken) {
            Utils.checkAuthentication();
            throw new Error("CSRF token not found for probe.");
          }
          const probeResponse = await API.gmFetch({
            method: "GET",
            url: "https://www.fab.com/i/users/context",
            headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" }
          });
          if (probeResponse.status === 429) {
            throw new Error("Probe failed with 429. Still rate-limited.");
          } else if (probeResponse.status >= 200 && probeResponse.status < 300) {
            await PagePatcher.handleSearchResponse({ status: 200 });
            Utils.logger("info", Utils.getText("log_connection_restored"));
            TaskRunner2.toggleExecution();
          } else {
            throw new Error(`Probe failed with unexpected status: ${probeResponse.status}`);
          }
        } catch (e) {
          Utils.logger("error", Utils.getText("log_recovery_failed", e.message));
          setTimeout(() => location.reload(), 2e3);
        }
      }, randomDelay);
    }, "runRecoveryProbe"),
    refreshVisibleStates: /* @__PURE__ */ __name(async () => {
      const API_ENDPOINT = "https://www.fab.com/i/users/me/listings-states";
      const API_CHUNK_SIZE = 24;
      const isElementInViewport = /* @__PURE__ */ __name((el) => {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
      }, "isElementInViewport");
      try {
        const csrfToken = Utils.getCookie("fab_csrftoken");
        if (!csrfToken) {
          Utils.checkAuthentication();
          throw new Error("CSRF token not found. Are you logged in?");
        }
        const uidsFromVisibleCards = new Set([...document.querySelectorAll(TaskRunner2.getVisibleCardSelector())].filter(isElementInViewport).filter((card) => {
          const link = card.querySelector(Config.SELECTORS.cardLink);
          if (!link) return false;
          const url = link.href.split("?")[0];
          return !Database.isDone(url) && !Database.isTodo(url) && TaskRunner2.isFreeCard(card);
        }).map((card) => card.querySelector(Config.SELECTORS.cardLink)?.href.match(/listings\/([a-f0-9-]+)/)?.[1]).filter(Boolean));
        const uidsFromFailedList = new Set(State.db.failed.map((task) => task.uid));
        const allUidsToCheck = Array.from(/* @__PURE__ */ new Set([...uidsFromVisibleCards, ...uidsFromFailedList]));
        if (allUidsToCheck.length === 0) {
          Utils.logger("info", Utils.getText("log_no_items_to_check"));
          return;
        }
        Utils.logger("debug", Utils.getText("log_checking_items", uidsFromVisibleCards.size, uidsFromFailedList.size));
        const ownedUids = /* @__PURE__ */ new Set();
        for (let i = 0; i < allUidsToCheck.length; i += API_CHUNK_SIZE) {
          const chunk = allUidsToCheck.slice(i, i + API_CHUNK_SIZE);
          const apiUrl = new URL(API_ENDPOINT);
          chunk.forEach((uid) => apiUrl.searchParams.append("listing_ids", uid));
          Utils.logger("debug", Utils.getText("log_processing_batch", Math.floor(i / API_CHUNK_SIZE) + 1, chunk.length));
          const response = await fetch(apiUrl.href, {
            headers: { "accept": "application/json, text/plain, */*", "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" }
          });
          if (!response.ok) {
            Utils.logger("warn", Utils.getText("log_batch_failed", response.status));
            continue;
          }
          const rawData = await response.json();
          const data = API.extractStateData(rawData, "RefreshStates");
          if (!data || !Array.isArray(data)) {
            Utils.logger("warn", Utils.getText("log_unexpected_data_format"));
            continue;
          }
          data.filter((item) => item.acquired).forEach((item) => ownedUids.add(item.uid));
          if (allUidsToCheck.length > i + API_CHUNK_SIZE) {
            await new Promise((r) => setTimeout(r, 250));
          }
        }
        Utils.logger("debug", Utils.getText("fab_dom_api_complete", ownedUids.size));
        let dbUpdated = false;
        const langPath = State.lang === "zh" ? "/zh-cn" : "";
        if (ownedUids.size > 0) {
          const initialFailedCount = State.db.failed.length;
          State.db.failed = State.db.failed.filter((failedTask) => !ownedUids.has(failedTask.uid));
          if (State.db.failed.length < initialFailedCount) {
            dbUpdated = true;
            ownedUids.forEach((uid) => {
              const url = `${window.location.origin}${langPath}/listings/${uid}`;
              Database.addDoneUrl(url);
            });
            Utils.logger("info", Utils.getText("log_cleared_from_failed", initialFailedCount - State.db.failed.length));
          }
        }
        if (dbUpdated) {
          await Database.saveFailed();
          await Database.saveDone();
        }
        TaskRunner2.runHideOrShow();
      } catch (e) {
        Utils.logger("error", Utils.getText("log_refresh_error"), e);
      }
    }, "refreshVisibleStates"),
    retryFailedTasks: /* @__PURE__ */ __name(async () => {
      if (State.db.failed.length === 0) {
        Utils.logger("info", Utils.getText("log_no_failed_tasks"));
        return;
      }
      const count = State.db.failed.length;
      Utils.logger("info", Utils.getText("log_requeuing_tasks", count));
      State.db.todo.push(...State.db.failed);
      State.db.failed = [];
      await Database.saveFailed();
      Utils.logger("info", Utils.getText("log_tasks_moved", count));
      if (UI4) UI4.update();
    }, "retryFailedTasks"),
    // 检查并清理超时(卡死)的 worker。被 watchdog 定时器与后台心跳(KeepAlive)共同调用。
    // 抽成独立方法,是为了让主线程定时器被后台节流时,心跳也能驱动这套清理。
    // 返回被清理的 worker 数量。
    checkStalledWorkers: /* @__PURE__ */ __name(async () => {
      if (!State.isExecuting) return 0;
      const now = Date.now();
      const STALL_TIMEOUT = Config.WORKER_TIMEOUT;
      const stalledWorkers = [];
      for (const workerId in State.runningWorkers) {
        const workerInfo = State.runningWorkers[workerId];
        if (!workerInfo) continue;
        if (workerInfo.instanceId !== Config.INSTANCE_ID) continue;
        if (now - workerInfo.startTime > STALL_TIMEOUT) {
          stalledWorkers.push({ workerId, task: workerInfo.task });
        }
      }
      if (stalledWorkers.length === 0) return 0;
      Utils.logger("warn", Utils.getText("log_stalled_workers", stalledWorkers.length));
      for (const stalledWorker of stalledWorkers) {
        const { workerId, task } = stalledWorker;
        const workerInfo = State.runningWorkers[workerId];
        const stallDuration = workerInfo ? ((Date.now() - workerInfo.startTime) / 1e3).toFixed(2) : "\u672A\u77E5";
        Utils.logger("error", Utils.getText("log_watchdog_stalled", workerId.substring(0, 12)));
        const _failRes = await Database.markAsFailed(task, {
          reason: "\u5DE5\u4F5C\u7EBF\u7A0B\u8D85\u65F6 (Watchdog)",
          logs: [`Worker ${workerId.substring(0, 12)} \u8D85\u65F6`, `\u8D85\u65F6\u65F6\u957F: ${stallDuration}s`],
          details: {
            workerId,
            stallDuration: `${stallDuration}s`,
            timeout: `${Config.WORKER_TIMEOUT / 1e3}s`
          }
        });
        if (!_failRes || !_failRes.retried) State.executionFailedTasks++;
        delete State.runningWorkers[workerId];
        State.activeWorkers = Math.max(0, State.activeWorkers - 1);
        await GM_deleteValue(workerId);
      }
      Utils.logger("info", Utils.getText("log_cleaned_workers", stalledWorkers.length, State.activeWorkers));
      if (UI4) UI4.update();
      return stalledWorkers.length;
    }, "checkStalledWorkers"),
    runWatchdog: /* @__PURE__ */ __name(() => {
      if (State.watchdogTimer) clearInterval(State.watchdogTimer);
      State.watchdogTimer = setInterval(async () => {
        if (!InstanceManager.isActive) return;
        if (!State.isExecuting || Object.keys(State.runningWorkers).length === 0) {
          clearInterval(State.watchdogTimer);
          State.watchdogTimer = null;
          return;
        }
        const cleaned = await TaskRunner2.checkStalledWorkers();
        if (cleaned > 0) {
          setTimeout(() => {
            if (State.isExecuting && State.activeWorkers < Config.MAX_CONCURRENT_WORKERS && State.db.todo.length > 0) {
              TaskRunner2.executeBatch();
            }
          }, 2e3);
        }
      }, 5e3);
    }, "runWatchdog"),
    executeBatch: /* @__PURE__ */ __name(async () => {
      if (!Utils.checkAuthentication()) return;
      if (!State.isWorkerTab && !InstanceManager.isActive) {
        Utils.logger("warn", Utils.getText("log_not_active_instance"));
        return;
      }
      if (!State.isExecuting) return;
      if (State.isDispatchingTasks) {
        Utils.logger("debug", "Task dispatching already in progress, skipping executeBatch.");
        return;
      }
      State.isDispatchingTasks = true;
      try {
        if (State.db.todo.length === 0 && State.activeWorkers === 0) {
          if (State.autoAddOnScroll) {
            State.isDispatchingTasks = false;
            TaskRunner2.attemptAutoScroll();
            return;
          }
          await TaskRunner2.stopExecutionAndSettle();
          State.isDispatchingTasks = false;
          return;
        }
        if (State.appStatus === "RATE_LIMITED") {
          Utils.logger("info", Utils.getText("log_rate_limited_continue"));
        }
        if (State.activeWorkers >= Config.MAX_CONCURRENT_WORKERS) {
          Utils.logger("info", Utils.getText("log_max_workers_reached", Config.MAX_CONCURRENT_WORKERS));
          State.isDispatchingTasks = false;
          return;
        }
        const inFlightUIDs = new Set(Object.values(State.runningWorkers).map((w) => w.task.uid));
        const todoList = [...State.db.todo];
        let dispatchedCount = 0;
        const dispatchedUIDs = /* @__PURE__ */ new Set();
        const slotsAvailable = Config.MAX_CONCURRENT_WORKERS - State.activeWorkers;
        const tasksToDispatch = [];
        for (const task of todoList) {
          if (tasksToDispatch.length >= slotsAvailable) break;
          if (inFlightUIDs.has(task.uid) || dispatchedUIDs.has(task.uid)) {
            Utils.logger("debug", Utils.getText("log_task_already_running", task.name));
            continue;
          }
          if (Database.isDone(task.url)) {
            Utils.logger("debug", Utils.getText("log_task_already_done", task.name));
            State.db.todo = State.db.todo.filter((t) => t.uid !== task.uid);
            Database.saveTodo();
            continue;
          }
          tasksToDispatch.push(task);
        }
        for (const task of tasksToDispatch) {
          dispatchedUIDs.add(task.uid);
          State.activeWorkers++;
          dispatchedCount++;
          const workerId = `worker_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
          State.runningWorkers[workerId] = {
            task,
            startTime: Date.now(),
            instanceId: Config.INSTANCE_ID
          };
          Utils.logger("debug", Utils.getText("log_dispatching_worker", workerId.substring(0, 12), task.name));
          const workerUrl = new URL(task.url);
          workerUrl.searchParams.set("workerId", workerId);
          await GM_setValue(workerId, {
            task,
            instanceId: Config.INSTANCE_ID
          });
          GM_openInTab(workerUrl.href, { active: false, insert: true });
        }
        if (dispatchedCount > 0) {
          Utils.logger("debug", Utils.getText("log_batch_dispatched", dispatchedCount));
        }
        if (!State.watchdogTimer && State.activeWorkers > 0) {
          TaskRunner2.runWatchdog();
        }
        if (UI4) UI4.update();
      } finally {
        State.isDispatchingTasks = false;
      }
    }, "executeBatch"),
    closeAllWorkerTabs: /* @__PURE__ */ __name(() => {
      const workerIds = Object.keys(State.runningWorkers);
      if (workerIds.length > 0) {
        Utils.logger("debug", Utils.getText("log_cleaning_workers_state", workerIds.length));
        for (const workerId of workerIds) {
          GM_deleteValue(workerId);
        }
        State.runningWorkers = {};
        State.activeWorkers = 0;
        Utils.logger("info", Utils.getText("log_workers_cleaned"));
      }
    }, "closeAllWorkerTabs"),
    processDetailPage: /* @__PURE__ */ __name(async () => {
      if (!Utils.checkAuthentication()) return;
      const urlParams = new URLSearchParams(window.location.search);
      const workerId = urlParams.get("workerId");
      if (!workerId) return;
      State.isWorkerTab = true;
      State.workerTaskId = workerId;
      const startTime = Date.now();
      let hasReported = false;
      let closeAttempted = false;
      let payload = null;
      const forceCloseTimer = setTimeout(() => {
        if (!closeAttempted) {
          console.log("\u5DE5\u4F5C\u6807\u7B7E\u9875\u8D85\u65F6\uFF0C\u4E3B\u52A8\u4E0A\u62A5\u5E76\u5173\u95ED");
          closeWorkerTab();
        }
      }, Math.max(1e4, Config.WORKER_TIMEOUT - 5e3));
      function closeWorkerTab() {
        closeAttempted = true;
        clearTimeout(forceCloseTimer);
        if (!hasReported && workerId) {
          try {
            GM_setValue(Config.DB_KEYS.WORKER_DONE, {
              workerId,
              success: false,
              logs: [Utils.getText("worker_closed")],
              task: payload?.task,
              instanceId: payload?.instanceId,
              executionTime: Date.now() - startTime
            });
          } catch (e) {
          }
        }
        try {
          window.close();
        } catch (error) {
          Utils.logger("error", Utils.getText("log_close_worker_failed", error.message));
          try {
            window.location.href = "about:blank";
          } catch (e) {
          }
        }
      }
      __name(closeWorkerTab, "closeWorkerTab");
      try {
        payload = await GM_getValue(workerId);
        if (!payload || !payload.task) {
          Utils.logger("info", Utils.getText("log_task_data_cleaned"));
          closeWorkerTab();
          return;
        }
        const activeInstance = await GM_getValue("fab_active_instance", null);
        if (activeInstance && activeInstance.id !== payload.instanceId) {
          Utils.logger("warn", Utils.getText("log_instance_mismatch", payload.instanceId, activeInstance.id));
          await GM_deleteValue(workerId);
          closeWorkerTab();
          return;
        }
        const currentTask = payload.task;
        const logBuffer = [`[${workerId.substring(0, 12)}] Started: ${currentTask.name}`];
        let success = false;
        try {
          const waitForPageReady = /* @__PURE__ */ __name(async () => {
            const maxWait = 15e3;
            const startTime2 = Date.now();
            let lastState = "";
            while (Date.now() - startTime2 < maxWait) {
              const currentState = document.readyState;
              const hasMainContent = document.querySelector('main, .product-detail, [class*="listing"], [class*="detail"]');
              const hasButtons = document.querySelectorAll("button").length > 0;
              const hasTitle = document.querySelector("h1, .fabkit-Heading--xl");
              if (currentState !== lastState) {
                logBuffer.push(`\u9875\u9762\u72B6\u6001: ${currentState}`);
                lastState = currentState;
              }
              const isReadyState = currentState === "interactive" || currentState === "complete";
              if (isReadyState && hasMainContent && (hasButtons || hasTitle)) {
                logBuffer.push(`\u9875\u9762\u5C31\u7EEA\u68C0\u6D4B\u901A\u8FC7: readyState=${currentState}, hasContent=true`);
                return true;
              }
              await new Promise((r) => setTimeout(r, 100));
            }
            logBuffer.push(`\u9875\u9762\u5C31\u7EEA\u68C0\u6D4B\u8D85\u65F6 (${maxWait}ms)\uFF0C\u7EE7\u7EED\u5C1D\u8BD5\u64CD\u4F5C`);
            return false;
          }, "waitForPageReady");
          const pageReady = await waitForPageReady();
          if (!pageReady) {
            logBuffer.push(`\u26A0\uFE0F \u8B66\u544A: \u9875\u9762\u53EF\u80FD\u672A\u5B8C\u5168\u52A0\u8F7D\uFF0C\u8FD9\u53EF\u80FD\u5BFC\u81F4\u64CD\u4F5C\u5931\u8D25`);
          }
          const is404Page = (() => {
            const bodyText = document.body ? document.body.textContent : "";
            const title = document.title || "";
            const h1 = document.querySelector("h1");
            const h1Text = h1 ? h1.textContent : "";
            const NOT_FOUND_PHRASES = [
              "Sorry, we couldn't find that page",
              "\u62B1\u6B49\uFF0C\u627E\u4E0D\u5230\u8BE5\u9875\u9762",
              "\u627E\u4E0D\u5230\u8BE5\u9875\u9762",
              "Page not found",
              "404"
            ];
            return NOT_FOUND_PHRASES.some(
              (phrase) => bodyText.includes(phrase) || title.includes(phrase) || h1Text.includes(phrase)
            );
          })();
          if (is404Page) {
            logBuffer.push(`\u26A0\uFE0F \u68C0\u6D4B\u5230 404 \u9875\u9762\uFF08\u5546\u54C1\u5DF2\u4E0B\u67B6\u6216\u4E0D\u5B58\u5728\uFF09\uFF0C\u6807\u8BB0\u4E3A\u5DF2\u8DF3\u8FC7\u5E76\u5173\u95ED\u3002`);
            success = true;
            hasReported = true;
            GM_setValue(Config.DB_KEYS.WORKER_DONE, {
              workerId,
              success: true,
              logs: [...logBuffer, "\u5546\u54C1\u4E0D\u5B58\u5728 (404)\uFF0C\u5DF2\u81EA\u52A8\u8DF3\u8FC7"],
              task: currentTask,
              instanceId: payload.instanceId,
              executionTime: Date.now() - startTime
            });
            closeWorkerTab();
            return;
          }
          await (/* @__PURE__ */ __name(function waitForKeyElement(maxWait = 2e3) {
            const matchKey = /* @__PURE__ */ __name(() => {
              const buttons = document.querySelectorAll("button");
              for (const btn of buttons) {
                const t = Utils.normalizeWhitespace(btn.textContent || "");
                if (!t) continue;
                if (Config.ACQUISITION_TEXT_SET.has(t)) return true;
                if (Config.SAVED_TEXT_SET.has(t)) return true;
                if (Config.EXTERNAL_CTA_TEXT_SET.has(t)) return true;
              }
              const bodyText = document.body && document.body.textContent;
              if (bodyText) {
                for (const phrase of Config.SAVED_TEXT_SET) {
                  if (bodyText.includes(phrase)) return true;
                }
              }
              return false;
            }, "matchKey");
            if (matchKey()) return Promise.resolve();
            return new Promise((resolve) => {
              let done = false;
              const finish = /* @__PURE__ */ __name(() => {
                if (done) return;
                done = true;
                try {
                  observer.disconnect();
                } catch (e) {
                }
                clearTimeout(timer);
                resolve();
              }, "finish");
              const observer = new MutationObserver(() => {
                if (matchKey()) finish();
              });
              observer.observe(document.body, { childList: true, subtree: true });
              const timer = setTimeout(finish, maxWait);
            });
          }, "waitForKeyElement"))();
          const adultContentWarning = document.querySelector(".fabkit-Heading--xl");
          if (adultContentWarning && (adultContentWarning.textContent.includes("\u6210\u4EBA\u5185\u5BB9") || adultContentWarning.textContent.includes("Adult Content") || adultContentWarning.textContent.includes("Mature Content"))) {
            logBuffer.push(`\u68C0\u6D4B\u5230\u6210\u4EBA\u5185\u5BB9\u8B66\u544A\u5BF9\u8BDD\u6846\uFF0C\u81EA\u52A8\u70B9\u51FB"\u7EE7\u7EED"\u6309\u94AE...`);
            const continueButton = [...document.querySelectorAll("button.fabkit-Button--primary")].find(
              (btn) => btn.textContent.includes("\u7EE7\u7EED") || btn.textContent.includes("Continue")
            );
            if (continueButton) {
              Utils.deepClick(continueButton);
              logBuffer.push(`\u5DF2\u70B9\u51FB"\u7EE7\u7EED"\u6309\u94AE\uFF0C\u7B49\u5F85\u9875\u9762\u52A0\u8F7D...`);
              await new Promise((resolve) => setTimeout(resolve, 2e3));
            }
          }
          logBuffer.push(`=== \u9875\u9762\u72B6\u6001\u8BCA\u65AD\u5F00\u59CB ===`);
          const diagnosticReport = PageDiagnostics.diagnoseDetailPage();
          logBuffer.push(`\u9875\u9762\u6807\u9898: ${diagnosticReport.pageTitle}`);
          logBuffer.push(`\u53EF\u89C1\u6309\u94AE\u6570\u91CF: ${diagnosticReport.buttons.filter((btn) => btn.isVisible).length}`);
          logBuffer.push(`=== \u9875\u9762\u72B6\u6001\u8BCA\u65AD\u7ED3\u675F ===`);
          try {
            const csrfToken = Utils.getCookie("fab_csrftoken");
            if (!csrfToken) throw new Error("CSRF token not found for API check.");
            const statesUrl = new URL("https://www.fab.com/i/users/me/listings-states");
            statesUrl.searchParams.append("listing_ids", currentTask.uid);
            const response = await API.gmFetch({
              method: "GET",
              url: statesUrl.href,
              headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" }
            });
            let statesData;
            try {
              statesData = JSON.parse(response.responseText);
              if (!Array.isArray(statesData)) {
                statesData = API.extractStateData(statesData, "SingleItemCheck");
              }
            } catch (e) {
              logBuffer.push(`\u89E3\u6790API\u54CD\u5E94\u5931\u8D25: ${e.message}`);
              statesData = [];
            }
            const isOwned = Array.isArray(statesData) && statesData.some((s) => s && s.uid === currentTask.uid && s.acquired);
            if (isOwned) {
              logBuffer.push(`API check confirms item is already owned.`);
              success = true;
            } else {
              logBuffer.push(`API check confirms item is not owned. Proceeding to UI interaction.`);
            }
          } catch (apiError) {
            logBuffer.push(`API ownership check failed: ${apiError.message}. Falling back to UI-based check.`);
          }
          if (!success) {
            const isItemOwned = /* @__PURE__ */ __name(() => {
              const criteria = Config.OWNED_SUCCESS_CRITERIA;
              const snackbar = document.querySelector('.fabkit-Snackbar-root, div[class*="Toast-root"]');
              if (snackbar && criteria.snackbarText.some((text) => snackbar.textContent.includes(text))) {
                return { owned: true, reason: `Snackbar text "${snackbar.textContent}"` };
              }
              const successHeader = document.querySelector("h2");
              if (successHeader && criteria.h2Text.some((text) => successHeader.textContent.includes(text))) {
                return { owned: true, reason: `H2 text "${successHeader.textContent}"` };
              }
              const allButtons = [...document.querySelectorAll("button, a.fabkit-Button-root")];
              const ownedButton = allButtons.find((btn) => criteria.buttonTexts.some((keyword) => btn.textContent.includes(keyword)));
              if (ownedButton) return { owned: true, reason: `Button text "${ownedButton.textContent}"` };
              return { owned: false };
            }, "isItemOwned");
            const initialState = isItemOwned();
            if (initialState.owned) {
              logBuffer.push(`Item already owned on page load (UI Fallback PASS: ${initialState.reason}).`);
              success = true;
            } else {
              const externalState = TaskRunner2.getExternalProductState(document);
              if (externalState.handled) {
                logBuffer.push(`Detected non-purchasable external listing (${externalState.reason}). Marking task as handled.`);
                success = true;
              }
            }
            if (!success) {
              const allVisibleButtons = [...document.querySelectorAll("button")].filter((btn) => {
                const rect = btn.getBoundingClientRect();
                const text = btn.textContent.trim();
                return rect.width > 0 && rect.height > 0 && text.length > 0;
              });
              const criticalKeywords = [...Config.ACQUISITION_TEXT_SET, ...Config.FREE_TEXT_SET, "\u8BB8\u53EF", "License", "Select", "\u9009\u62E9", "Add", "\u6DFB\u52A0", "Library", "\u5E93"];
              const criticalButtons = allVisibleButtons.filter((btn) => {
                const text = btn.textContent;
                return criticalKeywords.some((key) => text.includes(key));
              });
              logBuffer.push(`=== \u6309\u94AE\u68C0\u6D4B: \u53EF\u89C1=${allVisibleButtons.length}, \u5173\u952E=${criticalButtons.length} ===`);
              if (criticalButtons.length > 0) {
                criticalButtons.slice(0, 5).forEach((btn, i) => {
                  logBuffer.push(`  \u5173\u952E\u6309\u94AE${i + 1}: "${btn.textContent.trim().substring(0, 40)}"`);
                });
              } else if (allVisibleButtons.length > 0) {
                allVisibleButtons.slice(0, 3).forEach((btn, i) => {
                  logBuffer.push(`  \u6309\u94AE${i + 1}: "${btn.textContent.trim().substring(0, 40)}"`);
                });
              }
              const licenseButton = allVisibleButtons.find((btn) => {
                const text = Utils.normalizeWhitespace(btn.textContent);
                return text.includes("\u9009\u62E9\u8BB8\u53EF") || text.includes("Select license") || btn.getAttribute("aria-haspopup") === "true" && TaskRunner2.isFreeCard(btn);
              });
              if (licenseButton) {
                logBuffer.push(`Multi-license item detected. Setting up observer for dropdown.`);
                try {
                  await new Promise((resolve, reject) => {
                    const observer = new MutationObserver((mutationsList) => {
                      for (const mutation of mutationsList) {
                        if (mutation.addedNodes.length > 0) {
                          for (const node of mutation.addedNodes) {
                            if (node.nodeType !== 1) continue;
                            const clickableParent = TaskRunner2.findFreeLicenseOption(node);
                            if (clickableParent) {
                              logBuffer.push(`Found explicit free license option, clicking it.`);
                              Utils.deepClick(clickableParent);
                              observer.disconnect();
                              resolve();
                              return;
                            }
                          }
                        }
                      }
                    });
                    observer.observe(document.body, { childList: true, subtree: true });
                    logBuffer.push(`Clicking license button to open dropdown.`);
                    Utils.deepClick(licenseButton);
                    setTimeout(() => {
                      logBuffer.push(`Second attempt to click license button.`);
                      Utils.deepClick(licenseButton);
                    }, 1500);
                    setTimeout(() => {
                      observer.disconnect();
                      reject(new Error("Timeout (5s): The free/personal option did not appear."));
                    }, 5e3);
                  });
                  logBuffer.push(`License selected, waiting for UI update.`);
                  await new Promise((r) => setTimeout(r, 2e3));
                  if (isItemOwned().owned) {
                    logBuffer.push(`Item became owned after license selection.`);
                    success = true;
                  }
                } catch (licenseError) {
                  logBuffer.push(`License selection failed: ${licenseError.message}`);
                }
              }
              if (!success) {
                const freshButtons = [...document.querySelectorAll("button")].filter((btn) => {
                  const rect = btn.getBoundingClientRect();
                  const text = btn.textContent.trim();
                  return rect.width > 0 && rect.height > 0 && text.length > 0;
                });
                const freshCritical = freshButtons.filter((btn) => {
                  const text = btn.textContent;
                  return criticalKeywords.some((key) => text.includes(key));
                });
                logBuffer.push(`=== \u91CD\u65B0\u68C0\u6D4B: \u53EF\u89C1=${freshButtons.length}, \u5173\u952E=${freshCritical.length} ===`);
                if (freshCritical.length > 0) {
                  freshCritical.slice(0, 3).forEach((btn, i) => {
                    logBuffer.push(`  \u5173\u952E\u6309\u94AE${i + 1}: "${btn.textContent.trim().substring(0, 40)}"`);
                  });
                }
                let actionButton = freshButtons.find((btn) => {
                  const text = Utils.normalizeWhitespace(btn.textContent).toLowerCase();
                  const isPopup = btn.getAttribute("aria-haspopup") === "true";
                  const matchesKeyword = [...Config.ACQUISITION_TEXT_SET].some(
                    (keyword) => text.includes(keyword.toLowerCase())
                  );
                  return !isPopup && matchesKeyword;
                });
                if (!actionButton) {
                  actionButton = freshButtons.find((btn) => {
                    const text = Utils.normalizeWhitespace(btn.textContent).toLowerCase();
                    return [...Config.ACQUISITION_TEXT_SET].some(
                      (keyword) => text.includes(keyword.toLowerCase())
                    );
                  });
                }
                if (!actionButton) {
                  actionButton = freshButtons.find((btn) => {
                    const text = Utils.normalizeWhitespace(btn.textContent);
                    const isPopup = btn.getAttribute("aria-haspopup") === "true";
                    const hasFreeText = [...Config.FREE_TEXT_SET].some((freeWord) => text.includes(freeWord));
                    const hasDiscount = /-\s*100\s*%\s*(?:OFF|折扣)?/i.test(text);
                    const hasPersonal = text.includes("\u4E2A\u4EBA") || text.includes("Personal");
                    return hasFreeText && hasDiscount && hasPersonal;
                  });
                  if (actionButton) {
                    logBuffer.push(`Found limited-time free license button: "${actionButton.textContent.trim().substring(0, 50)}"`);
                  }
                }
                if (!actionButton) {
                  actionButton = freshButtons.find((btn) => {
                    const text = btn.textContent.toLowerCase();
                    return text.includes("add") && text.includes("library") || text.includes("\u6DFB\u52A0") && text.includes("\u5E93");
                  });
                  if (actionButton) {
                    logBuffer.push(`\u901A\u8FC7\u5907\u7528\u65B9\u6848\u627E\u5230\u6309\u94AE: "${actionButton.textContent.trim().substring(0, 50)}"`);
                  }
                }
                if (actionButton) {
                  logBuffer.push(`Found add button [${actionButton.textContent.trim().substring(0, 30)}], clicking it.`);
                  Utils.deepClick(actionButton);
                  try {
                    await new Promise((resolve, reject) => {
                      const timeout = 6e4;
                      const startTime2 = Date.now();
                      const interval = setInterval(() => {
                        const currentState = isItemOwned();
                        if (currentState.owned) {
                          logBuffer.push(`Successfully owned (UI Match: ${currentState.reason})`);
                          success = true;
                          clearInterval(interval);
                          resolve();
                          return;
                        }
                        const allButtonsWithShadow = Utils.findAllButtonsWithShadow();
                        let checkoutBtn = allButtonsWithShadow.find(
                          (btn) => btn.classList.contains("payment-order-confirm__btn")
                        );
                        if (!checkoutBtn) {
                          checkoutBtn = allButtonsWithShadow.find((btn) => {
                            const rect = btn.getBoundingClientRect();
                            if (rect.width === 0 || rect.height === 0) return false;
                            const text = Utils.normalizeWhitespace(btn.textContent).toLowerCase();
                            if (text.includes("buy now") || text.includes("\u7ACB\u5373\u8D2D\u4E70")) return false;
                            const isCheckoutContext = btn.ownerDocument !== document || window.location.pathname.includes("/payment/");
                            if (isCheckoutContext) {
                              if (text.includes("add to library") || text.includes("\u6DFB\u52A0\u5230\u5E93") || text.includes("add to account") || text.includes("\u6DFB\u52A0\u5230\u8D26\u6237")) {
                                return true;
                              }
                            }
                            return text.includes("place order") || text.includes("\u4E0B\u5355") || text.includes("checkout") || text.includes("\u7ED3\u8D26") || text.includes("complete order") || text.includes("\u5B8C\u6210\u8BA2\u5355") || text.includes("confirm");
                          });
                        }
                        if (checkoutBtn && !checkoutBtn.disabled) {
                          const lastClickTime = parseInt(checkoutBtn.dataset.lastClickTime || "0");
                          const now = Date.now();
                          if (now - lastClickTime > 2e3) {
                            logBuffer.push(`Found checkout/place order button [${checkoutBtn.textContent.trim()}], clicking it.`);
                            checkoutBtn.dataset.lastClickTime = now.toString();
                            Utils.deepClick(checkoutBtn);
                          }
                        }
                        if (Date.now() - startTime2 > timeout) {
                          clearInterval(interval);
                          reject(new Error(`Timeout waiting for page to enter an 'owned' state. (UI might be stuck)`));
                        }
                      }, 500);
                    });
                  } catch (timeoutError) {
                    logBuffer.push(`Timeout waiting for ownership: ${timeoutError.message}`);
                  }
                } else {
                  logBuffer.push(`Could not find an add button.`);
                }
              }
            }
          }
        } catch (error) {
          logBuffer.push(`A critical error occurred: ${error.message}`);
          success = false;
        } finally {
          if (!success) {
            try {
              const vIframe = document.querySelector('iframe[src*="hcaptcha"], iframe[src*="recaptcha"], iframe[src*="captcha"], iframe[src*="turnstile"], iframe[src*="challenges.cloudflare.com"], iframe[src*="arkoselabs"]');
              const bodyText = document.body && document.body.innerText || "";
              const vPhrases = ["\u4EBA\u673A\u9A8C\u8BC1", "\u786E\u8BA4\u60A8\u662F\u771F\u4EBA", "\u8BF7\u5B8C\u6210\u5B89\u5168\u9A8C\u8BC1", "\u6211\u4E0D\u662F\u673A\u5668\u4EBA", "Verify you are human", "I'm not a robot", "Checking your browser", "complete the security check", "unusual traffic"];
              if (vIframe || vPhrases.some((p) => bodyText.includes(p))) {
                logBuffer.push(Utils.getText("worker_captcha"));
              }
            } catch (e) {
            }
          }
          try {
            hasReported = true;
            await GM_setValue(Config.DB_KEYS.WORKER_DONE, {
              workerId,
              success,
              logs: logBuffer,
              task: currentTask,
              instanceId: payload.instanceId,
              executionTime: Date.now() - startTime
            });
          } catch (error) {
            console.error("Error setting worker done value:", error);
          }
          try {
            await GM_deleteValue(workerId);
          } catch (error) {
            console.error("Error deleting worker value:", error);
          }
          closeWorkerTab();
        }
      } catch (error) {
        Utils.logger("error", `Worker tab error: ${error.message}`);
        closeWorkerTab();
      }
    }, "processDetailPage"),
    // 节流标记:100ms 内多次调用只执行一次,消除 Observer/timer 风暴
    selectorWithSuffix: /* @__PURE__ */ __name((selector, suffix) => {
      return selector.split(",").map((part) => `${part.trim()}${suffix}`).join(", ");
    }, "selectorWithSuffix"),
    getVisibleCardSelector: /* @__PURE__ */ __name(() => {
      return TaskRunner2.selectorWithSuffix(Config.SELECTORS.card, ':not([data-fab-hidden="true"])');
    }, "getVisibleCardSelector"),
    isHideModeActive: /* @__PURE__ */ __name(() => {
      return State.hideSaved || State.hideDiscountedPaid || State.hidePaid;
    }, "isHideModeActive"),
    getHideModeKey: /* @__PURE__ */ __name(() => {
      return [
        State.hideSaved ? "saved" : "",
        State.hideDiscountedPaid ? "discounted" : "",
        State.hidePaid ? "paid" : ""
      ].join("|");
    }, "getHideModeKey"),
    isCardHidden: /* @__PURE__ */ __name((card) => {
      return card?.getAttribute?.("data-fab-hidden") === "true" || card?.style?.display === "none";
    }, "isCardHidden"),
    invalidateCardCountCache: /* @__PURE__ */ __name(() => {
      State.cardCountCache.dirty = true;
    }, "invalidateCardCountCache"),
    refreshCardCountCache: /* @__PURE__ */ __name((cardsArg = null) => {
      if (typeof document === "undefined") {
        State.cardCountCache.total = 0;
        State.cardCountCache.hidden = 0;
        State.cardCountCache.visible = 0;
        State.cardCountCache.dirty = false;
        State.cardCountCache.documentRef = null;
        State.cardCountCache.href = "";
        State.hiddenThisPageCount = 0;
        return { total: 0, hidden: 0, visible: 0 };
      }
      const cards = cardsArg ? Array.from(cardsArg) : Array.from(document.querySelectorAll(Config.SELECTORS.card));
      const hidden = cards.reduce((count, card) => count + (TaskRunner2.isCardHidden(card) ? 1 : 0), 0);
      const total = cards.length;
      const visible = total - hidden;
      State.cardCountCache.total = total;
      State.cardCountCache.hidden = hidden;
      State.cardCountCache.visible = visible;
      State.cardCountCache.dirty = false;
      State.cardCountCache.documentRef = document;
      State.cardCountCache.href = typeof window !== "undefined" ? window.location?.href || "" : "";
      State.hiddenThisPageCount = hidden;
      return { total, hidden, visible };
    }, "refreshCardCountCache"),
    getCardCounts: /* @__PURE__ */ __name((forceRefresh = false) => {
      const cache = State.cardCountCache;
      const href = typeof window !== "undefined" ? window.location?.href || "" : "";
      const documentChanged = typeof document !== "undefined" && cache.documentRef !== document;
      const hrefChanged = cache.href !== href;
      if (forceRefresh || cache.dirty || documentChanged || hrefChanged) {
        return TaskRunner2.refreshCardCountCache();
      }
      return {
        total: cache.total,
        hidden: cache.hidden,
        visible: cache.visible
      };
    }, "getCardCounts"),
    adjustCardCountCacheHidden: /* @__PURE__ */ __name((delta) => {
      const cache = State.cardCountCache;
      if (cache.dirty) return;
      cache.hidden = Math.max(0, Math.min(cache.total, cache.hidden + delta));
      cache.visible = Math.max(0, cache.total - cache.hidden);
      State.hiddenThisPageCount = cache.hidden;
    }, "adjustCardCountCacheHidden"),
    setCardHidden: /* @__PURE__ */ __name((card, hidden) => {
      if (!card) return;
      const wasHidden = TaskRunner2.isCardHidden(card);
      if (hidden) {
        if (card.style) card.style.display = "none";
        card.setAttribute?.("data-fab-hidden", "true");
      } else {
        if (card.style) card.style.display = "";
        card.removeAttribute?.("data-fab-hidden");
      }
      const isHidden = TaskRunner2.isCardHidden(card);
      if (wasHidden !== isHidden) {
        TaskRunner2.adjustCardCountCacheHidden(isHidden ? 1 : -1);
      }
    }, "setCardHidden"),
    resetHiddenCardState: /* @__PURE__ */ __name((cardsArg) => {
      Array.from(cardsArg || []).forEach((card) => {
        card.removeAttribute?.("data-fab-processed");
        TaskRunner2.setCardHidden(card, false);
      });
    }, "resetHiddenCardState"),
    shouldHideCard: /* @__PURE__ */ __name((card) => {
      const isFinished = State.hideSaved && TaskRunner2.isCardFinished(card);
      const isDiscountedPaid = State.hideDiscountedPaid && TaskRunner2.isDiscountedPaidCard(card);
      const isPaidAndHidden = State.hidePaid && !TaskRunner2.isFreeCard(card);
      return isFinished || isDiscountedPaid || isPaidAndHidden;
    }, "shouldHideCard"),
    _runHideOrShowTimer: null,
    scheduleHideOrShow: /* @__PURE__ */ __name(() => {
      if (TaskRunner2._runHideOrShowTimer) return;
      TaskRunner2._runHideOrShowTimer = setTimeout(() => {
        TaskRunner2._runHideOrShowTimer = null;
        TaskRunner2.runHideOrShow();
      }, 100);
    }, "scheduleHideOrShow"),
    runHideOrShow: /* @__PURE__ */ __name(() => {
      if (TaskRunner2._runHideOrShowTimer) {
        clearTimeout(TaskRunner2._runHideOrShowTimer);
        TaskRunner2._runHideOrShowTimer = null;
      }
      if (!TaskRunner2.isHideModeActive()) {
        const allCards = document.querySelectorAll(Config.SELECTORS.card);
        TaskRunner2.refreshCardCountCache(allCards);
        TaskRunner2.resetHiddenCardState(allCards);
        State.lastHideModeKey = TaskRunner2.getHideModeKey();
        if (UI4) UI4.update();
        return;
      }
      const hideModeKey = TaskRunner2.getHideModeKey();
      if (State.lastHideModeKey !== hideModeKey) {
        State.lastHideModeKey = hideModeKey;
        TaskRunner2.invalidateCardCountCache();
        const allCards = document.querySelectorAll(Config.SELECTORS.card);
        TaskRunner2.refreshCardCountCache(allCards);
        TaskRunner2.resetHiddenCardState(allCards);
      } else {
        TaskRunner2.getCardCounts();
      }
      const cards = document.querySelectorAll(TaskRunner2.getVisibleCardSelector());
      let actuallyHidden = 0;
      let hasUnsettledCards = false;
      const unsettledCards = [];
      cards.forEach((card) => {
        if (card.getAttribute("data-fab-processed") === "true" && TaskRunner2.isCardHidden(card)) {
          TaskRunner2.setCardHidden(card, true);
          return;
        }
        if (!TaskRunner2.isCardSettled(card)) {
          hasUnsettledCards = true;
          unsettledCards.push(card);
        }
      });
      if (hasUnsettledCards && unsettledCards.length > 0) {
        if (State.hideRetryTimer) return;
        Utils.logger("debug", Utils.getText("log_unsettled_cards", unsettledCards.length));
        State.hideRetryTimer = setTimeout(() => {
          State.hideRetryTimer = null;
          TaskRunner2.runHideOrShow();
        }, 2e3);
      } else if (State.hideRetryTimer) {
        clearTimeout(State.hideRetryTimer);
        State.hideRetryTimer = null;
      }
      const cardsToHide = [];
      cards.forEach((card) => {
        if (!TaskRunner2.isCardSettled(card)) {
          return;
        }
        if (TaskRunner2.isCardHidden(card)) {
          TaskRunner2.setCardHidden(card, true);
          return;
        }
        card.setAttribute("data-fab-processed", "true");
        if (TaskRunner2.shouldHideCard(card)) {
          cardsToHide.push(card);
        }
      });
      if (cardsToHide.length > 0) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_prepare_hide", cardsToHide.length));
        }
        cardsToHide.sort(() => Math.random() - 0.5);
        if (cardsToHide.length > 30) {
          const FRAME_BATCH = 20;
          let offset = 0;
          const hideNextFrame = /* @__PURE__ */ __name(() => {
            const end = Math.min(offset + FRAME_BATCH, cardsToHide.length);
            for (let i = offset; i < end; i++) {
              TaskRunner2.setCardHidden(cardsToHide[i], true);
              actuallyHidden++;
            }
            offset = end;
            if (offset < cardsToHide.length) {
              requestAnimationFrame(hideNextFrame);
            } else {
              if (State.debugMode) {
                Utils.logger("debug", Utils.getText("debug_hide_completed", actuallyHidden));
              }
              setTimeout(() => {
                if (UI4) UI4.update();
                TaskRunner2.checkVisibilityAndRefresh();
              }, 300);
            }
          }, "hideNextFrame");
          requestAnimationFrame(hideNextFrame);
        } else {
          const batchSize = 10;
          const batches = Math.ceil(cardsToHide.length / batchSize);
          const initialDelay = 200;
          for (let i = 0; i < batches; i++) {
            const start = i * batchSize;
            const end = Math.min(start + batchSize, cardsToHide.length);
            const currentBatch = cardsToHide.slice(start, end);
            const batchDelay = initialDelay + i * 300 + Math.random() * 300;
            const isLastBatch = i === batches - 1;
            setTimeout(() => {
              let batchHidden = 0;
              currentBatch.forEach((card, index) => {
                const cardDelay = index * 50 + Math.random() * 100;
                setTimeout(() => {
                  TaskRunner2.setCardHidden(card, true);
                  actuallyHidden++;
                  batchHidden++;
                  if (batchHidden === currentBatch.length) {
                    if (UI4) UI4.update();
                    if (isLastBatch) {
                      if (State.debugMode) {
                        Utils.logger("debug", Utils.getText("debug_hide_completed", actuallyHidden));
                      }
                      setTimeout(() => {
                        TaskRunner2.checkVisibilityAndRefresh();
                      }, 300);
                    }
                  }
                }, cardDelay);
              });
            }, batchDelay);
          }
        }
      }
      if (cardsToHide.length === 0) {
        if (UI4) UI4.update();
        TaskRunner2.checkVisibilityAndRefresh();
      }
      return;
    }, "runHideOrShow"),
    checkVisibilityAndRefresh: /* @__PURE__ */ __name(() => {
      const cards = document.querySelectorAll(TaskRunner2.getVisibleCardSelector());
      let needsReprocessing = false;
      cards.forEach((card) => {
        const isProcessed = card.getAttribute("data-fab-processed") === "true";
        if (!isProcessed && TaskRunner2.isCardSettled(card)) needsReprocessing = true;
      });
      if (needsReprocessing) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_unprocessed_cards_simple"));
        }
        setTimeout(() => TaskRunner2.runHideOrShow(), 100);
        return;
      }
      const { visible: visibleCards } = TaskRunner2.getCardCounts();
      if (State.debugMode) {
        Utils.logger("debug", Utils.getText("debug_visible_after_hide", visibleCards, State.hiddenThisPageCount));
      }
      if (UI4) UI4.update();
      if (visibleCards === 0) {
        if (State.appStatus === "RATE_LIMITED" && State.autoRefreshEmptyPage) {
          if (State.isRefreshScheduled) {
            Utils.logger("debug", Utils.getText("refresh_plan_exists"));
            return;
          }
          if (State.db.todo.length > 0 || State.activeWorkers > 0) {
            Utils.logger("debug", Utils.getText("log_refresh_cancelled_tasks", State.db.todo.length, State.activeWorkers));
            return;
          }
          Utils.logger("info", Utils.getText("log_all_hidden_rate_limited"));
          const randomDelay = 3e3 + Math.random() * 2e3;
          if (countdownRefresh2) {
            countdownRefresh2(randomDelay, Utils.getText("rate_limit_no_visible_reason"));
          }
        } else if (State.appStatus === "NORMAL" && State.hiddenThisPageCount > 0) {
          Utils.logger("debug", Utils.getText("page_status_hidden_no_visible", State.hiddenThisPageCount));
          if (State.autoAddOnScroll) {
            TaskRunner2.attemptAutoScroll();
          }
        }
      }
    }, "checkVisibilityAndRefresh"),
    ensureTasksAreExecuted: /* @__PURE__ */ __name(() => {
      if (State.db.todo.length === 0) return;
      if (State.isExecuting) {
        if (State.activeWorkers === 0) {
          Utils.logger("info", Utils.getText("log_ensure_tasks"));
          TaskRunner2.executeBatch();
        }
        return;
      }
      Utils.logger("info", Utils.getText("log_auto_start_execution", State.db.todo.length));
      TaskRunner2.startExecution();
    }, "ensureTasksAreExecuted"),
    checkVisibleCardsStatus: /* @__PURE__ */ __name(async () => {
      if (State.isCheckingStatus) {
        return;
      }
      State.isCheckingStatus = true;
      try {
        const visibleCards = [...document.querySelectorAll(TaskRunner2.getVisibleCardSelector())];
        if (visibleCards.length === 0) {
          return;
        }
        let hasUnsettledCards = false;
        const unsettledCards = [];
        visibleCards.forEach((card) => {
          if (!TaskRunner2.isCardSettled(card)) {
            hasUnsettledCards = true;
            unsettledCards.push(card);
          }
        });
        if (hasUnsettledCards && unsettledCards.length > 0) {
        }
        const allItems = [];
        let confirmedOwned = 0;
        visibleCards.forEach((card) => {
          const link = card.querySelector(Config.SELECTORS.cardLink);
          const uidMatch = link?.href.match(/listings\/([a-f0-9-]+)/);
          if (uidMatch && uidMatch[1]) {
            const uid = uidMatch[1];
            const url = link.href.split("?")[0];
            if (Database.isDone(url)) return;
            allItems.push({ uid, url, element: card });
          }
        });
        if (allItems.length === 0) {
          return;
        }
        Utils.logger("debug", Utils.getText("fab_dom_checking_status", allItems.length));
        const uids = allItems.map((item) => item.uid);
        const statesData = await API.checkItemsOwnership(uids);
        const ownedUids = new Set(
          statesData.filter((state) => state && state.acquired).map((state) => state.uid)
        );
        for (const item of allItems) {
          if (ownedUids.has(item.uid)) {
            if (Database.addDoneUrl(item.url)) {
              confirmedOwned++;
            }
            State.db.failed = State.db.failed.filter((f) => f.uid !== item.uid);
            State.db.todo = State.db.todo.filter((t) => t.uid !== item.uid);
          }
        }
        if (confirmedOwned > 0) {
          await Database.saveDone();
          await Database.saveFailed();
          Utils.logger("debug", Utils.getText("fab_dom_api_complete", confirmedOwned));
          Utils.logger("debug", Utils.getText("fab_dom_refresh_complete", confirmedOwned));
          if (UI4) UI4.update();
          if (State.hideSaved || State.hideDiscountedPaid || State.hidePaid) {
            TaskRunner2.runHideOrShow();
          }
        } else {
          Utils.logger("debug", Utils.getText("fab_dom_no_new_owned"));
        }
      } catch (error) {
        Utils.logger("error", Utils.getText("log_check_status_error", error.message));
        if (error.message && error.message.includes("429")) {
          RateLimitManager.enterRateLimitedState("[Fab DOM Refresh] 429\u9519\u8BEF");
        }
      } finally {
        State.isCheckingStatus = false;
      }
    }, "checkVisibleCardsStatus"),
    scanAndAddTasks: /* @__PURE__ */ __name(async (cards) => {
      if (!State.autoAddOnScroll) return;
      if (!State.isAuthenticated) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("auth_scan_blocked"));
        }
        return;
      }
      if (State.isScanningTasks) {
        Utils.logger("debug", `\u5DF2\u6709\u626B\u63CF\u4EFB\u52A1\u8FDB\u884C\u4E2D\uFF0C\u8DF3\u8FC7\u672C\u6B21\u8C03\u7528 (${cards.length} \u5F20\u5361\u7247)`);
        return;
      }
      State.isScanningTasks = true;
      try {
        if (!window._apiWaitStatus) {
          window._apiWaitStatus = {
            isWaiting: false,
            pendingCards: [],
            lastApiActivity: 0,
            apiCheckInterval: null
          };
        }
        if (window._apiWaitStatus.isWaiting) {
          window._apiWaitStatus.pendingCards = [...window._apiWaitStatus.pendingCards, ...cards];
          Utils.logger("info", Utils.getText("debug_api_wait_in_progress", cards.length));
          return;
        }
        window._apiWaitStatus.isWaiting = true;
        window._apiWaitStatus.pendingCards = [...cards];
        window._apiWaitStatus.lastApiActivity = Date.now();
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_wait_api_response", cards.length));
        }
        const waitForApiCompletion = /* @__PURE__ */ __name(() => {
          return new Promise((resolve) => {
            if (window._apiWaitStatus.apiCheckInterval) {
              clearInterval(window._apiWaitStatus.apiCheckInterval);
              window._apiWaitStatus.apiCheckInterval = null;
            }
            const maxWaitTime = 1e4;
            const startTime = Date.now();
            window._apiWaitStatus.apiCheckInterval = setInterval(() => {
              const now = Date.now();
              const timeSinceLastActivity = now - window._apiWaitStatus.lastApiActivity;
              const totalWaitTime = now - startTime;
              if (totalWaitTime > maxWaitTime || timeSinceLastActivity > 2e3) {
                clearInterval(window._apiWaitStatus.apiCheckInterval);
                window._apiWaitStatus.apiCheckInterval = null;
                resolve();
              }
            }, 200);
          });
        }, "waitForApiCompletion");
        try {
          await waitForApiCompletion();
        } catch (error) {
          Utils.logger("error", Utils.getText("auto_add_api_error", error.message));
        }
        const cardsToProcess = [...window._apiWaitStatus.pendingCards];
        window._apiWaitStatus.pendingCards = [];
        window._apiWaitStatus.isWaiting = false;
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_api_wait_complete", cardsToProcess.length));
        }
        const newlyAddedList = [];
        let skippedAlreadyOwned = 0;
        let skippedInTodo = 0;
        let skippedUnsettled = 0;
        cardsToProcess.forEach((card) => {
          const link = card.querySelector(Config.SELECTORS.cardLink);
          const url = link ? link.href.split("?")[0] : null;
          if (!url) return;
          if (!TaskRunner2.isCardSettled(card)) {
            skippedUnsettled++;
            return;
          }
          if (Database.isDone(url)) {
            skippedAlreadyOwned++;
            return;
          }
          if (Database.isTodo(url)) {
            skippedInTodo++;
            return;
          }
          const text = card.textContent || "";
          if (text.includes("\u5DF2\u4FDD\u5B58\u5728\u6211\u7684\u5E93\u4E2D") || text.includes("\u5DF2\u4FDD\u5B58") || text.includes("Saved to My Library") || text.includes("In your library")) {
            skippedAlreadyOwned++;
            return;
          }
          const icons = card.querySelectorAll("i.fabkit-Icon--intent-success, i.edsicon-check-circle-filled");
          if (icons.length > 0) {
            skippedAlreadyOwned++;
            return;
          }
          const uidMatch = url.match(/listings\/([a-f0-9-]+)/);
          if (uidMatch && uidMatch[1]) {
            const uid = uidMatch[1];
            if (DataCache.ownedStatus.has(uid)) {
              const status = DataCache.ownedStatus.get(uid);
              if (status && status.acquired) {
                skippedAlreadyOwned++;
                return;
              }
            }
          }
          if (!TaskRunner2.isFreeCard(card)) return;
          const name = card.querySelector('a[aria-label*="\u521B\u4F5C\u7684"], a[aria-label*="by "]')?.textContent.trim() || card.querySelector('a[href*="/listings/"]')?.textContent.trim() || Utils.getText("untitled");
          newlyAddedList.push({ name, url, type: "detail", uid: url.split("/").pop() });
        });
        if (skippedUnsettled > 0 && !State.autoAddRetryTimer) {
          State.autoAddRetryTimer = setTimeout(() => {
            State.autoAddRetryTimer = null;
            if (State.autoAddOnScroll) {
              TaskRunner2.scanAndAddTasks(document.querySelectorAll(TaskRunner2.getVisibleCardSelector())).catch((error) => Utils.logger("error", `\u81EA\u52A8\u6DFB\u52A0\u91CD\u8BD5\u5931\u8D25: ${error.message}`));
            }
          }, 2e3);
        } else if (skippedUnsettled === 0 && State.autoAddRetryTimer) {
          clearTimeout(State.autoAddRetryTimer);
          State.autoAddRetryTimer = null;
        }
        if (newlyAddedList.length > 0 || skippedAlreadyOwned > 0 || skippedInTodo > 0) {
          if (newlyAddedList.length > 0) {
            const existingUids = new Set(State.db.todo.map((t) => t.uid));
            const existingUrls = new Set(State.db.todo.map((t) => t.url.split("?")[0]));
            const uniqueNewTasks = newlyAddedList.filter((task) => {
              const cleanUrl = task.url.split("?")[0];
              const isDuplicate = existingUids.has(task.uid) || existingUrls.has(cleanUrl);
              if (isDuplicate) {
                Utils.logger("debug", `\u8DF3\u8FC7\u91CD\u590D\u4EFB\u52A1: ${task.name} (uid: ${task.uid})`);
              }
              return !isDuplicate;
            });
            if (uniqueNewTasks.length > 0) {
              State.db.todo.push(...uniqueNewTasks);
              Utils.logger("info", Utils.getText("auto_add_new_tasks", uniqueNewTasks.length));
              if (uniqueNewTasks.length < newlyAddedList.length) {
                Utils.logger("debug", `\u8FC7\u6EE4\u4E86 ${newlyAddedList.length - uniqueNewTasks.length} \u4E2A\u91CD\u590D\u4EFB\u52A1`);
              }
              Database.saveTodo();
              State.autoScrollAttempts = 0;
            } else {
              Utils.logger("debug", `\u6240\u6709 ${newlyAddedList.length} \u4E2A\u4EFB\u52A1\u90FD\u662F\u91CD\u590D\u7684\uFF0C\u5DF2\u8DF3\u8FC7`);
            }
          }
          if (skippedAlreadyOwned > 0 || skippedInTodo > 0) {
            Utils.logger("debug", Utils.getText("debug_filter_owned", skippedAlreadyOwned, skippedInTodo));
          }
          if (State.isExecuting) {
            State.executionTotalTasks = State.db.todo.length;
            TaskRunner2.executeBatch();
          } else if (State.autoAddOnScroll) {
            TaskRunner2.startExecution();
          }
          if (UI4) UI4.update();
        }
      } finally {
        State.isScanningTasks = false;
      }
    }, "scanAndAddTasks"),
    handleRateLimit: /* @__PURE__ */ __name(async (url) => {
      await RateLimitManager.enterRateLimitedState(url || "\u7F51\u7EDC\u8BF7\u6C42");
    }, "handleRateLimit"),
    reportTaskDone: /* @__PURE__ */ __name(async (task, success) => {
      try {
        await GM_setValue(Config.DB_KEYS.WORKER_DONE, {
          workerId: `worker_task_${task.uid}`,
          success,
          logs: [Utils.getText("task_report", success ? Utils.getText("task_success") : Utils.getText("task_failed"), task.name || task.uid)],
          task,
          instanceId: Config.INSTANCE_ID,
          executionTime: 0
        });
        Utils.logger("info", Utils.getText("task_report", success ? Utils.getText("task_success") : Utils.getText("task_failed"), task.name || task.uid));
      } catch (error) {
        Utils.logger("error", Utils.getText("log_report_error", error.message));
      }
    }, "reportTaskDone"),
    onQueueCompleted: null,
    stopExecutionAndSettle: /* @__PURE__ */ __name(async () => {
      if (State.watchdogTimer) {
        clearInterval(State.watchdogTimer);
        State.watchdogTimer = null;
      }
      KeepAlive.stop();
      TaskRunner2.closeAllWorkerTabs();
      if (typeof TaskRunner2.onQueueCompleted === "function") {
        await TaskRunner2.onQueueCompleted();
      } else {
        Utils.logger("info", Utils.getText("log_all_tasks_completed"));
        State.isExecuting = false;
        Database.saveExecutingState();
        Database.saveTodo();
        if (UI4) UI4.update();
      }
    }, "stopExecutionAndSettle"),
    attemptAutoScroll: /* @__PURE__ */ __name(async () => {
      if (State.isAutoScrolling) return;
      State.isAutoScrolling = true;
      if (typeof State.autoScrollAttempts === "undefined") {
        State.autoScrollAttempts = 0;
      }
      const maxScrollAttempts = 3;
      Utils.logger("info", Utils.getText("auto_scroll_attempt", State.autoScrollAttempts + 1, maxScrollAttempts));
      const getCurrentCardTotal = /* @__PURE__ */ __name(() => {
        try {
          return TaskRunner2.getCardCounts().total;
        } catch (_error) {
          return 0;
        }
      }, "getCurrentCardTotal");
      const previousCardTotal = getCurrentCardTotal();
      const previousScrollY = typeof window !== "undefined" ? window.scrollY : 0;
      const tempRestoredCards = [];
      if (typeof document !== "undefined" && typeof document.querySelectorAll === "function") {
        document.querySelectorAll('[data-fab-hidden="true"]').forEach((card) => {
          if (card.style && card.style.display === "none") {
            card.style.display = "";
            card.style.visibility = "hidden";
            tempRestoredCards.push(card);
          }
        });
      }
      const previousScrollHeight = typeof document !== "undefined" && document.documentElement ? document.documentElement.scrollHeight : 0;
      if (typeof window !== "undefined" && typeof window.scrollTo === "function") {
        window.scrollTo(0, previousScrollHeight);
        if (typeof window.dispatchEvent === "function") {
          window.dispatchEvent(new Event("scroll"));
        }
      }
      if (tempRestoredCards.length > 0) {
        setTimeout(() => {
          tempRestoredCards.forEach((card) => {
            card.style.visibility = "";
            card.style.display = "none";
          });
        }, 200);
      }
      setTimeout(async () => {
        State.isAutoScrolling = false;
        const currentScrollHeight = typeof document !== "undefined" && document.documentElement ? document.documentElement.scrollHeight : 0;
        const currentScrollY = typeof window !== "undefined" ? window.scrollY : 0;
        const newTodoCount = State.db.todo.length;
        const currentCardTotal = getCurrentCardTotal();
        if (newTodoCount > 0) {
          Utils.logger("info", Utils.getText("auto_scroll_success", newTodoCount));
          if (!State.isExecuting) {
            TaskRunner2.startExecution();
          }
          return;
        }
        const newCardCount = currentCardTotal - previousCardTotal;
        if (newCardCount > 0) {
          State.autoScrollAttempts = 0;
          Utils.logger("debug", Utils.getText("auto_scroll_cards_loaded", newCardCount));
          TaskRunner2.runHideOrShow();
          TaskRunner2.attemptAutoScroll();
          return;
        }
        const reachedBottom = typeof window !== "undefined" && window.innerHeight + currentScrollY >= currentScrollHeight - 50 || currentScrollHeight === previousScrollHeight && currentScrollY === previousScrollY && previousScrollY > 0;
        State.autoScrollAttempts++;
        if (State.autoScrollAttempts >= maxScrollAttempts) {
          if (reachedBottom) {
            Utils.logger("info", Utils.getText("auto_scroll_reached_bottom"));
          } else {
            Utils.logger("info", Utils.getText("auto_scroll_no_new_items", maxScrollAttempts));
          }
          if (UI4 && typeof UI4.showToast === "function") {
            UI4.showToast(Utils.getText("toast_reached_bottom"), true);
          }
          const canQuery = typeof document !== "undefined" && typeof document.querySelectorAll === "function";
          const counts = canQuery ? TaskRunner2.getCardCounts() : { total: 0, hidden: 0, visible: 0 };
          if (counts.visible === 0 && counts.hidden >= 20) {
            const recoveryCursor = State.savedCursor;
            if (recoveryCursor) {
              try {
                const lastRecoveryCursor = sessionStorage.getItem("fab_helper_last_recovery_cursor");
                if (recoveryCursor !== lastRecoveryCursor) {
                  sessionStorage.setItem("fab_helper_recovery_cursor", recoveryCursor);
                  sessionStorage.setItem("fab_helper_last_recovery_cursor", recoveryCursor);
                  Utils.logger("warn", Utils.getText("log_auto_scroll_stuck_96_refresh", counts.hidden));
                  setTimeout(() => {
                    if (typeof window !== "undefined" && window.location) {
                      window.location.reload();
                    }
                  }, 1500);
                  return;
                } else {
                  Utils.logger("info", Utils.getText("log_auto_scroll_stuck_recovery_failed"));
                }
              } catch (e) {
              }
            }
          }
          if (State.isExecuting && State.rememberScrollPosition && State.savedCursor) {
            Utils.logger("warn", Utils.getText("log_auto_scroll_stuck_refresh"));
            setTimeout(() => {
              if (typeof window !== "undefined" && window.location) {
                window.location.reload();
              }
            }, 1500);
            return;
          }
          if (State.isExecuting) {
            await TaskRunner2.stopExecutionAndSettle();
          } else {
            State.autoScrollAttempts = 0;
            State.isAutoScrolling = false;
          }
          return;
        }
        Utils.logger("debug", Utils.getText("auto_scroll_waiting"));
        TaskRunner2.attemptAutoScroll();
      }, 3e3);
    }, "attemptAutoScroll")
  };

  // src/modules/ui.js
  var TaskRunner3 = null;
  function setTaskRunnerReference(taskRunnerModule) {
    TaskRunner3 = taskRunnerModule;
  }
  __name(setTaskRunnerReference, "setTaskRunnerReference");
  var UI5 = {
    init: /* @__PURE__ */ __name(() => {
      return UI5.create();
    }, "init"),
    create: /* @__PURE__ */ __name(() => {
      const acquisitionButton = [...document.querySelectorAll("button")].find(
        (btn) => [...Config.ACQUISITION_TEXT_SET].some((keyword) => btn.textContent.includes(keyword))
      );
      const downloadTexts = ["\u4E0B\u8F7D", "Download"];
      const downloadButton = [...document.querySelectorAll('a[href*="/download/"], button')].find(
        (btn) => downloadTexts.some((text) => btn.textContent.includes(text))
      );
      if (acquisitionButton || downloadButton) {
        const urlParams = new URLSearchParams(window.location.search);
        if (urlParams.has("workerId")) return false;
        Utils.logger("info", "On a detail page (detected by action buttons), skipping UI creation.");
        return false;
      }
      if (document.getElementById(Config.UI_CONTAINER_ID)) return true;
      const styles = `
            :root {
                --bg-color: rgba(28, 28, 30, 0.9);
                --border-color: rgba(255, 255, 255, 0.15);
                --text-color-primary: #f5f5f7;
                --text-color-secondary: #a0a0a5;
                --radius-l: 12px;
                --radius-m: 8px;
                --radius-s: 6px;
                --blue: #007aff; --pink: #ff2d55; --green: #34c759;
                --orange: #ff9500; --gray: #8e8e93; --dark-gray: #3a3a3c;
                --blue-bg: rgba(0, 122, 255, 0.2);
            }
            #${Config.UI_CONTAINER_ID} {
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 9999;
                background: var(--bg-color);
                backdrop-filter: blur(15px) saturate(1.8);
                -webkit-backdrop-filter: blur(15px) saturate(1.8);
                border: 1px solid var(--border-color);
                border-radius: var(--radius-l);
                color: var(--text-color-primary);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                width: 300px;
                font-size: 14px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            }
            #${Config.UI_CONTAINER_ID} *, #${Config.UI_CONTAINER_ID} *::before, #${Config.UI_CONTAINER_ID} *::after {
                box-sizing: border-box;
            }
            .fab-helper-tabs {
                display: flex;
                border-bottom: 1px solid var(--border-color);
            }
            .fab-helper-tabs button {
                flex: 1;
                padding: 10px 0;
                font-size: 14px;
                font-weight: 500;
                cursor: pointer;
                background: transparent;
                border: none;
                color: var(--text-color-secondary);
                transition: color 0.2s, border-bottom 0.2s;
                border-bottom: 2px solid transparent;
                display: flex;
                justify-content: center;
                align-items: center;
            }
            .fab-helper-tabs button.active {
                color: var(--text-color-primary);
                border-bottom: 2px solid var(--blue);
            }
            .fab-helper-tab-content {
                padding: 12px;
            }
            .fab-helper-status-bar {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
            }
            .fab-helper-status-item {
                background: var(--dark-gray);
                padding: 8px 6px;
                border-radius: var(--radius-m);
                font-size: 12px;
                color: var(--text-color-secondary);
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                gap: 2px;
                min-width: 0;
                flex-grow: 1;
                flex-basis: calc((100% - 12px) / 3);
            }
            .fab-helper-status-label {
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 4px;
                white-space: nowrap;
            }
            .fab-helper-status-item span {
                display: block;
                font-size: 18px;
                font-weight: 600;
                color: #fff;
                margin-top: 0;
            }
            .fab-helper-execute-btn {
                width: 100%;
                border: none;
                border-radius: var(--radius-m);
                padding: 12px 14px;
                font-size: 16px;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.2s ease;
                color: #fff;
                background: var(--blue);
                margin-bottom: 12px;
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
            }
            .fab-helper-execute-btn.executing {
                background: var(--pink);
            }
            .fab-helper-actions {
                display: flex;
                gap: 8px;
            }
            .fab-helper-actions button {
                flex: 1;
                min-width: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 5px;
                background: var(--dark-gray);
                border: none;
                border-radius: var(--radius-m);
                color: var(--text-color-primary);
                padding: 8px 6px;
                cursor: pointer;
                transition: background-color 0.2s;
                white-space: nowrap;
                font-size: 13.5px;
                font-weight: normal;
            }
            .fab-helper-actions button:hover {
                background: #4a4a4c;
            }
            .fab-log-container {
                padding: 0 12px 12px 12px;
                border-bottom: 1px solid var(--border-color);
                margin-bottom: 12px;
            }
            .fab-log-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 8px;
                margin-top: 8px;
            }
            .fab-log-header span {
                font-size: 14px;
                font-weight: 500;
                color: var(--text-color-secondary);
            }
            .fab-log-controls button {
                background: transparent;
                border: none;
                color: var(--text-color-secondary);
                cursor: pointer;
                padding: 4px;
                font-size: 18px;
                line-height: 1;
            }
            #${Config.UI_LOG_ID} {
                background: rgba(10,10,10,0.85);
                color: #ddd;
                font-size: 11px;
                line-height: 1.4;
                padding: 8px;
                border-radius: var(--radius-m);
                max-height: 150px;
                overflow-y: auto;
                min-height: 50px;
                display: flex;
                flex-direction: column-reverse;
                box-shadow: inset 0 1px 4px rgba(0,0,0,0.2);
                scrollbar-width: thin;
                scrollbar-color: rgba(255,255,255,0.3) rgba(0,0,0,0.2);
            }
            #${Config.UI_LOG_ID}::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }
            #${Config.UI_LOG_ID}::-webkit-scrollbar-track {
                background: rgba(0,0,0,0.2);
                border-radius: 4px;
            }
            #${Config.UI_LOG_ID}::-webkit-scrollbar-thumb {
                background: rgba(255,255,255,0.3);
                border-radius: 4px;
            }
            .fab-setting-row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 10px 0;
                border-bottom: 1px solid var(--border-color);
            }
            .fab-setting-row:last-child {
                border-bottom: none;
            }
            .fab-setting-label {
                font-size: 14px;
            }
            .fab-toggle-switch {
                position: relative;
                display: inline-block;
                width: 44px;
                height: 24px;
            }
            .fab-toggle-switch input {
                opacity: 0;
                width: 0;
                height: 0;
            }
            .fab-toggle-slider {
                position: absolute;
                cursor: pointer;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: var(--dark-gray);
                transition: .4s;
                border-radius: 24px;
            }
            .fab-toggle-slider:before {
                position: absolute;
                content: "";
                height: 20px;
                width: 20px;
                left: 2px;
                bottom: 2px;
                background-color: white;
                transition: .4s;
                border-radius: 50%;
            }
            input:checked + .fab-toggle-slider {
                background-color: var(--blue);
            }
            input:checked + .fab-toggle-slider:before {
                transform: translateX(20px);
            }
            .fab-debug-history-container {
                scrollbar-width: thin;
                scrollbar-color: rgba(255,255,255,0.3) rgba(0,0,0,0.2);
            }
            .fab-debug-history-container::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }
            .fab-debug-history-container::-webkit-scrollbar-track {
                background: rgba(0,0,0,0.2);
                border-radius: 4px;
            }
            .fab-debug-history-container::-webkit-scrollbar-thumb {
                background: rgba(255,255,255,0.3);
                border-radius: 4px;
            }
            .fab-helper-toast-container {
                position: fixed;
                bottom: 20px;
                left: 20px;
                z-index: 10000;
                display: flex;
                flex-direction: column;
                gap: 10px;
                pointer-events: none;
            }
            .fab-helper-toast {
                pointer-events: auto;
                background: var(--bg-color);
                backdrop-filter: blur(15px) saturate(1.8);
                -webkit-backdrop-filter: blur(15px) saturate(1.8);
                border: 1px solid var(--border-color);
                border-radius: var(--radius-m);
                padding: 12px 16px;
                color: var(--text-color-primary);
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 12px;
                min-width: 250px;
                max-width: 350px;
                animation: fab-helper-slide-in 0.3s ease-out;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            }
            .fab-helper-toast-content {
                flex: 1;
                font-size: 13px;
                line-height: 1.4;
            }
            .fab-helper-toast-close {
                cursor: pointer;
                background: transparent;
                border: none;
                color: var(--text-color-secondary);
                font-size: 18px;
                font-weight: bold;
                padding: 0 4px;
                line-height: 1;
                transition: color 0.2s;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .fab-helper-toast-close:hover {
                color: var(--pink);
            }
            @keyframes fab-helper-slide-in {
                from {
                    transform: translateY(20px);
                    opacity: 0;
                }
                to {
                    transform: translateY(0);
                    opacity: 1;
                }
            }
        `;
      const styleSheet = document.createElement("style");
      styleSheet.innerText = styles;
      document.head.appendChild(styleSheet);
      const container = document.createElement("div");
      container.id = Config.UI_CONTAINER_ID;
      State.UI.container = container;
      const header = document.createElement("div");
      header.style.cssText = "padding: 8px 12px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center;";
      const title = document.createElement("span");
      title.textContent = Utils.getText("app_title");
      title.style.fontWeight = "600";
      const version = document.createElement("span");
      version.textContent = `v${GM_info.script.version}`;
      version.style.cssText = "font-size: 12px; color: var(--text-color-secondary); background: var(--dark-gray); padding: 2px 5px; border-radius: var(--radius-s);";
      header.append(title, version);
      container.appendChild(header);
      const tabContainer = document.createElement("div");
      tabContainer.className = "fab-helper-tabs";
      const tabs = ["dashboard", "settings", "debug"];
      tabs.forEach((tabName) => {
        const btn = document.createElement("button");
        btn.textContent = Utils.getText(`tab_${tabName}`);
        btn.onclick = () => UI5.switchTab(tabName);
        if (tabName === "dashboard") btn.classList.add("active");
        tabContainer.appendChild(btn);
        State.UI.tabs[tabName] = btn;
      });
      container.appendChild(tabContainer);
      const dashboardContent = document.createElement("div");
      dashboardContent.className = "fab-helper-tab-content";
      dashboardContent.style.display = "block";
      State.UI.tabContents.dashboard = dashboardContent;
      const statusBar = document.createElement("div");
      statusBar.className = "fab-helper-status-bar";
      const createStatusItem = /* @__PURE__ */ __name((id, label, icon) => {
        const item = document.createElement("div");
        item.className = "fab-helper-status-item";
        item.innerHTML = `<div class="fab-helper-status-label">${icon} ${label}</div><span id="${id}">0</span>`;
        return item;
      }, "createStatusItem");
      State.UI.statusVisible = createStatusItem("fab-status-visible", Utils.getText("visible"), "\u{1F441}\uFE0F");
      State.UI.statusTodo = createStatusItem("fab-status-todo", Utils.getText("todo"), "\u{1F4E5}");
      State.UI.statusDone = createStatusItem("fab-status-done", Utils.getText("added"), "\u2705");
      State.UI.statusFailed = createStatusItem("fab-status-failed", Utils.getText("failed"), "\u274C");
      State.UI.statusFailed.style.cursor = "pointer";
      State.UI.statusFailed.title = Utils.getText("tooltip_open_failed");
      State.UI.statusFailed.onclick = () => {
        if (State.db.failed.length === 0) {
          Utils.logger("info", Utils.getText("failed_list_empty"));
          return;
        }
        if (window.confirm(Utils.getText("confirm_open_failed", State.db.failed.length))) {
          Utils.logger("info", Utils.getText("opening_failed_items", State.db.failed.length));
          State.db.failed.forEach((task) => {
            GM_openInTab(task.url, { active: false });
          });
        }
      };
      State.UI.statusHidden = createStatusItem("fab-status-hidden", Utils.getText("hidden"), "\u{1F648}");
      statusBar.append(State.UI.statusTodo, State.UI.statusDone, State.UI.statusFailed, State.UI.statusVisible, State.UI.statusHidden);
      State.UI.statusKeepAlive = document.createElement("div");
      State.UI.statusKeepAlive.className = "fab-helper-keepalive-status";
      State.UI.statusKeepAlive.style.cssText = "font-size:11px;opacity:0.8;margin:4px 2px 0;display:none;";
      State.UI.statusKeepAlive.title = "\u540E\u53F0\u4FDD\u6D3B\uFF1A\u2665\u5FC3\u8DF3(Worker,\u4E0D\u88AB\u8282\u6D41) + \u2744\uFE0F\u9632\u51BB\u7ED3(WebRTC)\u3002\u540E\u53F0/\u9501\u5C4F\u65F6\u5E94\u6301\u7EED\u5237\u65B0\uFF1B\u82E5\u663E\u793A\u672A\u542F\u52A8\u8BF4\u660E Worker \u88AB\u9875\u9762 CSP \u62E6\u622A";
      State.UI.execBtn = document.createElement("button");
      State.UI.execBtn.className = "fab-helper-execute-btn";
      State.UI.execBtn.onclick = () => TaskRunner3 && TaskRunner3.toggleExecution();
      if (State.isExecuting) {
        State.UI.execBtn.innerHTML = `<span>${Utils.getText("executing")}</span>`;
        State.UI.execBtn.classList.add("executing");
      } else {
        State.UI.execBtn.textContent = Utils.getText("execute");
        State.UI.execBtn.classList.remove("executing");
      }
      const actionButtons = document.createElement("div");
      actionButtons.className = "fab-helper-actions";
      State.UI.syncBtn = document.createElement("button");
      State.UI.syncBtn.textContent = "\u{1F504} " + Utils.getText("sync");
      State.UI.syncBtn.onclick = () => TaskRunner3 && TaskRunner3.refreshVisibleStates();
      State.UI.hideBtn = document.createElement("button");
      State.UI.hideBtn.onclick = () => TaskRunner3 && TaskRunner3.toggleHideSaved();
      State.UI.retryFailedBtn = document.createElement("button");
      State.UI.retryFailedBtn.textContent = "\u{1F501} " + Utils.getText("retry_failed");
      State.UI.retryFailedBtn.onclick = () => TaskRunner3 && TaskRunner3.retryFailedTasks();
      actionButtons.append(State.UI.syncBtn, State.UI.hideBtn, State.UI.retryFailedBtn);
      const logContainer = document.createElement("div");
      logContainer.className = "fab-log-container";
      const logHeader = document.createElement("div");
      logHeader.className = "fab-log-header";
      const logTitle = document.createElement("span");
      logTitle.textContent = Utils.getText("operation_log");
      const logControls = document.createElement("div");
      logControls.className = "fab-log-controls";
      const copyLogBtn = document.createElement("button");
      copyLogBtn.innerHTML = "\u{1F4C4}";
      copyLogBtn.title = Utils.getText("copyLog");
      copyLogBtn.onclick = () => {
        navigator.clipboard.writeText(State.UI.logPanel.innerText).then(() => {
          const originalText = copyLogBtn.textContent;
          copyLogBtn.textContent = "\u2705";
          setTimeout(() => {
            copyLogBtn.textContent = originalText;
          }, 1500);
        }).catch((err) => Utils.logger("error", "Failed to copy log:", err));
      };
      const clearLogBtn = document.createElement("button");
      clearLogBtn.innerHTML = "\u{1F5D1}\uFE0F";
      clearLogBtn.title = Utils.getText("clearLog");
      clearLogBtn.onclick = () => {
        State.UI.logPanel.innerHTML = "";
      };
      logControls.append(copyLogBtn, clearLogBtn);
      logHeader.append(logTitle, logControls);
      State.UI.logPanel = document.createElement("div");
      State.UI.logPanel.id = Config.UI_LOG_ID;
      logContainer.append(logHeader, State.UI.logPanel);
      const positionContainer = document.createElement("div");
      positionContainer.className = "fab-helper-position-container";
      positionContainer.style.cssText = "margin: 8px 0; padding: 6px 8px; background-color: rgba(0,0,0,0.05); border-radius: 4px; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center;";
      const positionIcon = document.createElement("span");
      positionIcon.textContent = Utils.getText("position_indicator");
      positionIcon.style.marginRight = "4px";
      const positionInfo = document.createElement("span");
      positionInfo.textContent = Utils.decodeCursor(State.savedCursor);
      positionInfo.style.cssText = "flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;";
      State.UI.savedPositionDisplay = positionInfo;
      positionContainer.appendChild(positionIcon);
      positionContainer.appendChild(positionInfo);
      const clearPositionBtn = document.createElement("button");
      clearPositionBtn.textContent = "\u{1F504}";
      clearPositionBtn.title = Utils.getText("clear_position_tooltip");
      clearPositionBtn.style.cssText = "background: none; border: none; cursor: pointer; margin-left: auto; font-size: 14px; padding: 0 4px; opacity: 0.7; transition: opacity 0.2s;";
      clearPositionBtn.onmouseover = () => {
        clearPositionBtn.style.opacity = "1";
      };
      clearPositionBtn.onmouseout = () => {
        clearPositionBtn.style.opacity = "0.7";
      };
      clearPositionBtn.onclick = async () => {
        if (State.savedCursor) {
          if (confirm(Utils.getText("confirm_reset_position"))) {
            State.savedCursor = null;
            await GM_deleteValue(Config.DB_KEYS.LAST_CURSOR);
            State.UI.savedPositionDisplay.textContent = Utils.getText("no_saved_position");
            window.location.reload();
          }
        } else {
          Utils.logger("info", Utils.getText("no_position_to_reset"));
        }
      };
      positionContainer.appendChild(clearPositionBtn);
      dashboardContent.append(logContainer, positionContainer, statusBar, State.UI.statusKeepAlive, State.UI.execBtn, actionButtons);
      container.appendChild(dashboardContent);
      const settingsContent = document.createElement("div");
      settingsContent.className = "fab-helper-tab-content";
      settingsContent.style.display = "none";
      const createSettingRow = /* @__PURE__ */ __name((labelText, stateKey) => {
        const row = document.createElement("div");
        row.className = "fab-setting-row";
        const label = document.createElement("span");
        label.className = "fab-setting-label";
        label.textContent = labelText;
        const switchContainer = document.createElement("label");
        switchContainer.className = "fab-toggle-switch";
        const input = document.createElement("input");
        input.type = "checkbox";
        input.checked = State[stateKey];
        input.onchange = (e) => {
          e.stopPropagation();
          e.preventDefault();
          if (!TaskRunner3) return;
          if (stateKey === "autoAddOnScroll") {
            TaskRunner3.toggleAutoAdd();
          } else if (stateKey === "rememberScrollPosition") {
            TaskRunner3.toggleRememberPosition();
          } else if (stateKey === "autoResumeAfter429") {
            TaskRunner3.toggleAutoResume();
          } else if (stateKey === "autoRefreshEmptyPage") {
            TaskRunner3.toggleAutoRefreshEmpty();
          } else if (stateKey === "hideDiscountedPaid") {
            TaskRunner3.toggleHideDiscountedPaid();
          } else if (stateKey === "hidePaid") {
            TaskRunner3.toggleHidePaid();
          } else if (stateKey === "blockLargeResources") {
            TaskRunner3.toggleBlockResources();
          }
          e.target.checked = State[stateKey];
        };
        const slider = document.createElement("span");
        slider.className = "fab-toggle-slider";
        switchContainer.append(input, slider);
        row.append(label, switchContainer);
        return row;
      }, "createSettingRow");
      const autoAddSetting = createSettingRow(Utils.getText("setting_auto_add_scroll"), "autoAddOnScroll");
      settingsContent.appendChild(autoAddSetting);
      const rememberPosSetting = createSettingRow(Utils.getText("setting_remember_position"), "rememberScrollPosition");
      settingsContent.appendChild(rememberPosSetting);
      const autoResumeSetting = createSettingRow(Utils.getText("setting_auto_resume_429"), "autoResumeAfter429");
      settingsContent.appendChild(autoResumeSetting);
      const autoRefreshEmptySetting = createSettingRow(Utils.getText("setting_auto_refresh"), "autoRefreshEmptyPage");
      settingsContent.appendChild(autoRefreshEmptySetting);
      const hideDiscountedPaidSetting = createSettingRow(Utils.getText("setting_hide_discounted"), "hideDiscountedPaid");
      settingsContent.appendChild(hideDiscountedPaidSetting);
      const hidePaidSetting = createSettingRow(Utils.getText("setting_hide_paid"), "hidePaid");
      settingsContent.appendChild(hidePaidSetting);
      const blockResourcesSetting = createSettingRow(Utils.getText("setting_block_large_resources"), "blockLargeResources");
      settingsContent.appendChild(blockResourcesSetting);
      const resetButton = document.createElement("button");
      resetButton.textContent = Utils.getText("clear_all_data");
      resetButton.style.cssText = "width: 100%; margin-top: 15px; background-color: var(--pink); color: white; padding: 10px; border-radius: var(--radius-m); border: none; cursor: pointer;";
      resetButton.onclick = Database.resetAllData;
      settingsContent.appendChild(resetButton);
      const debugModeRow = document.createElement("div");
      debugModeRow.className = "fab-setting-row";
      debugModeRow.title = Utils.getText("setting_debug_tooltip");
      const debugLabel = document.createElement("span");
      debugLabel.className = "fab-setting-label";
      debugLabel.textContent = Utils.getText("debug_mode");
      debugLabel.style.color = "#ff9800";
      const debugSwitchContainer = document.createElement("label");
      debugSwitchContainer.className = "fab-toggle-switch";
      const debugInput = document.createElement("input");
      debugInput.type = "checkbox";
      debugInput.checked = State.debugMode;
      debugInput.onchange = (e) => {
        State.debugMode = e.target.checked;
        debugModeRow.classList.toggle("active", State.debugMode);
        Utils.logger("info", Utils.getText("log_debug_mode_toggle", State.debugMode ? Utils.getText("status_enabled") : Utils.getText("status_disabled")));
        GM_setValue("fab_helper_debug_mode", State.debugMode);
      };
      const debugSlider = document.createElement("span");
      debugSlider.className = "fab-toggle-slider";
      debugSwitchContainer.append(debugInput, debugSlider);
      debugModeRow.append(debugLabel, debugSwitchContainer);
      debugModeRow.classList.toggle("active", State.debugMode);
      settingsContent.appendChild(debugModeRow);
      State.UI.tabContents.settings = settingsContent;
      container.appendChild(settingsContent);
      const debugContent = document.createElement("div");
      debugContent.className = "fab-helper-tab-content";
      debugContent.style.display = "none";
      State.UI.debugContent = debugContent;
      const debugHeader = document.createElement("div");
      debugHeader.style.cssText = "display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 10px; gap: 8px;";
      const debugTitle = document.createElement("h4");
      debugTitle.textContent = Utils.getText("status_history");
      debugTitle.style.cssText = "margin: 0; font-size: 14px; white-space: nowrap;";
      const debugControls = document.createElement("div");
      debugControls.style.cssText = "display: flex; gap: 6px; flex-wrap: wrap;";
      const copyHistoryBtn = document.createElement("button");
      copyHistoryBtn.textContent = Utils.getText("copy_btn");
      copyHistoryBtn.title = "\u590D\u5236\u8BE6\u7EC6\u5386\u53F2\u8BB0\u5F55";
      copyHistoryBtn.style.cssText = "background: var(--dark-gray); border: 1px solid var(--border-color); color: var(--text-color-secondary); padding: 4px 8px; border-radius: var(--radius-m); cursor: pointer;";
      copyHistoryBtn.onclick = () => {
        if (State.statusHistory.length === 0) {
          Utils.logger("info", Utils.getText("no_history_to_copy"));
          return;
        }
        const formatEntry = /* @__PURE__ */ __name((entry) => {
          const date = new Date(entry.endTime).toLocaleString();
          if (entry.type === "STARTUP") {
            return `\u{1F680} ${Utils.getText("script_startup")}
  - ${Utils.getText("time_label")}: ${date}
  - ${Utils.getText("info_label")}: ${entry.message || ""}`;
          } else {
            const type = entry.type === "NORMAL" ? `\u2705 ${Utils.getText("normal_period")}` : `\u{1F6A8} ${Utils.getText("rate_limited_period")}`;
            let details = `${Utils.getText("duration_label")}: ${entry.duration !== void 0 && entry.duration !== null ? entry.duration.toFixed(2) : Utils.getText("unknown_duration")}s`;
            if (entry.requests !== void 0) {
              details += `, ${Utils.getText("requests_label")}: ${entry.requests}${Utils.getText("requests_unit")}`;
            }
            return `${type}
  - ${Utils.getText("ended_at")}: ${date}
  - ${details}`;
          }
        }, "formatEntry");
        const fullLog = State.statusHistory.map(formatEntry).join("\n\n");
        navigator.clipboard.writeText(fullLog).then(() => {
          const originalText = copyHistoryBtn.textContent;
          copyHistoryBtn.textContent = Utils.getText("copied_success");
          setTimeout(() => {
            copyHistoryBtn.textContent = originalText;
          }, 2e3);
        }).catch((err) => Utils.logger("error", Utils.getText("log_copy_failed"), err));
      };
      const clearHistoryBtn = document.createElement("button");
      clearHistoryBtn.textContent = Utils.getText("clear_btn");
      clearHistoryBtn.title = "\u6E05\u7A7A\u5386\u53F2\u8BB0\u5F55";
      clearHistoryBtn.style.cssText = "background: var(--dark-gray); border: 1px solid var(--border-color); color: var(--text-color-secondary); padding: 4px 8px; border-radius: var(--radius-m); cursor: pointer;";
      clearHistoryBtn.onclick = async () => {
        if (window.confirm(Utils.getText("confirm_clear_history"))) {
          State.statusHistory = [];
          await GM_deleteValue(Config.DB_KEYS.STATUS_HISTORY);
          const currentSessionEntry = {
            type: "STARTUP",
            duration: 0,
            endTime: (/* @__PURE__ */ new Date()).toISOString(),
            message: Utils.getText("history_cleared_new_session")
          };
          await RateLimitManager.addToHistory(currentSessionEntry);
          UI5.updateDebugTab();
          Utils.logger("info", Utils.getText("status_history_cleared"));
        }
      };
      const diagnosisBtn = document.createElement("button");
      diagnosisBtn.textContent = Utils.getText("page_diagnosis");
      diagnosisBtn.style.cssText = "background: #2196F3; border: 1px solid #1976D2; color: white; padding: 4px 8px; border-radius: var(--radius-m); cursor: pointer; white-space: nowrap;";
      diagnosisBtn.onclick = () => {
        try {
          const report = PageDiagnostics.diagnoseDetailPage();
          PageDiagnostics.logDiagnosticReport(report);
          Utils.logger("info", Utils.getText("page_diagnosis_complete"));
        } catch (error) {
          Utils.logger("error", Utils.getText("page_diagnosis_failed", error.message));
        }
      };
      debugControls.append(copyHistoryBtn, clearHistoryBtn, diagnosisBtn);
      debugHeader.append(debugTitle, debugControls);
      const historyListContainer = document.createElement("div");
      historyListContainer.style.cssText = "max-height: 250px; overflow-y: auto; background: rgba(10,10,10,0.85); color: #ddd; padding: 8px; border-radius: var(--radius-m);";
      historyListContainer.className = "fab-debug-history-container";
      State.UI.historyContainer = historyListContainer;
      debugContent.append(debugHeader, historyListContainer);
      State.UI.tabContents.debug = debugContent;
      container.appendChild(debugContent);
      document.body.appendChild(container);
      return true;
    }, "create"),
    update: /* @__PURE__ */ __name(() => {
      if (!State.UI.container) return;
      const titleElement = State.UI.container.querySelector('span[style*="font-weight: 600"]');
      if (titleElement) {
        titleElement.textContent = Utils.getText("app_title");
      }
      const tabs = ["dashboard", "settings", "debug"];
      tabs.forEach((tabName) => {
        const tabButton = State.UI.tabs[tabName];
        if (tabButton) {
          tabButton.textContent = Utils.getText(`tab_${tabName}`);
        }
      });
      if (State.UI.syncBtn) {
        State.UI.syncBtn.textContent = "\u{1F504} " + Utils.getText("sync");
      }
      const todoCount = State.db.todo.length;
      const doneCount = State.db.done.length;
      const failedCount = State.db.failed.length;
      const cardCounts = TaskRunner3?.getCardCounts ? TaskRunner3.getCardCounts() : {
        total: document.querySelectorAll(Config.SELECTORS.card).length,
        hidden: State.hiddenThisPageCount,
        visible: Math.max(0, document.querySelectorAll(Config.SELECTORS.card).length - State.hiddenThisPageCount)
      };
      const hiddenCount = cardCounts.hidden;
      const visibleCount = cardCounts.visible;
      if (State.UI.statusTodo) State.UI.statusTodo.querySelector("span").textContent = todoCount;
      if (State.UI.statusDone) State.UI.statusDone.querySelector("span").textContent = doneCount;
      if (State.UI.statusFailed) State.UI.statusFailed.querySelector("span").textContent = failedCount;
      if (State.UI.statusHidden) State.UI.statusHidden.querySelector("span").textContent = hiddenCount;
      if (State.UI.statusVisible) State.UI.statusVisible.querySelector("span").textContent = visibleCount;
      const statusLabelUpdates = [
        { element: State.UI.statusVisible, icon: "\u{1F441}\uFE0F", key: "visible" },
        { element: State.UI.statusTodo, icon: "\u{1F4E5}", key: "todo" },
        { element: State.UI.statusDone, icon: "\u2705", key: "added" },
        { element: State.UI.statusFailed, icon: "\u274C", key: "failed" },
        { element: State.UI.statusHidden, icon: "\u{1F648}", key: "hidden" }
      ];
      statusLabelUpdates.forEach(({ element, icon, key }) => {
        const labelDiv = element?.querySelector(".fab-helper-status-label");
        if (labelDiv) {
          labelDiv.textContent = `${icon} ${Utils.getText(key)}`;
        }
      });
      if (State.UI.execBtn) {
        if (State.isExecuting) {
          State.UI.execBtn.innerHTML = `<span>${Utils.getText("executing")}</span>`;
          State.UI.execBtn.classList.add("executing");
          if (State.executionTotalTasks > 0) {
            const progress = State.executionCompletedTasks + State.executionFailedTasks;
            const percentage = Math.round(progress / State.executionTotalTasks * 100);
            State.UI.execBtn.title = Utils.getText("tooltip_executing_progress", progress, State.executionTotalTasks, percentage);
          } else {
            State.UI.execBtn.title = Utils.getText("tooltip_executing");
          }
        } else {
          State.UI.execBtn.textContent = Utils.getText("execute");
          State.UI.execBtn.classList.remove("executing");
          State.UI.execBtn.title = Utils.getText("tooltip_start_tasks");
        }
      }
      if (State.UI.statusKeepAlive) {
        if (!State.isExecuting) {
          State.UI.statusKeepAlive.style.display = "none";
        } else {
          State.UI.statusKeepAlive.style.display = "";
          const ka = KeepAlive.getStatus();
          if (!ka.heartbeatAlive) {
            State.UI.statusKeepAlive.textContent = `\u{1F6E1}\uFE0F ${Utils.getText("keepalive_label")}: ${Utils.getText("keepalive_dead")}`;
          } else {
            const ago = ka.lastTickAgoMs == null ? "?" : Math.round(ka.lastTickAgoMs / 1e3);
            const fg = ka.freezeGuard === "open" ? "\u2744\uFE0Fon" : ka.freezeGuard === "off" ? "\u2744\uFE0Foff" : "\u2744\uFE0F\u2026";
            State.UI.statusKeepAlive.textContent = `\u{1F6E1}\uFE0F ${Utils.getText("keepalive_label")}: \u2665${ago}s \xB7 ${fg}`;
          }
        }
      }
      if (State.UI.hideBtn) {
        State.UI.hideBtn.textContent = (State.hideSaved ? "\u{1F648} " : "\u{1F441}\uFE0F ") + (State.hideSaved ? Utils.getText("show") : Utils.getText("hide"));
      }
      if (State.UI.retryFailedBtn) {
        State.UI.retryFailedBtn.disabled = State.db.failed.length === 0;
        State.UI.retryFailedBtn.title = State.db.failed.length > 0 ? Utils.getText("retry_failed") + ` (${State.db.failed.length})` : Utils.getText("no_failed_tasks");
      }
    }, "update"),
    removeAllOverlays: /* @__PURE__ */ __name(() => {
      document.querySelectorAll(Config.SELECTORS.card).forEach((card) => {
        const overlay = card.querySelector(".fab-helper-overlay");
        if (overlay) overlay.remove();
        card.style.opacity = "1";
      });
    }, "removeAllOverlays"),
    showToast: /* @__PURE__ */ __name((message, isPersistent = false) => {
      let container = document.getElementById("fab-helper-toast-container");
      if (!container) {
        container = document.createElement("div");
        container.id = "fab-helper-toast-container";
        container.className = "fab-helper-toast-container";
        document.body.appendChild(container);
      }
      const existingToasts = container.querySelectorAll(".fab-helper-toast");
      for (const existing of existingToasts) {
        if (existing.querySelector(".fab-helper-toast-content")?.textContent === message) {
          return;
        }
      }
      const toast = document.createElement("div");
      toast.className = "fab-helper-toast";
      const content = document.createElement("div");
      content.className = "fab-helper-toast-content";
      content.textContent = message;
      toast.appendChild(content);
      const closeBtn = document.createElement("button");
      closeBtn.className = "fab-helper-toast-close";
      closeBtn.innerHTML = "&times;";
      closeBtn.title = Utils.getText("close") || "Close";
      closeBtn.onclick = () => {
        toast.style.animation = "none";
        toast.style.transition = "opacity 0.2s, transform 0.2s";
        toast.style.opacity = "0";
        toast.style.transform = "translateY(10px)";
        setTimeout(() => toast.remove(), 200);
      };
      toast.appendChild(closeBtn);
      container.appendChild(toast);
      if (!isPersistent) {
        setTimeout(() => {
          if (toast.parentNode) {
            closeBtn.click();
          }
        }, 5e3);
      }
    }, "showToast"),
    switchTab: /* @__PURE__ */ __name((tabName) => {
      for (const name in State.UI.tabs) {
        State.UI.tabs[name].classList.toggle("active", name === tabName);
        State.UI.tabContents[name].style.display = name === tabName ? "block" : "none";
      }
      if (tabName === "debug") {
        UI5.updateDebugTab();
      }
    }, "switchTab"),
    updateDebugTab: /* @__PURE__ */ __name(() => {
      if (!State.UI.historyContainer) return;
      State.UI.historyContainer.innerHTML = "";
      const createHistoryItem = /* @__PURE__ */ __name((entry) => {
        const item = document.createElement("div");
        item.style.cssText = "padding: 8px; margin-bottom: 8px; background: rgba(50,50,55,0.5); border-radius: 6px; border-left: 3px solid;";
        if (entry.type === "STARTUP") {
          item.style.borderLeftColor = "#2196F3";
          item.innerHTML = `
                    <div style="font-weight: 500; color: #fff;">\u{1F680} ${Utils.getText("script_startup")}</div>
                    <div style="font-size: 12px; color: var(--text-color-secondary); padding-left: 22px;">
                        <div>${Utils.getText("time_label")}: ${new Date(entry.endTime).toLocaleString()}</div>
                        ${entry.message ? `<div>${Utils.getText("info_label")}: ${entry.message}</div>` : ""}
                    </div>
                `;
        } else {
          const isNormal = entry.type === "NORMAL";
          item.style.borderLeftColor = isNormal ? "var(--green)" : "var(--orange)";
          const icon = isNormal ? "\u2705" : "\u{1F6A8}";
          const title = isNormal ? Utils.getText("normal_period") : Utils.getText("rate_limited_period");
          const durationText = entry.duration !== void 0 && entry.duration !== null ? entry.duration.toFixed(2) : Utils.getText("unknown_duration");
          let detailsHtml = `<div>${Utils.getText("duration_label")}: <strong>${durationText}s</strong></div>`;
          if (entry.requests !== void 0) {
            detailsHtml += `<div>${Utils.getText("requests_label")}: <strong>${entry.requests}</strong>${Utils.getText("requests_unit")}</div>`;
          }
          detailsHtml += `<div>${Utils.getText("ended_at")}: ${new Date(entry.endTime).toLocaleString()}</div>`;
          item.innerHTML = `
                    <div style="font-weight: 500; color: #fff;"><span style="font-size: 18px;">${icon}</span> ${title}</div>
                    <div style="font-size: 12px; color: var(--text-color-secondary); padding-left: 26px;">${detailsHtml}</div>
                `;
        }
        return item;
      }, "createHistoryItem");
      const createCurrentStatusItem = /* @__PURE__ */ __name(() => {
        const item = document.createElement("div");
        item.style.cssText = "padding: 12px; margin-bottom: 10px; background: rgba(0,122,255,0.15); border-radius: 8px; border: 1px solid rgba(0,122,255,0.3);";
        const header = document.createElement("div");
        header.style.cssText = "display: flex; align-items: center; gap: 8px;";
        const icon = State.appStatus === "NORMAL" ? "\u2705" : "\u{1F6A8}";
        const color = State.appStatus === "NORMAL" ? "var(--green)" : "var(--orange)";
        const titleText = State.appStatus === "NORMAL" ? Utils.getText("current_normal") : Utils.getText("current_rate_limited");
        header.innerHTML = `<span style="font-size: 18px;">${icon}</span> <strong style="color: ${color};">${titleText}</strong>`;
        const details = document.createElement("div");
        details.style.cssText = "font-size: 12px; color: var(--text-color-secondary); padding-left: 26px;";
        const startTime = State.appStatus === "NORMAL" ? State.normalStartTime : State.rateLimitStartTime;
        const duration = startTime ? ((Date.now() - startTime) / 1e3).toFixed(2) : Utils.getText("status_unknown_duration");
        let detailsHtml = `<div>${Utils.getText("status_ongoing_label")}<strong>${duration}s</strong></div>`;
        if (State.appStatus === "NORMAL") {
          detailsHtml += `<div>${Utils.getText("status_requests_label")}<strong>${State.successfulSearchCount}</strong></div>`;
        }
        const startTimeDisplay = startTime ? new Date(startTime).toLocaleString() : Utils.getText("status_unknown_time");
        detailsHtml += `<div>${Utils.getText("status_started_at_label")}${startTimeDisplay}</div>`;
        details.innerHTML = detailsHtml;
        item.append(header, details);
        State.UI.historyContainer.appendChild(item);
      }, "createCurrentStatusItem");
      createCurrentStatusItem();
      if (State.statusHistory.length === 0) {
        const emptyMessage = document.createElement("div");
        emptyMessage.style.cssText = "color: #888; text-align: center; padding: 20px;";
        emptyMessage.textContent = Utils.getText("no_history");
        State.UI.historyContainer.appendChild(emptyMessage);
        return;
      }
      const reversedHistory = [...State.statusHistory].reverse();
      reversedHistory.forEach((entry) => State.UI.historyContainer.appendChild(createHistoryItem(entry)));
    }, "updateDebugTab")
  };

  // src/index.js
  (function() {
    try {
      const blockEnabled = typeof GM_getValue !== "undefined" ? GM_getValue("fab_block_resources_v1", true) : true;
      if (blockEnabled) {
        const meta = document.createElement("meta");
        meta.httpEquiv = "Content-Security-Policy";
        meta.content = "img-src 'none'; media-src 'none'; font-src 'none'; frame-src 'self' https://*.hcaptcha.com https://*.recaptcha.net https://*.google.com https://challenges.cloudflare.com; child-src 'self' https://*.hcaptcha.com https://*.recaptcha.net https://*.google.com https://challenges.cloudflare.com;";
        const appendCspToHead = /* @__PURE__ */ __name(() => {
          if (!document.head || meta.isConnected) return false;
          document.head.appendChild(meta);
          return true;
        }, "appendCspToHead");
        if (!appendCspToHead()) {
          const onHeadReady = /* @__PURE__ */ __name(() => {
            if (appendCspToHead()) {
              document.removeEventListener("DOMContentLoaded", onHeadReady);
            }
          }, "onHeadReady");
          document.addEventListener("DOMContentLoaded", onHeadReady, { once: true });
          if (document.documentElement && typeof MutationObserver !== "undefined") {
            const headObserver = new MutationObserver(() => {
              if (appendCspToHead()) headObserver.disconnect();
            });
            headObserver.observe(document.documentElement, { childList: true });
          }
        }
        const style = document.createElement("style");
        style.textContent = `
                img, source, picture, video, iframe:not([src*="/payment/web/purchase"]):not([src*="hcaptcha"]):not([src*="recaptcha"]):not([src*="captcha"]):not([src*="turnstile"]):not([src*="challenges.cloudflare.com"]):not([src*="arkoselabs"]), [style*="background-image"] {
                    display: none !important;
                    background-image: none !important;
                }
            `;
        if (document.documentElement) {
          document.documentElement.appendChild(style);
        } else {
          document.appendChild(style);
        }
        const blockImage = /* @__PURE__ */ __name((el) => {
          if (el.tagName === "IMG") {
            if (el.src && !el.src.startsWith("data:")) {
              el.setAttribute("data-original-src", el.src);
              el.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
            }
            if (el.srcset) {
              el.setAttribute("data-original-srcset", el.srcset);
              el.srcset = "";
            }
          } else if (el.tagName === "SOURCE") {
            if (el.srcset) {
              el.setAttribute("data-original-srcset", el.srcset);
              el.srcset = "";
            }
          }
        }, "blockImage");
        const traverseAndBlock = /* @__PURE__ */ __name((node) => {
          if (node.nodeType !== 1) return;
          if (node.tagName === "IMG" || node.tagName === "SOURCE") {
            blockImage(node);
          }
          const descendants = node.querySelectorAll("img, source");
          descendants.forEach(blockImage);
        }, "traverseAndBlock");
        const observer = new MutationObserver((mutations) => {
          for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
              traverseAndBlock(node);
            }
          }
        });
        observer.observe(document.documentElement || document, {
          childList: true,
          subtree: true
        });
        console.log("[Fab Helper] Injected CSP, CSS, and MutationObserver to block images/media/iframes/fonts.");
      }
    } catch (e) {
      console.error("[Fab Helper] Failed to inject CSP:", e);
    }
  })();
  var currentCountdownInterval = null;
  var currentRefreshTimeout = null;
  function countdownRefresh3(delay, reason = "\u5907\u9009\u65B9\u6848") {
    if (State.isRefreshScheduled) {
      Utils.logger("debug", Utils.getText("refresh_plan_exists").replace("(429\u81EA\u52A8\u6062\u590D)", `(${reason})`));
      return;
    }
    State.isRefreshScheduled = true;
    if (currentCountdownInterval) {
      clearInterval(currentCountdownInterval);
      currentCountdownInterval = null;
    }
    if (currentRefreshTimeout) {
      clearTimeout(currentRefreshTimeout);
      currentRefreshTimeout = null;
    }
    const seconds = delay ? (delay / 1e3).toFixed(1) : "\u672A\u77E5";
    Utils.logger("debug", `\u{1F504} ${reason}\u542F\u52A8\uFF01\u5C06\u5728 ${seconds} \u79D2\u540E\u5237\u65B0\u9875\u9762\u5C1D\u8BD5\u6062\u590D...`);
    let remainingSeconds = Math.ceil(delay / 1e3);
    currentCountdownInterval = setInterval(() => {
      remainingSeconds--;
      if (remainingSeconds <= 0) {
        clearInterval(currentCountdownInterval);
        currentCountdownInterval = null;
        Utils.logger("debug", `\u23F1\uFE0F \u5012\u8BA1\u65F6\u7ED3\u675F\uFF0C\u6B63\u5728\u5237\u65B0\u9875\u9762...`);
      } else {
        Utils.logger("debug", Utils.getText("auto_refresh_countdown", remainingSeconds));
        if (!State.isRefreshScheduled) {
          Utils.logger("debug", `\u23F9\uFE0F \u68C0\u6D4B\u5230\u5237\u65B0\u5DF2\u88AB\u53D6\u6D88\uFF0C\u505C\u6B62\u5012\u8BA1\u65F6`);
          clearInterval(currentCountdownInterval);
          currentCountdownInterval = null;
          if (currentRefreshTimeout) {
            clearTimeout(currentRefreshTimeout);
            currentRefreshTimeout = null;
          }
          return;
        }
        if (remainingSeconds % 3 === 0) {
          checkRateLimitStatus().then((isNotLimited) => {
            if (isNotLimited) {
              Utils.logger("debug", `\u23F1\uFE0F \u68C0\u6D4B\u5230API\u9650\u901F\u5DF2\u89E3\u9664\uFF0C\u53D6\u6D88\u5237\u65B0...`);
              clearInterval(currentCountdownInterval);
              currentCountdownInterval = null;
              if (currentRefreshTimeout) {
                clearTimeout(currentRefreshTimeout);
                currentRefreshTimeout = null;
              }
              State.isRefreshScheduled = false;
              if (State.appStatus === "RATE_LIMITED") {
                RateLimitManager.exitRateLimitedState();
              }
              return;
            }
            if (State.appStatus === "RATE_LIMITED") {
              const actualVisibleCount = parseInt(document.getElementById("fab-status-visible")?.textContent || "0");
              if (State.db.todo.length > 0 || State.activeWorkers > 0) {
                clearInterval(currentCountdownInterval);
                clearTimeout(currentRefreshTimeout);
                currentCountdownInterval = null;
                currentRefreshTimeout = null;
                State.isRefreshScheduled = false;
                Utils.logger("info", `\u23F9\uFE0F \u68C0\u6D4B\u5230\u6709 ${State.db.todo.length} \u4E2A\u5F85\u529E\u4EFB\u52A1\u548C ${State.activeWorkers} \u4E2A\u6D3B\u52A8\u5DE5\u4F5C\u7EBF\u7A0B\uFF0C\u5DF2\u53D6\u6D88\u81EA\u52A8\u5237\u65B0\u3002`);
                return;
              }
            } else {
              const visibleCount = parseInt(document.getElementById("fab-status-visible")?.textContent || "0");
              if (State.db.todo.length > 0 || State.activeWorkers > 0 || visibleCount > 0) {
                clearInterval(currentCountdownInterval);
                clearTimeout(currentRefreshTimeout);
                currentCountdownInterval = null;
                currentRefreshTimeout = null;
                State.isRefreshScheduled = false;
                Utils.logger("warn", "\u26A0\uFE0F \u5237\u65B0\u6761\u4EF6\u5DF2\u53D8\u5316\uFF0C\u81EA\u52A8\u5237\u65B0\u5DF2\u53D6\u6D88\u3002");
                return;
              }
            }
          }).catch(() => {
          });
        }
      }
    }, 1e3);
    currentRefreshTimeout = setTimeout(() => {
      const visibleCount = parseInt(document.getElementById("fab-status-visible")?.textContent || "0");
      if (State.appStatus === "RATE_LIMITED") {
        if (State.db.todo.length > 0 || State.activeWorkers > 0) {
          Utils.logger("warn", "\u26A0\uFE0F \u6700\u540E\u4E00\u523B\u68C0\u67E5\uFF1A\u5237\u65B0\u6761\u4EF6\u4E0D\u6EE1\u8DB3\uFF0C\u81EA\u52A8\u5237\u65B0\u5DF2\u53D6\u6D88\u3002");
          State.isRefreshScheduled = false;
          return;
        }
        if (visibleCount === 0) {
          Utils.logger("info", `\u{1F504} \u9875\u9762\u4E0A\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\u4E14\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u5C06\u6267\u884C\u81EA\u52A8\u5237\u65B0\u3002`);
          window.location.href = window.location.href;
        } else {
          State.isRefreshScheduled = false;
          return;
        }
      } else {
        if (State.db.todo.length > 0 || State.activeWorkers > 0 || visibleCount > 0) {
          Utils.logger("warn", "\u26A0\uFE0F \u6700\u540E\u4E00\u523B\u68C0\u67E5\uFF1A\u5237\u65B0\u6761\u4EF6\u4E0D\u6EE1\u8DB3\uFF0C\u81EA\u52A8\u5237\u65B0\u5DF2\u53D6\u6D88\u3002");
          State.isRefreshScheduled = false;
        } else {
          window.location.href = window.location.href;
        }
      }
    }, delay);
  }
  __name(countdownRefresh3, "countdownRefresh");
  async function checkRateLimitStatus() {
    try {
      const { hidden: hiddenCards, visible: actualVisibleCards } = TaskRunner2.getCardCounts();
      const visibleCountElement = document.getElementById("fab-status-visible");
      if (visibleCountElement) {
        visibleCountElement.textContent = actualVisibleCards.toString();
      }
      if (State.appStatus === "RATE_LIMITED" && actualVisibleCards === 0) {
        return false;
      }
      if (actualVisibleCards === 0 && hiddenCards > 25) {
        return false;
      }
      if (window.performance && window.performance.getEntriesByType) {
        const recentRequests = window.performance.getEntriesByType("resource").filter((r) => r.name.includes("/i/listings/search") || r.name.includes("/i/users/me/listings-states")).filter((r) => Date.now() - r.startTime < 1e4);
        if (recentRequests.length > 0) {
          const has429 = recentRequests.some((r) => r.responseStatus === 429);
          if (has429) return false;
          const hasSuccess = recentRequests.some((r) => r.responseStatus >= 200 && r.responseStatus < 300);
          if (hasSuccess) return true;
        }
        return State.appStatus === "NORMAL";
      }
      return State.appStatus === "NORMAL";
    } catch (error) {
      Utils.logger("error", `\u68C0\u67E5\u9650\u901F\u72B6\u6001\u51FA\u9519: ${error.message}`);
      return false;
    }
  }
  __name(checkRateLimitStatus, "checkRateLimitStatus");
  setUIReference(UI5);
  setUIReference2(UI5);
  setUIReference3(UI5);
  setDependencies2({
    countdownRefresh: countdownRefresh3
  });
  setTaskRunnerReference(TaskRunner2);
  setDependencies({
    UI: UI5,
    TaskRunner: TaskRunner2,
    countdownRefresh: countdownRefresh3
  });
  var _ownedStatusUpdateTimer = null;
  function triggerOwnedStatusUpdate() {
    if (State.isWorkerTab) return;
    clearTimeout(_ownedStatusUpdateTimer);
    _ownedStatusUpdateTimer = setTimeout(() => {
      if (State.hideSaved) {
        try {
          TaskRunner2.scheduleHideOrShow();
        } catch (e) {
        }
      }
      try {
        TaskRunner2.checkVisibleCardsStatus().catch(() => {
        });
      } catch (e) {
      }
    }, 50);
  }
  __name(triggerOwnedStatusUpdate, "triggerOwnedStatusUpdate");
  function parseListingsStatesUrl(urlString) {
    try {
      const urlObj = new URL(urlString, window.location.origin);
      const uids = [];
      let paramName = "listing_ids";
      if (urlObj.searchParams.has("listing_ids")) {
        uids.push(...urlObj.searchParams.getAll("listing_ids"));
        paramName = "listing_ids";
      } else if (urlObj.searchParams.has("listing_uids")) {
        uids.push(...urlObj.searchParams.getAll("listing_uids"));
        paramName = "listing_uids";
      }
      return { uids, paramName, urlObj };
    } catch (e) {
      return { uids: [], paramName: "listing_ids", urlObj: null };
    }
  }
  __name(parseListingsStatesUrl, "parseListingsStatesUrl");
  function createOwnedStateResponses(uids) {
    return uids.map((uid) => ({
      uid,
      acquired: true,
      lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
    }));
  }
  __name(createOwnedStateResponses, "createOwnedStateResponses");
  function defineMockXhrProperty(xhr, name, value) {
    try {
      Object.defineProperty(xhr, name, {
        get: /* @__PURE__ */ __name(() => value, "get"),
        configurable: true
      });
    } catch (e) {
    }
  }
  __name(defineMockXhrProperty, "defineMockXhrProperty");
  function dispatchMockXhrEvent(xhr, type) {
    try {
      xhr.dispatchEvent(new Event(type));
    } catch (e) {
    }
  }
  __name(dispatchMockXhrEvent, "dispatchMockXhrEvent");
  function respondWithMockListingsStates(xhr, uids) {
    const mockResponses = createOwnedStateResponses(uids);
    const responseText = JSON.stringify(mockResponses);
    DataCache.saveOwnedStatus(mockResponses);
    triggerOwnedStatusUpdate();
    defineMockXhrProperty(xhr, "readyState", 4);
    defineMockXhrProperty(xhr, "status", 200);
    defineMockXhrProperty(xhr, "statusText", "OK");
    defineMockXhrProperty(xhr, "responseURL", xhr._url || "");
    defineMockXhrProperty(xhr, "responseText", responseText);
    defineMockXhrProperty(xhr, "response", xhr.responseType === "json" ? mockResponses : responseText);
    setTimeout(() => {
      dispatchMockXhrEvent(xhr, "readystatechange");
      dispatchMockXhrEvent(xhr, "load");
      dispatchMockXhrEvent(xhr, "loadend");
    }, 0);
  }
  __name(respondWithMockListingsStates, "respondWithMockListingsStates");
  function parsePricesInfoUrl(urlString) {
    try {
      const urlObj = new URL(urlString, window.location.origin);
      return {
        offerIds: urlObj.searchParams.getAll("offer_ids"),
        urlObj
      };
    } catch (e) {
      return { offerIds: [], urlObj: null };
    }
  }
  __name(parsePricesInfoUrl, "parsePricesInfoUrl");
  function splitDoneOfferIds(offerIds) {
    const doneOfferIds = [];
    const activeOfferIds = [];
    offerIds.forEach((offerId) => {
      const listingUid = DataCache.getListingUidForOffer(offerId);
      if (listingUid && Database.isDone(`https://www.fab.com/listings/${listingUid}`)) {
        doneOfferIds.push(offerId);
      } else {
        activeOfferIds.push(offerId);
      }
    });
    return { doneOfferIds, activeOfferIds };
  }
  __name(splitDoneOfferIds, "splitDoneOfferIds");
  function createEmptyPricesResponse() {
    return { offers: [] };
  }
  __name(createEmptyPricesResponse, "createEmptyPricesResponse");
  function createPricesResponse(offerIds) {
    const { result: cachedOffers } = DataCache.getPrices(offerIds);
    return { offers: cachedOffers };
  }
  __name(createPricesResponse, "createPricesResponse");
  function respondWithMockPricesInfo(xhr, offerIds = []) {
    const responseData = offerIds.length > 0 ? createPricesResponse(offerIds) : createEmptyPricesResponse();
    const responseText = JSON.stringify(responseData);
    defineMockXhrProperty(xhr, "readyState", 4);
    defineMockXhrProperty(xhr, "status", 200);
    defineMockXhrProperty(xhr, "statusText", "OK");
    defineMockXhrProperty(xhr, "responseURL", xhr._url || "");
    defineMockXhrProperty(xhr, "responseText", responseText);
    defineMockXhrProperty(xhr, "response", xhr.responseType === "json" ? responseData : responseText);
    setTimeout(() => {
      dispatchMockXhrEvent(xhr, "readystatechange");
      dispatchMockXhrEvent(xhr, "load");
      dispatchMockXhrEvent(xhr, "loadend");
    }, 0);
  }
  __name(respondWithMockPricesInfo, "respondWithMockPricesInfo");
  function setupXHRInterceptor() {
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function(method, url, ...args) {
      if (url && typeof url === "string" && url.includes("/i/users/me/listings-states")) {
        const { uids, paramName, urlObj } = parseListingsStatesUrl(url);
        if (uids.length > 0) {
          const doneUids = [];
          const activeUids = [];
          uids.forEach((uid) => {
            const itemUrl = `https://www.fab.com/listings/${uid}`;
            if (Database.isDone(itemUrl)) {
              doneUids.push(uid);
            } else {
              activeUids.push(uid);
            }
          });
          if (doneUids.length > 0) {
            this._doneUids = doneUids;
            this._allUids = uids;
            this._paramName = paramName;
            if (activeUids.length > 0) {
              urlObj.searchParams.delete(paramName);
              activeUids.forEach((uid) => urlObj.searchParams.append(paramName, uid));
              url = urlObj.toString();
              Utils.logger("debug", `[XHR Intercept] \u8FC7\u6EE4\u5DF2\u5165\u5E93\u5546\u54C1: \u539F\u8BF7\u6C42 ${uids.length} \u4E2A, \u8FC7\u6EE4 ${doneUids.length} \u4E2A, \u5B9E\u9645\u8BF7\u6C42 ${activeUids.length} \u4E2A`);
            } else {
              this._allDoneLocalResponse = true;
              Utils.logger("debug", `[XHR Intercept] \u6240\u6709 ${uids.length} \u4E2A\u5546\u54C1\u5747\u5DF2\u672C\u5730\u5165\u5E93\uFF0C\u8F6C\u6362\u4E3A\u5355 ID \u5360\u4F4D\u8BF7\u6C42`);
            }
          }
        }
      } else if (url && typeof url === "string" && url.includes("/i/listings/prices-infos")) {
        const { offerIds, urlObj } = parsePricesInfoUrl(url);
        if (offerIds.length > 0) {
          const { doneOfferIds, activeOfferIds } = splitDoneOfferIds(offerIds);
          if (doneOfferIds.length > 0) {
            this._donePriceOfferIds = doneOfferIds;
            if (activeOfferIds.length > 0) {
              urlObj.searchParams.delete("offer_ids");
              activeOfferIds.forEach((offerId) => urlObj.searchParams.append("offer_ids", offerId));
              url = urlObj.toString();
              Utils.logger("debug", `[XHR Intercept] Filtered done listing price requests: original=${offerIds.length}, skipped=${doneOfferIds.length}, requested=${activeOfferIds.length}`);
            } else {
              this._allDonePricesLocalResponse = true;
              Utils.logger("debug", `[XHR Intercept] Skipped ${offerIds.length} price requests for local done listings`);
            }
          }
        }
      }
      this._url = url;
      return originalOpen.apply(this, [method, url, ...args]);
    };
    XMLHttpRequest.prototype.send = function(...args) {
      const xhr = this;
      if (xhr._allDoneLocalResponse && xhr._allUids && xhr._allUids.length > 0) {
        respondWithMockListingsStates(xhr, xhr._allUids);
        return;
      }
      if (xhr._allDonePricesLocalResponse && xhr._donePriceOfferIds && xhr._donePriceOfferIds.length > 0) {
        respondWithMockPricesInfo(xhr, xhr._donePriceOfferIds);
        return;
      }
      if (xhr._url && typeof xhr._url === "string") {
        xhr.addEventListener("readystatechange", function() {
          if (xhr.readyState === 4 && xhr.status === 200 && xhr._doneUids && xhr._doneUids.length > 0) {
            try {
              const originalText = xhr.responseText;
              const rawData = JSON.parse(originalText);
              const serverResults = Array.isArray(rawData) ? rawData : API.extractStateData(rawData, "XHRInterceptor") || [];
              const mockDoneResponses = createOwnedStateResponses(xhr._doneUids);
              const mergedResults = [...serverResults, ...mockDoneResponses];
              DataCache.saveOwnedStatus(mergedResults);
              triggerOwnedStatusUpdate();
              const mergedText = JSON.stringify(mergedResults);
              Object.defineProperty(xhr, "responseText", { get: /* @__PURE__ */ __name(() => mergedText, "get"), configurable: true });
              Object.defineProperty(xhr, "response", {
                get: /* @__PURE__ */ __name(() => {
                  if (xhr.responseType === "json") {
                    return mergedResults;
                  }
                  return mergedText;
                }, "get"),
                configurable: true
              });
            } catch (e) {
              Utils.logger("error", `XHR \u72B6\u6001\u62E6\u622A\u5408\u5E76\u5931\u8D25: ${e.message}`);
            }
          }
        });
        xhr.addEventListener("load", function() {
          if (xhr.readyState === 4 && xhr.status === 200 && !xhr._doneUids) {
            try {
              const responseData = JSON.parse(xhr.responseText);
              if (xhr._url.includes("/i/listings/search") && responseData.results && Array.isArray(responseData.results)) {
                DataCache.saveListings(responseData.results);
              } else if (xhr._url.includes("/i/users/me/listings-states")) {
                if (Array.isArray(responseData)) {
                  DataCache.saveOwnedStatus(responseData);
                  triggerOwnedStatusUpdate();
                } else {
                  const extractedData = API.extractStateData(responseData, "XHRInterceptor");
                  if (Array.isArray(extractedData) && extractedData.length > 0) {
                    DataCache.saveOwnedStatus(extractedData);
                    triggerOwnedStatusUpdate();
                  }
                }
              } else if (xhr._url.includes("/i/listings/prices-infos") && responseData.offers && Array.isArray(responseData.offers)) {
                DataCache.savePrices(responseData.offers);
              }
            } catch (e) {
            }
          }
          if (xhr._url && xhr._url.includes("/i/listings/search")) {
            if (xhr.status === 429) {
              Utils.logger("warn", Utils.getText("detected_api_429_status", xhr._url));
              if (typeof window.enterRateLimitedState === "function") {
                window.enterRateLimitedState();
              }
            }
          }
        });
      }
      return originalSend.apply(this, args);
    };
  }
  __name(setupXHRInterceptor, "setupXHRInterceptor");
  function setupFetchInterceptor() {
    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
      let url = args[0]?.toString() || "";
      if (url.includes("/i/users/me/listings-states")) {
        const { uids, paramName, urlObj } = parseListingsStatesUrl(url);
        if (uids.length > 0) {
          const doneUids = [];
          const activeUids = [];
          uids.forEach((uid) => {
            const itemUrl = `https://www.fab.com/listings/${uid}`;
            if (Database.isDone(itemUrl)) {
              doneUids.push(uid);
            } else {
              activeUids.push(uid);
            }
          });
          if (doneUids.length > 0) {
            const mockDoneResponses = createOwnedStateResponses(doneUids);
            if (activeUids.length === 0) {
              Utils.logger("info", `[Fetch Intercept] \u6240\u6709 ${uids.length} \u4E2A\u5546\u54C1\u5747\u5DF2\u672C\u5730\u5165\u5E93\uFF0C\u5B8C\u5168\u7ED5\u8FC7\u7F51\u7EDC\u8BF7\u6C42`);
              DataCache.saveOwnedStatus(mockDoneResponses);
              triggerOwnedStatusUpdate();
              return new Response(JSON.stringify(mockDoneResponses), {
                status: 200,
                statusText: "OK",
                headers: { "Content-Type": "application/json" }
              });
            }
            urlObj.searchParams.delete(paramName);
            activeUids.forEach((uid) => urlObj.searchParams.append(paramName, uid));
            url = urlObj.toString();
            args[0] = url;
            Utils.logger("debug", `[Fetch Intercept] \u8FC7\u6EE4\u5DF2\u5165\u5E93\u5546\u54C1: \u539F\u8BF7\u6C42 ${uids.length} \u4E2A, \u8FC7\u6EE4 ${doneUids.length} \u4E2A, \u5B9E\u9645\u8BF7\u6C42 ${activeUids.length} \u4E2A`);
            if (window._apiWaitStatus) {
              window._apiWaitStatus.lastApiActivity = Date.now();
            }
            try {
              const response = await originalFetch.apply(this, args);
              if (response.ok) {
                const clonedResponse = response.clone();
                const rawData = await clonedResponse.json();
                const serverResults = Array.isArray(rawData) ? rawData : API.extractStateData(rawData, "FetchInterceptor") || [];
                const mergedResults = [...serverResults, ...mockDoneResponses];
                DataCache.saveOwnedStatus(mergedResults);
                triggerOwnedStatusUpdate();
                return new Response(JSON.stringify(mergedResults), {
                  status: response.status,
                  statusText: response.statusText,
                  headers: response.headers
                });
              }
              return response;
            } catch (e) {
              Utils.logger("error", `\u62E6\u622A fetch listings-states \u5931\u8D25\uFF0C\u4F7F\u7528\u539F\u59CB fetch \u56DE\u9000: ${e.message}`);
              return originalFetch.apply(this, args);
            }
          }
        }
      }
      if (url.includes("/i/listings/prices-infos")) {
        const { offerIds, urlObj } = parsePricesInfoUrl(url);
        if (offerIds.length > 0) {
          const { doneOfferIds, activeOfferIds } = splitDoneOfferIds(offerIds);
          if (doneOfferIds.length > 0) {
            if (activeOfferIds.length === 0) {
              Utils.logger("debug", `[Fetch Intercept] Skipped ${offerIds.length} price requests for local done listings`);
              return new Response(JSON.stringify(createPricesResponse(doneOfferIds)), {
                status: 200,
                statusText: "OK",
                headers: { "Content-Type": "application/json" }
              });
            }
            urlObj.searchParams.delete("offer_ids");
            activeOfferIds.forEach((offerId) => urlObj.searchParams.append("offer_ids", offerId));
            url = urlObj.toString();
            args[0] = url;
            Utils.logger("debug", `[Fetch Intercept] Filtered done listing price requests: original=${offerIds.length}, skipped=${doneOfferIds.length}, requested=${activeOfferIds.length}`);
          }
        }
      }
      if (url.includes("/i/listings/search") || url.includes("/i/users/me/listings-states") || url.includes("/i/listings/prices-infos")) {
        if (window._apiWaitStatus) {
          window._apiWaitStatus.lastApiActivity = Date.now();
        }
        try {
          const response = await originalFetch.apply(this, args);
          if (response.ok) {
            const clonedResponse = response.clone();
            clonedResponse.json().then((data) => {
              if (url.includes("/i/listings/search") && data.results && Array.isArray(data.results)) {
                DataCache.saveListings(data.results);
              } else if (url.includes("/i/users/me/listings-states")) {
                if (Array.isArray(data)) {
                  DataCache.saveOwnedStatus(data);
                  triggerOwnedStatusUpdate();
                } else {
                  const extractedData = API.extractStateData(data, "FetchInterceptor");
                  if (Array.isArray(extractedData) && extractedData.length > 0) {
                    DataCache.saveOwnedStatus(extractedData);
                    triggerOwnedStatusUpdate();
                  }
                }
              } else if (url.includes("/i/listings/prices-infos") && data.offers && Array.isArray(data.offers)) {
                DataCache.savePrices(data.offers);
              }
            }).catch(() => {
            });
          }
          return response;
        } catch (e) {
          return originalFetch.apply(this, args);
        }
      }
      return originalFetch.apply(this, args);
    };
  }
  __name(setupFetchInterceptor, "setupFetchInterceptor");
  function setupRequestInterceptors() {
    try {
      setupXHRInterceptor();
      setupFetchInterceptor();
      setInterval(() => DataCache.cleanupExpired(), 6e4);
      Utils.logger("debug", "\u8BF7\u6C42\u62E6\u622A\u548C\u7F13\u5B58\u7CFB\u7EDF\u5DF2\u521D\u59CB\u5316");
    } catch (e) {
      Utils.logger("error", `\u521D\u59CB\u5316\u8BF7\u6C42\u62E6\u622A\u5668\u5931\u8D25: ${e.message}`);
    }
  }
  __name(setupRequestInterceptors, "setupRequestInterceptors");
  async function runDomDependentPart() {
    if (State.hasRunDomPart) return;
    if (State.isWorkerTab) {
      State.hasRunDomPart = true;
      return;
    }
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has("workerId")) {
      Utils.logger("debug", `\u5DE5\u4F5C\u6807\u7B7E\u9875DOM\u90E8\u5206\u521D\u59CB\u5316\uFF0C\u8DF3\u8FC7UI\u521B\u5EFA`);
      State.hasRunDomPart = true;
      return;
    }
    const uiCreated = UI5.create();
    if (!uiCreated) {
      Utils.logger("info", Utils.getText("log_detail_page"));
      State.hasRunDomPart = true;
      return;
    }
    UI5.update();
    UI5.updateDebugTab();
    UI5.switchTab("dashboard");
    State.hasRunDomPart = true;
    window.enterRateLimitedState = function(source = Utils.getText("rate_limit_source_global_call")) {
      RateLimitManager.enterRateLimitedState(source);
    };
    window.recordNetworkRequest = function(source = "\u7F51\u7EDC\u8BF7\u6C42", hasResults = true) {
      if (hasResults) {
        RateLimitManager.recordSuccessfulRequest(source, hasResults);
      }
    };
    setInterval(() => {
      if (State.appStatus === "NORMAL") {
        let pageText = "";
        const uiContainer = document.getElementById(Config.UI_CONTAINER_ID);
        if (uiContainer) {
          const originalDisplay = uiContainer.style.display;
          uiContainer.style.display = "none";
          pageText = document.body.innerText || "";
          uiContainer.style.display = originalDisplay;
        } else {
          pageText = document.body.innerText || "";
        }
        if (pageText.includes("Too many requests") || pageText.includes("rate limit") || pageText.match(/\{\s*"detail"\s*:\s*"Too many requests"\s*\}/i)) {
          Utils.logger("warn", Utils.getText("page_content_rate_limit_detected"));
          RateLimitManager.enterRateLimitedState(Utils.getText("rate_limit_source_page_content"));
        }
      }
    }, 5e3);
    const checkIsErrorPage = /* @__PURE__ */ __name((title, text) => {
      const isCloudflareTitle = title.includes("Cloudflare") || title.includes("Attention Required");
      const is429Text = text.includes("429") || text.includes("Too Many Requests") || text.includes("Too many requests");
      if (isCloudflareTitle || is429Text) {
        Utils.logger("warn", `[\u9875\u9762\u52A0\u8F7D] \u68C0\u6D4B\u5230429\u9519\u8BEF\u9875\u9762`);
        window.enterRateLimitedState("\u9875\u9762\u5185\u5BB9429\u68C0\u6D4B");
        return true;
      }
      return false;
    }, "checkIsErrorPage");
    checkIsErrorPage(document.title, document.body.innerText || "");
    if (State.appStatus === "RATE_LIMITED") {
      Utils.logger("debug", Utils.getText("log_auto_resume_page_loading"));
      const isRecovered = await RateLimitManager.checkRateLimitStatus();
      if (isRecovered) {
        Utils.logger("info", Utils.getText("log_recovery_probe_success"));
        if (State.appStatus === "RATE_LIMITED") {
          await RateLimitManager.exitRateLimitedState("\u9875\u9762\u52A0\u8F7D\u63A2\u6D4B\u6210\u529F");
        }
        if (State.db.todo.length > 0 && !State.isExecuting) {
          Utils.logger("info", Utils.getText("log_found_todo_auto_resume", State.db.todo.length));
          State.isExecuting = true;
          Database.saveExecutingState();
          TaskRunner2.executeBatch();
        }
      } else {
        Utils.logger("warn", Utils.getText("log_recovery_probe_failed"));
        if (State.activeWorkers === 0 && State.db.todo.length === 0) {
          const randomDelay = 5e3 + Math.random() * 1e4;
          countdownRefresh3(randomDelay, Utils.getText("countdown_refresh_source"));
        }
      }
    }
    const containerSelectors = ["main", "#main", ".AssetGrid-root", ".fabkit-responsive-grid-container"];
    let targetNode = null;
    for (const selector of containerSelectors) {
      targetNode = document.querySelector(selector);
      if (targetNode) break;
    }
    if (!targetNode) targetNode = document.body;
    const observer = new MutationObserver((mutationsList) => {
      const hasNewContent = mutationsList.some(
        (mutation) => [...mutation.addedNodes].some(
          (node) => node.nodeType === 1 && (node.matches(Config.SELECTORS.card) || node.querySelector(Config.SELECTORS.card))
        )
      );
      if (hasNewContent) {
        TaskRunner2.invalidateCardCountCache();
        clearTimeout(State.observerDebounceTimer);
        State.observerDebounceTimer = setTimeout(() => {
          if (State.debugMode) {
            Utils.logger("debug", `[Observer] ${Utils.getText("debug_new_content_loading")}`);
          }
          const shouldProcessNewCards = State.hideSaved || State.hideDiscountedPaid || State.hidePaid || State.autoAddOnScroll || State.isExecuting || State.db.todo.length > 0;
          if (!shouldProcessNewCards) return;
          TaskRunner2.checkVisibleCardsStatus().then(() => {
            if (State.hideSaved || State.hideDiscountedPaid || State.hidePaid) {
              TaskRunner2.scheduleHideOrShow();
            }
            if (State.autoAddOnScroll) {
              TaskRunner2.scanAndAddTasks(document.querySelectorAll(TaskRunner2.getVisibleCardSelector())).catch((error) => Utils.logger("error", `\u81EA\u52A8\u6DFB\u52A0\u4EFB\u52A1\u5931\u8D25: ${error.message}`));
            }
          }).catch(() => {
            if (State.hideSaved || State.hideDiscountedPaid || State.hidePaid) {
              TaskRunner2.scheduleHideOrShow();
            }
          });
        }, 300);
      }
    });
    observer.observe(targetNode, { childList: true, subtree: true });
    Utils.logger("debug", `\u2705 Core DOM observer is now active on <${targetNode.tagName.toLowerCase()}>.`);
    TaskRunner2.runHideOrShow();
    if (State.autoAddOnScroll) {
      setTimeout(() => {
        Utils.logger("debug", "\u9875\u9762\u52A0\u8F7D\u5B8C\u6210\uFF0C\u6B63\u5728\u6267\u884C\u521D\u59CB\u5546\u54C1\u626B\u63CF...");
        TaskRunner2.scanAndAddTasks(document.querySelectorAll(TaskRunner2.getVisibleCardSelector())).catch((error) => Utils.logger("error", `\u521D\u59CB\u626B\u63CF\u4EFB\u52A1\u5931\u8D25: ${error.message}`));
      }, 3e3);
    }
    setInterval(() => {
      if (!State.hideSaved && !State.hideDiscountedPaid && !State.hidePaid) return;
      const cards = document.querySelectorAll(TaskRunner2.getVisibleCardSelector());
      let unprocessedCount = 0;
      cards.forEach((card) => {
        if (card.getAttribute("data-fab-processed") === "true" && TaskRunner2.isCardHidden(card)) return;
        const isProcessed = card.getAttribute("data-fab-processed") === "true";
        if (!isProcessed) {
          unprocessedCount++;
        } else {
          const shouldBeHidden = TaskRunner2.shouldHideCard(card);
          const isHidden = TaskRunner2.isCardHidden(card);
          if (shouldBeHidden !== isHidden) {
            card.removeAttribute("data-fab-processed");
            unprocessedCount++;
          }
        }
      });
      if (unprocessedCount > 0) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_unprocessed_cards", unprocessedCount));
        }
        TaskRunner2.scheduleHideOrShow();
      }
    }, 1e4);
    setInterval(() => {
      if (State.db.todo.length === 0) return;
      const initialTodoCount = State.db.todo.length;
      State.db.todo = State.db.todo.filter((task) => {
        const url = task.url.split("?")[0];
        return !Database.isDone(url);
      });
      if (State.db.todo.length < initialTodoCount) {
        Utils.logger("info", `[\u81EA\u52A8\u6E05\u7406] \u4ECE\u5F85\u529E\u5217\u8868\u4E2D\u79FB\u9664\u4E86 ${initialTodoCount - State.db.todo.length} \u4E2A\u5DF2\u5B8C\u6210\u7684\u4EFB\u52A1\u3002`);
        UI5.update();
      }
    }, 1e4);
    let lastCardCount = TaskRunner2.getCardCounts().total;
    let noNewCardsCounter = 0;
    let lastScrollY = window.scrollY;
    setInterval(() => {
      if (State.appStatus !== "NORMAL") return;
      const currentCardCount = TaskRunner2.getCardCounts().total;
      if (window.scrollY > lastScrollY + 100 && currentCardCount === lastCardCount) {
        noNewCardsCounter++;
        if (noNewCardsCounter >= 3) {
          Utils.logger("warn", `${Utils.getText("implicit_rate_limit_detection")}`);
          RateLimitManager.enterRateLimitedState(Utils.getText("source_implicit_rate_limit"));
          noNewCardsCounter = 0;
        }
      } else if (currentCardCount > lastCardCount) {
        noNewCardsCounter = 0;
      }
      lastCardCount = currentCardCount;
      lastScrollY = window.scrollY;
    }, 5e3);
    setInterval(async () => {
      try {
        if (UI5) UI5.update();
        const { visible: actualVisibleCards } = TaskRunner2.getCardCounts();
        if (State.appStatus === "RATE_LIMITED" && actualVisibleCards === 0 && State.autoRefreshEmptyPage) {
          if (!State.isRefreshScheduled && !currentCountdownInterval && !currentRefreshTimeout) {
            Utils.logger("debug", Utils.getText("log_all_hidden_rate_limited"));
            const randomDelay = 3e3 + Math.random() * 2e3;
            countdownRefresh3(randomDelay, Utils.getText("rate_limit_no_visible_reason"));
          }
        }
      } catch (error) {
        Utils.logger("error", `\u9875\u9762\u72B6\u6001\u68C0\u67E5\u51FA\u9519: ${error.message}`);
      }
    }, 1e4);
    setInterval(() => {
      if (State.db.todo.length === 0) return;
      TaskRunner2.ensureTasksAreExecuted();
    }, 5e3);
    setInterval(async () => {
      try {
        if (State.appStatus !== "NORMAL") return;
        if (window.performance && window.performance.getEntriesByType) {
          const navigationEntries = window.performance.getEntriesByType("navigation");
          if (navigationEntries && navigationEntries.length > 0) {
            const lastNavigation = navigationEntries[0];
            if (lastNavigation.responseStatus === 429) {
              Utils.logger("warn", `[HTTP\u72B6\u6001\u68C0\u6D4B] \u68C0\u6D4B\u5230\u5BFC\u822A\u8BF7\u6C42\u72B6\u6001\u7801\u4E3A429\uFF01`);
              if (typeof window.enterRateLimitedState === "function") {
                window.enterRateLimitedState();
              }
            }
          }
        }
      } catch (error) {
      }
    }, 1e4);
  }
  __name(runDomDependentPart, "runDomDependentPart");
  function ensureUILoaded() {
    if (!document.getElementById(Config.UI_CONTAINER_ID)) {
      Utils.logger("warn", "\u68C0\u6D4B\u5230UI\u672A\u52A0\u8F7D\uFF0C\u5C1D\u8BD5\u91CD\u65B0\u521D\u59CB\u5316...");
      setTimeout(() => {
        try {
          runDomDependentPart();
        } catch (error) {
          Utils.logger("error", `UI\u91CD\u65B0\u521D\u59CB\u5316\u5931\u8D25: ${error.message}`);
        }
      }, 1e3);
    }
  }
  __name(ensureUILoaded, "ensureUILoaded");
  async function main() {
    window.pageLoadTime = Date.now();
    TaskRunner2.onQueueCompleted = async () => {
      Utils.logger("info", "\u6240\u6709\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002");
      State.isExecuting = false;
      Database.saveExecutingState();
      await Database.saveTodo();
      if (UI5 && typeof UI5.showToast === "function") {
        UI5.showToast(Utils.getText("toast_all_tasks_completed"), true);
      }
      if (State.appStatus === "RATE_LIMITED") {
        Utils.logger("info", "\u6240\u6709\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u4E14\u5904\u4E8E\u9650\u901F\u72B6\u6001\uFF0C\u5C06\u5237\u65B0\u9875\u9762\u5C1D\u8BD5\u6062\u590D...");
        const randomDelay = 3e3 + Math.random() * 5e3;
        countdownRefresh3(randomDelay, "\u4EFB\u52A1\u5B8C\u6210\u540E\u9650\u901F\u6062\u590D");
      }
      UI5.update();
    };
    Utils.logger("info", Utils.getText("log_script_starting"));
    Utils.detectLanguage();
    const hasCookie = Utils.checkAuthentication(true);
    if (!hasCookie) {
      Utils.logger("warn", "\u8D26\u53F7\u672A\u767B\u5F55\uFF0C\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u53D7\u9650");
      State.isAuthenticated = false;
    } else {
      State.isAuthenticated = true;
    }
    const urlParams = new URLSearchParams(window.location.search);
    const workerId = urlParams.get("workerId");
    if (workerId) {
      State.isWorkerTab = true;
      State.workerTaskId = workerId;
      if (!hasCookie || !await Utils.verifyServerSession()) {
        Utils.logger("error", Utils.getText("auth_worker_aborted"));
        return;
      }
      await InstanceManager.init();
      Utils.logger("info", `\u5DE5\u4F5C\u6807\u7B7E\u9875\u521D\u59CB\u5316\u5B8C\u6210\uFF0C\u5F00\u59CB\u5904\u7406\u4EFB\u52A1...`);
      await TaskRunner2.processDetailPage();
      return;
    }
    await InstanceManager.init();
    await Database.load();
    KeepAlive.setTick(async () => {
      if (State.isWorkerTab || !State.isExecuting) {
        KeepAlive.stop();
        return;
      }
      InstanceManager.ping();
      KeepAlive.poke();
      await TaskRunner2.checkStalledWorkers();
      if (State.isExecuting && State.activeWorkers < Config.MAX_CONCURRENT_WORKERS && State.db.todo.length > 0) {
        TaskRunner2.executeBatch();
      }
    });
    if (hasCookie) {
      Utils.verifyServerSession().then((ok) => {
        if (!ok) {
          Utils.logger("warn", Utils.getText("auth_session_invalid"));
          State.isAuthenticated = false;
        }
      });
    }
    const storedExecutingState = await GM_getValue(Config.DB_KEYS.IS_EXECUTING, false);
    if (State.isExecuting !== storedExecutingState) {
      Utils.logger("info", Utils.getText("log_execution_state_inconsistent", storedExecutingState ? "\u6267\u884C\u4E2D" : "\u5DF2\u505C\u6B62"));
      State.isExecuting = storedExecutingState;
    }
    await RateLimitManager.initStatus();
    setupRequestInterceptors();
    await PagePatcher.init();
    const tempTasks = await GM_getValue("temp_todo_tasks", null);
    if (tempTasks && tempTasks.length > 0) {
      Utils.logger("info", `\u4ECE429\u6062\u590D\uFF1A\u627E\u5230 ${tempTasks.length} \u4E2A\u4E34\u65F6\u4FDD\u5B58\u7684\u5F85\u529E\u4EFB\u52A1\uFF0C\u6B63\u5728\u6062\u590D...`);
      State.db.todo = tempTasks;
      await GM_deleteValue("temp_todo_tasks");
    }
    State.valueChangeListeners.push(GM_addValueChangeListener(Config.DB_KEYS.WORKER_DONE, async (key, oldValue, newValue) => {
      if (!newValue) return;
      try {
        await GM_deleteValue(Config.DB_KEYS.WORKER_DONE);
        const { workerId: workerId2, success, task, logs, instanceId, executionTime } = newValue;
        if (instanceId !== Config.INSTANCE_ID) {
          Utils.logger("info", `\u6536\u5230\u6765\u81EA\u5176\u4ED6\u5B9E\u4F8B [${instanceId}] \u7684\u5DE5\u4F5C\u62A5\u544A\uFF0C\u5F53\u524D\u5B9E\u4F8B [${Config.INSTANCE_ID}] \u5C06\u5FFD\u7565\u3002`);
          return;
        }
        if (!workerId2 || !task) {
          Utils.logger("error", "\u6536\u5230\u65E0\u6548\u7684\u5DE5\u4F5C\u62A5\u544A\u3002\u7F3A\u5C11workerId\u6216task\u3002");
          return;
        }
        if (State.runningWorkers[workerId2]) {
          delete State.runningWorkers[workerId2];
          State.activeWorkers--;
        }
        if (logs && logs.length) {
          logs.forEach((log) => {
            Utils.logger("debug", log);
          });
        }
        const isZh = State.lang === "zh";
        const timeSuffix = executionTime ? ` (${Utils.getText("task_execution_time", (executionTime / 1e3).toFixed(2))})` : "";
        if (success) {
          const successMsg = isZh ? `\u2705 \u4EFB\u52A1\u5B8C\u6210: ${task.name}` : `\u2705 Task completed: ${task.name}`;
          Utils.logger("info", successMsg + timeSuffix);
          await Database.markAsDone(task);
          State.sessionCompleted.add(Database.normalizeListingUrl(task.url));
          State.executionCompletedTasks++;
        } else {
          const errorLog = logs && logs.length ? logs.find((log) => log.includes(Utils.getText("worker_captcha"))) || logs.find((log) => log.includes("Error") || log.includes("Timeout") || log.includes("failed") || log.includes("Critical")) || logs[logs.length - 1] : isZh ? "\u5DE5\u4F5C\u6807\u7B7E\u9875\u62A5\u544A\u5931\u8D25" : "Worker tab reported failure";
          const cleanError = errorLog ? errorLog.replace(/^\[[a-f0-9-]+\]\s*/i, "") : isZh ? "\u672A\u77E5\u539F\u56E0" : "Unknown reason";
          const failMsg = isZh ? `\u274C \u4EFB\u52A1\u5931\u8D25: ${task.name} (${cleanError})` : `\u274C Task failed: ${task.name} (${cleanError})`;
          Utils.logger("warn", failMsg + timeSuffix);
          const _failRes = await Database.markAsFailed(task, {
            reason: cleanError,
            logs: logs || [],
            details: {
              executionTime: executionTime ? `${(executionTime / 1e3).toFixed(2)}s` : "\u672A\u77E5",
              workerId: workerId2,
              instanceId
            }
          });
          if (!_failRes || !_failRes.retried) State.executionFailedTasks++;
        }
        UI5.update();
        if (State.isExecuting && State.activeWorkers < Config.MAX_CONCURRENT_WORKERS && State.db.todo.length > 0) {
          setTimeout(() => TaskRunner2.executeBatch(), 200);
        }
        if (State.isExecuting && State.db.todo.length === 0 && State.activeWorkers === 0) {
          if (State.autoAddOnScroll) {
            TaskRunner2.attemptAutoScroll();
          } else {
            await TaskRunner2.stopExecutionAndSettle();
          }
        }
        TaskRunner2.runHideOrShow();
      } catch (error) {
        Utils.logger("error", `\u5904\u7406\u5DE5\u4F5C\u62A5\u544A\u65F6\u51FA\u9519: ${error.message}`);
      }
    }));
    State.valueChangeListeners.push(GM_addValueChangeListener(Config.DB_KEYS.IS_EXECUTING, (key, oldValue, newValue) => {
      if (!State.isWorkerTab && State.isExecuting !== newValue) {
        Utils.logger("info", Utils.getText("execution_status_changed", newValue ? Utils.getText("status_executing") : Utils.getText("status_stopped")));
        State.isExecuting = newValue;
        UI5.update();
      }
    }));
    window._fabHelperLauncherActive = window._fabHelperLauncherActive || false;
    if (!window._fabHelperLauncherActive) {
      window._fabHelperLauncherActive = true;
      const launcherInterval = setInterval(() => {
        if (document.readyState === "interactive" || document.readyState === "complete") {
          if (!State.hasRunDomPart) {
            Utils.logger("info", "[Launcher] DOM is ready. Running main script logic...");
            (async () => {
              try {
                await runDomDependentPart();
              } catch (e) {
                Utils.logger("error", `[Launcher] Error in runDomDependentPart: ${e.message}`);
                console.error("[Fab Helper] runDomDependentPart error:", e);
                State.hasRunDomPart = true;
              }
            })();
          }
          if (State.hasRunDomPart) {
            clearInterval(launcherInterval);
            window._fabHelperLauncherActive = false;
            Utils.logger("debug", "[Launcher] Main logic has been launched or skipped. Launcher is now idle.");
          }
        }
      }, 500);
    }
    let lastNetworkActivityTime = Date.now();
    window.recordNetworkActivity = function() {
      lastNetworkActivityTime = Date.now();
    };
    if (typeof document !== "undefined") {
      document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === "visible") {
          lastNetworkActivityTime = Date.now();
          Utils.logger("debug", "[Visibility] \u9875\u9762\u91CD\u65B0\u53EF\u89C1\uFF0C\u91CD\u7F6E\u65E0\u7F51\u7EDC\u6D3B\u52A8\u8BA1\u65F6\u3002");
        }
      });
    }
    setInterval(() => {
      if (State.appStatus === "NORMAL" && State.isExecuting && (State.db.todo.length > 0 || State.activeWorkers > 0)) {
        const inactiveTime = Date.now() - lastNetworkActivityTime;
        if (inactiveTime > 3e4) {
          Utils.logger("warn", `\u26A0\uFE0F \u68C0\u6D4B\u5230\u5728\u4EFB\u52A1\u6267\u884C\u72B6\u6001\u4E0B\u5DF2\u6301\u7EED ${Math.floor(inactiveTime / 1e3)} \u79D2\u65E0\u7F51\u7EDC\u6D3B\u52A8\uFF0C\u5373\u5C06\u5F3A\u5236\u5237\u65B0\u9875\u9762\u4EE5\u6062\u590D\u4EFB\u52A1...`);
          setTimeout(() => {
            window.location.reload();
          }, 1500);
        }
      }
    }, 5e3);
    Utils.logger("info", Utils.getText("log_init"));
  }
  __name(main, "main");
  window.addEventListener("beforeunload", () => {
    InstanceManager.cleanup();
    Utils.cleanup();
  });
  window.addEventListener("load", () => {
    setTimeout(ensureUILoaded, 2e3);
  });
  async function handleWakeRecovery() {
    if (State.isWorkerTab) return;
    if (!State.isExecuting && State.db.todo.length === 0) return;
    Utils.logger("info", Utils.getText("log_wake_recovery"));
    await RateLimitManager.checkInactivity(false);
    const cleaned = await TaskRunner2.checkStalledWorkers();
    if (cleaned > 0) {
      Utils.logger("warn", Utils.getText("log_wake_cleanup_stale", cleaned));
    }
    if (State.db.todo.length > 0) {
      if (!State.isExecuting) {
        Utils.logger("info", Utils.getText("log_wake_restarting", State.db.todo.length));
        TaskRunner2.startExecution();
      } else if (State.activeWorkers < Config.MAX_CONCURRENT_WORKERS) {
        Utils.logger("info", Utils.getText("log_wake_restarting", State.db.todo.length));
        TaskRunner2.executeBatch();
      }
    }
  }
  __name(handleWakeRecovery, "handleWakeRecovery");
  var wakeRecoveryTimer = null;
  var triggerWakeRecovery = /* @__PURE__ */ __name(() => {
    clearTimeout(wakeRecoveryTimer);
    wakeRecoveryTimer = setTimeout(handleWakeRecovery, 1e3);
  }, "triggerWakeRecovery");
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") {
      setTimeout(ensureUILoaded, 500);
      triggerWakeRecovery();
    }
  });
  window.addEventListener("focus", () => {
    triggerWakeRecovery();
  });
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", main);
  } else {
    main();
  }
})();