Greasy Fork

来自缓存

Greasy Fork is available in English.

我堡不可说自动查重

我堡不可说站点严格查重

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         我堡不可说自动查重
// @namespace    http://tampermonkey.net/
// @version      1.0.4
// @author       guyuanwind
// @description  我堡不可说站点严格查重
// @match        *://*/details.php*
// @match        https://*.m-team.cc/detail/*
// @match        https://*.m-team.io/detail/*
// @match        https://*.m-team.vip/detail/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *.m-team.cc
// @connect      *.m-team.io
// @connect      *.m-team.vip
// @connect      ourbits.club
// @connect      springsunday.net
// @run-at       document-idle
// @license      GPL-3.0
// @noframes
// ==/UserScript==


/* eslint-env browser */
(function () {
  'use strict';

  // GM 别名
  const GMxhr = GM_xmlhttpRequest;
  const GMset = GM_setValue;
  const GMget = GM_getValue;

  // =================================================================
  // ==================== 站点禁止规则配置 =======================
  // =================================================================
  const BANNED_RESOURCES_RULES = {
    nvme: { // "不可说"
      generalGroups: [ 'fgt', 'nsbc', 'batweb', 'gpthd', 'dreamhd', 'blacktv', 'catweb', 'xiaomi', 'huawei', 'momoweb', 'ddhdtv', 'seeweb', 'tagweb', 'sonyhd', 'minihd', 'bitstv', 'ctrlhd', 'alt', 'nukehd', 'zerotv', 'hottv', 'enttv', 'gamehd', 'parkhd', 'xunlei', 'bestweb', 'tbmaxub', 'smy', 'seehd', 'verypsp', 'dwr', 'xlmv', 'xjctv', 'mp4ba', '13city', 'goddramas', 'toothless', 'ytsmx', 'frds', 'beitai', 'vcb', 'ubits', 'ubweb' ],
      partialGroups: { general: ['ying'] },
      typeSpecific: {
        encode: ['hdh', 'hds', 'eleph', 'dream', 'bmdru'],
        diy: ['hdhome', 'hdsky'],
        remux: ['dream', 'hdh', 'hds', 'dyz-movie'],
        'web-dl': ['hdh', 'hds']
      },
      bannedTypes: []
    },
    ourbits: { // "我堡"
      generalGroups: ['frds'],
      partialGroups: { general: [] },
      typeSpecific: {},
      bannedTypes: ['remux']
    }
  };

  // 工具
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
  const text = (el) => (el?.textContent || '').trim();

  function mergePreferRicher(items) {
    const map = new Map();
    for (const it of items) {
      const key = `${it.link}__${it.title}`;
      if (!map.has(key)) { map.set(key, it); continue; }
      const old = map.get(key);
      const oldScore = (old.subtitle || '').length + (old.tags?.length || 0) * 10;
      const newScore = (it.subtitle || '').length + (it.tags?.length || 0) * 10;
      if (newScore > oldScore) map.set(key, it);
    }
    return Array.from(map.values());
  }

  // 内置站点
  const SITES = [
    { key: 'ourbits', name: '我堡', icon: 'https://icon.xiaoge.org/images/pt/OurBits.png', baseUrl: 'https://ourbits.club' },
    { key: 'nvme', name: '不可说', icon: 'https://icon.xiaoge.org/images/pt/Nvme.png', baseUrl: 'https://springsunday.net' }
  ];
  const DOMAIN_TO_KEY = {
    'ourbits.club': 'ourbits',
    'springsunday.net': 'nvme',
    'hhanclub.top': 'hhc',
    'audiences.me': 'aud',
    // MTeam站点域名映射
    'kp.m-team.cc': 'mteam',
    'pt.m-team.cc': 'mteam',
    'tp.m-team.cc': 'mteam',
    'xp.m-team.cc': 'mteam',
    'm-team.cc': 'mteam',
    'm-team.io': 'mteam',
    'm-team.vip': 'mteam'
  };
  const SEARCH_PATH = '/torrents.php';
  const KV = {
    ENABLE_PREFIX: 'xs_enable_',
    COOKIE_PREFIX: 'xs_cookie_',
    AUTO_QUERY: 'xs_auto_query'
  };
  const DEFAULT_ENABLED = { ourbits: true, nvme: true };
  const MAX_PAGES = 5;
  const PARALLEL = 2;

  // 自动查询开关相关函数
  function getAutoQueryEnabled() {
    return GMget(KV.AUTO_QUERY, true); // 默认开启自动查询
  }

  function setAutoQueryEnabled(enabled) {
    GMset(KV.AUTO_QUERY, enabled);
  }

  function applySearchParams(u, siteKey, searchArea = '0') {
    u.searchParams.set('spstate', '0');
    u.searchParams.set('inclbookmarked', '0');
    u.searchParams.set('search_area', searchArea);
    u.searchParams.set('search_mode', '0');
    if (siteKey === 'nvme') {
      u.searchParams.set('incldead', '0');
    } else {
      u.searchParams.set('incldead', '1');
    }
  }

  if (window.__xsInjected) return;
  window.__xsInjected = true;

  // 样式
  const style = document.createElement('style');
  style.textContent = `
  .xs-panel{position:fixed;top:12px;right:12px;width:clamp(360px,45vw,600px);max-width:55vw;background:#fff;border-radius:12px;box-shadow:0 12px 36px rgba(0,0,0,.18);z-index:2147483647;border:1px solid rgba(0,0,0,.08);overflow:hidden;max-height:85vh;overflow-y:auto;display:none}
  .xs-panel.show{display:block}
  .xs-toggle-btn{position:fixed;right:16px;top:50%;transform:translateY(-50%);width:72px;height:72px;background:#111;color:#fff;border:none;border-radius:12px;cursor:pointer;z-index:2147483646;box-shadow:-2px 2px 12px rgba(0,0,0,.25);display:flex;align-items:center;justify-content:center;padding:8px;transition:all 0.3s ease;overflow:hidden}
  .xs-toggle-btn img{width:100%;height:100%;object-fit:contain;border-radius:6px}
  .xs-toggle-btn:hover{background:#333;width:78px}
  .xs-toggle-btn.active{background:#4CAF50}
  .xs-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:#111;color:#fff}
  .xs-close{background:transparent;color:#fff;border:0;font-size:18px;cursor:pointer}
  .xs-section{border-bottom:1px solid #eee}
  .xs-section .hd{padding:8px 10px;font-weight:600;background:#fafafa}
  .xs-body{padding:10px}
  .xs-sites{display:flex;flex-direction:column;gap:8px}
  .xs-site{border:1px solid #eee;border-radius:10px;padding:8px;display:grid;grid-template-columns:48px 1fr auto;gap:8px;align-items:center}
  .xs-site .logo{width:36px;height:36px;border-radius:8px;object-fit:contain;background:#fff;border:1px solid #eee}
  .xs-site .meta{display:flex;flex-direction:column;gap:3px}
  .xs-site .meta .name{font-weight:600}
  .xs-site .meta .status{font-size:12px;color:#666}
  .xs-site .ops{display:flex;gap:6px;align-items:center}
  .xs-btn{padding:5px 8px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer;font-size:12px}
  .xs-btn.ghost{background:#666}
  .xs-cookie-input{width:100%;padding:7px 9px;border:1px solid #ddd;border-radius:8px;outline:none;font-family:monospace}
  .xs-search{display:flex;gap:6px}
  .xs-input{flex:1;padding:7px 9px;border:1px solid #ddd;border-radius:8px;outline:none}
  .xs-go{padding:7px 10px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer}
  .xs-res{max-height:56vh;overflow:auto;padding-bottom:10px}
  .xs-msg{padding:10px;background:#f7f7f7;border:1px dashed #ddd;border-radius:8px;margin:8px 0}
  .xs-err{background:#fff5f5;border-color:#ffd0d0;color:#8a1f1f}
  .xs-group{border:1px solid #eee;border-radius:10px;margin-top:8px}
  .xs-group-hd{padding:7px 9px;background:#fafafa;font-weight:600;display:flex;align-items:center;justify-content:space-between}
  .xs-group-bd{padding:8px}
  .xs-card{padding:8px;border:1px solid #eee;border-radius:10px;margin-top:8px;word-wrap:break-word;overflow-wrap:break-word}
  .xs-title{font-weight:600;text-decoration:none;color:#111}
  .xs-sub{color:#444;margin-top:6px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
  .xs-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
  .xs-tag{font-size:12px;padding:2px 6px;border-radius:999px;border:1px solid #ddd;background:#fafafa}
  details summary { cursor: pointer; font-weight: bold; margin: 4px 0; }
  .item-type { font-size: 10px; color: #fff; background-color: #28a745; padding: 2px 6px; border-radius: 4px; margin-left: 8px; vertical-align: middle; font-weight: bold; }
  .item-type.remux { background-color: #6f42c1; }
  .item-type.webdl { background-color: #007bff; }
  .item-type.diy { background-color: #fd7e14; }
  .item-type.original { background-color: #dc3545; }
  .item-type.encode { background-color: #20c997; }
  .xs-auto-setting{display:flex;flex-direction:column;gap:12px}
  .xs-switch-label{display:flex;justify-content:space-between;align-items:center;font-size:14px;font-weight:600}
  .xs-switch{position:relative;display:inline-block;width:44px;height:22px}
  .xs-switch input{opacity:0;width:0;height:0}
  .xs-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ddd;border-radius:22px;transition:all 0.3s ease}
  .xs-slider:before{position:absolute;content:"";height:18px;width:18px;left:2px;top:2px;background-color:white;border-radius:50%;transition:all 0.3s ease;box-shadow:0 2px 4px rgba(0,0,0,0.2)}
  input:checked + .xs-slider{background-color:#4CAF50}
  input:checked + .xs-slider:before{transform:translateX(22px)}
  .xs-manual-btn{background:#4CAF50;color:white;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px;width:100%}
  .xs-manual-btn:hover{background:#45a049}
  `;
  document.documentElement.appendChild(style);

  // 创建悬浮切换按钮
  const toggleBtn = document.createElement('button');
  toggleBtn.className = 'xs-toggle-btn';
  toggleBtn.innerHTML = '<img src="https://icon.xiaoge.org/images/pt/OurBits.png" alt="查重">';
  toggleBtn.title = '查重面板';
  document.documentElement.appendChild(toggleBtn);

  const panel = document.createElement('div');
  panel.className = 'xs-panel';
  panel.innerHTML = `
    <div class="xs-head"><strong>自动查重与规则校验</strong><button class="xs-close" title="关闭">×</button></div>
    <div class="xs-section">
      <div class="hd">查询设置</div>
      <div class="xs-body">
        <div class="xs-auto-setting">
          <label class="xs-switch-label">
            <span>自动查询重复</span>
            <div class="xs-switch">
              <input type="checkbox" class="xs-auto-query-toggle" ${getAutoQueryEnabled() ? 'checked' : ''}>
              <span class="xs-slider"></span>
            </div>
          </label>
          <div class="xs-manual-query" style="display: ${getAutoQueryEnabled() ? 'none' : 'block'};">
            <button class="xs-manual-btn">手动查询本详情页重复</button>
          </div>
        </div>
      </div>
    </div>
    <div class="xs-section"><div class="hd">站点配置</div><div class="xs-body"><div class="xs-sites"></div></div></div>
    <div class="xs-section"><div class="hd">手动搜索</div><div class="xs-body"><div class="xs-search"><input class="xs-input xs-q" placeholder="输入关键字"><button class="xs-go">搜索</button></div><div class="xs-res"></div></div></div>`;
  document.documentElement.appendChild(panel);

  // 切换面板显示/隐藏
  const togglePanel = () => {
    const isShown = panel.classList.toggle('show');
    toggleBtn.classList.toggle('active', isShown);
    console.log(`[查重] 面板${isShown ? '显示' : '隐藏'}`);
  };

  toggleBtn.addEventListener('click', togglePanel);

  const qInput = panel.querySelector('.xs-q');
  panel.querySelector('.xs-close').addEventListener('click', togglePanel);
  qInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') panel.querySelector('.xs-go').click(); });

  // 自动查询开关事件监听器
  const autoToggle = panel.querySelector('.xs-auto-query-toggle');
  const manualQueryDiv = panel.querySelector('.xs-manual-query');
  const manualBtn = panel.querySelector('.xs-manual-btn');

  autoToggle.addEventListener('change', async () => {
    const enabled = autoToggle.checked;
    setAutoQueryEnabled(enabled);
    manualQueryDiv.style.display = enabled ? 'none' : 'block';
    console.log(`[查重] 自动查询已${enabled ? '开启' : '关闭'}`);

    // 如果用户切换到自动查询,立即执行当前页面的查重
    if (enabled) {
      console.log('[查重] 开启自动查询,立即执行当前页面查重...');
      try {
        await performDupeCheck();
      } catch (error) {
        console.error('[查重] 自动执行查重失败:', error);
      }
    }
  });

  // 手动查询按钮事件监听器
  manualBtn.addEventListener('click', async () => {
    console.log('[查重] 用户点击手动查询按钮');
    manualBtn.disabled = true;
    manualBtn.textContent = '查询中...';

    try {
      await performDupeCheck();
    } catch (error) {
      console.error('[查重] 手动查询失败:', error);
    } finally {
      manualBtn.disabled = false;
      manualBtn.textContent = '手动查询本详情页重复';
    }
  });

  const getEnabled = (k) => GMget(KV.ENABLE_PREFIX + k, DEFAULT_ENABLED[k] ?? false);
  const setEnabled = (k, v) => GMset(KV.ENABLE_PREFIX + k, !!v);
  const getCookie = (k) => GMget(KV.COOKIE_PREFIX + k, '');
  const setCookie = (k, v) => GMset(KV.COOKIE_PREFIX + k, v || '');

  function renderSites() {
    const sitesWrap = panel.querySelector('.xs-sites');
    sitesWrap.innerHTML = SITES.map(s => {
      const enabled = getEnabled(s.key);
      const cookie = getCookie(s.key);
      return `
        <div class="xs-site" data-key="${s.key}">
          <img class="logo" src="${s.icon}" alt="${esc(s.name)}">
          <div class="meta">
            <div class="name">${esc(s.name)}</div>
            <div class="status">${cookie ? 'Cookie:已设置' : 'Cookie:未设置'}</div>
            ${cookie ? '' : `<input class="xs-cookie-input" placeholder="粘贴 ${esc(s.name)} 的 document.cookie">`}
          </div>
          <div class="ops">
            <span class="xs-switch ${enabled ? 'on' : ''}" data-action="toggle" title="启用/禁用"></span>
            ${cookie ? `<button class="xs-btn ghost" data-action="reset">重置Cookie</button>` : `<button class="xs-btn" data-action="save">保存Cookie</button>`}
          </div>
        </div>`;
    }).join('');
    sitesWrap.addEventListener('click', (e) => {
      const target = e.target;
      const action = target.dataset.action;
      const card = target.closest('.xs-site');
      if (!action || !card) return;
      const k = card.dataset.key;
      if (action === 'toggle') {
        setEnabled(k, !target.classList.contains('on'));
        target.classList.toggle('on');
      } else if (action === 'save') {
        const v = (card.querySelector('.xs-cookie-input').value || '').trim();
        if (!v) return alert('请粘贴完整的 document.cookie');
        setCookie(k, v);
        renderSites();
      } else if (action === 'reset') {
        if (!confirm('确认清除此站 Cookie?')) return;
        setCookie(k, '');
        renderSites();
      }
    });
  }
  renderSites();

  panel.querySelector('.xs-go').addEventListener('click', async () => {
    const q = qInput.value.trim();
    const resEl = panel.querySelector('.xs-res');
    if (!q) {
      resEl.innerHTML = `<div class="xs-msg">请输入关键词</div>`;
      return;
    }
    const actives = SITES.filter((s) => getEnabled(s.key));
    if (!actives.length) {
      resEl.innerHTML = `<div class="xs-msg">请先开启至少一个站点开关</div>`;
      return;
    }
    resEl.innerHTML = `<div class="xs-msg">正在搜索…</div>`;
    try {
      const groups = await Promise.all(actives.map((s) => searchOneSite(s, q)));
      resEl.innerHTML = '';
      let total = 0;
      for (const g of groups) {
        total += g.items.length;
        renderGroup(g);
      }
      resEl.insertAdjacentHTML('afterbegin', `<div class="xs-msg">合计 ${total} 条(${groups.length} 个站点)</div>`);
    } catch (e) {
      resEl.innerHTML = `<div class="xs-msg xs-err">搜索失败:${esc(e?.message || e)}</div>`;
    }
  });

  async function searchOneSite(site, q, searchArea = '0') {
    const cookie = getCookie(site.key);
    if (!cookie) return { site, items: [], error: '未设置 Cookie' };
    const firstUrl = buildUrl(site.baseUrl, q, site.key, searchArea);
    
    // 【调试日志】打印搜索信息
    const searchTypeLabel = searchArea === '4' ? 'IMDb链接' : searchArea === '0' ? '标题' : '其他';
    console.log(`[查重] ${site.name} - 执行搜索: 关键词="${q}", 范围=${searchTypeLabel}(${searchArea}), URL=${firstUrl}`);
    
    const firstDoc = await fetchDocWithCookie(firstUrl, cookie, site.baseUrl);
    let items = extractBySite(firstDoc, site);
    
    console.log(`[查重] ${site.name} - 第1页提取到 ${items.length} 条结果`);
    
    const pages = collectPageUrls(firstDoc, firstUrl, site.baseUrl, MAX_PAGES - 1);
    if (pages.length > 0) {
      const docs = await fetchBatch(pages, PARALLEL, (u) => fetchDocWithCookie(u, cookie, site.baseUrl));
      for (const d of docs) {
        items = items.concat(extractBySite(d, site));
      }
    }
    items = mergePreferRicher(items);
    
    console.log(`[查重] ${site.name} - 搜索完成,共提取到 ${items.length} 条结果`);
    
    return { site, items, error: null };
  }

  function buildUrl(base, kw, key, searchArea = '0') {
    const u = new URL(SEARCH_PATH, base);
    applySearchParams(u, key, searchArea);
    u.searchParams.set('search', kw);
    return u.toString();
  }

  function fetchDocWithCookie(url, cookie, base) {
    return new Promise((resolve, reject) => {
      GMxhr({
        method: 'GET',
        url,
        headers: { Accept: 'text/html', Cookie: cookie, Referer: base },
        onload: (r) => {
          if (r.status >= 200 && r.status < 300) {
            resolve(new DOMParser().parseFromString(r.responseText, 'text/html'));
          } else {
            reject(new Error(`HTTP ${r.status}`));
          }
        },
        onerror: () => reject(new Error('网络或跨域被拦截'))
      });
    });
  }

  async function fetchBatch(urls, parallel, fn) {
    const out = [];
    for (let i = 0; i < urls.length; i += parallel) {
      const slice = urls.slice(i, i + parallel);
      const results = await Promise.allSettled(slice.map(fn));
      for (const x of results) {
        if (x.status === 'fulfilled') out.push(x.value);
      }
    }
    return out;
  }

  function collectPageUrls(doc, baseUrl, baseSite, maxCount) {
    const out = new Map();
    const base = new URL(baseUrl);
    doc.querySelectorAll('a[href*="torrents.php"]').forEach(a => {
      const href = a.getAttribute('href');
      if (!href) return;
      try {
        const u = new URL(href, baseSite);
        if (u.pathname === base.pathname && u.searchParams.get('search') === base.searchParams.get('search')) {
          const page = parseInt(u.searchParams.get('page'), 10);
          if (page > 0) out.set(u.toString(), page);
        }
      } catch (e) {}
    });
    return Array.from(out.keys()).sort((a,b) => out.get(a) - out.get(b)).slice(0, maxCount);
  }

  function extractBySite(doc, site) {
    const host = new URL(site.baseUrl).host;
    const key = DOMAIN_TO_KEY[host] || site.key;
    if (key === 'nvme') return extractSSD(doc, site.baseUrl);
    if (key === 'ourbits') return extractOurbits(doc, site.baseUrl);
    // 观众和HHC的搜索结果页结构未知,暂不实现
    return [];
  }

  function extractSSD(doc, base) {
    const items = [];
    doc.querySelectorAll('table.torrentname').forEach(table => {
      const titleLink = table.querySelector('a[href^="details.php"]');
      if (!titleLink) return;
      const href = titleLink.getAttribute('href') || '';
      if (/dllist=1#seeders/.test(href)) return;
      const link = new URL(href, base).toString();
      const title = text(titleLink);
      const subtitle = text(table.querySelector('.torrent-smalldescr > span:last-child'));
      // 修复标签提取逻辑:从内层span提取标签文本
      let tags = Array.from(table.querySelectorAll('.torrent-smalldescr > a > span.torrent-tag'))
          .map(s => {
            // 尝试从内层span获取标签文本,如果没有则使用外层span的文本
            const innerSpan = s.querySelector('span');
            return text(innerSpan || s);
          });
      tags = enhanceTagsFromText(tags, `${title} ${subtitle}`);
      const mainTitleInfo = extractMainTitle(title);
      const subtitleTitleInfo = extractSubtitleTitle(subtitle);
      // 季号优先从主标题提取,如果主标题没有则从副标题提取
      const season = mainTitleInfo.season || subtitleTitleInfo.season;
      // 年份提取(优先从主标题,其次副标题)
      const year = extractYear(title) || extractYear(subtitle);
      console.log(`[查重] 不可说搜索结果季号提取: "${title}" + "${subtitle}" -> 主标题季号=${mainTitleInfo.season || 'null'}, 副标题季号=${subtitleTitleInfo.season || 'null'}, 最终季号=${season || 'null'}, 年份=${year || 'null'}`);
      items.push({ 
        title, 
        subtitle, 
        tags, 
        link, 
        mainTitle: mainTitleInfo.title, 
        subtitleTitle: subtitleTitleInfo.title, 
        season: season,
        year: year,
        siteKey: 'nvme' 
      });
    });
    return items;
  }

  function extractOurbits(doc, base) {
    const items = [];
    doc.querySelectorAll('#torrenttable tr.sticky_blank td.embedded').forEach(cell => {
      const titleLink = cell.querySelector('a[href^="details.php?id="]');
      if (!titleLink) return;
      const link = new URL(titleLink.getAttribute('href') || '', base).toString();
      const title = text(titleLink);
      let subtitle = '';
      const br = cell.querySelector('br');
      if (br) {
        let currentNode = br.nextSibling;
        while (currentNode) {
            if (currentNode.nodeType === Node.TEXT_NODE) { // 只拼接纯文本节点
                subtitle += currentNode.textContent;
            }
            currentNode = currentNode.nextSibling;
        }
        subtitle = subtitle.replace(/^[\s\|]+|[\s\|]+$/g, '').trim(); // 清理首尾的 | 和空格
      }
      let tags = Array.from(cell.querySelectorAll('span.tag')).map(s => text(s));
      tags = enhanceTagsFromText(tags, `${title} ${subtitle}`);
      const mainTitleInfo = extractMainTitle(title);
      const subtitleTitleInfo = extractSubtitleTitle(subtitle);
      // 季号优先从主标题提取,如果主标题没有则从副标题提取
      const season = mainTitleInfo.season || subtitleTitleInfo.season;
      // 年份提取(优先从主标题,其次副标题)
      const year = extractYear(title) || extractYear(subtitle);
      console.log(`[查重] 我堡搜索结果季号提取: "${title}" + "${subtitle}" -> 主标题季号=${mainTitleInfo.season || 'null'}, 副标题季号=${subtitleTitleInfo.season || 'null'}, 最终季号=${season || 'null'}, 年份=${year || 'null'}`);
      items.push({ 
        title, 
        subtitle, 
        tags, 
        link, 
        mainTitle: mainTitleInfo.title, 
        subtitleTitle: subtitleTitleInfo.title, 
        season: season,
        year: year,
        siteKey: 'ourbits' 
      });
    });
    return items;
  }

  function renderGroup(g) {
    const resEl = panel.querySelector('.xs-res');
    const box = document.createElement('div');
    box.className = 'xs-group';
    box.innerHTML = `<div class="xs-group-hd"><span>${esc(g.site.name)}</span><span>共 ${g.items.length} 条</span></div><div class="xs-group-bd"></div>`;
    const body = box.querySelector('.xs-group-bd');
    if (!g.items.length) {
      body.innerHTML = `<div class="xs-msg">未找到相关条目${g.error ? `(${esc(g.error)})` : ''}</div>`;
    } else {
      body.innerHTML = g.items.map(it => {
        const itemType = getItemType(it);
        const typeClass = getItemTypeClass(itemType);
        const seasonInfo = it.season ? `<span class="season-info" style="color: #666; font-size: 12px; margin-left: 8px;">[${it.season}]</span>` : '';
        const yearInfo = it.year ? `<span class="year-info" style="color: #999; font-size: 12px; margin-left: 4px;">[${it.year}]</span>` : '';
        return `
        <div class="xs-card">
          <div style="display: flex; align-items: flex-start; margin-bottom: 4px; gap: 8px;">
            <a class="xs-title" href="${it.link}" target="_blank" rel="noopener noreferrer" style="flex: 1;">${esc(it.title || '(无主标题)')}${seasonInfo}${yearInfo}</a>
            <span class="item-type ${typeClass}" style="flex-shrink: 0;">${esc(itemType)}</span>
          </div>
          ${it.subtitle ? `<div class="xs-sub">${esc(it.subtitle)}</div>` : ''}
          ${it.tags && it.tags.length ? `<div class="xs-tags">${it.tags.map(t => `<span class="xs-tag">${esc(t)}</span>`).join('')}</div>` : ''}
        </div>`;
      }).join('');
    }
    resEl.appendChild(box);
  }

  function extractReleaseGroup(title) {
    if (!title || !title.includes('-')) return null;
    const parts = title.split('-');
    let group = parts.pop().trim();
    if (!group) return null;
    
    // 移除方括号及其后的所有内容(如 [50%] 剩余时间:3天20时)
    const bracketIndex = group.indexOf('[');
    if (bracketIndex !== -1) {
      group = group.substring(0, bracketIndex).trim();
    }
    
    if (!group) return null;
    const commonNonGroups = ['web', 'dl', 'web-dl', 'remux', 'blu-ray', 'bluray'];
    if (commonNonGroups.includes(group.toLowerCase())) return null;
    return group;
  }

  function extractMainTitle(title) {
    if (!title) return { title: '', season: null, fullText: '' };
    
    let mainTitle = title;
    
    // 1. 先提取季号信息(用于后续处理)
    const season = extractSeason(title);
    
    // 2. 移除结尾的方括号标签(如 [免费][一般资源] 等)
    mainTitle = mainTitle.replace(/\s*\[[^\]]+\]\s*/g, '');
    
    // 3. 【核心改进】先找到分辨率的位置,只在分辨率之前提取影片名
    // PT站点标题格式通常是:影片名 年份 分辨率 来源 编码 音频-制作组
    const resolutionPattern = /\b(4320p|2160p|1080p|1080i|720p|576p|576i|480p|480i|4K|8K|SD)\b/i;
    const resolutionMatch = mainTitle.match(resolutionPattern);
    
    if (resolutionMatch) {
      // 如果找到分辨率,只保留分辨率之前的内容
      mainTitle = mainTitle.substring(0, resolutionMatch.index).trim();
      console.log(`[查重] 主标题提取: 找到分辨率"${resolutionMatch[0]}",提取前面的内容: "${mainTitle}"`);
    } else {
      // 如果没有分辨率标记,尝试找到其他明显的技术参数起始位置
      const fallbackPattern = /(WEB-DL|WEBDL|WEBRip|BluRay|Blu-ray|HDTV|Remux|x264|x265|H\.264|H\.265|HEVC)/i;
      const fallbackMatch = mainTitle.match(fallbackPattern);
      if (fallbackMatch) {
        mainTitle = mainTitle.substring(0, fallbackMatch.index).trim();
        console.log(`[查重] 主标题提取: 未找到分辨率,但找到技术标识"${fallbackMatch[0]}",提取前面的内容: "${mainTitle}"`);
      }
    }
    
    // 4. 移除季号和集数信息
    const seasonPatterns = [
      /S\d{1,2}E\d+-?E?\d*/gi,                // S01E01-E03, S01E01
      /S\d{1,2}-S\d{1,2}/gi,                  // S01-S05
      /S\d{1,2}/gi,                           // S01, S08
      /第\d{1,2}季/g,                         // 第1季, 第8季
      /Season\s*\d{1,2}/gi,                   // Season 1, Season 8
      /\d{1,2}(?:st|nd|rd|th)\s*Season/gi,    // 1st Season
      /\d{1,2}x\d+/gi,                        // 1x01
    ];
    
    for (const pattern of seasonPatterns) {
      mainTitle = mainTitle.replace(pattern, '');
    }
    
    // 5. 移除年份(通常紧跟在影片名后面)
    mainTitle = mainTitle.replace(/\s+\b(19|20)\d{2}\b/g, '');
    
    // 6. 移除版本信息
    const versionPatterns = [
      /\s+(Director'?s?\s*Cut|Extended\s*Edition|Uncut|International\s*Version)/gi,
      /\s+(Limited\s*Edition|Special\s*Edition|Anniversary\s*Edition)/gi,
      /\s+(Remaster|4K\s*Remaster|IMAX|Open\s*Matte)/gi,
      /\s+(PROPER|REPACK|COMPLETE)/gi,
    ];
    
    for (const pattern of versionPatterns) {
      mainTitle = mainTitle.replace(pattern, '');
    }
    
    // 7. 移除开头的类型标识
    mainTitle = mainTitle.replace(/^(美剧|英剧|韩剧|日剧|电影|纪录片)[::]\s*/, '');
    
    // 8. 移除罗马数字(通常表示系列、章节等)
    // 罗马数字:Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ
    mainTitle = mainTitle.replace(/\s*[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+\s*$/i, '');
    
    // 9. 清理多余空格和标点
    mainTitle = mainTitle.replace(/\s+/g, ' ').trim();
    mainTitle = mainTitle.replace(/[-\s.]+$/, '').trim();
    mainTitle = mainTitle.replace(/^[-\s.]+/, '').trim();
    
    // 10. 额外清理:移除可能遗留的单个数字或短标识符
    mainTitle = mainTitle.replace(/\s+\d{1,2}$/, ''); // 移除结尾的单独数字
    mainTitle = mainTitle.replace(/\s+[A-Z]{1,3}$/, ''); // 移除结尾的短大写字母组合
    
    console.log(`[查重] 主标题提取: "${title}" -> 标题: "${mainTitle}", 季号: ${season || 'null'}`);
    
    return {
      title: mainTitle.trim(),
      season: season,
      fullText: title
    };
  }

  function extractSubtitleTitle(subtitle) {
    if (!subtitle) return { title: '', season: null, fullText: '' };
    
    let subtitleTitle = subtitle;
    
    // 1. 先提取季号信息
    const season = extractSeason(subtitle);
    
    // 2. 副标题通常包含中文译名、其他语言名称等,按分隔符分割取主要部分
    // 常见分隔符:| / [ ] * 「 」 ● ★ 丨(优先按主要分隔符分割)
    const delimiterRegex = /[\|\/丨\[\]\*「」●★]/;
    const parts = subtitleTitle.split(delimiterRegex);
    
    // 【核心改进】过滤掉包含元数据标识和描述性文字的部分
    const metadataPatterns = [
      /^(类别|类型|导演|主演|编剧|制片|语言|地区|上映|片长|评分|简介|tags?|imdb|douban)[::]/i,
      /^(category|director|actor|writer|producer|language|region|runtime|rating|description)[::]/i,
    ];
    
    // 描述性词汇模式(这些通常不是片名,而是描述)
    const descriptivePatterns = [
      /国产.*?(动画|剧集|电影|真人秀|综艺|纪录片)/i,
      /\d+D(动画|电影|剧集)/i,  // 3D动画、2D动画等
      /(武侠|仙侠|玄幻|科幻|奇幻|悬疑|剧情|喜剧|爱情).*?(动画|剧集|电影)/i,
    ];
    
    const validParts = parts.filter(part => {
      const cleanPart = part.trim();
      if (!cleanPart) return false;
      
      // 检查是否是元数据行
      for (const pattern of metadataPatterns) {
        if (pattern.test(cleanPart)) {
          console.log(`[查重] 副标题提取: 过滤元数据行: "${cleanPart}"`);
          return false;
        }
      }
      
      // 检查是否是描述性文字
      for (const pattern of descriptivePatterns) {
        if (pattern.test(cleanPart)) {
          console.log(`[查重] 副标题提取: 过滤描述性文字: "${cleanPart}"`);
          return false;
        }
      }
      
      return true;
    });
    
    // 【改进选择策略】优先选择第一个简短有效的中文片名
    // 策略:1. 有中文 2. 长度适中(2-20个字符)3. 优先选择靠前的
    let bestPart = '';
    
    for (const part of validParts) {
      const cleanPart = part.trim();
      if (!cleanPart) continue;
      
      // 计算中文字符数量
      const chineseChars = (cleanPart.match(/[\u4e00-\u9fff]/g) || []).length;
      
      // 如果包含中文且长度合理(2-20个字符),优先选择
      if (chineseChars >= 2 && cleanPart.length <= 20) {
        bestPart = cleanPart;
        console.log(`[查重] 副标题提取: 选择简短中文片名: "${bestPart}"`);
        break; // 找到第一个符合条件的就使用
      }
      
      // 备选:如果还没找到,选择中文字符最多的
      if (!bestPart && chineseChars > 0) {
        bestPart = cleanPart;
      }
    }
    
    subtitleTitle = bestPart || (validParts.length > 0 ? validParts[0].trim() : '');
    
    // 3. 移除类型前缀(美剧、电影等)
    subtitleTitle = subtitleTitle.replace(/^(美剧|英剧|韩剧|日剧|电影|纪录片|动漫|综艺)[::]\s*/, '');
    
    // 4. 移除季号和集数信息(在提取中文之前先清理,避免残留数字)
    const seasonEpisodePatterns = [
      /第[一二三四五六七八九十\d]+季/g,       // 第1季, 第八季
      /Season\s*\d+/gi,                       // Season 1, Season 8
      /\d+(?:st|nd|rd|th)\s*Season/gi,        // 1st Season
      /S\d{1,2}E\d+-?E?\d*/gi,                // S01E01-E03
      /S\d{1,2}/gi,                           // S01, S08
      /第\d+部/g,                             // 第1部
      /Part\s*\d+/gi,                         // Part 1
      /全\d+集/g,                             // 全24集
      /共\d+集/g,                             // 共24集
      /\d+集全/g,                             // 24集全
      /\d+集/g,                               // 24集
      /\d+话/g,                               // 24话
      /Episodes?\s*\d+/gi,                    // Episode 24
    ];
    
    for (const pattern of seasonEpisodePatterns) {
      subtitleTitle = subtitleTitle.replace(pattern, '');
    }
    
    // 5. 智能提取纯中文影片名称
    if (/[\u4e00-\u9fff]/.test(subtitleTitle)) {
      // 提取所有中文字符(包括中文标点),去除英文、数字和其他符号
      const chineseChars = subtitleTitle.match(/[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g);
      if (chineseChars && chineseChars.length > 0) {
        // 取最长的中文片段
        subtitleTitle = chineseChars.reduce((a, b) => a.length > b.length ? a : b).trim();
      }
    }
    
    // 6. 移除年份信息(可能残留的)
    subtitleTitle = subtitleTitle.replace(/\d{4}/g, '');
    
    // 7. 移除所有剩余的数字(避免像"8"这样的残留)
    subtitleTitle = subtitleTitle.replace(/\d+/g, '');
    
    // 8. 移除版本信息
    const versionPatterns = [
      /导演剪辑版|导演版|加长版|完整版|未删减版|国际版|剧场版/g,
      /Director'?s?\s*Cut|Extended\s*Edition|Uncut|International\s*Version/gi,
      /Limited\s*Edition|Special\s*Edition|Anniversary\s*Edition/gi,
    ];
    
    for (const pattern of versionPatterns) {
      subtitleTitle = subtitleTitle.replace(pattern, '');
    }
    
    // 9. 移除常见的无意义词汇
    const meaninglessPatterns = [
      /高清|蓝光|字幕|中字|中文字幕|英语|国语|粤语|类型|内封|网飞/g,
      /HD|BluRay|Subtitles?|Chinese|English|Netflix|Type/gi,
    ];
    
    for (const pattern of meaninglessPatterns) {
      subtitleTitle = subtitleTitle.replace(pattern, '');
    }
    
    // 10. 清理多余空格和标点
    subtitleTitle = subtitleTitle.replace(/\s+/g, ' ').trim();
    subtitleTitle = subtitleTitle.replace(/[::·・\-\s、,。!?]+$/, '').trim(); // 移除结尾的标点
    subtitleTitle = subtitleTitle.replace(/^[::·・\-\s、,。!?]+/, '').trim(); // 移除开头的标点
    
    console.log(`[查重] 副标题提取: "${subtitle}" -> 标题: "${subtitleTitle}", 季号: ${season || 'null'}`);
    
    return {
      title: subtitleTitle.trim(),
      season: season,
      fullText: subtitle
    };
  }

  function isWebDLSource(title, subtitle) { const t = `${title} ${subtitle}`.toLowerCase(); return ['web-dl', 'webdl'].some(k => t.includes(k)); }
  function isRemuxSource(title, subtitle) { return `${title} ${subtitle}`.toLowerCase().includes('remux'); }
  function isBluRaySource(title, subtitle) { const t = `${title} ${subtitle}`.toLowerCase(); return ['blu-ray', 'bluray'].some(k=>t.includes(k)) && ['avc', 'hevc', 'vc-1', 'h.264', 'h.265', 'mpeg-2'].some(k=>t.includes(k)) && !t.includes('remux'); }
  function isEncodeSource(title) { const t = title.toLowerCase(); return t.includes('x264') || t.includes('x265'); }

  function extractTagsFromBrackets(text) {
    if (!text) return [];
    const bracketRegex = /\[([^\]]+)\]/g;
    const extractedTags = [];
    let match;
    while ((match = bracketRegex.exec(text)) !== null) {
      extractedTags.push(...match[1].split('|').map(tag => tag.trim()).filter(Boolean));
    }
    return extractedTags;
  }

  function enhanceTagsFromText(initialTags, text) {
    const hdrRules = [
      { keywords: ['hdr10+'], tag: 'HDR10+' },
      { keywords: ['dolby vision', 'dovi', '杜比视界'], tag: 'DoVi' },
      { keywords: ['hdr10'], tag: 'HDR10' },
      { keywords: ['菁彩hdr', 'vivid'], tag: '菁彩HDR' },
      { keywords: ['hlg'], tag: 'HLG' },
    ];
    
    // 保留所有原始标签
    let finalTags = [...initialTags];
    
    // 仅添加HDR类型的增强标签(从标题和副标题文本中识别)
    if (text) {
      const lowerText = text.toLowerCase();
      const foundHdrTags = new Set();
      for (const rule of hdrRules) {
        // 只从文本内容中识别HDR标签,不从现有标签推断
        if (rule.keywords.some(k => lowerText.includes(k))) {
          foundHdrTags.add(rule.tag);
        }
      }
      // 添加识别到的HDR标签
      finalTags.push(...Array.from(foundHdrTags));
    }
    
    // HDR优先级处理:如果有HDR10+则移除HDR10
    if (finalTags.includes('HDR10+')) {
      finalTags = finalTags.filter(tag => tag !== 'HDR10');
    }
    
    // 去重并返回
    return [...new Set(finalTags)];
  }

  function getBluRaySubType(title, subtitle, tags, siteKey = null) {
    const text = `${title} ${subtitle}`.toLowerCase();
    const tagsLower = tags.map(t => t.toLowerCase());

    // 优先应用通用规则
    if (tagsLower.some(t => t.includes('原生'))) return 'original';
    if (['欧版原盘', '美版原盘', '日版原盘', '韩版原盘', '港版原盘', '台版原盘', '国版原盘', '德版原盘', '法版原盘', '英版原盘', '澳版原盘', '加版原盘', '俄版原盘', '意版原盘', '西版原盘', '北欧版原盘', '印版原盘'].some(k => text.includes(k))) return 'original';
    if (tagsLower.some(t => t.includes('diy')) || text.includes('diy')) return 'diy';

    // 应用站点专属的补充规则
    if (siteKey === 'ourbits') {
      // 我堡:如果没有diy标签,则判断为原盘
      return 'original';
    }
    if (siteKey === 'nvme') {
      // 不可说:如果没有原生标签,则判断为diy
      return 'diy';
    }

    // 其他未定义规则的站点,默认归类为原盘
    return 'original';
  }

  function extractYear(text) { const m = text.match(/\b(19|20)\d{2}\b/); return m ? parseInt(m[0]) : null; }
  
  function extractIMDb(text) {
    if (!text) return null;
    // 匹配 IMDb 编号格式:tt + 7-8位数字
    const patterns = [
      /imdb\.com\/title\/(tt\d{7,8})/i,  // 从链接中提取
      /\b(tt\d{7,8})\b/i,                 // 直接匹配编号
    ];
    
    for (const pattern of patterns) {
      const match = text.match(pattern);
      if (match) {
        console.log(`[查重] IMDb编号提取: "${text}" -> ${match[1]}`);
        return match[1];
      }
    }
    
    console.log(`[查重] IMDb编号提取: "${text}" -> null`);
    return null;
  }

  function extractSeason(text) {
    if (!text) return null;
    
    // 清理掉可能的分隔符和标点,避免匹配到错误的内容
    const cleanText = text.replace(/[|\/\[\]]/g, ' ');
    
    // 支持多种季号格式
    const patterns = [
      { regex: /\bs(\d{1,2})e\d+/i, group: 1 },              // S01E01 (提取季号)
      { regex: /\bs(\d{1,2})\b/i, group: 1 },                // S01, S1
      { regex: /第([一二三四五六七八九十\d]{1,2})季/, group: 1 },  // 第1季, 第八季
      { regex: /season\s+(\d{1,2})\b/i, group: 1 },          // Season 1
      { regex: /(\d{1,2})(?:st|nd|rd|th)\s+season/i, group: 1 },  // 1st season
      { regex: /\b(\d{1,2})x\d+/i, group: 1 },               // 1x01 (提取季号)
      { regex: /第(\d{1,2})部/, group: 1 },                  // 第1部
      { regex: /part\s+(\d{1,2})\b/i, group: 1 },            // Part 1
      { regex: /vol\.?\s*(\d{1,2})\b/i, group: 1 },          // Vol 1
    ];
    
    for (const {regex, group} of patterns) {
      const match = cleanText.match(regex);
      if (match) {
        let seasonNum;
        const matchedText = match[group];
        
        // 处理中文数字
        const chineseNumbers = {'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9,'十':10};
        if (chineseNumbers[matchedText]) {
          seasonNum = chineseNumbers[matchedText];
        } else {
          seasonNum = parseInt(matchedText);
        }
        
        if (seasonNum > 0 && seasonNum <= 99) {
          const result = `S${seasonNum.toString().padStart(2, '0')}`;
          console.log(`[查重] 季号提取: "${text}" -> ${result}`);
          return result;
        }
      }
    }
    
    console.log(`[查重] 季号提取: "${text}" -> null`);
    return null;
  }

  function extractResolution(text) {
    const patterns = [/(\d{3,4}[pP])/, /(4K|8K)/i];
    const resolutions = new Set();
    patterns.forEach(p => {
      const match = text.match(p);
      if (match) resolutions.add(match[0].toLowerCase());
    });
    if (resolutions.has('4k')) resolutions.add('2160p');
    if (resolutions.has('2160p')) resolutions.add('4k');
    return Array.from(resolutions);
  }

  // 标准化主标题函数(忽略空格和标点符号)- 全局函数供所有查重类型使用
  function normalizeMainTitle(title) {
    if (!title) return '';
    return title.toLowerCase()
      .replace(/[\s\.\-'_\[\]()()【】]/g, '')  // 移除空格和常见标点符号
      .trim();
  }

  // 主标题完全一致检查函数
  function checkMainTitleExactMatch(sourceMainTitle, items, siteName, typeName) {
    const sourceNormalized = normalizeMainTitle(sourceMainTitle);
    console.log(`[查重] ${siteName} - ${typeName}主标题检查: 源站主标题="${sourceMainTitle}" -> 标准化="${sourceNormalized}"`);
    
    return items.filter(i => {
      const itemNormalized = normalizeMainTitle(i.mainTitle);
      const isMatch = sourceNormalized === itemNormalized;
      
      if (isMatch) {
        console.log(`[查重] ${siteName} - ${typeName}主标题完全一致: "${i.mainTitle}" (标准化:"${itemNormalized}") -> 直接匹配`);
      }
      
      return isMatch;
    });
  }

  function areHdrTagsExactlyMatched(sourceTags, itemTags) {
    const targetKeywords = new Set(['dovi', 'hdr10', 'hdr10+', '菁彩hdr', 'hlg']);
    const normalize = t => t.toLowerCase().replace(/\s+/g, '');
    const sourceHdr = new Set(sourceTags.map(normalize).filter(t => targetKeywords.has(t)));
    const itemHdr = new Set(itemTags.map(normalize).filter(t => targetKeywords.has(t)));
    if (sourceTags.length > 0 && sourceHdr.size !== itemHdr.size) return false;
    for (const tag of sourceHdr) if (!itemHdr.has(tag)) return false;
    return true;
  }

  function checkWebDLDupe(sourceInfo, searchResults) {
    return searchResults.map(siteResult => {
      const allResults = [
        ...(siteResult.mainTitleResults.passedItems || []), 
        ...(siteResult.subtitleTitleResults.passedItems || []),
        ...(siteResult.imdbResults.passedItems || [])
      ];
      const webdlResults = allResults.filter(item => isWebDLSource(item.title, item.subtitle));
      if (webdlResults.length === 0) return { site: siteResult.site.name, isDupe: false, reason: '无同为WEB-DL类型的搜索结果', matchedItems: [] };
      const sourceYear = extractYear(sourceInfo.originalTitle);
      const sourceSeason = extractSeason(sourceInfo.originalTitle);
      const yearOrSeasonMatched = webdlResults.filter(item => {
          const itemYear = extractYear(item.title);
          const itemSeason = extractSeason(item.title);
          if (sourceYear !== null && itemYear !== null) return sourceYear === itemYear;
          if (sourceSeason !== null && itemSeason !== null) return sourceSeason.toLowerCase() === itemSeason.toLowerCase();
          return false;
      });
      if ((sourceYear || sourceSeason) && yearOrSeasonMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `年份(${sourceYear || 'N/A'})或季号(${sourceSeason || 'N/A'})不一致`, matchedItems: [] };
      const sourceRes = extractResolution(sourceInfo.originalTitle);
      const resMatched = sourceRes.length > 0 ? yearOrSeasonMatched.filter(i => extractResolution(i.title).some(r => sourceRes.includes(r))) : yearOrSeasonMatched;
      if (sourceRes.length > 0 && resMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `分辨率不一致 (源站: ${sourceRes.join(', ')})`, matchedItems: [] };
      
      // 【新规则】主标题完全一致检查(最高优先级)
      const mainTitleMatched = checkMainTitleExactMatch(sourceInfo.mainTitle, resMatched, siteResult.site.name, 'WEB-DL');
      if (mainTitleMatched.length > 0) {
        return { site: siteResult.site.name, isDupe: true, reason: 'WEB-DL类型且主标题完全一致', matchedItems: mainTitleMatched };
      }
      
      // 【原有规则】HDR标签检查
      const matchedItems = resMatched.filter(i => areHdrTagsExactlyMatched(sourceInfo.tags, i.tags));
      if (sourceInfo.tags.length === 0) return { site: siteResult.site.name, isDupe: true, reason: '源站无HDR标签,通过检查', matchedItems: resMatched };
      if (matchedItems.length > 0) return { site: siteResult.site.name, isDupe: true, reason: '同为WEB-DL类型,且通过所有检查', matchedItems };
      return { site: siteResult.site.name, isDupe: false, reason: 'HDR标签不完全一致', matchedItems: [] };
    });
  }

  function checkBluRayDupe(sourceInfo, searchResults) {
    const sourceBluRayType = sourceInfo.type; // 直接使用已计算的类型,确保一致性
    console.log(`[查重] 源站BluRay类型: ${sourceBluRayType}`);

    return searchResults.map(siteResult => {
      const allResults = [
        ...(siteResult.mainTitleResults.passedItems || []), 
        ...(siteResult.subtitleTitleResults.passedItems || []),
        ...(siteResult.imdbResults.passedItems || [])
      ];

      const bluRayResults = allResults.filter(i => isBluRaySource(i.title, i.subtitle));

      // 严格筛选:DIY和原盘类型必须完全匹配
      const targetResults = bluRayResults.filter(i => {
        const itemBluRayType = getBluRaySubType(i.title, i.subtitle, i.tags, siteResult.site.key);
        const isTypeMatch = itemBluRayType === sourceBluRayType;

        if (!isTypeMatch) {
          console.log(`[查重] ${siteResult.site.name} - BluRay类型不匹配,已过滤: 源(${sourceBluRayType}) vs 结果(${itemBluRayType}) - ${i.title}`);
        }

        return isTypeMatch;
      });

      if (targetResults.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `无同为 ${sourceBluRayType} 类型的搜索结果`, matchedItems: [] };
      const sourceYear = extractYear(sourceInfo.originalTitle);
      const sourceSeason = extractSeason(sourceInfo.originalTitle);
      console.log(`[查重] ${siteResult.site.name} - BluRay年份季号提取: 源站标题="${sourceInfo.originalTitle}" -> 年份=${sourceYear}, 季号=${sourceSeason}`);
      const yearOrSeasonMatched = targetResults.filter(item => {
          const itemYear = extractYear(item.title);
          const itemSeason = extractSeason(item.title);
          
          console.log(`[查重] ${siteResult.site.name} - 年份季号匹配检查: "${item.title}" -> 年份=${itemYear}, 季号=${itemSeason}`);
          
          // 年份匹配检查
          if (sourceYear !== null && itemYear !== null) {
            const yearMatch = sourceYear === itemYear;
            console.log(`[查重] ${siteResult.site.name} - 年份匹配: 源(${sourceYear}) vs 结果(${itemYear}) -> ${yearMatch}`);
            return yearMatch;
          }
          
          // 季号匹配检查
          if (sourceSeason !== null && itemSeason !== null) {
            const seasonMatch = sourceSeason.toLowerCase() === itemSeason.toLowerCase();
            console.log(`[查重] ${siteResult.site.name} - 季号匹配: 源(${sourceSeason}) vs 结果(${itemSeason}) -> ${seasonMatch}`);
            return seasonMatch;
          }
          
          // 如果源站和结果都没有年份和季号信息,认为匹配(主要针对电影)
          if (sourceYear === null && itemYear === null && sourceSeason === null && itemSeason === null) {
            console.log(`[查重] ${siteResult.site.name} - 双方都无年份季号信息,认为匹配`);
            return true;
          }
          
          // 如果只有一方有年份/季号信息,认为不匹配
          console.log(`[查重] ${siteResult.site.name} - 年份季号信息不对称,不匹配`);
          return false;
      });
      
      console.log(`[查重] ${siteResult.site.name} - 年份季号匹配结果: ${yearOrSeasonMatched.length} 条`);
      if ((sourceYear || sourceSeason) && yearOrSeasonMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `年份(${sourceYear || 'N/A'})或季号(${sourceSeason || 'N/A'})不一致`, matchedItems: [] };
      
      const sourceRes = extractResolution(sourceInfo.originalTitle);
      console.log(`[查重] ${siteResult.site.name} - 源站分辨率: [${sourceRes.join(', ')}]`);
      
      const resMatched = sourceRes.length > 0 ? yearOrSeasonMatched.filter(i => {
        const itemRes = extractResolution(i.title);
        const hasMatchingRes = itemRes.some(r => sourceRes.includes(r));
        console.log(`[查重] ${siteResult.site.name} - 分辨率匹配检查: "${i.title}" -> [${itemRes.join(', ')}] -> ${hasMatchingRes}`);
        return hasMatchingRes;
      }) : yearOrSeasonMatched;
      
      console.log(`[查重] ${siteResult.site.name} - 分辨率匹配结果: ${resMatched.length} 条`);
      if (sourceRes.length > 0 && resMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `分辨率不一致 (源站: ${sourceRes.join(', ')})`, matchedItems: [] };
      let matchedItems = [];
      if (sourceBluRayType === 'original') {
        // 【新规则】主标题完全一致检查(最高优先级)
        const mainTitleMatched = checkMainTitleExactMatch(sourceInfo.mainTitle, resMatched, siteResult.site.name, '原盘');
        if (mainTitleMatched.length > 0) {
          matchedItems = mainTitleMatched;
          console.log(`[查重] ${siteResult.site.name} - 原盘主标题完全一致,直接匹配: ${mainTitleMatched.length} 条`);
        } else {
          // 【原有规则】版本匹配检查
          const sourceSub = sourceInfo.originalSubtitle || '';
          const versionKeywords = ['欧版原盘', '美版原盘', '日版原盘', '韩版原盘', '港版原盘', '台版原盘', '国版原盘'];
          const sourceVersion = versionKeywords.find(k => sourceSub.includes(k));
          console.log(`[查重] ${siteResult.site.name} - 原盘版本检查: 源站副标题="${sourceSub}", 版本关键词="${sourceVersion || '无'}"`);
          
          matchedItems = resMatched.filter(i => {
            const matches = sourceVersion ? (i.subtitle || '').includes(sourceVersion) : true;
            console.log(`[查重] ${siteResult.site.name} - 原盘版本匹配: "${i.subtitle || ''}" -> ${matches}`);
            return matches;
          });
        }
      } else { // diy
        // 【新规则】主标题完全一致检查(最高优先级)
        const mainTitleMatched = checkMainTitleExactMatch(sourceInfo.mainTitle, resMatched, siteResult.site.name, 'DIY原盘');
        if (mainTitleMatched.length > 0) {
          matchedItems = mainTitleMatched;
          console.log(`[查重] ${siteResult.site.name} - DIY原盘主标题完全一致,直接匹配: ${mainTitleMatched.length} 条`);
        } else {
          // 【原有规则】中字检查
          const sourceHasChinese = sourceInfo.tags.some(t => ['中字', '中文', '国语'].includes(t));
          console.log(`[查重] ${siteResult.site.name} - DIY中字检查: 源站中字=${sourceHasChinese}`);
          
          matchedItems = resMatched.filter(i => {
            const matches = sourceHasChinese ? i.tags.some(t => ['中字', '中文', '国语'].includes(t)) : true;
            console.log(`[查重] ${siteResult.site.name} - DIY中字匹配: [${i.tags.join(', ')}] -> ${matches}`);
            return matches;
          });
        }
      }
      
      console.log(`[查重] ${siteResult.site.name} - 最终匹配结果: ${matchedItems.length} 条`);
      if (matchedItems.length > 0) {
        console.log(`[查重] ${siteResult.site.name} - 🎯 检测到重复!匹配的种子:`, matchedItems.map(i => i.title));
        return { site: siteResult.site.name, isDupe: true, reason: `存在匹配的 ${sourceBluRayType} 类型种子`, matchedItems };
      }
      console.log(`[查重] ${siteResult.site.name} - ❌ 未检测到重复`);
      return { site: siteResult.site.name, isDupe: false, reason: `未找到符合所有条件的 ${sourceBluRayType} 类型种子`, matchedItems: [] };
    });
  }

  function checkRemuxDupe(sourceInfo, searchResults) {
    return searchResults.map(siteResult => {
      const allResults = [
        ...(siteResult.mainTitleResults.passedItems || []), 
        ...(siteResult.subtitleTitleResults.passedItems || []),
        ...(siteResult.imdbResults.passedItems || [])
      ];
      const remuxResults = allResults.filter(item => isRemuxSource(item.title, item.subtitle));
      if (remuxResults.length === 0) return { site: siteResult.site.name, isDupe: false, reason: '无同为REMUX类型的搜索结果', matchedItems: [] };
      const sourceYear = extractYear(sourceInfo.originalTitle);
      const sourceSeason = extractSeason(sourceInfo.originalTitle);
      const yearOrSeasonMatched = remuxResults.filter(item => {
          const itemYear = extractYear(item.title);
          const itemSeason = extractSeason(item.title);
          if (sourceYear !== null && itemYear !== null) return sourceYear === itemYear;
          if (sourceSeason !== null && itemSeason !== null) return sourceSeason.toLowerCase() === itemSeason.toLowerCase();
          return false;
      });
      if ((sourceYear || sourceSeason) && yearOrSeasonMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `年份(${sourceYear || 'N/A'})或季号(${sourceSeason || 'N/A'})不一致`, matchedItems: [] };
      const sourceRes = extractResolution(sourceInfo.originalTitle);
      const resMatched = sourceRes.length > 0 ? yearOrSeasonMatched.filter(i => extractResolution(i.title).some(r => sourceRes.includes(r))) : yearOrSeasonMatched;
      if (sourceRes.length > 0 && resMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `分辨率不一致 (源站: ${sourceRes.join(', ')})`, matchedItems: [] };
      
      // 【新规则】主标题完全一致检查(最高优先级)
      const mainTitleMatched = checkMainTitleExactMatch(sourceInfo.mainTitle, resMatched, siteResult.site.name, 'REMUX');
      if (mainTitleMatched.length > 0) {
        return { site: siteResult.site.name, isDupe: true, reason: 'REMUX类型且主标题完全一致', matchedItems: mainTitleMatched };
      }
      
      // 【原有规则】中字检查
      const sourceHasChinese = sourceInfo.tags.some(t => ['中字', '中文', '国语'].includes(t));
      const matchedItems = resMatched.filter(i => sourceHasChinese ? i.tags.some(t => ['中字', '中文', '国语'].includes(t)) : true);
      if (matchedItems.length > 0) return { site: siteResult.site.name, isDupe: true, reason: '存在匹配的REMUX类型种子', matchedItems };
      return { site: siteResult.site.name, isDupe: false, reason: '中字标签不匹配', matchedItems: [] };
    });
  }

  function checkEncodeDupe(sourceInfo, searchResults) {
    const sourceRes = extractResolution(sourceInfo.originalTitle);
    const sourceIs2160p = sourceRes.includes('2160p');
    return searchResults.map(siteResult => {
      const allResults = [
        ...(siteResult.mainTitleResults.passedItems || []), 
        ...(siteResult.subtitleTitleResults.passedItems || []),
        ...(siteResult.imdbResults.passedItems || [])
      ];
      const encodeResults = allResults.filter(item => isEncodeSource(item.title));
      if (encodeResults.length === 0) return { site: siteResult.site.name, isDupe: false, reason: '无同为压制类型的搜索结果', matchedItems: [] };
      const sourceYear = extractYear(sourceInfo.originalTitle);
      const sourceSeason = extractSeason(sourceInfo.originalTitle);
      const yearOrSeasonMatched = encodeResults.filter(item => {
          const itemYear = extractYear(item.title);
          const itemSeason = extractSeason(item.title);
          if (sourceYear !== null && itemYear !== null) return sourceYear === itemYear;
          if (sourceSeason !== null && itemSeason !== null) return sourceSeason.toLowerCase() === itemSeason.toLowerCase();
          return false;
      });
      if ((sourceYear || sourceSeason) && yearOrSeasonMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `年份(${sourceYear || 'N/A'})或季号(${sourceSeason || 'N/A'})不一致`, matchedItems: [] };
      const resMatched = sourceRes.length > 0 ? yearOrSeasonMatched.filter(i => extractResolution(i.title).some(r => sourceRes.includes(r))) : yearOrSeasonMatched;
      if (resMatched.length === 0) return { site: siteResult.site.name, isDupe: false, reason: `分辨率不一致 (源站: ${sourceRes.join(', ')})`, matchedItems: [] };
      
      // 【新规则】主标题完全一致检查(最高优先级)
      const mainTitleMatched = checkMainTitleExactMatch(sourceInfo.mainTitle, resMatched, siteResult.site.name, '压制');
      if (mainTitleMatched.length > 0) {
        return { site: siteResult.site.name, isDupe: true, reason: '压制类型且主标题完全一致', matchedItems: mainTitleMatched };
      }
      
      // 【原有规则】分辨率和HDR检查
      if (sourceIs2160p) {
        const matchedItems = resMatched.filter(i => areHdrTagsExactlyMatched(sourceInfo.tags, i.tags));
        if (sourceInfo.tags.length === 0) return { site: siteResult.site.name, isDupe: true, reason: '源站无HDR标签,通过检查', matchedItems: resMatched };
        if (matchedItems.length > 0) return { site: siteResult.site.name, isDupe: true, reason: '同为压制类型,且年份/季号、分辨率、HDR标签均一致', matchedItems };
        return { site: siteResult.site.name, isDupe: false, reason: '2160p分辨率下HDR标签不完全匹配', matchedItems: [] };
      } else {
        return { site: siteResult.site.name, isDupe: true, reason: '同为压制类型,且年份/季号、分辨率一致', matchedItems: resMatched };
      }
    });
  }

  function validateResourceForSite(sourceInfo, siteKey) {
    const rules = BANNED_RESOURCES_RULES[siteKey];
    if (!rules) return { allowed: true, reason: '' };
    const releaseGroup = sourceInfo.releaseGroup ? sourceInfo.releaseGroup.toLowerCase() : null;
    if (releaseGroup && rules.generalGroups.includes(releaseGroup)) return { allowed: false, reason: `通用禁止小组: ${sourceInfo.releaseGroup}` };
    if (releaseGroup && rules.partialGroups.general.some(p => releaseGroup.startsWith(p))) {
        const matchedRule = rules.partialGroups.general.find(p => releaseGroup.startsWith(p));
        return { allowed: false, reason: `通用禁止小组 (部分匹配): ${matchedRule}*` };
    }
    if (rules.bannedTypes.includes(sourceInfo.type)) return { allowed: false, reason: `禁止资源类型: ${sourceInfo.type.toUpperCase()}` };
    const typeSpecificRules = rules.typeSpecific[sourceInfo.type] || [];
    if (releaseGroup && typeSpecificRules.some(p => releaseGroup.startsWith(p))) {
        const matchedRule = typeSpecificRules.find(p => releaseGroup.startsWith(p));
        return { allowed: false, reason: `该类型禁止小组: ${matchedRule}*` };
    }
    return { allowed: true, reason: '允许发布' };
  }

  function extractAudiences(doc) {
    let title = '', subtitle = '', category = '', description = '';
    let tags = [];

    // 精准提取标题,过滤折扣标签和其他额外信息
    const h1Element = doc.querySelector('h1#top');
    if (h1Element) {
      // 克隆元素以避免修改原始DOM
      const clonedH1 = h1Element.cloneNode(true);
      
      // 移除所有<b>标签(包含折扣信息如[50%])
      clonedH1.querySelectorAll('b').forEach(b => b.remove());
      
      // 移除所有<font>标签
      clonedH1.querySelectorAll('font').forEach(font => font.remove());
      
      // 获取清理后的文本并去除多余空白
      title = text(clonedH1).replace(/\s+/g, ' ').trim();
      
      console.log(`[查重] Audiences标题提取: 原始="${text(h1Element)}" -> 清理后="${title}"`);
    }

    // 尝试获取描述信息
    const descrEl = doc.querySelector('#kdescr');
    if (descrEl) {
        description = text(descrEl);
    }

    const rows = doc.querySelectorAll('table > tbody > tr');
    rows.forEach(row => {
        const keyCell = row.querySelector('td.rowhead');
        const valueCell = row.querySelector('td.rowfollow');
        if(!keyCell || !valueCell) return;

        const keyText = text(keyCell);
        if (keyText.includes('副标题')) {
            subtitle = text(valueCell);
        } else if (keyText.includes('基本信息')) {
            const infoText = text(valueCell);
            const catMatch = infoText.match(/类型:\s*(\S+)/);
            if(catMatch) category = catMatch[1];
        } else if (keyText.includes('标签')) {
            tags = Array.from(valueCell.querySelectorAll('.tags')).map(tag => text(tag));
        }
    });

    // 提取 IMDb 编号(从描述或页面链接中)
    const imdb = extractIMDb(description) || extractIMDb(doc.body.innerHTML);

    return { title, subtitle, tags, category, description, imdb };
  }

  function extractHHC(doc) {
    let title = '', subtitle = '', category = '', description = '';
    let tags = [];

    // 尝试获取描述信息
    const descrEl = doc.querySelector('#kdescr');
    if (descrEl) {
        description = text(descrEl);
    }

    const mainGridDivs = doc.querySelectorAll('.bg-content_bg > .grid > div');
    for (let i = 0; i < mainGridDivs.length; i += 2) {
        const keyDiv = mainGridDivs[i], valueDiv = mainGridDivs[i + 1];
        if (!keyDiv || !valueDiv) continue;
        const keyText = text(keyDiv);
        if (keyText === '标题') title = text(valueDiv);
        else if (keyText === '副标题') subtitle = text(valueDiv);
        else if (keyText === '标签') tags = Array.from(valueDiv.querySelectorAll('a > span')).map(span => text(span));
    }
    doc.querySelectorAll('.bg-content_bg > .grid .grid.grid-cols-4 > div').forEach(div => {
        const divText = text(div);
        if (divText.startsWith('类型:')) category = divText.replace('类型:', '').trim();
    });
    
    // 提取 IMDb 编号(从描述或页面链接中)
    const imdb = extractIMDb(description) || extractIMDb(doc.body.innerHTML);
    
    return { title, subtitle, tags, category, description, imdb };
  }

  // MTeam站点专用:等待页面完全加载
  async function waitForMTeamPageLoad() {
    const maxAttempts = 30; // 最多等待15秒 (30 * 500ms)
    let attempts = 0;

    return new Promise((resolve) => {
      const checkPageReady = () => {
        attempts++;
        console.log(`[查重] MTeam-第${attempts}次检查页面状态...`);

        // 检查页面标题是否已更新(不只是"M-Team")
        const pageTitle = document.title;
        const hasFullTitle = pageTitle && pageTitle !== 'M-Team' && pageTitle.includes('種子詳情');

        // 检查关键DOM元素是否存在
        const h2Count = document.querySelectorAll('h2').length;
        const alignMiddleCount = document.querySelectorAll('.align-middle').length;
        const hasContent = h2Count > 0 || alignMiddleCount > 0;

        // 检查是否有种子信息内容
        const bodyText = document.body ? document.body.textContent : '';
        const hasTorrentInfo = bodyText.includes('◎') || bodyText.includes('2160p') || bodyText.includes('1080p');

        console.log(`[查重] MTeam-页面检查: title="${pageTitle}", hasFullTitle=${hasFullTitle}, h2=${h2Count}, alignMiddle=${alignMiddleCount}, hasTorrentInfo=${hasTorrentInfo}`);

        if ((hasFullTitle || hasContent || hasTorrentInfo) && attempts >= 3) {
          // 页面看起来准备好了,再等一点点让内容稳定
          console.log('[查重] MTeam-页面似乎已加载,开始提取信息...');
          setTimeout(() => {
            const info = extractMTeam(document);
            resolve(info);
          }, 1000);
          return;
        }

        if (attempts >= maxAttempts) {
          console.log('[查重] MTeam-等待超时,使用当前页面状态...');
          const info = extractMTeam(document);
          resolve(info);
          return;
        }

        // 继续等待
        setTimeout(checkPageReady, 500);
      };

      // 开始检查
      checkPageReady();
    });
  }

  function extractMTeam(doc) {
    let title = '', subtitle = '', category = '', description = '';
    let tags = [];

    // 方法1: 根据实际DOM结构提取主标题
    // 从用户截图看到的结构: <h2 class="pr-[2em]"><span class="align-middle">标题内容</span></h2>
    let titleEl = doc.querySelector('h2[class*="pr-"] > span.align-middle');
    if (titleEl) {
      title = text(titleEl);
      console.log(`[查重] MTeam-方法1找到标题: ${title}`);
    }

    // 方法2: 尝试更通用的h2选择器
    if (!title) {
      titleEl = doc.querySelector('h2 > span.align-middle');
      if (titleEl) {
        title = text(titleEl);
        console.log(`[查重] MTeam-方法2找到标题: ${title}`);
      }
    }

    // 方法3: 尝试任何h2标签内的align-middle span
    if (!title) {
      titleEl = doc.querySelector('span.align-middle');
      if (titleEl) {
        const parentH2 = titleEl.closest('h2');
        if (parentH2) {
          title = text(titleEl);
          console.log(`[查重] MTeam-方法3找到标题: ${title}`);
        }
      }
    }

    // 方法4: 从页面标题中提取 (fallback方案)
    if (!title) {
      const pageTitle = doc.title;
      console.log(`[查重] MTeam-页面标题: ${pageTitle}`);
      // 页面标题格式通常为 "M-Team - TP __ 種子詳情 _标题内容_ - Powered by mTorrent"
      const titleMatch = pageTitle.match(/_([^_]+)_.*?- Powered by mTorrent/);
      if (titleMatch) {
        title = titleMatch[1];
        console.log(`[查重] MTeam-方法4从页面标题提取: ${title}`);
      }
    }

    // 方法5: 通过React组件查找 (MTeam是React应用)
    if (!title) {
      // 查找可能包含标题的各种元素
      const candidates = [
        'h2 span',
        '[class*="title"] span',
        '[class*="torrent"] h2',
        '.ant-typography h2',
        'h2[class*="text"]',
        // 添加更多可能的选择器
        'h1 span',
        'h3 span',
        '[class*="name"] span',
        '[class*="heading"] span'
      ];

      for (const selector of candidates) {
        const el = doc.querySelector(selector);
        if (el) {
          const text_content = text(el);
          // 检查是否像种子标题(包含常见的编码格式、分辨率等)
          if (text_content && (text_content.includes('2160p') || text_content.includes('1080p') || text_content.includes('720p') ||
              text_content.includes('WEB-DL') || text_content.includes('BluRay') || text_content.includes('REMUX') ||
              text_content.includes('x264') || text_content.includes('x265') || text_content.includes('HEVC'))) {
            title = text_content;
            console.log(`[查重] MTeam-方法5找到可能的标题: ${title} (选择器: ${selector})`);
            break;
          }
        }
      }
    }

    // 方法6: 如果所有方法都失败,尝试从所有文本中查找最长的包含种子特征的文本
    if (!title) {
      console.log('[查重] MTeam-尝试方法6:从所有文本中查找种子标题...');
      const allText = doc.body ? doc.body.textContent : '';
      const lines = allText.split('\n').map(line => line.trim()).filter(line => line.length > 20);

      for (const line of lines) {
        if ((line.includes('2160p') || line.includes('1080p') || line.includes('720p')) &&
            (line.includes('x264') || line.includes('x265') || line.includes('HEVC') ||
             line.includes('WEB-DL') || line.includes('BluRay') || line.includes('REMUX')) &&
            line.length < 200) { // 不要太长的文本
          title = line;
          console.log(`[查重] MTeam-方法6从文本内容找到标题: ${title}`);
          break;
        }
      }
    }

    // 优先从页面顶部的小标题元素提取副标题(MTeam特有的副标题样式)
    // 方法1: 查找特定的CSS类 text-mt-gray-4 leading-[1.425]
    let subtitleEl = doc.querySelector('p.text-mt-gray-4, p[class*="text-mt-gray"]');
    if (subtitleEl && text(subtitleEl).length > 10) {
      subtitle = text(subtitleEl);
      console.log(`[查重] MTeam-方法1从页面顶部提取副标题: ${subtitle}`);
    }

    // 方法2: 查找包含leading-[1.425]类的p元素
    if (!subtitle) {
      const candidates = doc.querySelectorAll('p[class*="leading-"], p[class*="text-mt-"], p[class*="gray-"]');
      for (const el of candidates) {
        const text_content = text(el);
        if (text_content && text_content.includes('|') && text_content.length > 10 && text_content.length < 300) {
          subtitle = text_content;
          console.log(`[查重] MTeam-方法2从CSS类元素提取副标题: ${subtitle}`);
          break;
        }
      }
    }

    // 方法3: 查找可能的副标题元素(包含特定内容的p标签)
    if (!subtitle) {
      const pElements = doc.querySelectorAll('p');
      for (const el of pElements) {
        const text_content = text(el);
        // 查找包含常见副标题特征的元素:包含 | 分隔符,包含中文,长度适中
        if (text_content &&
            text_content.includes('|') &&
            /[\u4e00-\u9fff]/.test(text_content) && // 包含中文
            text_content.length > 10 &&
            text_content.length < 300 &&
            (text_content.includes('字幕') || text_content.includes('收藏版') || text_content.includes('/'))) {
          subtitle = text_content;
          console.log(`[查重] MTeam-方法3从p元素提取副标题: ${subtitle}`);
          break;
        }
      }
    }

    // 提取描述信息
    const descrEl = doc.querySelector('#kdescr, .descr, .torrent-description, [class*="descr"]');
    if (descrEl) {
      description = text(descrEl);
    }

    // 备用方案:如果以上方法都没找到副标题,才从页面内容中查找◎标记的中文信息
    if (!subtitle) {
      console.log('[查重] MTeam-前面方法未找到副标题,使用备用方案从◎标记提取');
      const allTextContent = doc.body ? doc.body.textContent || '' : '';
      const chineseNameMatch = allTextContent.match(/◎中文名[ \s]*([^\n\r◎]+)/);
      const translateNameMatch = allTextContent.match(/◎译[ \s]*名[ \s]*([^\n\r◎]+)/);
      const filmNameMatch = allTextContent.match(/◎片[ \s]*名[ \s]*([^\n\r◎]+)/);

      if (chineseNameMatch) {
        subtitle = chineseNameMatch[1].trim();
        console.log(`[查重] MTeam-备用方案从中文名提取: ${subtitle}`);
      } else if (translateNameMatch) {
        subtitle = translateNameMatch[1].trim();
        console.log(`[查重] MTeam-备用方案从译名提取: ${subtitle}`);
      } else if (filmNameMatch && !filmNameMatch[1].includes(title)) {
        subtitle = filmNameMatch[1].trim();
        console.log(`[查重] MTeam-备用方案从片名提取: ${subtitle}`);
      }
    }

    // 智能分类识别
    category = '电影'; // 默认
    if (title && (/S\d+E\d+|第\d+季|Season|Episode|\bEP\d+|\bE\d+/i.test(title) ||
        /剧集|电视剧|TV Series/i.test(description))) {
      category = '剧集';
    } else if (/动漫|动画|Anime/i.test(description) || /动画|アニメ/i.test(title)) {
      category = '动漫';
    }

    console.log(`[查重] MTeam标题提取结果: title="${title}", subtitle="${subtitle}", category="${category}"`);
    console.log(`[查重] MTeam页面结构调试 - 找到的h2元素:`, doc.querySelectorAll('h2').length);
    console.log(`[查重] MTeam页面结构调试 - 找到的align-middle元素:`, doc.querySelectorAll('.align-middle').length);

    // 提取 IMDb 编号(从描述或页面链接中)
    const imdb = extractIMDb(description) || extractIMDb(doc.body ? doc.body.innerHTML : '');

    return { title, subtitle, tags, category, description, imdb };
  }

  function extractGenericDetails(doc) {
    // 精准提取标题,过滤折扣标签和其他额外信息
    let title = '';
    const h1Element = doc.querySelector('h1');
    if (h1Element) {
      // 克隆元素以避免修改原始DOM
      const clonedH1 = h1Element.cloneNode(true);
      
      // 移除所有<b>标签(包含折扣信息如[50%]、[FREE]等)
      clonedH1.querySelectorAll('b').forEach(b => b.remove());
      
      // 移除所有<font>标签
      clonedH1.querySelectorAll('font').forEach(font => font.remove());
      
      // 移除所有<img>标签(图标等)
      clonedH1.querySelectorAll('img').forEach(img => img.remove());
      
      // 移除所有包含折扣信息的<span>标签
      clonedH1.querySelectorAll('span').forEach(span => {
        const className = span.className || '';
        const textContent = span.textContent || '';
        // 如果span包含折扣类名或折扣文本,则移除
        if (className.includes('down') || className.includes('free') || className.includes('pro') || 
            /\[\d+%\]|FREE|免费|促销/i.test(textContent)) {
          span.remove();
        }
      });
      
      // 获取清理后的文本并去除多余空白
      title = text(clonedH1).replace(/\s+/g, ' ').trim();
      
      console.log(`[查重] 通用标题提取: 原始="${text(h1Element)}" -> 清理后="${title}"`);
    }
    
    let subtitle = '', description = '', tags = [], category = '电影';

    // 尝试获取描述信息
    const descrEl = doc.querySelector('#kdescr, .descr, .torrent-description');
    if (descrEl) {
        description = text(descrEl);
    }

    doc.querySelectorAll('tr').forEach(row => {
      const cells = row.querySelectorAll('td');
      if (cells.length < 2) return;
      const key = text(cells[0]).toLowerCase();
      const valCell = cells[1];
      if (key.includes('副标题')) subtitle = text(valCell);
      else if (key.includes('标签')) tags = Array.from(valCell.querySelectorAll('span, a')).map(el => text(el)).filter(Boolean);
      else if (key.includes('类型')) {
        const catText = text(valCell).toLowerCase();
        if (catText.includes('剧集')) category = '剧集';
        else if (catText.includes('动漫')) category = '动漫';
      }
    });
    
    // 提取 IMDb 编号(从描述或页面链接中)
    const imdb = extractIMDb(description) || extractIMDb(doc.body.innerHTML);
    
    return { title, subtitle, tags, category, description, imdb };
  }

  const T_S_MAP = {
      '臺': '台', '戰': '战', '門': '门', '隻': '只', '鐵': '铁', '長': '长', '簡': '简', '約': '约',
      '闆': '板', '無': '无', '組': '组', '邊': '边', '讓': '让', '話': '话', '語': '语', '頭': '头',
      '銀': '银', '樣': '样', '塊': '块', '牠': '它', '牠': '它', '兇': '凶', '國': '国', '圍': '围',
      '們': '们', '發': '发', '葉': '叶', '書': '书', '見': '见', '種': '种', '號': '号', '畫': '画'
      // 注:此为常用字映射表,无法覆盖所有简繁体字。
  };

  function normalizeSubtitle(subtitle) {
      if (!subtitle) return '';
      let simplified = '';
      for (const char of subtitle) {
          simplified += T_S_MAP[char] || char;
      }
      return simplified.replace(/[\s\.\-']/g, '');
  }

  function areSubtitlesEqual(subA, subB) {
      if (!subA && !subB) return true; // Both are empty
      if (!subA || !subB) return false;
      return normalizeSubtitle(subA) === normalizeSubtitle(subB);
  }

  function areMainTitlesEqual(titleA, titleB) {
      if (!titleA && !titleB) return true; // Both are empty
      if (!titleA || !titleB) return false;
      const normalize = (str) => str.toLowerCase().replace(/[\s\.\-']/g, '');
      return normalize(titleA) === normalize(titleB);
  }

  function isSimilar(strA, strB) {
    if (!strA || !strB) return false;
    const sA = strA.toLowerCase().replace(/[\s\.\-']/g, '');
    const sB = strB.toLowerCase().replace(/[\s\.\-']/g, '');
    return sA.includes(sB) || sB.includes(sA);
  }

  function filterSearchResults(allResults, sourceInfo) {
      return allResults.map(siteResult => {
          const newSiteResult = { ...siteResult };
          ['mainTitleResults', 'subtitleTitleResults', 'imdbResults'].forEach(key => {
              if (siteResult[key]?.items) {
                  const passedItems = [], failedItems = [];

                  siteResult[key].items.forEach(item => {
                      // 规则更新:同时检查主标题、副标题和季号
                      const mainTitlesMatch = areMainTitlesEqual(item.mainTitle, sourceInfo.mainTitle);
                      const subtitlesMatch = areSubtitlesEqual(item.subtitleTitle, sourceInfo.subtitleTitle);
                      
                      // 季号匹配检查
                      const seasonMatch = (sourceInfo.season && item.season) 
                          ? sourceInfo.season === item.season 
                          : (!sourceInfo.season && !item.season); // 都没有季号也算匹配
                      
                      const searchSourceLabel = key === 'imdbResults' ? 'IMDb搜索' : key === 'subtitleTitleResults' ? '副标题搜索' : '主标题搜索';
                      console.log(`[查重] ${siteResult.site.name} - ${searchSourceLabel}筛选检查: "${item.title}"`);
                      console.log(`[查重] - 主标题匹配: ${mainTitlesMatch} (源:"${sourceInfo.mainTitle}" vs 结果:"${item.mainTitle}")`);
                      console.log(`[查重] - 副标题匹配: ${subtitlesMatch} (源:"${sourceInfo.subtitleTitle}" vs 结果:"${item.subtitleTitle}")`);
                      console.log(`[查重] - 季号匹配: ${seasonMatch} (源:"${sourceInfo.season || 'null'}" vs 结果:"${item.season || 'null'}")`);
                      
                      // 【调试】详细显示标题比较过程
                      if (!mainTitlesMatch && !subtitlesMatch) {
                          console.log(`[查重] - 🔍 标题匹配失败详细分析:`);
                          console.log(`[查重] - 📝 源站完整标题: "${sourceInfo.title || ''}"`);
                          console.log(`[查重] - 📝 源站完整副标题: "${sourceInfo.subtitle || ''}"`);
                          console.log(`[查重] - 📝 搜索结果完整标题: "${item.title || ''}"`);
                          console.log(`[查重] - 📝 搜索结果完整副标题: "${item.subtitle || ''}"`);
                          console.log(`[查重] - 🎯 标准化后主标题对比: "${sourceInfo.mainTitle?.toLowerCase?.().replace(/[\s\.\-']/g, '') || ''}" vs "${item.mainTitle?.toLowerCase?.().replace(/[\s\.\-']/g, '') || ''}"`);
                          console.log(`[查重] - 🎯 标准化后副标题对比: "${sourceInfo.subtitleTitle || ''}" vs "${item.subtitleTitle || ''}" (经过简繁转换)`);
                      }

                      // 只有当标题匹配且季号匹配时,才通过筛选
                      if ((mainTitlesMatch || subtitlesMatch) && seasonMatch) {
                          // 额外检查:对于BluRay类型,确保子类型匹配
                          if ((sourceInfo.type === 'diy' || sourceInfo.type === 'original') && isBluRaySource(item.title, item.subtitle)) {
                              const itemBluRayType = getBluRaySubType(item.title, item.subtitle, item.tags, siteResult.site.key);
                              console.log(`[查重] ${siteResult.site.name} - BluRay筛选检查: 源(${sourceInfo.type}) vs 结果(${itemBluRayType}) - ${item.title}`);
                              if (itemBluRayType !== sourceInfo.type) {
                                  const reason = `BluRay子类型不匹配:源站为${sourceInfo.type}类型,但搜索结果为${itemBluRayType}类型。根据筛选规则,${sourceInfo.type === 'diy' ? 'DIY类型不匹配原盘结果' : '原盘类型不匹配DIY结果'}。`;
                                  failedItems.push({ ...item, failureReason: reason });
                                  console.log(`[查重] ${siteResult.site.name} - 初步筛选阶段过滤BluRay类型不匹配: 源(${sourceInfo.type}) vs 结果(${itemBluRayType}) - ${item.title}`);
                                  return;
                              } else {
                                  console.log(`[查重] ${siteResult.site.name} - BluRay类型匹配,通过筛选: 源(${sourceInfo.type}) vs 结果(${itemBluRayType}) - ${item.title}`);
                              }
                          }
                          // 设置搜索来源标识
                          let searchSource = 'main';
                          if (key === 'subtitleTitleResults') searchSource = 'sub';
                          if (key === 'imdbResults') searchSource = 'imdb';
                          passedItems.push({ ...item, searchSource });
                          console.log(`[查重] ${siteResult.site.name} - 通过筛选: ${item.title}`);
                      } else {
                          // 构建失败原因
                          let reason = '';
                          if (!mainTitlesMatch && !subtitlesMatch) {
                              reason = `主标题与副标题均不匹配。<br><small> - 主标题 (源:"${esc(sourceInfo.mainTitle)}", 结果:"${esc(item.mainTitle)}")</small><br><small> - 副标题 (源:"${esc(sourceInfo.subtitleTitle)}", 结果:"${esc(item.subtitleTitle)}")</small>`;
                          } else if (!seasonMatch) {
                              reason = `季号不匹配。<br><small> - 季号 (源:"${sourceInfo.season || '无'}", 结果:"${item.season || '无'}")</small>`;
                          }
                          failedItems.push({ ...item, failureReason: reason });
                          console.log(`[查重] ${siteResult.site.name} - 未通过筛选: ${item.title} - ${reason.replace(/<[^>]*>/g, '')}`);
                      }
                  });
                  newSiteResult[key] = { passedItems, failedItems };
              }
          });
          return newSiteResult;
      });
  }

  // 核心查重检查函数(独立的逻辑)
  async function performDupeCheck() {
    // 检测是否为支持的详情页面
    const isMTeamDetail = /\/detail\/\d+/.test(window.location.pathname);
    const isTraditionalDetail = window.location.pathname.includes('details.php');

    if (!isMTeamDetail && !isTraditionalDetail) return;
    if (!isMTeamDetail && (!document.querySelector('h1#top') && !document.querySelector('h1') && !document.querySelector('.bg-content_bg'))) return;

    let initialInfo;
    const currentSiteKey = DOMAIN_TO_KEY[window.location.hostname];

    // MTeam站点需要等待React应用完全渲染
    if (currentSiteKey === 'mteam') {
      console.log('[查重] MTeam-开始等待页面完全加载...');
      initialInfo = await waitForMTeamPageLoad();
    } else {
      switch (currentSiteKey) {
          case 'hhc': initialInfo = extractHHC(document); break;
          case 'aud': initialInfo = extractAudiences(document); break;
          default: initialInfo = extractGenericDetails(document);
      }
    }

    const { title, subtitle, tags: initialTags, category, description, imdb } = initialInfo;

    const mainTitleInfo = extractMainTitle(title);
    const subtitleTitleInfo = extractSubtitleTitle(subtitle);
    
    // 季号优先从主标题提取,如果主标题没有则从副标题提取
    const season = mainTitleInfo.season || subtitleTitleInfo.season;
    
    // 年份提取(优先从主标题,其次副标题)
    const year = extractYear(title) || extractYear(subtitle);
    
    console.log(`[查重] 源站信息提取: 主标题季号=${mainTitleInfo.season || 'null'}, 副标题季号=${subtitleTitleInfo.season || 'null'}, 最终季号=${season || 'null'}, 年份=${year || 'null'}, IMDb=${imdb || 'null'}`);

    const     sourceInfo = {
        originalTitle: title,
        originalSubtitle: subtitle,
        mainTitle: mainTitleInfo.title,
        subtitleTitle: subtitleTitleInfo.title,
        season: season,
        year: year,
        imdb: imdb,
        tags: enhanceTagsFromText(initialTags, `${title} ${subtitle} ${description}`),
        category,
        releaseGroup: extractReleaseGroup(title),
        siteKey: currentSiteKey,
        type: (() => {
            const enhancedTags = enhanceTagsFromText(initialTags, `${title} ${subtitle} ${description}`);
            if (isRemuxSource(title, subtitle)) return 'remux';
            if (isWebDLSource(title, subtitle)) return 'web-dl';
            if (isBluRaySource(title, subtitle)) return getBluRaySubType(title, subtitle, enhancedTags, currentSiteKey);
            if (isEncodeSource(title)) return 'encode';
            return 'unknown';
        })()
    };

    if (!sourceInfo.mainTitle && !sourceInfo.subtitleTitle && !sourceInfo.originalTitle) {
        return updateSearchResults('无法提取关键标题信息,请检查页面结构或为该站添加专属解析器。');
    }

    showSearchPanel(sourceInfo);

    if (currentSiteKey === 'nvme' && sourceInfo.type === 'encode') {
      const titleLower = sourceInfo.originalTitle.toLowerCase();
      if (titleLower.includes('1080p') && titleLower.includes('x264') && titleLower.includes('10bit') && sourceInfo.category !== '动漫') {
        return updateSearchResults('规则校验失败:不可说站点不允许发布非动画类型的 1080p x264 10bit 资源。');
      }
      if (titleLower.includes('1080p') && titleLower.includes('x265') && sourceInfo.category !== '动漫') {
        return updateSearchResults('规则校验失败:不可说站点不允许发布非动画类型的 1080p x265 资源。');
      }
    }

    try {
      const enabledSites = SITES.filter(site => getEnabled(site.key) && site.key !== currentSiteKey);
      if (enabledSites.length === 0) return updateSearchResults('请先开启至少一个目标站点的开关');

      const allResults = await Promise.all(enabledSites.map(async site => {
        console.log(`[查重] 开始搜索站点: ${site.name}, mainTitle="${sourceInfo.mainTitle}", subtitleTitle="${sourceInfo.subtitleTitle}", imdb="${sourceInfo.imdb || 'null'}"`);
        
        const searches = [
          sourceInfo.mainTitle ? searchOneSite(site, sourceInfo.mainTitle, '0') : Promise.resolve({ site, items: [], error: null }),
          sourceInfo.subtitleTitle ? searchOneSite(site, sourceInfo.subtitleTitle, '0') : Promise.resolve({ site, items: [], error: null })
        ];
        
        // 如果有IMDb编号,增加IMDb搜索
        if (sourceInfo.imdb) {
          console.log(`[查重] ${site.name} - 准备执行IMDb搜索: "${sourceInfo.imdb}"`);
          searches.push(searchOneSite(site, sourceInfo.imdb, '4'));
        }
        
        const [mainTitleResults, subtitleTitleResults, imdbResults] = await Promise.all(searches);
        
        console.log(`[查重] ${site.name} - 搜索结果汇总: 主标题${mainTitleResults?.items?.length || 0}条, 副标题${subtitleTitleResults?.items?.length || 0}条, IMDb${imdbResults?.items?.length || 0}条`);
        
        return { site, mainTitleResults, subtitleTitleResults, imdbResults: imdbResults || { site, items: [], error: null } };
      }));

      const filteredResults = filterSearchResults(allResults, sourceInfo);

      let dupeResults = null;
      if (sourceInfo.type === 'remux') dupeResults = checkRemuxDupe(sourceInfo, filteredResults);
      else if (sourceInfo.type === 'web-dl') dupeResults = checkWebDLDupe(sourceInfo, filteredResults);
      else if (sourceInfo.type === 'diy' || sourceInfo.type === 'original') dupeResults = checkBluRayDupe(sourceInfo, filteredResults);
      else if (sourceInfo.type === 'encode') dupeResults = checkEncodeDupe(sourceInfo, filteredResults);

      const finalResultsWithValidation = (dupeResults || enabledSites.map(s=>({site:s.name, isDupe: false, reason: '未进行查重(类型未知)', matchedItems: []}))).map(result => {
        const siteInfo = SITES.find(s => s.name === result.site);
        const validation = validateResourceForSite(sourceInfo, siteInfo.key);
        return { ...result, validation };
      });

      updateSearchResults(null, filteredResults, sourceInfo, finalResultsWithValidation);
    } catch (error) {
      updateSearchResults('搜索失败:' + error.message);
    }
  }

  async function autoDetectDupe() {
    // 检查是否启用自动查询
    if (!getAutoQueryEnabled()) {
      console.log('[查重] 自动查询已关闭,不执行自动查重');
      return;
    }

    // 防止重复执行标记
    if (document.querySelector('#dupe-detection-completed')) return;
    const marker = document.createElement('div');
    marker.id = 'dupe-detection-completed';
    marker.style.display = 'none';
    document.body.appendChild(marker);

    console.log('[查重] 自动查询已开启,开始执行查重...');
    
    // 自动查询时显示面板
    panel.classList.add('show');
    toggleBtn.classList.add('active');
    console.log('[查重] 自动查询模式,面板自动显示');
    
    await performDupeCheck();
  }

  function showSearchPanel(sourceInfo) {
    panel.querySelector('.xs-head strong').textContent = '自动查重与规则校验';
    const tagsHtml = sourceInfo.tags.length > 0 ? `<div style="margin-bottom:6px"><strong>提取标签:</strong>${sourceInfo.tags.map(tag => `<span style="background:#e8f4fd;color:#1890ff;padding:2px 6px;border-radius:3px;font-size:11px;margin-right:4px">${esc(tag)}</span>`).join('')}</div>` : '';
    const groupHtml = sourceInfo.releaseGroup ? `<div><strong>发布小组:</strong>${esc(sourceInfo.releaseGroup)}</div>` : '';
    const typeText = sourceInfo.type === 'unknown'
        ? `<span style="color: red; font-weight: bold;">无法识别类型</span>`
        : esc(sourceInfo.type.toUpperCase());

    const imdbHtml = sourceInfo.imdb 
      ? `<div style="color:#9b59b6;"><strong>➤ IMDb:</strong><a href="https://www.imdb.com/title/${esc(sourceInfo.imdb)}/" target="_blank" rel="noopener noreferrer" style="color:#9b59b6;text-decoration:underline;">${esc(sourceInfo.imdb)}</a></div>` 
      : '';
    
    const sourceInfoHtml = `
      <div class="xs-group" style="border:1px solid #eee;border-radius:10px;margin-bottom:8px">
        <div class="xs-group-hd">源种子信息</div>
        <div class="xs-group-bd" style="padding:8px; font-size:12px; line-height:1.8;">
          <div><strong>原始标题:</strong>${esc(sourceInfo.originalTitle)}</div>
          <div><strong>原始副标题:</strong>${esc(sourceInfo.originalSubtitle)}</div>
          <div style="color:#0366d6;"><strong>➤ 搜索主标题:</strong>${esc(sourceInfo.mainTitle)}</div>
          <div style="color:#0366d6;"><strong>➤ 搜索副标题:</strong>${esc(sourceInfo.subtitleTitle)}</div>
          <div style="color:#28a745;"><strong>➤ 季号:</strong>${sourceInfo.season ? esc(sourceInfo.season) : '无'}</div>
          <div style="color:#ff6b6b;"><strong>➤ 年份:</strong>${sourceInfo.year ? sourceInfo.year : '无'}</div>
          ${imdbHtml}
          <div><strong>分类:</strong>${esc(sourceInfo.category)}</div>
          <div><strong>识别类型:</strong>${typeText}</div>
          ${groupHtml}
          ${tagsHtml}
        </div>
      </div>
      <div id="search-results">正在搜索中...</div>`;
    panel.querySelector('.xs-res').innerHTML = sourceInfoHtml;
  }

  function getItemType(item) {
    const { title, subtitle, tags, siteKey } = item;
    if (isRemuxSource(title, subtitle)) return 'Remux';
    if (isWebDLSource(title, subtitle)) return 'WEB-DL';
    if (isBluRaySource(title, subtitle)) {
        const subType = getBluRaySubType(title, subtitle, tags, siteKey);
        if (subType === 'diy') return 'Blu-ray DIY';
        if (subType === 'original') return '原盘';
    }
    if (isEncodeSource(title)) return 'Encode';
    return '无法识别类型';
  }

  function getItemTypeClass(itemType) {
    if (itemType === 'Remux') return 'remux';
    if (itemType === 'WEB-DL') return 'webdl';
    if (itemType === 'Blu-ray DIY') return 'diy';
    if (itemType === '原盘') return 'original';
    if (itemType === 'Encode') return 'encode';
    return '';
  }

  function isSameType(item, sourceInfo) {
    const sourceType = sourceInfo.type;
    if (sourceType === 'remux') return isRemuxSource(item.title, item.subtitle);
    if (sourceType === 'web-dl') return isWebDLSource(item.title, item.subtitle);
    if (sourceType === 'diy' || sourceType === 'original') {
        if (!isBluRaySource(item.title, item.subtitle)) return false;
        const itemBluRayType = getBluRaySubType(item.title, item.subtitle, item.tags);
        const isMatch = itemBluRayType === sourceType;

        // 调试日志:显示BluRay类型匹配情况
        if (!isMatch) {
          console.log(`[查重] BluRay子类型不匹配: 源(${sourceType}) vs 结果(${itemBluRayType}) - ${item.title}`);
        }

        return isMatch;
    }
    if (sourceType === 'encode') return isEncodeSource(item.title);
    return false;
  }

  function updateSearchResults(error, results = null, sourceInfo = null, dupeResults = null) {
    const resultsEl = document.querySelector('#search-results');
    if (!resultsEl) return;
    if (error) {
      resultsEl.innerHTML = `<div class="xs-msg xs-err">${esc(error)}</div>`;
      return;
    }

    let html = '';
    if (dupeResults) {
      html += `<div class="xs-group"><div class="xs-group-hd">查重与规则校验结果</div><div class="xs-group-bd" style="padding:8px;">`;
      for (const result of dupeResults) {
        const { validation } = result;
        let cardStyle, statusText, reasonText, matchedItemsHtml = '';
        if (!validation.allowed) {
          cardStyle = 'background:#ffebe6;border:1px solid #ffccc7;color:#cf1322';
          statusText = '🚫 禁止发布';
          reasonText = validation.reason;
        } else {
          if (result.isDupe) {
            cardStyle = 'background:#fffbe6;border:1px solid #ffe58f;';
            statusText = '⚠️ 发现重复';
          } else {
            cardStyle = 'background:#f6ffed;border:1px solid #b7eb8f;';
            statusText = '✅ 未发现重复';
          }
          reasonText = result.reason || '该站点允许发布此资源';
          if (result.matchedItems && result.matchedItems.length > 0) {
            matchedItemsHtml = '<div style="margin-top:6px;"><strong>匹配项:</strong>';
            result.matchedItems.forEach((item, index) => {
                // --- 核心改进:丰富匹配项的展示信息 ---
                const itemType = getItemType(item);
                const typeClass = getItemTypeClass(itemType);
                const typeBadge = `<span class="item-type ${typeClass}" style="margin-left: 8px; font-size: 10px;">${esc(itemType)}</span>`;
                const seasonInfo = item.season ? `<span class="season-info" style="color: #666; font-size: 11px; margin-left: 4px;">[${item.season}]</span>` : '';
                const yearInfo = item.year ? `<span class="year-info" style="color: #999; font-size: 11px; margin-left: 2px;">[${item.year}]</span>` : '';

                const subtitleHtml = item.subtitle ? `<div class="xs-sub" style="font-size: 11px; margin-left: 10px; margin-top: 3px;">${esc(item.subtitle)}</div>` : '';
                const tagsHtml = item.tags && item.tags.length > 0 ? `<div class="xs-tags" style="margin-top: 5px; margin-left: 10px;">${item.tags.map(t => `<span class="xs-tag">${esc(t)}</span>`).join('')}</div>` : '';
                const hr = index < result.matchedItems.length - 1 ? '<hr style="margin: 6px 0; border: none; border-top: 1px dashed #ddd;">' : '';

                matchedItemsHtml += `<div>
                                      <div style="display: flex; align-items: flex-start; gap: 8px;">
                                        <a href="${item.link}" target="_blank" style="text-decoration:none; flex: 1;">${esc(item.title)}${seasonInfo}${yearInfo}</a>
                                        ${typeBadge}
                                      </div>
                                      ${subtitleHtml}
                                      ${tagsHtml}
                                      ${hr}
                                    </div>`;
                // --- 改进结束 ---
            });
            matchedItemsHtml += '</div>';
          }
        }
        html += `<div style="padding:8px;margin-bottom:8px;border-radius:6px;font-size:12px;${cardStyle}"><div style="display:flex;justify-content:space-between;margin-bottom:4px;font-weight:bold;"><span>${esc(result.site)}</span><span>${statusText}</span></div><div style="color:#666;font-size:11px;">${esc(reasonText)}</div>${matchedItemsHtml}</div>`;
      }
      html += '</div></div>';
    }

    if (results) {
        const renderResultList = (items) => {
            if (!items || items.length === 0) return '<div class="xs-msg" style="padding: 5px; margin: 0;">无结果</div>';
            return items.map(item => {
                const itemType = getItemType(item);
                const typeClass = getItemTypeClass(itemType);
                const seasonInfo = item.season ? `<span class="season-info" style="color: #666; font-size: 12px; margin-left: 8px;">[${item.season}]</span>` : '';
                const yearInfo = item.year ? `<span class="year-info" style="color: #999; font-size: 12px; margin-left: 4px;">[${item.year}]</span>` : '';
                return `
              <div class="xs-card" style="margin-top: 4px; padding: 6px;">
                <div style="display: flex; align-items: flex-start; margin-bottom: 4px; gap: 8px;">
                  <a class="xs-title" href="${item.link}" target="_blank" style="flex: 1;">${esc(item.title)}${seasonInfo}${yearInfo}</a>
                  <span class="item-type ${typeClass}" style="flex-shrink: 0;">${esc(itemType)}</span>
                </div>
                ${item.subtitle ? `<div class="xs-sub" style="font-size: 11px;">${esc(item.subtitle)}</div>` : ''}
                ${item.tags && item.tags.length ? `<div class="xs-tags" style="margin-top: 5px;">${item.tags.map(t => `<span class="xs-tag">${esc(t)}</span>`).join('')}</div>` : ''}
                ${item.failureReason ? `<div style="color: #c41a1a; font-size: 11px; margin-top: 4px; background: #fffbe6; padding: 3px 5px; border-radius: 3px;"><b>筛选失败:</b> ${item.failureReason}</div>` : ''}
              </div>`;
            }).join('');
        };

        html += '<div class="xs-group"><div class="xs-group-hd">原始搜索结果详情</div></div>';
        const searchResultContainer = document.createElement('div');
        searchResultContainer.innerHTML = html;

        results.forEach(siteResult => {
            const { site, mainTitleResults, subtitleTitleResults, imdbResults } = siteResult;
            let siteHtml = '';

            const processResultsForDisplay = (results, sourceInfo) => {
                const originalPassed = results.passedItems || [];
                const originalFailed = results.failedItems || [];

                const sameTypePassed = [];
                const differentTypePassed = [];

                originalPassed.forEach(item => {
                    if (isSameType(item, sourceInfo)) {
                        sameTypePassed.push(item);
                    } else {
                        const itemType = getItemType(item);
                        const reason = `类型不匹配 (源: "${sourceInfo.type.toUpperCase()}", 此结果: "${itemType}")`;
                        differentTypePassed.push({ ...item, failureReason: reason });
                    }
                });
                const combinedFailed = [...differentTypePassed, ...originalFailed];
                return { passed: sameTypePassed, failed: combinedFailed };
            };

            if (sourceInfo.mainTitle && mainTitleResults) {
                const displayResults = processResultsForDisplay(mainTitleResults, sourceInfo);
                siteHtml += `<div class="xs-group" style="margin: 8px;"><div class="xs-group-hd" style="background:#f0f8ff;"><span>${esc(site.name)} - 主标题搜索 ("${esc(sourceInfo.mainTitle)}")</span></div>
                <div class="xs-group-bd">
                    <details open><summary>✅ 通过筛选的结果 (${displayResults.passed.length} 条)</summary>${renderResultList(displayResults.passed)}</details>
                    <details open><summary>❌ 未通过筛选的结果 (${displayResults.failed.length} 条)</summary>${renderResultList(displayResults.failed)}</details>
                </div></div>`;
            }

            if (sourceInfo.subtitleTitle && subtitleTitleResults) {
                const displayResults = processResultsForDisplay(subtitleTitleResults, sourceInfo);
                siteHtml += `<div class="xs-group" style="margin: 8px;"><div class="xs-group-hd" style="background:#f0f8ff;"><span>${esc(site.name)} - 副标题搜索 ("${esc(sourceInfo.subtitleTitle)}")</span></div>
                 <div class="xs-group-bd">
                    <details open><summary>✅ 通过筛选的结果 (${displayResults.passed.length} 条)</summary>${renderResultList(displayResults.passed)}</details>
                    <details open><summary>❌ 未通过筛选的结果 (${displayResults.failed.length} 条)</summary>${renderResultList(displayResults.failed)}</details>
                </div></div>`;
            }

            if (sourceInfo.imdb && imdbResults) {
                const displayResults = processResultsForDisplay(imdbResults, sourceInfo);
                siteHtml += `<div class="xs-group" style="margin: 8px;"><div class="xs-group-hd" style="background:#e6f7ff;"><span>${esc(site.name)} - IMDb搜索 ("${esc(sourceInfo.imdb)}")</span></div>
                 <div class="xs-group-bd">
                    <details open><summary>✅ 通过筛选的结果 (${displayResults.passed.length} 条)</summary>${renderResultList(displayResults.passed)}</details>
                    <details open><summary>❌ 未通过筛选的结果 (${displayResults.failed.length} 条)</summary>${renderResultList(displayResults.failed)}</details>
                </div></div>`;
            }
            if(siteHtml) searchResultContainer.innerHTML += siteHtml;
        });
       resultsEl.innerHTML = searchResultContainer.innerHTML;
    } else if (dupeResults) {
        resultsEl.innerHTML = html;
    }
  }

  function main() {
    const currentSiteKey = DOMAIN_TO_KEY[window.location.hostname];
    const isMTeam = currentSiteKey === 'mteam';

    const run = () => {
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', () => {
            if (isMTeam) {
              console.log('[查重] MTeam站点-DOMContentLoaded后延迟3秒启动...');
              setTimeout(autoDetectDupe, 3000);
            } else {
              autoDetectDupe();
            }
          });
        } else {
          if (isMTeam) {
            console.log('[查重] MTeam站点-页面已加载,延迟3秒启动...');
            setTimeout(autoDetectDupe, 3000);
          } else {
            autoDetectDupe();
          }
        }
    };

    // MTeam需要更长的初始延迟等待React应用启动
    const initialDelay = isMTeam ? 2000 : 500;
    if (isMTeam) {
      console.log('[查重] MTeam站点检测到,初始延迟2秒...');
    }
    setTimeout(run, initialDelay);
  }

  main();

})();