Fab Helper

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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();
  }
})();