Fab Helper

Fab Helper 优化版 - 减少API请求,提高性能,增强稳定性,修复限速刷新

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Fab Helper
// @name:zh-CN   Fab Helper
// @name:en      Fab Helper
// @namespace    https://www.fab.com/
// @version      3.5.1-20260106032624
// @description  Fab Helper 优化版 - 减少API请求,提高性能,增强稳定性,修复限速刷新
// @description:zh-CN  Fab Helper 优化版 - 减少API请求,提高性能,增强稳定性,修复限速刷新
// @description:en  Fab Helper Optimized - Reduced API requests, improved performance, enhanced stability, fixed rate limit refresh
// @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-idle
// ==/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",
    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",
    // 应用标题和标签
    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...",
    // 调试日志消息
    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.",
    // 限速检测来源
    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.",
    // 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_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",
    // 脚本启动和初始化
    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.",
    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_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...",
    // 账号验证
    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"
  };

  // 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",
    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",
    // 应用标题和标签
    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...",
    // 调试日志消息
    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",
    // 限速检测来源
    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",
    // 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_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",
    // 脚本启动和初始化
    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",
    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_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...",
    // 账号验证
    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"
  };

  // 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: 3e4,
    // 工作标签页超时时间
    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"
      // 无商品可见时自动刷新
      // 其他键值用于会话或主标签页持久化
    },
    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"]),
    // 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", "\u8D77\u59CB\u4EF7\u683C \u514D\u8D39"]),
    // 添加一个实例ID,用于防止多实例运行
    INSTANCE_ID: "fab_instance_id_" + Math.random().toString(36).substring(2, 15)
  };

  // src/state.js
  var State = {
    db: {
      todo: [],
      // 待办任务列表
      done: [],
      // 已完成任务列表
      failed: []
      // 失败任务列表
    },
    hideSaved: false,
    // 是否隐藏已保存项目
    autoAddOnScroll: false,
    // 是否在滚动时自动添加任务
    rememberScrollPosition: false,
    // 是否记住滚动位置
    autoResumeAfter429: false,
    // 是否在429后自动恢复
    autoRefreshEmptyPage: 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,
    // 是否正在检查限速状态
    // --- 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,
    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,
    // 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 (type === "debug") {
        if (!State.debugMode) {
          return;
        }
        console.log(`${Config.SCRIPT_NAME} [DEBUG]`, ...args);
        if (State.UI && State.UI.logPanel) {
          const logEntry = document.createElement("div");
          logEntry.style.cssText = "padding: 2px 4px; border-bottom: 1px solid #444; font-size: 11px; color: #888;";
          const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
          logEntry.innerHTML = `<span style="color: #888;">[${timestamp}]</span> <span style="color: #8a8;">[DEBUG]</span> ${args.join(" ")}`;
          State.UI.logPanel.prepend(logEntry);
          while (State.UI.logPanel.children.length > 100) {
            State.UI.logPanel.removeChild(State.UI.logPanel.lastChild);
          }
        }
        return;
      }
      if (State.isWorkerTab) {
        if (type === "error" || args.some((arg) => typeof arg === "string" && arg.includes("Worker"))) {
          console[type](`${Config.SCRIPT_NAME} [Worker]`, ...args);
        }
        return;
      }
      console[type](`${Config.SCRIPT_NAME}`, ...args);
      if (State.UI && State.UI.logPanel) {
        const logEntry = document.createElement("div");
        logEntry.style.cssText = "padding: 2px 4px; border-bottom: 1px solid #444; font-size: 11px;";
        const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
        logEntry.innerHTML = `<span style="color: #888;">[${timestamp}]</span> ${args.join(" ")}`;
        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;
      setTimeout(() => {
        const pageWindow = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
        Utils.logger("info", `Performing deep click on element: <${element.tagName.toLowerCase()} class="${element.className}">`);
        const pointerDownEvent = new PointerEvent("pointerdown", { view: pageWindow, bubbles: true, cancelable: true });
        const mouseDownEvent = new MouseEvent("mousedown", { view: pageWindow, bubbles: true, cancelable: true });
        const mouseUpEvent = new MouseEvent("mouseup", { view: pageWindow, bubbles: true, cancelable: true });
        element.dispatchEvent(pointerDownEvent);
        element.dispatchEvent(mouseDownEvent);
        element.dispatchEvent(mouseUpEvent);
        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"),
    // 添加游标解码函数
    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]) {
          const itemName = decodeURIComponent(match[1].replace(/\+/g, " "));
          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"));
        }
        return false;
      }
      return true;
    }, "checkAuthentication")
  };

  // 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.log("=== \u9875\u9762\u72B6\u6001\u8BCA\u65AD\u62A5\u544A ===");
      console.log(`\u9875\u9762: ${report.url}`);
      console.log(`\u6807\u9898: ${report.pageTitle}`);
      console.log(`--- \u6309\u94AE\u4FE1\u606F (${report.buttons.length}\u4E2A) ---`);
      report.buttons.forEach((btn) => {
        if (btn.isVisible) {
          console.log(`\u6309\u94AE: "${btn.text}" (\u53EF\u89C1: ${btn.isVisible}, \u7981\u7528: ${btn.isDisabled})`);
        }
      });
      console.log(`--- \u8BB8\u53EF\u9009\u9879 (${report.licenseOptions.length}\u4E2A) ---`);
      report.licenseOptions.forEach((opt) => {
        if (opt.isVisible) {
          console.log(`\u8BB8\u53EF: "${opt.text}" (\u53EF\u89C1: ${opt.isVisible}, \u89D2\u8272: ${opt.role})`);
        }
      });
      console.log(`--- \u4EF7\u683C\u4FE1\u606F ---`);
      Object.entries(report.priceInfo).forEach(([, price]) => {
        if (price.isVisible) {
          console.log(`\u4EF7\u683C: "${price.text}"`);
        }
      });
      console.log(`--- \u62E5\u6709\u72B6\u6001 ---`);
      Object.entries(report.ownedStatus).forEach(([, status]) => {
        if (status.isVisible) {
          console.log(`\u72B6\u6001: "${status.text}"`);
        }
      });
      console.log("=== \u8BCA\u65AD\u62A5\u544A\u7ED3\u675F ===");
    }, "logDiagnosticReport")
  };

  // src/modules/data-cache.js
  var DataCache = {
    // 商品数据缓存 - 键为商品ID,值为商品数据
    listings: /* @__PURE__ */ new Map(),
    // 拥有状态缓存 - 键为商品ID,值为拥有状态对象
    ownedStatus: /* @__PURE__ */ new Map(),
    // 价格缓存 - 键为报价ID,值为价格信息对象
    prices: /* @__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);
        }
      });
    }, "saveListings"),
    // 添加到等待列表
    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 = {
    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.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);
      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 previousDuration = persistedStatus && persistedStatus.startTime ? ((Date.now() - persistedStatus.startTime) / 1e3).toFixed(2) : "0.00";
        Utils.logger("warn", `Script starting in RATE_LIMITED state. 429 period has lasted at least ${previousDuration}s.`);
      }
      State.statusHistory = await GM_getValue(Config.DB_KEYS.STATUS_HISTORY, []);
      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"),
    // 保存无商品自动刷新设置
    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);
        State.db.todo = [];
        State.db.done = [];
        State.db.failed = [];
        State.savedCursor = null;
        Utils.logger("info", "\u6240\u6709\u811A\u672C\u6570\u636E\uFF08\u5305\u62EC\u6EDA\u52A8\u8BB0\u5FC6\uFF09\u5DF2\u91CD\u7F6E\u3002");
        if (UI2) {
          UI2.removeAllOverlays();
          UI2.update();
        }
      }
    }, "resetAllData"),
    isDone: /* @__PURE__ */ __name((url) => {
      if (!url) return false;
      return State.db.done.includes(url.split("?")[0]);
    }, "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 cleanUrl = task.url.split("?")[0];
      if (!Database.isDone(cleanUrl)) {
        State.db.done.push(cleanUrl);
        changed = true;
      }
      if (changed) {
        await Database.saveDone();
      }
    }, "markAsDone"),
    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;
      }
      const initialTodoCount = State.db.todo.length;
      State.db.todo = State.db.todo.filter((t) => t.uid !== task.uid);
      let changed = State.db.todo.length < initialTodoCount;
      const failedTask = {
        ...task,
        failedAt: (/* @__PURE__ */ new Date()).toISOString(),
        failureReason: failureInfo.reason || "\u672A\u77E5\u539F\u56E0",
        errorDetails: failureInfo.details || null,
        workerLogs: failureInfo.logs || [],
        retryCount: (task.retryCount || 0) + 1
      };
      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);
      }
      changed = true;
      if (changed) {
        await Database.saveTodo();
        await Database.saveFailed();
      }
    }, "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,
    // 检查是否与最后一条记录重复
    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 totalCards = document.querySelectorAll(Config.SELECTORS.card).length;
      const hiddenCards = document.querySelectorAll(`${Config.SELECTORS.card}[style*="display: none"]`).length;
      const actualVisibleCards = totalCards - hiddenCards;
      const visibleCountElement = document.getElementById("fab-status-visible");
      if (visibleCountElement) {
        visibleCountElement.textContent = actualVisibleCards.toString();
      }
      State.hiddenThisPageCount = hiddenCards;
      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 = 5e3 + Math.random() * 2e3;
        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 (!hasResults) {
        Utils.logger("info", `\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++;
      Utils.logger("info", Utils.getText("rate_limit_success_request", State.consecutiveSuccessCount, State.requiredSuccessCount, source));
      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"));
        const 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;
            }
          }
        }
        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")
  };

  // src/modules/page-patcher.js
  var PagePatcher = {
    _patchHasBeenApplied: false,
    _lastSeenCursor: null,
    _lastCheckedUrl: null,
    _bodyObserver: null,
    // State for request debouncing
    _debounceXhrTimer: null,
    _pendingXhr: null,
    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.`);
        }
      } 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.checkCurrentSortFromUrl();
      if (typeof MutationObserver !== "undefined") {
        const bodyObserver = new MutationObserver(() => {
          if (window.location.href !== this._lastCheckedUrl) {
            this._lastCheckedUrl = window.location.href;
            this.checkCurrentSortFromUrl();
            Utils.detectLanguage();
          }
        });
        bodyObserver.observe(document.body, {
          childList: true,
          subtree: true
        });
        this._bodyObserver = bodyObserver;
      }
      window.addEventListener("popstate", () => {
        this.checkCurrentSortFromUrl();
        Utils.detectLanguage();
      });
      window.addEventListener("hashchange", () => {
        this.checkCurrentSortFromUrl();
        Utils.detectLanguage();
      });
      this._lastCheckedUrl = window.location.href;
    },
    // 从URL中检查当前排序方式并更新设置
    checkCurrentSortFromUrl() {
      try {
        const url = new URL(window.location.href);
        const sortParam = url.searchParams.get("sort_by");
        if (!sortParam) return;
        let matchedOption = null;
        if (State.sortOptions) {
          for (const [key, option] of Object.entries(State.sortOptions)) {
            if (option.value === sortParam) {
              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
          ));
          State.savedCursor = null;
          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"));
        }
      } catch (e) {
        Utils.logger("warn", Utils.getText("log_sort_check_error", e.message));
      }
    },
    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) {
      return typeof url === "string" && url.includes("/i/listings/search") && !url.includes("aggregate_on=") && !url.includes("count=0");
    },
    shouldPatchUrl(url) {
      if (typeof url !== "string") return false;
      if (this._patchHasBeenApplied) return false;
      if (!State.rememberScrollPosition || !State.savedCursor) 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;
      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;
        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) {
          let isValidPosition = true;
          let decodedCursor = "";
          try {
            decodedCursor = atob(newCursor);
            const filterKeywords = [
              "Nude+Tennis+Racket",
              "Nordic+Beach+Boulder",
              "Nordic+Beach+Rock"
            ];
            if (filterKeywords.some((keyword) => decodedCursor.includes(keyword))) {
              Utils.logger("info", Utils.getText("log_cursor_skip_known_position", decodedCursor));
              isValidPosition = false;
            }
            if (isValidPosition && this._lastSeenCursor) {
              try {
                let newItemName = "";
                let lastItemName = "";
                if (decodedCursor.includes("p=")) {
                  const match = decodedCursor.match(/p=([^&]+)/);
                  if (match && match[1]) {
                    newItemName = decodeURIComponent(match[1].replace(/\+/g, " "));
                  }
                }
                const lastDecoded = atob(this._lastSeenCursor);
                if (lastDecoded.includes("p=")) {
                  const match = lastDecoded.match(/p=([^&]+)/);
                  if (match && match[1]) {
                    lastItemName = decodeURIComponent(match[1].replace(/\+/g, " "));
                  }
                }
                if (newItemName && lastItemName) {
                  const getFirstWord = /* @__PURE__ */ __name((text) => text.trim().substring(0, 3), "getFirstWord");
                  const newFirstWord = getFirstWord(newItemName);
                  const lastFirstWord = getFirstWord(lastItemName);
                  const sortParam = urlObj.searchParams.get("sort_by") || "";
                  const isReverseSort = sortParam.startsWith("-");
                  if (isReverseSort && sortParam.includes("title") && newFirstWord > lastFirstWord || !isReverseSort && sortParam.includes("title") && newFirstWord < lastFirstWord) {
                    Utils.logger("info", Utils.getText(
                      "log_cursor_skip_backtrack",
                      newItemName,
                      lastItemName,
                      isReverseSort ? Utils.getText("log_sort_descending") : Utils.getText("log_sort_ascending")
                    ));
                    isValidPosition = false;
                  }
                }
              } catch (compareError) {
              }
            }
          } catch (decodeError) {
          }
          if (isValidPosition) {
            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 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 hasSpecialFilters = params.has("query") || params.has("category") || params.has("subcategory") || params.has("tag");
                    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._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;
        self._debounceXhrTimer = setTimeout(() => {
          if (State.debugMode) {
            Utils.logger("debug", Utils.getText("log_debounce_sending", this._url));
          }
          listenerAwareSend.apply(self._pendingXhr, args);
          self._pendingXhr = null;
        }, 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(async (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) {
            try {
              const clonedResponse = response.clone();
              const text = await clonedResponse.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 hasSpecialFilters = params.has("query") || params.has("category") || params.has("subcategory") || params.has("tag");
                  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 (e) {
            }
          }
          return response;
        });
      };
    }
  };

  // src/modules/instance-manager.js
  var InstanceManager = {
    isActive: false,
    lastPingTime: 0,
    pingInterval: null,
    // 初始化实例管理
    init: /* @__PURE__ */ __name(async function() {
      try {
        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")
  };

  // src/modules/task-runner.js
  var UI4 = null;
  function setUIReference3(uiModule) {
    UI4 = uiModule;
  }
  __name(setUIReference3, "setUIReference");
  var TaskRunner2 = {
    // 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;
      if (!link) {
        const icons = card.querySelectorAll("i.fabkit-Icon--intent-success, i.edsicon-check-circle-filled");
        if (icons.length > 0) return true;
        const text = card.textContent || "";
        return 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");
      }
      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) {
        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(url)) return true;
      }
      return false;
    }, "isCardFinished"),
    // Check if a card represents a free item
    isFreeCard: /* @__PURE__ */ __name((card) => {
      const cardText = card.textContent || "";
      const hasFreeKeyword = [...Config.FREE_TEXT_SET].some((freeWord) => cardText.includes(freeWord));
      const has100PercentDiscount = cardText.includes("-100%");
      const priceMatch = cardText.match(/\$(\d+(?:\.\d{2})?)/g);
      if (priceMatch) {
        const hasNonZeroPrice = priceMatch.some((price) => {
          const numValue = parseFloat(price.replace("$", ""));
          return numValue > 0;
        });
        if (hasNonZeroPrice && !hasFreeKeyword) return false;
        if (hasNonZeroPrice && hasFreeKeyword) {
          if (cardText.includes("\u8D77\u59CB\u4EF7\u683C \u514D\u8D39") || cardText.includes("Starting at Free")) return true;
          if (cardText.match(/起始价格\s*\$[1-9]/) || cardText.match(/Starting at\s*\$[1-9]/i)) return false;
        }
      }
      return hasFreeKeyword || has100PercentDiscount;
    }, "isFreeCard"),
    // Toggle execution state
    toggleExecution: /* @__PURE__ */ __name(() => {
      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;
      }
      if (State.autoAddOnScroll) {
        Utils.logger("info", Utils.getText("log_auto_add_enabled"));
        TaskRunner2.checkVisibleCardsStatus().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(Config.SELECTORS.card);
      const newlyAddedList = [];
      let alreadyInQueueCount = 0;
      let ownedCount = 0;
      let skippedCount = 0;
      const isCardSettled = /* @__PURE__ */ __name((card) => {
        return card.querySelector(`${Config.SELECTORS.freeStatus}, ${Config.SELECTORS.ownedStatus}`) !== null;
      }, "isCardSettled");
      cards.forEach((card) => {
        if (card.style.display === "none") return;
        if (!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));
      State.isExecuting = true;
      Database.saveExecutingState();
      State.executionTotalTasks = State.db.todo.length;
      State.executionCompletedTasks = 0;
      State.executionFailedTasks = 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 actualVisibleCount = document.querySelectorAll(`${Config.SELECTORS.card}:not([style*="display: none"])`).length;
        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"),
    stop: /* @__PURE__ */ __name(() => {
      if (!State.isExecuting) return;
      State.isExecuting = false;
      Database.saveExecutingState();
      Database.saveTodo();
      GM_deleteValue(Config.DB_KEYS.TASK);
      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(Config.SELECTORS.card)].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);
        }).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("info", 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}`;
              if (!Database.isDone(url)) {
                State.db.done.push(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"),
    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 now = Date.now();
        const STALL_TIMEOUT = Config.WORKER_TIMEOUT;
        const stalledWorkers = [];
        for (const workerId in State.runningWorkers) {
          const workerInfo = State.runningWorkers[workerId];
          if (workerInfo.instanceId !== Config.INSTANCE_ID) continue;
          if (now - workerInfo.startTime > STALL_TIMEOUT) {
            stalledWorkers.push({ workerId, task: workerInfo.task });
          }
        }
        if (stalledWorkers.length > 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)));
            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`
              }
            });
            State.executionFailedTasks++;
            delete State.runningWorkers[workerId];
            State.activeWorkers--;
            await GM_deleteValue(workerId);
          }
          Utils.logger("info", Utils.getText("log_cleaned_workers", stalledWorkers.length, State.activeWorkers));
          if (UI4) UI4.update();
          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("info", Utils.getText("log_dispatching_in_progress"));
        return;
      }
      State.isDispatchingTasks = true;
      try {
        if (State.db.todo.length === 0 && State.activeWorkers === 0) {
          Utils.logger("info", Utils.getText("log_all_tasks_completed"));
          State.isExecuting = false;
          Database.saveExecutingState();
          Database.saveTodo();
          if (State.watchdogTimer) {
            clearInterval(State.watchdogTimer);
            State.watchdogTimer = null;
          }
          TaskRunner2.closeAllWorkerTabs();
          if (UI4) UI4.update();
          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();
        for (const task of todoList) {
          if (State.activeWorkers >= Config.MAX_CONCURRENT_WORKERS) 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;
          }
          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));
          await GM_setValue(workerId, {
            task,
            instanceId: Config.INSTANCE_ID
          });
          const workerUrl = new URL(task.url);
          workerUrl.searchParams.set("workerId", workerId);
          GM_openInTab(workerUrl.href, { active: false, insert: true });
          await new Promise((resolve) => setTimeout(resolve, 500));
        }
        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("\u5F3A\u5236\u5173\u95ED\u5DE5\u4F5C\u6807\u7B7E\u9875");
          try {
            window.close();
          } catch (e) {
            console.error("\u5173\u95ED\u5DE5\u4F5C\u6807\u7B7E\u9875\u5931\u8D25:", e);
          }
        }
      }, 6e4);
      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;
              }
              if (currentState === "complete" && 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, 500));
            }
            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`);
          }
          await new Promise((resolve) => setTimeout(resolve, 2e3));
          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 allVisibleButtons = [...document.querySelectorAll("button")].filter((btn) => {
                const rect = btn.getBoundingClientRect();
                return rect.width > 0 && rect.height > 0;
              });
              logBuffer.push(`=== \u6309\u94AE\u68C0\u6D4B\u5F00\u59CB (\u5171 ${allVisibleButtons.length} \u4E2A\u53EF\u89C1\u6309\u94AE) ===`);
              allVisibleButtons.slice(0, 15).forEach((btn, i) => {
                const text = btn.textContent.trim().substring(0, 60);
                logBuffer.push(`  \u6309\u94AE${i + 1}: "${text}"`);
              });
              const licenseButton = allVisibleButtons.find(
                (btn) => btn.textContent.includes("\u9009\u62E9\u8BB8\u53EF") || btn.textContent.includes("Select license")
              );
              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 freeTextElement = Array.from(node.querySelectorAll("span, div")).find(
                              (el) => Array.from(el.childNodes).some((cn) => {
                                if (cn.nodeType !== 3) return false;
                                const text = cn.textContent.trim();
                                return [...Config.FREE_TEXT_SET].some((freeWord) => text === freeWord) || text === "\u4E2A\u4EBA" || text === "Personal";
                              })
                            );
                            if (freeTextElement) {
                              const clickableParent = freeTextElement.closest('[role="option"], button, label, input[type="radio"]');
                              if (clickableParent) {
                                logBuffer.push(`Found free/personal 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();
                  return rect.width > 0 && rect.height > 0;
                });
                logBuffer.push(`=== \u91CD\u65B0\u68C0\u6D4B\u6309\u94AE (\u5171 ${freshButtons.length} \u4E2A\u53EF\u89C1\u6309\u94AE) ===`);
                freshButtons.slice(0, 10).forEach((btn, i) => {
                  const text = btn.textContent.trim().substring(0, 60);
                  logBuffer.push(`  \u6309\u94AE${i + 1}: "${text}"`);
                });
                let actionButton = freshButtons.find((btn) => {
                  const text = btn.textContent.toLowerCase();
                  return [...Config.ACQUISITION_TEXT_SET].some(
                    (keyword) => text.includes(keyword.toLowerCase())
                  );
                });
                if (!actionButton) {
                  actionButton = freshButtons.find((btn) => {
                    const text = btn.textContent;
                    const hasFreeText = [...Config.FREE_TEXT_SET].some((freeWord) => text.includes(freeWord));
                    const hasDiscount = text.includes("-100%");
                    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, clicking it.`);
                  Utils.deepClick(actionButton);
                  try {
                    await new Promise((resolve, reject) => {
                      const timeout = 25e3;
                      const interval = setInterval(() => {
                        const currentState = isItemOwned();
                        if (currentState.owned) {
                          logBuffer.push(`Item became owned after clicking add button: ${currentState.reason}`);
                          success = true;
                          clearInterval(interval);
                          resolve();
                        }
                      }, 500);
                      setTimeout(() => {
                        clearInterval(interval);
                        reject(new Error(`Timeout waiting for page to enter an 'owned' state.`));
                      }, timeout);
                    });
                  } 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 {
          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"),
    runHideOrShow: /* @__PURE__ */ __name(() => {
      State.hiddenThisPageCount = 0;
      const cards = document.querySelectorAll(Config.SELECTORS.card);
      let actuallyHidden = 0;
      let hasUnsettledCards = false;
      const unsettledCards = [];
      const isCardSettled = /* @__PURE__ */ __name((card) => {
        return card.querySelector(`${Config.SELECTORS.freeStatus}, ${Config.SELECTORS.ownedStatus}`) !== null;
      }, "isCardSettled");
      cards.forEach((card) => {
        if (!isCardSettled(card)) {
          hasUnsettledCards = true;
          unsettledCards.push(card);
        }
      });
      if (hasUnsettledCards && unsettledCards.length > 0) {
        Utils.logger("info", Utils.getText("log_unsettled_cards", unsettledCards.length));
        setTimeout(() => TaskRunner2.runHideOrShow(), 2e3);
        return;
      }
      const cardsToHide = [];
      cards.forEach((card) => {
        const isProcessed = card.getAttribute("data-fab-processed") === "true";
        if (isProcessed && card.style.display === "none") {
          State.hiddenThisPageCount++;
          return;
        }
        const isFinished = TaskRunner2.isCardFinished(card);
        if (State.hideSaved && isFinished) {
          cardsToHide.push(card);
          State.hiddenThisPageCount++;
          card.setAttribute("data-fab-processed", "true");
        } else {
          card.setAttribute("data-fab-processed", "true");
        }
      });
      if (cardsToHide.length > 0) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_prepare_hide", cardsToHide.length));
        }
        cardsToHide.sort(() => Math.random() - 0.5);
        const batchSize = 10;
        const batches = Math.ceil(cardsToHide.length / batchSize);
        const initialDelay = 1e3;
        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;
          setTimeout(() => {
            currentBatch.forEach((card, index) => {
              const cardDelay = index * 50 + Math.random() * 100;
              setTimeout(() => {
                card.style.display = "none";
                actuallyHidden++;
                if (actuallyHidden === cardsToHide.length) {
                  if (State.debugMode) {
                    Utils.logger("debug", Utils.getText("debug_hide_completed", actuallyHidden));
                  }
                  setTimeout(() => {
                    if (UI4) UI4.update();
                    TaskRunner2.checkVisibilityAndRefresh();
                  }, 300);
                }
              }, cardDelay);
            });
          }, batchDelay);
        }
      }
      if (State.hideSaved) {
        const visibleCards = Array.from(cards).filter((card) => !TaskRunner2.isCardFinished(card));
        visibleCards.forEach((card) => {
          card.style.display = "";
        });
        if (cardsToHide.length === 0) {
          if (UI4) UI4.update();
          TaskRunner2.checkVisibilityAndRefresh();
        }
      } else {
        cards.forEach((card) => {
          card.style.display = "";
        });
        if (UI4) UI4.update();
      }
    }, "runHideOrShow"),
    checkVisibilityAndRefresh: /* @__PURE__ */ __name(() => {
      const cards = document.querySelectorAll(Config.SELECTORS.card);
      let needsReprocessing = false;
      cards.forEach((card) => {
        const isProcessed = card.getAttribute("data-fab-processed") === "true";
        if (!isProcessed) needsReprocessing = true;
      });
      if (needsReprocessing) {
        if (State.debugMode) {
          Utils.logger("debug", Utils.getText("debug_unprocessed_cards_simple"));
        }
        setTimeout(() => TaskRunner2.runHideOrShow(), 100);
        return;
      }
      const visibleCards = Array.from(cards).filter((card) => {
        if (card.style.display === "none") return false;
        const computedStyle = window.getComputedStyle(card);
        return computedStyle.display !== "none" && computedStyle.visibility !== "hidden";
      }).length;
      if (State.debugMode) {
        Utils.logger("debug", Utils.getText("debug_visible_after_hide", visibleCards, State.hiddenThisPageCount));
      }
      const visibleCountElement = document.getElementById("fab-status-visible");
      if (visibleCountElement) {
        visibleCountElement.textContent = visibleCards.toString();
      }
      if (visibleCards === 0) {
        if (State.appStatus === "RATE_LIMITED" && State.autoRefreshEmptyPage) {
          if (State.isRefreshScheduled) {
            Utils.logger("info", Utils.getText("refresh_plan_exists"));
            return;
          }
          Utils.logger("info", Utils.getText("log_all_hidden_rate_limited"));
          State.isRefreshScheduled = true;
          setTimeout(() => {
            const currentVisibleCards = Array.from(document.querySelectorAll(Config.SELECTORS.card)).filter((card) => card.style.display !== "none").length;
            if (State.db.todo.length > 0 || State.activeWorkers > 0) {
              Utils.logger("info", Utils.getText("log_refresh_cancelled_tasks", State.db.todo.length, State.activeWorkers));
              State.isRefreshScheduled = false;
              return;
            }
            if (currentVisibleCards === 0 && State.appStatus === "RATE_LIMITED" && State.autoRefreshEmptyPage) {
              Utils.logger("info", Utils.getText("log_refreshing"));
              window.location.href = window.location.href;
            } else {
              Utils.logger("info", Utils.getText("log_refresh_cancelled_visible", currentVisibleCards));
              State.isRefreshScheduled = false;
            }
          }, 2e3);
        } else if (State.appStatus === "NORMAL" && State.hiddenThisPageCount > 0) {
          Utils.logger("debug", Utils.getText("page_status_hidden_no_visible", State.hiddenThisPageCount));
        }
      }
    }, "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 () => {
      try {
        const visibleCards = [...document.querySelectorAll(Config.SELECTORS.card)];
        if (visibleCards.length === 0) {
          Utils.logger("info", Utils.getText("log_no_visible_cards"));
          return;
        }
        let hasUnsettledCards = false;
        const unsettledCards = [];
        const isCardSettled = /* @__PURE__ */ __name((card) => {
          return card.querySelector(`${Config.SELECTORS.freeStatus}, ${Config.SELECTORS.ownedStatus}`) !== null;
        }, "isCardSettled");
        visibleCards.forEach((card) => {
          if (!isCardSettled(card)) {
            hasUnsettledCards = true;
            unsettledCards.push(card);
          }
        });
        if (hasUnsettledCards && unsettledCards.length > 0) {
          Utils.logger("info", Utils.getText("log_waiting_for_cards", unsettledCards.length));
          await new Promise((resolve) => setTimeout(resolve, 3e3));
          return TaskRunner2.checkVisibleCardsStatus();
        }
        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 (State.db.done.includes(url)) return;
            allItems.push({ uid, url, element: card });
          }
        });
        if (allItems.length === 0) {
          Utils.logger("debug", Utils.getText("debug_no_cards_to_check"));
          return;
        }
        Utils.logger("info", 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 (!State.db.done.includes(item.url)) {
              State.db.done.push(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("info", Utils.getText("fab_dom_api_complete", confirmedOwned));
          Utils.logger("info", Utils.getText("fab_dom_refresh_complete", confirmedOwned));
        } 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");
        }
      }
    }, "checkVisibleCardsStatus"),
    scanAndAddTasks: /* @__PURE__ */ __name(async (cards) => {
      if (!State.autoAddOnScroll) 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);
            }
            const maxWaitTime = 1e4;
            const startTime = Date.now();
            const originalFetch = window.fetch;
            window.fetch = function(...args) {
              const url = args[0]?.toString() || "";
              if (url.includes("/listings-states") || url.includes("/listings/search")) {
                window._apiWaitStatus.lastApiActivity = Date.now();
              }
              return originalFetch.apply(this, args);
            };
            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.fetch = originalFetch;
                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;
        cardsToProcess.forEach((card) => {
          const link = card.querySelector(Config.SELECTORS.cardLink);
          const url = link ? link.href.split("?")[0] : null;
          if (!url) 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 (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();
            } 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")
  };

  // 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;
            }
        `;
      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.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();
      actionButtons.append(State.UI.syncBtn, State.UI.hideBtn);
      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;";
      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);
      State.UI.savedPositionDisplay = positionInfo;
      positionContainer.appendChild(positionIcon);
      positionContainer.appendChild(positionInfo);
      dashboardContent.append(logContainer, positionContainer, statusBar, 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();
          }
          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 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 visibleCount = document.querySelectorAll(Config.SELECTORS.card).length - State.hiddenThisPageCount;
      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 = State.hiddenThisPageCount;
      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.hideBtn) {
        State.UI.hideBtn.textContent = (State.hideSaved ? "\u{1F648} " : "\u{1F441}\uFE0F ") + (State.hideSaved ? Utils.getText("show") : Utils.getText("hide"));
      }
    }, "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"),
    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
  var currentCountdownInterval = null;
  var currentRefreshTimeout = null;
  function countdownRefresh2(delay, reason = "\u5907\u9009\u65B9\u6848") {
    if (State.isRefreshScheduled) {
      Utils.logger("info", 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(countdownRefresh2, "countdownRefresh");
  async function checkRateLimitStatus() {
    try {
      const totalCards = document.querySelectorAll(Config.SELECTORS.card).length;
      const hiddenCards = document.querySelectorAll(`${Config.SELECTORS.card}[style*="display: none"]`).length;
      const actualVisibleCards = totalCards - hiddenCards;
      const visibleCountElement = document.getElementById("fab-status-visible");
      if (visibleCountElement) {
        visibleCountElement.textContent = actualVisibleCards.toString();
      }
      State.hiddenThisPageCount = hiddenCards;
      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);
  setTaskRunnerReference(TaskRunner2);
  setDependencies({
    UI: UI5,
    TaskRunner: TaskRunner2,
    countdownRefresh: countdownRefresh2
  });
  function setupXHRInterceptor() {
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function(...args) {
      this._url = args[1];
      return originalOpen.apply(this, args);
    };
    XMLHttpRequest.prototype.send = function(...args) {
      const xhr = this;
      if (xhr._url && typeof xhr._url === "string") {
        xhr.addEventListener("load", function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            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);
                } else {
                  const extractedData = API.extractStateData(responseData, "XHRInterceptor");
                  if (Array.isArray(extractedData) && extractedData.length > 0) {
                    DataCache.saveOwnedStatus(extractedData);
                  }
                }
              } 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) {
      const url = args[0]?.toString() || "";
      if (url.includes("/i/listings/search") || url.includes("/i/users/me/listings-states") || url.includes("/i/listings/prices-infos")) {
        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);
                } else {
                  const extractedData = API.extractStateData(data, "FetchInterceptor");
                  if (Array.isArray(extractedData) && extractedData.length > 0) {
                    DataCache.saveOwnedStatus(extractedData);
                  }
                }
              } 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") {
        const 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.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;
          countdownRefresh2(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) {
        clearTimeout(State.observerDebounceTimer);
        State.observerDebounceTimer = setTimeout(() => {
          if (State.debugMode) {
            Utils.logger("debug", `[Observer] ${Utils.getText("debug_new_content_loading")}`);
          }
          setTimeout(() => {
            TaskRunner2.checkVisibleCardsStatus().then(() => {
              setTimeout(() => {
                if (State.hideSaved) {
                  TaskRunner2.runHideOrShow();
                }
              }, 1e3);
              if (State.appStatus === "NORMAL" || State.autoAddOnScroll) {
                setTimeout(() => {
                  TaskRunner2.scanAndAddTasks(document.querySelectorAll(Config.SELECTORS.card)).catch((error) => Utils.logger("error", `\u81EA\u52A8\u6DFB\u52A0\u4EFB\u52A1\u5931\u8D25: ${error.message}`));
                }, 500);
              }
            }).catch(() => {
              setTimeout(() => {
                if (State.hideSaved) {
                  TaskRunner2.runHideOrShow();
                }
              }, 1500);
            });
          }, 2e3);
        }, 500);
      }
    });
    observer.observe(targetNode, { childList: true, subtree: true });
    Utils.logger("debug", `\u2705 Core DOM observer is now active on <${targetNode.tagName.toLowerCase()}>.`);
    TaskRunner2.runHideOrShow();
    setInterval(() => {
      if (!State.hideSaved) return;
      const cards = document.querySelectorAll(Config.SELECTORS.card);
      let unprocessedCount = 0;
      cards.forEach((card) => {
        const isProcessed = card.getAttribute("data-fab-processed") === "true";
        if (!isProcessed) {
          unprocessedCount++;
        } else {
          const isFinished = TaskRunner2.isCardFinished(card);
          const shouldBeHidden = isFinished && State.hideSaved;
          const isHidden = card.style.display === "none";
          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.runHideOrShow();
      }
    }, 3e3);
    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 !State.db.done.includes(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 = document.querySelectorAll(Config.SELECTORS.card).length;
    let noNewCardsCounter = 0;
    let lastScrollY = window.scrollY;
    setInterval(() => {
      if (State.appStatus !== "NORMAL") return;
      const currentCardCount = document.querySelectorAll(Config.SELECTORS.card).length;
      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 {
        const totalCards = document.querySelectorAll(Config.SELECTORS.card).length;
        const visibleCards = Array.from(document.querySelectorAll(Config.SELECTORS.card)).filter((card) => {
          if (card.style.display === "none") return false;
          const computedStyle = window.getComputedStyle(card);
          return computedStyle.display !== "none" && computedStyle.visibility !== "hidden";
        });
        const actualVisibleCards = visibleCards.length;
        const hiddenCards = totalCards - actualVisibleCards;
        const visibleCountElement = document.getElementById("fab-status-visible");
        if (visibleCountElement) {
          visibleCountElement.textContent = actualVisibleCards.toString();
        }
        State.hiddenThisPageCount = hiddenCards;
        if (State.appStatus === "RATE_LIMITED" && actualVisibleCards === 0 && State.autoRefreshEmptyPage) {
          if (!window._pendingZeroVisibleRefresh && !currentCountdownInterval && !currentRefreshTimeout) {
            Utils.logger("info", `[\u72B6\u6001\u76D1\u63A7] \u68C0\u6D4B\u5230\u9650\u901F\u72B6\u6001\u4E0B\u6CA1\u6709\u53EF\u89C1\u5546\u54C1\u4E14\u81EA\u52A8\u5237\u65B0\u5DF2\u5F00\u542F\uFF0C\u51C6\u5907\u5237\u65B0\u9875\u9762`);
            const randomDelay = 3e3 + Math.random() * 2e3;
            countdownRefresh2(randomDelay, "\u9650\u901F\u72B6\u6001\u65E0\u53EF\u89C1\u5546\u54C1");
          }
        }
      } 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();
    Utils.logger("info", Utils.getText("log_script_starting"));
    Utils.detectLanguage();
    const isLoggedIn = Utils.checkAuthentication(true);
    if (!isLoggedIn) {
      Utils.logger("warn", "\u8D26\u53F7\u672A\u767B\u5F55\uFF0C\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u53D7\u9650");
    }
    const urlParams = new URLSearchParams(window.location.search);
    const workerId = urlParams.get("workerId");
    if (workerId) {
      State.isWorkerTab = true;
      State.workerTaskId = workerId;
      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();
    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;
    }
    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 previousDuration = persistedStatus && persistedStatus.startTime ? ((Date.now() - persistedStatus.startTime) / 1e3).toFixed(2) : "0.00";
      Utils.logger("warn", Utils.getText("startup_rate_limited", previousDuration, persistedStatus.source || Utils.getText("status_unknown_source")));
    }
    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 (executionTime) {
          Utils.logger("info", Utils.getText("task_execution_time", executionTime ? (executionTime / 1e3).toFixed(2) : Utils.getText("status_unknown_duration")));
        }
        if (State.runningWorkers[workerId2]) {
          delete State.runningWorkers[workerId2];
          State.activeWorkers--;
        }
        if (logs && logs.length) {
          logs.forEach((log) => Utils.logger("info", log));
        }
        if (success) {
          Utils.logger("info", `\u2705 \u4EFB\u52A1\u5B8C\u6210: ${task.name}`);
          await Database.markAsDone(task);
          State.sessionCompleted.add(task.url);
          State.executionCompletedTasks++;
        } else {
          Utils.logger("warn", `\u274C \u4EFB\u52A1\u5931\u8D25: ${task.name}`);
          await Database.markAsFailed(task, {
            reason: "\u5DE5\u4F5C\u6807\u7B7E\u9875\u62A5\u544A\u5931\u8D25",
            logs: logs || [],
            details: {
              executionTime: executionTime ? `${(executionTime / 1e3).toFixed(2)}s` : "\u672A\u77E5",
              workerId: workerId2,
              instanceId
            }
          });
          State.executionFailedTasks++;
        }
        UI5.update();
        if (State.isExecuting && State.activeWorkers < Config.MAX_CONCURRENT_WORKERS && State.db.todo.length > 0) {
          setTimeout(() => TaskRunner2.executeBatch(), 1e3);
        }
        if (State.isExecuting && State.db.todo.length === 0 && State.activeWorkers === 0) {
          Utils.logger("info", "\u6240\u6709\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002");
          State.isExecuting = false;
          Database.saveExecutingState();
          await Database.saveTodo();
          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;
            countdownRefresh2(randomDelay, "\u4EFB\u52A1\u5B8C\u6210\u540E\u9650\u901F\u6062\u590D");
          }
          UI5.update();
        }
        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();
    };
    setInterval(() => {
      if (State.appStatus === "RATE_LIMITED") {
        const inactiveTime = Date.now() - lastNetworkActivityTime;
        if (inactiveTime > 3e4) {
          Utils.logger("warn", `\u26A0\uFE0F \u68C0\u6D4B\u5230\u5728\u9650\u901F\u72B6\u6001\u4E0B ${Math.floor(inactiveTime / 1e3)} \u79D2\u65E0\u7F51\u7EDC\u6D3B\u52A8\uFF0C\u5373\u5C06\u5F3A\u5236\u5237\u65B0\u9875\u9762...`);
          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);
  });
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") {
      setTimeout(ensureUILoaded, 500);
    }
  });
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", main);
  } else {
    main();
  }
})();