您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
我堡不可说站点严格查重
// ==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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); 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或关注我们的公众号极客氢云获取最新地址