我堡不可说自动查重

我堡不可说站点严格查重

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

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址