FC2ppvdb Enhanced Search

增强在 `fc2ppvdb.com` 网站上的浏览和搜索

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name FC2ppvdb Enhanced Search
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 增强在 `fc2ppvdb.com` 网站上的浏览和搜索
// @author ErrorRua
// @match https://fc2ppvdb.com/*
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_log
// @grant GM_saveTab
// @grant GM_download
// @connect bt4gprx.com
// @connect localhost
// @connect *
// @license MIT
// ==/UserScript==

;(function () {
  "use strict"

  // 请求重试相关常量
  const BT4G_REQUEST_DELAY_MS = 1000 // 直接请求BT4G时的间隔
  const PROXY_BT4G_REQUEST_DELAY_MS = 0 // 通过代理请求BT4G时的间隔

  // 主队列重试逻辑常量
  const MAX_FC2_ITEM_RETRIES = 3 // FC2项目最大重试次数
  const FC2_ITEM_RETRY_DELAY_MS = 5000 // 主队列中项目重试的延迟时间

  // 调试相关变量和配置
  const DEBUG_MODE = GM_getValue("DEBUG_MODE", false) // 默认关闭调试模式
  const DEBUG_LOG_MAX = GM_getValue("DEBUG_LOG_MAX", 500) // 日志最大条数
  let debugLogs = [] // 日志内存缓存

  const MAX_CONSECUTIVE_REQUEST_ERRORS = 5 // 最大连续请求错误次数
  let consecutiveRequestErrors = 0 // 当前连续请求错误次数
  let isScriptEnabled = true // 控制脚本是否继续运行的标志

  // 添加停止脚本的函数
  function stopScript() {
    isScriptEnabled = false
    consecutiveRequestErrors = 0
    observer.disconnect() // 断开观察器
    fc2ItemsToProcessQueue.clear() // 清空处理队列
    debugLog("脚本已停止:观察器已断开,处理队列已清空", "SCRIPT_STOPPED")
  }

  // 添加代理服务器配置
  const DEFAULT_PROXY_SERVER_URL = "http://localhost:15000" // 默认本地地址
  const PROXY_SERVER_URL = GM_getValue(
    "PROXY_SERVER_URL",
    DEFAULT_PROXY_SERVER_URL
  )
  let useProxy = GM_getValue("USE_PROXY", true) // 默认使用代理
  let batchSubmitEnabled = GM_getValue("BATCH_SUBMIT_ENABLED", false) // 默认关闭批量提交

  // 测试代理服务器连接并返回Promise
  function testProxyServerConnection(url) {
    return new Promise((resolve, reject) => {
      const testUrl = `${url}/health`
      debugLog(`测试代理服务器: ${testUrl}`, "PROXY_TEST")

      GM_xmlhttpRequest({
        method: "GET",
        url: testUrl,
        timeout: 5000,
        onload: function (response) {
          if (response.status === 200) {
            try {
              const result = JSON.parse(response.responseText)
              if (result.status === "ok") {
                debugLog("代理服务器测试成功", "PROXY_TEST_SUCCESS")
                resolve(true)
              } else {
                debugLog(
                  `代理服务器测试异常响应: ${response.responseText}`,
                  "PROXY_TEST_FAIL"
                )
                reject(new Error("代理服务器响应异常"))
              }
            } catch (e) {
              debugLog(`代理服务器测试解析错误: ${e.message}`, "PROXY_TEST_ERROR")
              reject(new Error("代理服务器响应格式错误"))
            }
          } else {
            debugLog(
              `代理服务器测试失败: HTTP ${response.status}`,
              "PROXY_TEST_FAIL"
            )
            reject(new Error(`代理服务器连接失败,HTTP状态码: ${response.status}`))
          }
        },
        onerror: function (error) {
          debugLog(
            `代理服务器测试错误: ${error.error || "未知错误"}`,
            "PROXY_TEST_ERROR"
          )
          reject(new Error(`代理服务器连接错误: ${error.error || "未知错误"}`))
        },
        ontimeout: function () {
          debugLog("代理服务器测试超时", "PROXY_TEST_TIMEOUT")
          reject(new Error("代理服务器连接超时"))
        },
      })
    })
  }

  // 初始化函数
  async function initializeScript() {
    if (useProxy) {
      try {
        await testProxyServerConnection(PROXY_SERVER_URL)
        debugLog("代理服务器连接成功,继续执行脚本", "INIT_SUCCESS")
        // 继续执行脚本的其他初始化操作
        startDomObserver()
        debouncedScanAndEnqueueFc2Items()
      } catch (error) {
        debugLog(`代理服务器连接失败: ${error.message}`, "INIT_FAIL")
        alert(`代理服务器连接失败: ${error.message}\n脚本已停止运行。`)
        stopScript()
        return
      }
    } else {
      // 如果不使用代理,直接继续执行
      debugLog("不使用代理模式,直接执行脚本", "INIT_DIRECT")
      startDomObserver()
      debouncedScanAndEnqueueFc2Items()
    }
  }

  // 启动脚本
  initializeScript()

  // 防抖函数
  function debounce(func, wait) {
    let timeout
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout)
        func(...args)
      }
      clearTimeout(timeout)
      timeout = setTimeout(later, wait)
    }
  }

  // 调试日志函数
  function debugLog(message, type = "INFO") {
    if (!DEBUG_MODE) return

    const timestamp = new Date().toISOString()
    const logEntry = `[${timestamp}][${type}] ${message}`

    // 输出到控制台
    console.log(logEntry)
    GM_log(logEntry)

    // 添加到日志缓存
    debugLogs.push(logEntry)
    if (debugLogs.length > DEBUG_LOG_MAX) {
      debugLogs.shift() // 超出最大条数时删除最旧的日志
    }

    // 将日志保存到本地存储
    GM_setValue("FC2_DEBUG_LOGS", debugLogs)
  }

  // 配置代理服务器
  function configureProxyServer() {
    const currentUrl = GM_getValue("PROXY_SERVER_URL", DEFAULT_PROXY_SERVER_URL)
    const newUrl = prompt("请输入代理服务器地址:", currentUrl)
    if (newUrl !== null && newUrl.trim() !== "") {
      GM_setValue("PROXY_SERVER_URL", newUrl.trim())
      alert(`代理服务器地址已更新为: ${newUrl.trim()}`)
      // 可选:测试新的代理服务器
      testProxyServer(newUrl.trim())
    }
  }

  // 测试代理服务器连接
  function testProxyServer(url) {
    const testUrl = `${url}/health`
    debugLog(`测试代理服务器: ${testUrl}`, "PROXY_TEST")

    GM_xmlhttpRequest({
      method: "GET",
      url: testUrl,
      timeout: 5000,
      onload: function (response) {
        if (response.status === 200) {
          try {
            const result = JSON.parse(response.responseText)
            if (result.status === "ok") {
              alert("代理服务器连接成功!")
              debugLog("代理服务器测试成功", "PROXY_TEST_SUCCESS")
            } else {
              alert("代理服务器响应异常,请检查配置。")
              debugLog(
                `代理服务器测试异常响应: ${response.responseText}`,
                "PROXY_TEST_FAIL"
              )
            }
          } catch (e) {
            alert("代理服务器响应格式错误。")
            debugLog(`代理服务器测试解析错误: ${e.message}`, "PROXY_TEST_ERROR")
          }
        } else {
          alert(`代理服务器连接失败,HTTP状态码: ${response.status}`)
          debugLog(
            `代理服务器测试失败: HTTP ${response.status}`,
            "PROXY_TEST_FAIL"
          )
        }
      },
      onerror: function (error) {
        alert(`代理服务器连接错误: ${error.error || "未知错误"}`)
        debugLog(
          `代理服务器测试错误: ${error.error || "未知错误"}`,
          "PROXY_TEST_ERROR"
        )
      },
      ontimeout: function () {
        alert("代理服务器连接超时。")
        debugLog("代理服务器测试超时", "PROXY_TEST_TIMEOUT")
      },
    })
  }

  // 清除调试日志
  function clearDebugLogs() {
    debugLogs = []
    GM_setValue("FC2_DEBUG_LOGS", debugLogs)
    debugLog("日志已清除", "SYSTEM")
  }

  // 切换调试模式
  function toggleDebugMode() {
    const newMode = !DEBUG_MODE
    GM_setValue("DEBUG_MODE", newMode)
    alert(`调试模式已${newMode ? "开启" : "关闭"}`)
    location.reload() // 重新加载页面应用新设置
  }
  // 导出日志到剪贴板
  function exportLogsToClipboard() {
    const logs = GM_getValue("FC2_DEBUG_LOGS", [])
    if (logs.length === 0) {
      alert("没有可导出的日志")
      return
    }

    const logsText = logs.join("\n")
    navigator.clipboard
      .writeText(logsText)
      .then(() => {
        alert(`已复制 ${logs.length} 条日志到剪贴板`)
      })
      .catch((err) => {
        alert(`复制失败: ${err}`)
        console.error("复制失败:", err)
      })
  }

  // 导出日志到文件
  function exportLogsToFile() {
    const logs = GM_getValue("FC2_DEBUG_LOGS", [])
    if (logs.length === 0) {
      alert("没有可导出的日志")
      return
    }

    const logsText = debugLogs.join("\n")
    const blob = new Blob([logsText], { type: "text/plain;charset=utf-8" })
    const url = URL.createObjectURL(blob)

    const timestamp = new Date()
      .toISOString()
      .replace(/[:.]/g, "-")
      .replace("T", "_")
      .split("Z")[0]
    const filename = `fc2_debug_logs_${timestamp}.txt`

    // 尝试使用GM_download (Tampermonkey API)
    try {
      if (typeof GM_download === "function") {
        GM_download({
          url: url,
          name: filename,
          saveAs: true,
          onload: () => {
            debugLog(`日志文件已保存: ${filename}`, "EXPORT")
            URL.revokeObjectURL(url)
          },
          onerror: (error) => {
            debugLog(
              `保存日志文件失败: ${
                error.error || error.statusText || "未知错误"
              }`,
              "ERROR"
            )
            URL.revokeObjectURL(url)
            // 如果GM_download失败,回退到创建下载链接的方法
            createDownloadLink(url, filename)
          },
        })
        return
      }
    } catch (e) {
      console.error("GM_download 不可用:", e)
    }

    // 回退方法:创建下载链接
    createDownloadLink(url, filename)
  }

  // 创建下载链接的辅助函数
  function createDownloadLink(url, filename) {
    const downloadLink = document.createElement("a")
    downloadLink.href = url
    downloadLink.download = filename
    downloadLink.style.display = "none"
    document.body.appendChild(downloadLink)

    downloadLink.click()

    // 清理
    setTimeout(() => {
      document.body.removeChild(downloadLink)
      URL.revokeObjectURL(url)
    }, 100)

    debugLog(`日志文件已导出: ${filename}`, "EXPORT")
  }

  // 本地缓存键名和过期时间(毫秒)
  const SEARCH_RESULT_CACHE_KEY = "fc2_bt4g_cache"
  const CACHE_EXPIRATION_MS = 12 * 60 * 60 * 1000 // 12小时
  // 读取本地缓存
  function loadSearchResultCache() {
    try {
      const rawCache = localStorage.getItem(SEARCH_RESULT_CACHE_KEY)
      debugLog(
        `读取本地缓存: ${
          rawCache ? rawCache.substring(0, 100) + "..." : "无缓存"
        }`,
        "CACHE"
      )
      if (!rawCache) return {}
      const cacheData = JSON.parse(rawCache)
      // 清理过期
      const now = Date.now()
      let expiredCount = 0
      Object.keys(cacheData).forEach((key) => {
        if (
          !cacheData[key] ||
          !cacheData[key].timestamp ||
          now - cacheData[key].timestamp > CACHE_EXPIRATION_MS
        ) {
          delete cacheData[key]
          expiredCount++
        }
      })
      if (expiredCount > 0) {
        debugLog(`清理了 ${expiredCount} 个过期缓存项`, "CACHE")
      }
      return cacheData
    } catch (err) {
      debugLog(`读取缓存出错: ${err.message}`, "ERROR")
      return {}
    }
  }

  // 保存本地缓存
  function saveSearchResultCache(cache) {
    try {
      localStorage.setItem(SEARCH_RESULT_CACHE_KEY, JSON.stringify(cache))
      // debugLog(`保存缓存: ${Object.keys(cache).length} 个项目`, "CACHE") // 此日志过于频繁,注释掉
    } catch (err) {
      debugLog(`保存缓存出错: ${err.message}`, "ERROR")
    }
  }

  // 通过ID删除缓存的函数
  function removeSearchResultCacheById(fc2Id) {
    if (typeof fc2Id !== "string" && typeof fc2Id !== "number") {
      console.error("removeSearchResultCacheById: fc2Id 必须是字符串或数字")
      debugLog("removeSearchResultCacheById: fc2Id 必须是字符串或数字", "ERROR")
      return
    }
    const idStr = String(fc2Id)
    if (searchResultsCache[idStr]) {
      delete searchResultsCache[idStr]
      saveSearchResultCache(searchResultsCache)
      console.log(`缓存已删除: ${idStr}`)
      debugLog(`缓存已删除: ${idStr}`, "CACHE_MANAGE")
    } else {
      console.log(`未找到缓存: ${idStr}`)
      debugLog(`未找到缓存: ${idStr}`, "CACHE_MANAGE")
    }
  }

  // 搜索结果缓存,避免重复请求
  let searchResultsCache = loadSearchResultCache()

  // 添加样式
  const style = document.createElement("style")
  style.textContent = `
        .fc2-id-container {
            position: absolute;
            top: 0;
            left: 0;
            margin: 0;
            padding: 0;
            line-height: inherit;
            z-index: 10;
        }
        .search-buttons {
            display: none;
            position: absolute;
            top: 100%;  /* 放在编号元素下方 */
            left: 0;    /* 左对齐 */
            z-index: 1000;
            background: #1f2937;
            border-radius: 4px;
            padding: 4px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            white-space: nowrap;
            margin-top: 2px; /* 添加一点间距 */
        }
        .search-buttons::before {
            content: '';
            position: absolute;
            top: -15px;  /* 创建检测区域 */
            left: 0;
            right: 0;
            height: 15px;  /* 增加检测高度 */
        }
        .fc2-id-container:hover .search-buttons.has-results,
        .search-buttons.has-results:hover {
            display: flex;
            gap: 4px;
        }
        .search-button {
            padding: 2px 8px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            color: white;
            font-size: 12px;
            transition: background 0.2s;
            white-space: nowrap;
        }
        .bt4g-button {
            background: #3b82f6;
        }
        .missav-button {
            background: #dc2626;
        }
        .preview-button {
            background: #10b981;
        }
        .search-button:hover {
            filter: brightness(1.1);
        }
        .fc2-id-container > span {
            display: inline-block;
            position: relative;
        }
        .search-status {
            position: absolute;
            top: 100%;
            left: 0;
            font-size: 10px;
            color: #6b7280;
            margin-top: 2px;
            background: rgba(31, 41, 55, 0.9);
            padding: 2px 4px;
            border-radius: 3px;
            white-space: nowrap;
        }
        .loading {
            color: #f59e0b;
        }
        .success {
            color: #10b981;
        }
        .error {
            color: #ef4444;
        }

        /* 调试模式指示器 */
        .debug-indicator {
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            color: #22c55e;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 12px;
            z-index: 10000;
            display: flex;
            align-items: center;
            gap: 5px;
        }
        .debug-indicator .status-dot {
            width: 8px;
            height: 8px;
            background-color: #22c55e;
            border-radius: 50%;
            animation: pulse 2s infinite;
        }
        @keyframes pulse {
            0% { opacity: 1; }
            50% { opacity: 0.4; }
            100% { opacity: 1; }
        }

        /* 预览对话框样式 */
        .preview-dialog {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #1f2937;
            padding: 20px;
            border-radius: 8px;
            z-index: 10000;
            max-width: 95vw;
            max-height: 90vh;
            overflow: auto;
            width: 1200px;
            scrollbar-width: thin;
            scrollbar-color: #4b5563 #1f2937;
        }
        .preview-dialog::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        .preview-dialog::-webkit-scrollbar-track {
            background: #1f2937;
            border-radius: 4px;
        }
        .preview-dialog::-webkit-scrollbar-thumb {
            background: #4b5563;
            border-radius: 4px;
        }
        .preview-dialog::-webkit-scrollbar-thumb:hover {
            background: #6b7280;
        }
        .preview-dialog.active {
            display: block;
        }
        .preview-dialog-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 9999;
        }
        .preview-dialog-overlay.active {
            display: block;
        }
        .preview-dialog-close {
            position: absolute;
            top: 10px;
            right: 10px;
            background: none;
            border: none;
            color: white;
            font-size: 20px;
            cursor: pointer;
            padding: 5px;
        }
        .preview-dialog-content {
            margin-top: 20px;
        }
        .preview-dialog-content img {
            max-width: 100%;
            height: auto;
            margin-bottom: 10px;
        }
        .preview-dialog-content video {
            max-width: 100%;
            margin-bottom: 10px;
        }
    `
  document.head.appendChild(style)
  // 创建调试状态指示器
  function createDebugIndicator() {
    if (!DEBUG_MODE) return

    // 更新样式以支持新的功能
    const indicatorStyle = document.createElement("style")
    indicatorStyle.textContent = `
      .debug-indicator {
        position: fixed;
        bottom: 10px;
        right: 10px;
        background: rgba(0, 0, 0, 0.8);
        color: #22c55e;
        padding: 5px 10px;
        border-radius: 4px;
        font-size: 12px;
        z-index: 10000;
        display: flex;
        align-items: center;
        gap: 5px;
        cursor: pointer;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
      }
      .debug-indicator .status-dot {
        width: 8px;
        height: 8px;
        background-color: #22c55e;
        border-radius: 50%;
        animation: pulse 2s infinite;
      }
      .debug-menu {
        position: absolute;
        bottom: 100%;
        right: 0;
        background: rgba(0, 0, 0, 0.9);
        border-radius: 4px;
        /* margin-bottom: 5px; */ /* 移除此行以消除间隙 */
        min-width: 120px;
        display: none;
        flex-direction: column;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
      }
      .debug-indicator:hover .debug-menu {
        display: flex;
      }
      .debug-menu-item {
        padding: 6px 12px;
        color: white;
        cursor: pointer;
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        font-size: 12px;
      }
      .debug-menu-item:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      .debug-menu-item:last-child {
        border-bottom: none;
      }
      @keyframes pulse {
        0% { opacity: 1; }
        50% { opacity: 0.4; }
        100% { opacity: 1; }
      }
    `
    document.head.appendChild(indicatorStyle)

    const indicator = document.createElement("div")
    indicator.className = "debug-indicator"

    const statusDot = document.createElement("span")
    statusDot.className = "status-dot"

    const text = document.createElement("span")
    const count = debugLogs.length
    text.textContent = `调试模式 (${count})`

    // 创建菜单
    const menu = document.createElement("div")
    menu.className = "debug-menu"

    // 添加菜单项
    const menuItems = [
      { text: "导出到剪贴板", action: exportLogsToClipboard },
      { text: "导出到文件", action: exportLogsToFile },
      { text: "清除日志", action: clearDebugLogs },
      { text: "关闭调试模式", action: toggleDebugMode },
    ]

    menuItems.forEach((item) => {
      const menuItem = document.createElement("div")
      menuItem.className = "debug-menu-item"
      menuItem.textContent = item.text
      menuItem.addEventListener("click", (e) => {
        e.stopPropagation()
        item.action()
      })
      menu.appendChild(menuItem)
    })

    indicator.appendChild(statusDot)
    indicator.appendChild(text)
    indicator.appendChild(menu)

    // 点击显示日志条数
    indicator.addEventListener("click", () => {
      const count = debugLogs.length
      text.textContent = `调试模式 (${count})`
    })

    document.body.appendChild(indicator)
    debugLog("调试状态指示器已创建", "UI")

    // 定期更新日志计数
    setInterval(() => {
      const count = debugLogs.length
      text.textContent = `调试模式 (${count})`
    }, 5000)
  }

  // 初始化调试指示器
  setTimeout(createDebugIndicator, 1000)

  // 切换是否使用代理
  function toggleUseProxy() {
    useProxy = !useProxy
    GM_setValue("USE_PROXY", useProxy)
    alert(`通过代理请求BT4G已${useProxy ? "开启" : "关闭"}`)
    // 可能需要重新加载或清除缓存以使更改立即生效
    searchResultsCache = {} // 清空缓存,以便重新检查
    saveSearchResultCache(searchResultsCache)
    fc2ItemsProcessed.clear() // 清除已处理记录,强制重新扫描
    // 重新扫描并处理页面上的项目
    scanAndEnqueueFc2Items()
    triggerFc2ItemsProcessing()
  }

  const fc2ItemRetryAttempts = new Map() // 记录FC2 ID的重试次数 (fc2Id -> attemptCount)

  // 通过代理服务器检查BT4G是否有搜索结果
  function checkBT4GAvailability(fc2Id) {
    // 首先检查脚本是否已禁用
    if (!isScriptEnabled) {
      debugLog("脚本已停止运行", "SCRIPT_DISABLED")
      return Promise.resolve({ status: "SCRIPT_DISABLED" })
    }

    // 首先检查缓存
    if (searchResultsCache[fc2Id] !== undefined) {
      debugLog(
        `BT4G缓存命中: ${fc2Id}, 结果: ${searchResultsCache[fc2Id].result}`,
        "BT4G_CACHE_HIT"
      )
      return Promise.resolve({
        status: searchResultsCache[fc2Id].result ? "SUCCESS" : "NOT_FOUND",
      })
    }

    // 使用代理服务器而非直接请求BT4G
    const proxyUrl = `${PROXY_SERVER_URL}/check_bt4g/${encodeURIComponent(
      fc2Id
    )}`

    return new Promise((resolve) => {
      if (!useProxy) {
        // 如果不使用代理,则直接请求 BT4G
        debugLog(`直接请求BT4G: ${fc2Id}`, "BT4G_DIRECT_REQUEST")
        const searchUrl = `https://bt4gprx.com/search/${encodeURIComponent(
          fc2Id
        )}`
        GM_xmlhttpRequest({
          method: "GET",
          url: searchUrl,
          onload: function (response) {
            if (response.status === 200) {
              debugLog(`BT4G响应: ${fc2Id}, 状态: 200. 分析内容中...`, "BT4G")
              consecutiveRequestErrors = 0 // 成功响应,重置错误计数器
              try {
                const parser = new DOMParser()
                const doc = parser.parseFromString(
                  response.responseText,
                  "text/html"
                )
                if (doc.querySelector(".list-group")) {
                  debugLog(
                    `BT4G分析: ${fc2Id}, 找到 .list-group. 视为有结果。`,
                    "BT4G_SUCCESS"
                  )
                  resolve({ status: "SUCCESS" })
                } else {
                  debugLog(
                    `BT4G分析: ${fc2Id}, 状态200但未找到.list-group,将由主队列重试`,
                    "BT4G_NO_RESULT_RETRY"
                  )
                  resolve({ status: "RETRY_MAIN_QUEUE" })
                }
              } catch (e) {
                debugLog(
                  `BT4G响应解析错误: ${fc2Id}, ${e.message}. 视为无结果。`,
                  "ERROR"
                )
                resolve({ status: "FAILED" })
              }
            } else if (response.status === 404) {
              debugLog(
                `BT4G响应: ${fc2Id}, 状态: 404. 视为无结果 (目标未找到)。`,
                "BT4G_NOT_FOUND"
              )
              consecutiveRequestErrors = 0 // 404是预期内的"未找到",重置错误计数器
              resolve({ status: "NOT_FOUND" })
            } else {
              debugLog(
                `BT4G响应: ${fc2Id}, 状态: ${response.status}. 视为无结果。`,
                "BT4G_FAIL"
              )
              consecutiveRequestErrors++
              if (consecutiveRequestErrors >= MAX_CONSECUTIVE_REQUEST_ERRORS) {
                alert("BT4G请求连续多次失败,脚本已停止运行。请检查网络连接或BT4G服务状态(被暂时封禁)。")
                stopScript()
              }
              resolve({ status: "FAILED" })
            }
          },
          onerror: function (error) {
            debugLog(
              `BT4G请求错误: ${fc2Id}, ${error.error || "未知错误"}`,
              "ERROR"
            )
            consecutiveRequestErrors++
            if (consecutiveRequestErrors >= MAX_CONSECUTIVE_REQUEST_ERRORS) {
              alert("BT4G请求连续多次失败,脚本已停止运行。请检查网络连接或BT4G服务状态(被暂时封禁)。")
              stopScript()
            }
            resolve({ status: "FAILED" })
          },
        })
        return
      }

      debugLog(`请求代理服务器: ${proxyUrl}`, "PROXY_REQUEST")
      GM_xmlhttpRequest({
        method: "GET",
        url: proxyUrl,
        onload: function (response) {
          if (response.status === 200) {
            try {
              const result = JSON.parse(response.responseText)
              debugLog(`代理响应: ${fc2Id}, 状态: ${result.status}`, "PROXY")
              consecutiveRequestErrors = 0 // 成功响应,重置错误计数器

              if (result.status === "success") {
                if (result.has_results) {
                  debugLog(`代理分析: ${fc2Id}, 有资源。`, "PROXY_SUCCESS")
                  resolve({ status: "SUCCESS" })
                } else {
                  debugLog(`代理分析: ${fc2Id}, 无资源。`, "PROXY_NO_RESULT")
                  resolve({ status: "RETRY_MAIN_QUEUE" })
                }
              } else if (result.status === "not_found") {
                debugLog(
                  `代理响应: ${fc2Id}, 状态: not_found。视为无资源。`,
                  "PROXY_NOT_FOUND"
                )
                resolve({ status: "NOT_FOUND" })
              } else {
                debugLog(
                  `代理响应错误: ${fc2Id}, ${result.message || "未知错误"}`,
                  "ERROR"
                )
                resolve({ status: "RETRY_MAIN_QUEUE" })
              }
            } catch (e) {
              debugLog(`代理响应解析错误: ${fc2Id}, ${e.message}`, "ERROR")
              resolve({ status: "RETRY_MAIN_QUEUE" })
            }
          } else {
            debugLog(
              `代理请求失败: ${fc2Id}, HTTP状态: ${response.status}`,
              "PROXY_FAIL"
            )
            consecutiveRequestErrors++
            if (consecutiveRequestErrors >= MAX_CONSECUTIVE_REQUEST_ERRORS) {
              alert("代理请求连续多次失败,脚本已停止运行。请检查代理服务器配置或BT4G服务状态(被暂时封禁)。")
              stopScript()
            }
            resolve({ status: "RETRY_MAIN_QUEUE" })
          }
        },
        onerror: function (error) {
          debugLog(
            `代理请求错误: ${fc2Id}, ${error.error || "未知错误"}`,
            "ERROR"
          )
          consecutiveRequestErrors++
          if (consecutiveRequestErrors >= MAX_CONSECUTIVE_REQUEST_ERRORS) {
            alert("代理请求连续多次失败,脚本已停止运行。请检查代理服务器配置或BT4G服务状态(被暂时封禁)。")
            stopScript()
          }
          resolve({ status: "FAILED" })
        },
      })
    })
  }

  // 添加已处理ID记录集合
  const fc2ItemsProcessed = new Set()

  // ====== FC2项目主处理队列及状态 ======
  const fc2ItemsToProcessQueue = new Set()
  let isProcessingFc2ItemsQueue = false

  // 处理单个FC2项目及其DOM元素
  async function processSingleFc2Item(fc2Id, itemElement) {
    debugLog(`开始处理单个FC2项目: ${fc2Id}`, "FC2_ITEM_PROCESS")

    // 确保容器存在或创建它
    let itemContainer = itemElement.parentElement.classList.contains(
      "fc2-id-container"
    )
      ? itemElement.parentElement
      : null
    let searchButtonsContainer
    let statusDisplaySpan

    if (!itemContainer) {
      const originalStyle = window.getComputedStyle(itemElement)
      itemContainer = document.createElement("div")
      itemContainer.className = "fc2-id-container"
      const topValue = originalStyle.top === "auto" ? "0px" : originalStyle.top
      const leftValue =
        originalStyle.left === "auto" ? "0px" : originalStyle.left

      itemContainer.style.cssText = `
              position: absolute;
              top: ${topValue};
              left: ${leftValue};
              z-index: 10;
          `
      const parent = itemElement.parentNode
      if (parent) {
        parent.style.position = "relative"
        parent.insertBefore(itemContainer, itemElement)
        itemContainer.appendChild(itemElement)
      } else {
        debugLog(`FC2项目 ${fc2Id} 没有父节点,无法插入容器`, "ERROR")
        return "FAILED" // 返回状态
      }

      searchButtonsContainer = document.createElement("div")
      searchButtonsContainer.className = "search-buttons"

      statusDisplaySpan = document.createElement("span")
      statusDisplaySpan.className = "search-status loading"
      statusDisplaySpan.textContent = "检查中..."
      itemContainer.appendChild(statusDisplaySpan)

      const bt4gSearchButton = document.createElement("button")
      bt4gSearchButton.className = "search-button bt4g-button"
      bt4gSearchButton.textContent = "BT4G"
      bt4gSearchButton.onclick = (e) => {
        e.stopPropagation()
        GM_openInTab(`https://bt4gprx.com/search/${fc2Id}`, { active: true })
      }

      const missavSearchButton = document.createElement("button")
      missavSearchButton.className = "search-button missav-button"
      missavSearchButton.textContent = "Missav"
      missavSearchButton.onclick = (e) => {
        e.stopPropagation()
        GM_openInTab(`https://missav.ws/cn/search/${fc2Id}`, { active: true })
      }

      const previewButton = document.createElement("button")
      previewButton.className = "search-button preview-button"
      previewButton.textContent = "预览"
      previewButton.onclick = (e) => {
        e.stopPropagation()
        showPreview(fc2Id)
      }

      searchButtonsContainer.appendChild(bt4gSearchButton)
      searchButtonsContainer.appendChild(missavSearchButton)
      searchButtonsContainer.appendChild(previewButton)
      itemContainer.appendChild(searchButtonsContainer)

      itemContainer.addEventListener("mouseenter", () => {
        if (searchButtonsContainer.classList.contains("has-results")) {
          searchButtonsContainer.style.display = "flex"
        }
      })
      itemContainer.addEventListener("mouseleave", () => {
        searchButtonsContainer.style.display = "none"
      })
    } else {
      searchButtonsContainer = itemContainer.querySelector(".search-buttons")
      statusDisplaySpan = itemContainer.querySelector(".search-status")
      if (statusDisplaySpan) {
        // 重置状态以便重新检查
        statusDisplaySpan.className = "search-status loading"
        statusDisplaySpan.textContent = "检查中..."
        statusDisplaySpan.style.display = "block"
      }
    }

    const bt4gCheckResult = await checkBT4GAvailability(fc2Id)
    const status = bt4gCheckResult.status

    if (status === "SUCCESS") {
      searchResultsCache[fc2Id] = { result: true, timestamp: Date.now() }
      saveSearchResultCache(searchResultsCache)
      if (searchButtonsContainer)
        searchButtonsContainer.classList.add("has-results")
      if (statusDisplaySpan) {
        statusDisplaySpan.className = "search-status success"
        statusDisplaySpan.textContent = "有资源"
        setTimeout(() => {
          if (statusDisplaySpan) statusDisplaySpan.style.display = "none"
        }, 2000)
      }
      itemElement.dataset.processed = "true" // 标记为已完全处理
      fc2ItemsProcessed.add(fc2Id) // 添加到已处理集合
    } else if (status === "NOT_FOUND") {
      searchResultsCache[fc2Id] = { result: false, timestamp: Date.now() }
      saveSearchResultCache(searchResultsCache)
      if (searchButtonsContainer)
        searchButtonsContainer.classList.remove("has-results")
      if (statusDisplaySpan) {
        statusDisplaySpan.className = "search-status error"
        statusDisplaySpan.textContent = "无资源"
        statusDisplaySpan.style.display = "block"
      }
      itemElement.dataset.processed = "true" // 标记为已完全处理 (即使没有结果)
      fc2ItemsProcessed.add(fc2Id)
    } else if (status === "RETRY_MAIN_QUEUE") {
      if (statusDisplaySpan) {
        statusDisplaySpan.className = "search-status loading" // 或特定的"重试中"样式
        statusDisplaySpan.textContent = "等待重试..."
        statusDisplaySpan.style.display = "block"
      }
      // 不标记为已处理,不缓存
    } else if (status === "FAILED") {
      if (statusDisplaySpan) {
        statusDisplaySpan.className = "search-status error"
        statusDisplaySpan.textContent = "检查失败"
        statusDisplaySpan.style.display = "block"
      }
      // 不标记为已处理,不缓存 (允许将来手动或自动重试)
    }

    debugLog(
      `完成处理单个FC2项目: ${fc2Id},状态: ${status}`,
      "FC2_ITEM_PROCESS_DONE"
    )
    return status // 返回状态给主队列处理器
  }

  // 处理FC2项目队列
  async function processFc2ItemsQueue() {
    if (!isScriptEnabled) {
      debugLog("脚本已停止运行,不再处理队列", "SCRIPT_DISABLED")
      return
    }
    
    if (isProcessingFc2ItemsQueue && fc2ItemsToProcessQueue.size === 0) {
      isProcessingFc2ItemsQueue = false
      debugLog("FC2项目处理队列在处理中变为空", "FC2_QUEUE_EMPTY_MID_PROCESS")
      return
    }
    if (fc2ItemsToProcessQueue.size === 0) {
      debugLog("FC2项目处理队列为空,无需处理", "FC2_QUEUE_EMPTY")
      isProcessingFc2ItemsQueue = false
      return
    }
    if (isProcessingFc2ItemsQueue) {
      debugLog(
        "FC2项目队列已在处理中,跳过本次触发",
        "FC2_QUEUE_BUSY_SKIP_TRIGGER"
      )
      return
    }

    isProcessingFc2ItemsQueue = true
    debugLog(
      `开始处理FC2项目队列,共${fc2ItemsToProcessQueue.size}项`,
      "FC2_QUEUE_START"
    )

    // 如果启用了批量提交功能,使用批量处理
    if (batchSubmitEnabled && useProxy) {
      const fc2Ids = Array.from(fc2ItemsToProcessQueue)
      debugLog(`批量提交队列中的 ${fc2Ids.length} 个FC2编号`, "BATCH_SUBMIT_START")

      // 分离需要批量处理和已缓存的编号
      const uncachedIds = []
      const cachedResults = new Map()

      fc2Ids.forEach(fc2Id => {
        if (searchResultsCache[fc2Id] !== undefined) {
          cachedResults.set(fc2Id, searchResultsCache[fc2Id])
          fc2ItemsToProcessQueue.delete(fc2Id)
        } else {
          uncachedIds.push(fc2Id)
        }
      })

      // 处理已缓存的编号
      if (cachedResults.size > 0) {
        debugLog(`从缓存中处理 ${cachedResults.size} 个编号`, "BATCH_CACHE_PROCESS")
        for (const [fc2Id, cacheData] of cachedResults) {
          const itemElement = Array.from(document.querySelectorAll("div.relative span.top-0"))
            .find(el => el.textContent.trim() === fc2Id)
          if (itemElement) {
            processSingleFc2Item(fc2Id, itemElement)
          }
        }
      }

      // 如果有未缓存的编号,进行批量处理
      if (uncachedIds.length > 0) {
        debugLog(`批量处理 ${uncachedIds.length} 个未缓存的编号`, "BATCH_PROCESS_UNCACHED")
        try {
          const response = await fetch(`${PROXY_SERVER_URL}/check_batch`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ fc2_ids: uncachedIds })
          })

          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`)
          }

          const result = await response.json()
          
          if (result.status === 'success') {
            let successCount = 0
            let notFoundCount = 0
            let errorCount = 0

            result.results.forEach(item => {
              // 从队列中移除已处理的编号
              fc2ItemsToProcessQueue.delete(item.fc2_id)

              if (item.status === 'success' && item.has_results) {
                successCount++
                searchResultsCache[item.fc2_id] = { result: true, timestamp: Date.now() }
                // 更新UI显示
                const itemElement = Array.from(document.querySelectorAll("div.relative span.top-0"))
                  .find(el => el.textContent.trim() === item.fc2_id)
                if (itemElement) {
                  processSingleFc2Item(item.fc2_id, itemElement)
                }
              } else if (item.status === 'not_found') {
                notFoundCount++
                searchResultsCache[item.fc2_id] = { result: false, timestamp: Date.now() }
                // 更新UI显示
                const itemElement = Array.from(document.querySelectorAll("div.relative span.top-0"))
                  .find(el => el.textContent.trim() === item.fc2_id)
                if (itemElement) {
                  processSingleFc2Item(item.fc2_id, itemElement)
                }
              } else {
                errorCount++
                // 对于错误的情况,将编号重新加入队列
                fc2ItemsToProcessQueue.add(item.fc2_id)
              }
            })

            saveSearchResultCache(searchResultsCache)
            debugLog(`批量提交完成: ${successCount}个有资源, ${notFoundCount}个无资源, ${errorCount}个错误`, "BATCH_SUBMIT_DONE")
            
            // 如果还有错误项,触发队列处理
            if (errorCount > 0) {
              triggerFc2ItemsProcessing()
            }
          } else {
            throw new Error(result.message || '未知错误')
          }
        } catch (error) {
          debugLog(`批量提交失败: ${error.message}`, "BATCH_SUBMIT_ERROR")
          // 批量提交失败时,回退到单个处理
          processFc2ItemsIndividually()
        }
      } else {
        debugLog("所有编号都已缓存,无需批量处理", "BATCH_ALL_CACHED")
      }
    } else {
      // 使用原有的单个处理逻辑
      processFc2ItemsIndividually()
    }

    isProcessingFc2ItemsQueue = false
    debugLog("FC2项目队列当前批次处理完成", "FC2_QUEUE_BATCH_DONE")
    
    // 检查是否仍有项目
    if (fc2ItemsToProcessQueue.size > 0) {
      debugLog(
        "FC2队列批处理后仍有项目,触发下一次处理",
        "FC2_QUEUE_TRIGGER_POST_BATCH"
      )
      triggerFc2ItemsProcessing()
    }
  }

  // 单个处理FC2项目的函数
  async function processFc2ItemsIndividually() {
    const idsToProcessThisBatch = Array.from(fc2ItemsToProcessQueue)

    for (const fc2Id of idsToProcessThisBatch) {
      fc2ItemsToProcessQueue.delete(fc2Id)
      debugLog(
        `从FC2队列取出: ${fc2Id},处理后队列剩余: ${fc2ItemsToProcessQueue.size}`,
        "FC2_QUEUE_PROCESS"
      )

      const itemElement = Array.from(
        document.querySelectorAll("div.relative span.top-0")
      ).find(
        (el) =>
          el.textContent.trim() === fc2Id &&
          !el.dataset.processed &&
          !fc2ItemsProcessed.has(fc2Id) &&
          !(
            el.parentElement &&
            el.parentElement.classList.contains("fc2-id-container") &&
            el.dataset.processed === "true"
          )
      )

      if (!itemElement) {
        debugLog(
          `未找到FC2 ID ${fc2Id} 对应的DOM元素或已被永久处理,跳过`,
          "FC2_QUEUE_SKIP_NO_ELEMENT"
        )
        fc2ItemRetryAttempts.delete(fc2Id)
        continue
      }

      if (
        fc2ItemsProcessed.has(fc2Id) &&
        itemElement.dataset.processed === "true"
      ) {
        debugLog(
          `FC2 ID ${fc2Id} 已被处理 (循环内检查),跳过`,
          "FC2_QUEUE_SKIP_PROCESSED"
        )
        fc2ItemRetryAttempts.delete(fc2Id)
        continue
      }

      const isCached = searchResultsCache[fc2Id] !== undefined
      const itemStatus = await processSingleFc2Item(fc2Id, itemElement)

      if (itemStatus === "RETRY_MAIN_QUEUE") {
        let attempts = fc2ItemRetryAttempts.get(fc2Id) || 0
        attempts++
        if (attempts <= MAX_FC2_ITEM_RETRIES) {
          fc2ItemRetryAttempts.set(fc2Id, attempts)
          debugLog(
            `FC2 ID ${fc2Id} 将在 ${FC2_ITEM_RETRY_DELAY_MS}ms 后重试 (尝试 ${attempts}/${MAX_FC2_ITEM_RETRIES})`,
            "FC2_QUEUE_RETRY_SCHEDULE"
          )
          setTimeout(() => {
            const currentElement = Array.from(
              document.querySelectorAll("div.relative span.top-0")
            ).find((el) => el.textContent.trim() === fc2Id)

            if (
              currentElement &&
              !fc2ItemsProcessed.has(fc2Id) &&
              currentElement.dataset.processed !== "true"
            ) {
              fc2ItemsToProcessQueue.add(fc2Id)
              debugLog(
                `FC2 ID ${fc2Id} 已重新加入队列进行重试`,
                "FC2_QUEUE_RETRY_ADD"
              )
              triggerFc2ItemsProcessing()
            } else {
              debugLog(
                `FC2 ID ${fc2Id} 在重试前已被处理或消失,取消重试`,
                "FC2_QUEUE_RETRY_CANCEL"
              )
              fc2ItemRetryAttempts.delete(fc2Id)
            }
          }, FC2_ITEM_RETRY_DELAY_MS)
        } else {
          debugLog(
            `FC2 ID ${fc2Id} 达到最大重试次数 (${MAX_FC2_ITEM_RETRIES}),标记为无资源`,
            "FC2_QUEUE_MAX_RETRIES"
          )
          fc2ItemRetryAttempts.delete(fc2Id)
          if (
            itemElement.parentElement &&
            itemElement.parentElement.classList.contains("fc2-id-container")
          ) {
            const statusSpan =
              itemElement.parentElement.querySelector(".search-status")
            if (statusSpan) {
              statusSpan.className = "search-status error"
              statusSpan.textContent = "重试失败"
              statusSpan.style.display = "block"
            }
          }
          searchResultsCache[fc2Id] = { result: false, timestamp: Date.now() }
          saveSearchResultCache(searchResultsCache)
          itemElement.dataset.processed = "true"
          fc2ItemsProcessed.add(fc2Id)
        }
      } else if (
        itemStatus === "SUCCESS" ||
        itemStatus === "NOT_FOUND" ||
        itemStatus === "FAILED"
      ) {
        fc2ItemRetryAttempts.delete(fc2Id)
        if (itemStatus === "FAILED" && !fc2ItemsProcessed.has(fc2Id)) {
          itemElement.dataset.processed = "true"
          fc2ItemsProcessed.add(fc2Id)
          debugLog(
            `FC2 ID ${fc2Id} 处理失败且不重试,标记为已处理`,
            "FC2_PROCESS_FAILED_FINAL"
          )
        }
      }

      const willBeRetriedViaTimeout =
        itemStatus === "RETRY_MAIN_QUEUE" &&
        (fc2ItemRetryAttempts.get(fc2Id) || 0) < MAX_FC2_ITEM_RETRIES

      const currentDelay = useProxy
        ? PROXY_BT4G_REQUEST_DELAY_MS
        : BT4G_REQUEST_DELAY_MS

      if (
        !isCached &&
        !willBeRetriedViaTimeout &&
        idsToProcessThisBatch.indexOf(fc2Id) < idsToProcessThisBatch.length - 1
      ) {
        debugLog(
          `[FC2 队列延迟] ID ${fc2Id} (未缓存, ${
            useProxy ? "代理" : "直连"
          }) 已处理。在处理下一项前延迟 ${currentDelay}ms。`,
          "FC2_DELAY"
        )
        await new Promise((resolve) => setTimeout(resolve, currentDelay))
      } else if (
        isCached &&
        !willBeRetriedViaTimeout &&
        idsToProcessThisBatch.indexOf(fc2Id) < idsToProcessThisBatch.length - 1
      ) {
        debugLog(
          `[FC2 队列缓存] ID ${fc2Id} (已缓存) 已处理。最小/无延迟。`,
          "FC2_DELAY_SKIP"
        )
      }
    }
  }

  function triggerFc2ItemsProcessing() {
    if (fc2ItemsToProcessQueue.size > 0 && !isProcessingFc2ItemsQueue) {
      debugLog("触发FC2项目队列处理", "FC2_QUEUE_TRIGGER")
      processFc2ItemsQueue()
    } else {
      if (isProcessingFc2ItemsQueue) {
        debugLog("FC2项目队列正在处理中,本次不启动新批次", "FC2_QUEUE_BUSY")
      }
      if (fc2ItemsToProcessQueue.size === 0) {
        debugLog("FC2项目队列为空,不启动处理", "FC2_QUEUE_EMPTY")
      }
    }
  }

  // DOM扫描与入队函数
  function scanAndEnqueueFc2Items() {
    if (!isScriptEnabled) {
      debugLog("脚本已停止运行,不再扫描新项目", "SCRIPT_DISABLED")
      return 0
    }

    const fc2IdElementSelector = "div.relative span.top-0"
    const fc2IdElements = document.querySelectorAll(fc2IdElementSelector)
    let newItemsEnqueuedCount = 0

    for (const element of fc2IdElements) {
      if (element.dataset.processed) continue
      if (
        element.parentElement &&
        element.parentElement.classList.contains("fc2-id-container")
      )
        continue

      const fc2IdText = element.textContent.trim()
      if (!/^\d+$/.test(fc2IdText)) continue

      if (fc2ItemsProcessed.has(fc2IdText)) {
        if (!element.dataset.processed) {
          element.dataset.processed = "true"
          debugLog(
            `标记已在fc2ItemsProcessed中的元素 ${fc2IdText} 为 processed`,
            "SCAN_ENQUEUE_MARK_PROCESSED"
          )
        }
        continue
      }

      if (!fc2ItemsToProcessQueue.has(fc2IdText)) {
        fc2ItemsToProcessQueue.add(fc2IdText)
        newItemsEnqueuedCount++
        debugLog(
          `FC2 ID ${fc2IdText} 加入处理队列,当前队列长度: ${fc2ItemsToProcessQueue.size}`,
          "SCAN_ENQUEUE_ADD"
        )
      }
    }

    if (newItemsEnqueuedCount > 0) {
      debugLog(
        `扫描完成,${newItemsEnqueuedCount} 个新FC2 ID加入队列`,
        "SCAN_ENQUEUE_DONE"
      )
      triggerFc2ItemsProcessing()
    } else if (fc2IdElements.length > 0) {
      // debugLog("扫描完成,未发现新的未处理FC2 ID", "SCAN_ENQUEUE_IDLE") // 此日志过于频繁,注释掉
    }
    return newItemsEnqueuedCount
  }

  // 强制刷新当前页面所有FC2编号的BT4G搜索(忽略缓存)
  function forceRefreshAllFc2Items() {
    debugLog("强制刷新所有FC2项目的BT4G搜索", "ACTION_FORCE_REFRESH")
    const fc2IdElementSelector = "div.relative span.top-0"
    const fc2IdElements = document.querySelectorAll(fc2IdElementSelector)
    debugLog(`找到 ${fc2IdElements.length} 个FC2元素需要刷新`, "REFRESH_START")

    let clearedCacheCount = 0
    fc2ItemRetryAttempts.clear() // 清空FC2项目重试计数
    debugLog("强制刷新:清空FC2项目重试计数", "REFRESH_CLEAR_RETRIES")
    fc2IdElements.forEach((element) => {
      const fc2IdText = element.textContent.trim()
      if (!/^\d+$/.test(fc2IdText)) return
      delete searchResultsCache[fc2IdText]
      clearedCacheCount++
      debugLog(`清除缓存: ${fc2IdText}`, "CACHE_CLEAR")
    })

    saveSearchResultCache(searchResultsCache)
    debugLog(`已清除 ${clearedCacheCount} 个缓存项`, "CACHE_CLEAR_DONE")

    let restoredElementsCount = 0
    fc2IdElements.forEach((element) => {
      if (
        element.parentElement &&
        element.parentElement.classList.contains("fc2-id-container")
      ) {
        const container = element.parentElement
        const parent = container.parentElement
        parent.replaceChild(element, container)
        restoredElementsCount++
      }
      element.removeAttribute("data-processed")

      const fc2IdText = element.textContent.trim()
      if (/^\d+$/.test(fc2IdText)) {
        fc2ItemsProcessed.delete(fc2IdText)
      }
    })

    debugLog(`已恢复 ${restoredElementsCount} 个元素原始结构`, "DOM_RESTORE")
    debouncedScanAndEnqueueFc2Items()
  }

  // 切换批量提交功能
  function toggleBatchSubmit() {
    batchSubmitEnabled = !batchSubmitEnabled
    GM_setValue("BATCH_SUBMIT_ENABLED", batchSubmitEnabled)
    alert(`批量提交功能已${batchSubmitEnabled ? "开启" : "关闭"}`)
  }

  // 注册菜单命令
  if (typeof GM_registerMenuCommand === "function") {
    GM_registerMenuCommand("强制刷新BT4G搜索", forceRefreshAllFc2Items)
    GM_registerMenuCommand("配置代理服务器", configureProxyServer)
    GM_registerMenuCommand("切换调试模式", toggleDebugMode)
    GM_registerMenuCommand("清除调试日志", clearDebugLogs)
    GM_registerMenuCommand("导出日志到剪贴板", exportLogsToClipboard)
    GM_registerMenuCommand("导出日志到文件", exportLogsToFile)
    GM_registerMenuCommand(
      `切换代理状态 (当前: ${useProxy ? "使用代理" : "直连BT4G"})`,
      toggleUseProxy
    )
    GM_registerMenuCommand(
      `切换批量提交 (当前: ${batchSubmitEnabled ? "开启" : "关闭"})`,
      toggleBatchSubmit
    )
  }

  const debouncedScanAndEnqueueFc2Items = debounce(() => {
    debugLog(
      "DOM变动或强制刷新触发 (debounced),开始扫描并入队FC2项目",
      "OBSERVER_SCAN_TRIGGER"
    )
    scanAndEnqueueFc2Items()
  }, 500)

  let observingBody = false // 跟踪是否正在观察document.body

  // MutationObserver配置
  const observer = new MutationObserver(mutationCallbackLogic)

  // MutationObserver的回调逻辑
  function mutationCallbackLogic(mutationsList, currentObserverInstance) {
    let isRelevantChangeDetected = false

    for (const mutation of mutationsList) {
      const target = mutation.target

      // 过滤器 1: 忽略在已处理容器内部发生的更改。
      // 这应该是初始设置后由脚本自身引起的更改的最常见情况。
      if (
        target.nodeType === Node.ELEMENT_NODE &&
        target.closest(".fc2-id-container")
      ) {
        // debugLog(`观察器: 忽略 .fc2-id-container 内部的变动。目标: ${target.tagName}, 类型: ${mutation.type}`, "OBSERVER_IGNORE_INTERNAL");
        continue // 忽略此变动,检查下一个
      }

      // 过滤器 2: 忽略脚本在FC2 ID元素上设置 'data-processed' 属性。
      if (
        mutation.type === "attributes" &&
        mutation.attributeName === "data-processed" &&
        target.nodeType === Node.ELEMENT_NODE &&
        target.matches("div.relative span.top-0") // FC2 ID 元素的选择器
      ) {
        // debugLog(`观察器: 忽略 'data-processed' 属性更改。目标: ${target.tagName}`, "OBSERVER_IGNORE_DATA_PROCESSED");
        continue // 忽略此变动
      }

      if (mutation.type === "childList") {
        // 过滤器 3: 忽略主容器的添加。
        const addedNodes = Array.from(mutation.addedNodes)
        if (
          addedNodes.some(
            (node) =>
              node.nodeType === Node.ELEMENT_NODE &&
              node.classList?.contains("fc2-id-container")
          )
        ) {
          // debugLog(`观察器: 忽略 .fc2-id-container 的添加。`, "OBSERVER_IGNORE_ADD_CONTAINER");
          continue // 忽略此变动
        }

        // 过滤器 4: 忽略我们刚刚标记为 'data-processed' 的FC2 ID span的移除 (可能是我们移动了它)。
        const removedNodes = Array.from(mutation.removedNodes)
        if (
          removedNodes.some(
            (node) =>
              node.nodeType === Node.ELEMENT_NODE &&
              node.matches("div.relative span.top-0") && // 是一个FC2 ID元素
              node.dataset.processed === "true" // 并且我们已经标记了它
          )
        ) {
          // debugLog(`观察器: 忽略一个 'data-processed' FC2 ID span 的移除 (可能正在被移动)。`, "OBSERVER_IGNORE_MOVE");
          continue // 忽略此变动
        }
      }

      // 如果以上过滤器都没有捕获到变动,则认为它是相关的。
      // debugLog(`观察器: 检测到相关变动。类型: ${mutation.type}, 目标: ${target.nodeName || 'text_node'}, 属性: ${mutation.attributeName || ''}`, "OBSERVER_RELEVANT_MUTATION");
      isRelevantChangeDetected = true
      break // 一个相关的变动就足够了
    }

    // 将观察器从 body 切换到 #writer-articles 的逻辑
    if (observingBody) {
      const targetContainer = document.getElementById("writer-articles")
      if (targetContainer) {
        debugLog(
          `检测到目标容器 writer-articles 出现,重新配置观察器`,
          "OBSERVER"
        )
        currentObserverInstance.disconnect() // 停止观察 document.body
        // 使用相同的观察器实例观察新目标
        observer.observe(targetContainer, {
          childList: true,
          subtree: true,
        })
        observingBody = false
        isRelevantChangeDetected = true // 主容器的出现是一个相关事件
      }
    }

    if (isRelevantChangeDetected) {
      // debugLog("观察器: 由于相关更改,调用 debouncedScanAndEnqueueFc2Items。", "OBSERVER_TRIGGER_SCAN");
      debouncedScanAndEnqueueFc2Items()
    } else {
      // debugLog("观察器: 过滤变动后未检测到相关更改。", "OBSERVER_NO_RELEVANT_CHANGES");
    }
  }

  // 启动观察器函数
  function startDomObserver() {
    const targetContainer = document.getElementById("writer-articles")
    if (targetContainer) {
      debugLog(`找到目标容器writer-articles,开始观察`, "OBSERVER_START_TARGET")
      observer.observe(targetContainer, {
        childList: true,
        subtree: true,
      })
      observingBody = false
    } else {
      debugLog(
        `未找到目标容器writer-articles,将观察整个 body`,
        "OBSERVER_START_BODY"
      )
      observer.observe(document.body, {
        childList: true,
        subtree: true,
      })
      observingBody = true
    }
  }
  // 启动观察器
  debugLog(`启动DOM观察器`, "INIT_OBSERVER")
  startDomObserver()
  // 初始调用
  debouncedScanAndEnqueueFc2Items()

  // 处理搜索页面跳转
  if (window.location.pathname === "/search") {
    debugLog(`检测到搜索页面`, "PAGE_SEARCH_DETECTED")
    const searchParams = new URLSearchParams(window.location.search)
    const searchQuery = searchParams.get("q")
    if (searchQuery) {
      debugLog(`搜索关键词: ${searchQuery}`, "PAGE_SEARCH_QUERY")
      checkBT4GAvailability(searchQuery).then((result) => {
        // result is an object {status: "..."}
        debugLog(
          `搜索关键词 ${searchQuery} 检查结果: ${
            result.status === "SUCCESS" ? "有资源" : "无资源或检查失败" // 更准确的日志
          }`,
          "PAGE_SEARCH_RESULT"
        )
        if (result.status === "SUCCESS") {
          const bt4gSearchUrl = `https://bt4gprx.com/search/${encodeURIComponent(
            searchQuery
          )}`
          debugLog(`自动跳转到: ${bt4gSearchUrl}`, "PAGE_SEARCH_REDIRECT")
          GM_openInTab(bt4gSearchUrl, { active: true })
        }
      })
      return // 结束脚本执行,因为已在搜索页面处理
    }
  }

  // 将需要从控制台调用的函数暴露到window对象
  if (DEBUG_MODE) {
    const targetWindow =
      typeof unsafeWindow !== "undefined" ? unsafeWindow : window
    targetWindow.fc2EnhancedSearch = {
      removeCacheById: removeSearchResultCacheById,
      getCache: () => searchResultsCache,
      getDebugLogs: () => debugLogs,
      clearDebugLogs: clearDebugLogs,
      toggleDebugMode: toggleDebugMode,
      forceRefreshBT4G: forceRefreshAllFc2Items,
      getFc2ItemProcessQueue: () => Array.from(fc2ItemsToProcessQueue),
      processFc2ItemQueueAsync: processFc2ItemsQueue,
      getFc2ItemRetryAttempts: () =>
        Object.fromEntries(fc2ItemRetryAttempts.entries()), // 用于调试重试
      testProxyServer: testProxyServer, // 新增调试方法
      getCurrentProxyUrl: () => PROXY_SERVER_URL, // 新增调试方法
      reenableScript: () => { // 修改重新启用脚本的功能
        isScriptEnabled = true
        consecutiveRequestErrors = 0
        debugLog("脚本已重新启用", "SCRIPT_REENABLED")
        startDomObserver() // 重新启动观察器
        debouncedScanAndEnqueueFc2Items() // 重新开始扫描
      },
      isScriptEnabled: () => isScriptEnabled // 新增检查脚本状态的功能
    }
    debugLog(
      `调试函数已挂载到 ${
        typeof unsafeWindow !== "undefined" ? "unsafeWindow" : "window"
      }.fc2EnhancedSearch`,
      "INIT"
    )
  }

  // 预览功能相关函数
  function showPreview(fc2Id) {
    const previewUrlHost = "https://baihuse.com"
    const previewUrl_Page = previewUrlHost + "/fc2daily/detail/FC2-PPV-" + fc2Id

    // 创建对话框和遮罩
    let dialog = document.querySelector('.preview-dialog')
    let overlay = document.querySelector('.preview-dialog-overlay')
    
    if (!dialog) {
      dialog = document.createElement('div')
      dialog.className = 'preview-dialog'
      document.body.appendChild(dialog)
      
      const closeButton = document.createElement('button')
      closeButton.className = 'preview-dialog-close'
      closeButton.textContent = '×'
      closeButton.onclick = () => {
        dialog.classList.remove('active')
        overlay.classList.remove('active')
      }
      dialog.appendChild(closeButton)
      
      const content = document.createElement('div')
      content.className = 'preview-dialog-content'
      dialog.appendChild(content)
    }
    
    if (!overlay) {
      overlay = document.createElement('div')
      overlay.className = 'preview-dialog-overlay'
      overlay.onclick = () => {
        dialog.classList.remove('active')
        overlay.classList.remove('active')
      }
      document.body.appendChild(overlay)
    }

    const content = dialog.querySelector('.preview-dialog-content')
    content.innerHTML = '<div style="text-align: center; color: white;">加载中...</div>'
    
    dialog.classList.add('active')
    overlay.classList.add('active')

    GM_xmlhttpRequest({
      method: "GET",
      url: previewUrl_Page,
      onload: (response) => {
        if (response.status === 200) {
          const parser = new DOMParser()
          const doc = parser.parseFromString(response.responseText, "text/html")
          const images = doc.querySelectorAll('img')
          const videos = doc.querySelectorAll('video')
          
          if (images.length < 2 && videos.length === 0) {
            content.innerHTML = '<div style="text-align: center; color: white;">暂无预览</div>'
            return
          }

          content.innerHTML = ''
          
          // 处理图片
          for (let i = 1; i < images.length - 1; i++) {
            const path = new URL(images[i].src).pathname
            const imgSrc = previewUrlHost + path
            
            const img = document.createElement('img')
            img.src = imgSrc
            img.style.width = '100%'
            img.style.height = 'auto'
            img.style.marginBottom = '10px'
            content.appendChild(img)
          }

          // 处理视频
          videos.forEach(v => {
            const path = new URL(v.src).pathname
            const videoSrc = previewUrlHost + path
            
            const video = document.createElement('video')
            video.src = videoSrc
            video.loop = true
            video.muted = true
            video.controls = true
            video.style.width = '100%'
            video.style.marginBottom = '10px'
            content.appendChild(video)
          })
        } else {
          content.innerHTML = '<div style="text-align: center; color: white;">加载失败</div>'
        }
      },
      onerror: () => {
        content.innerHTML = '<div style="text-align: center; color: white;">加载失败</div>'
      }
    })
  }
})()