、搜索框 value、
// 面包屑、乃至「没有找到 X」这句话本身),于是零结果页也能凑满 3 分被标成
// 「有资源」——实测对一部不存在的影片误报率高达 94%。
// 新算法只认「结果行」:链接文本或 href 命中片名,且该行带有资源证据
// (磁力/网盘直链、发布标记、年份、详情链接);命中文字若只是查询词的回显、
// 或该行自带「无结果」文案,一律否决。
// ============================================================
var NOISE_TAG = {SCRIPT:1,STYLE:1,TEXTAREA:1,TITLE:1,NOSCRIPT:1,IFRAME:1,SVG:1,
HEAD:1,SELECT:1,OPTION:1,TEMPLATE:1,FORM:1,NAV:1,BUTTON:1,HEADER:1,FOOTER:1};
var NOISE_HINT = /(search[-_]?(form|box|input)|searchform|site[-_]?nav|navbar|breadcrumb|crumb|copyright|sidebar|login|signin|advert|^ad($|[-_])|related|recommend|hot[-_]?(list|search)|sugg|autocomplete|logo|menu|top[-_]?nav)/i;
var ROW_CLASS = /(item|result|torrent|card|list|post|entry|vod|movie|film|search|box|row|col|detail|share|resource|playlist)/i;
// v5.4.4: 左右加词边界。旧版无边界,'favicon'/'comments-navi' 里的 'avi' 会
// 让新闻卡片凑出发布标记,把无资源站点标绿(影猫导航误报根因之一)
var RELEASE = /(^|[^a-z])(1080p|720p|2160p|4k|uhd|blu[\s-]?ray|bd[\s-]?(rip|remux)|web[\s-]?dl|webrip|x26[45]|h[\s.]?26[45]|hevc|av1|mkv|mp4|avi|rmvb|dvdrip|hdtv|remux|aac|ac-?3|eac3|dts|atmos|hdr|10bit|国语|粤语|中文字幕|中字|中英双字|英字|无修|高清|超清|蓝光|杜比|原版|精校|s\d{1,2}e\d{1,2}|第\s*\d+\s*集|全\s*\d+\s*[集话])(?![a-z])/i;
var RESOURCE_HREF = /(magnet:|ed2k:\/\/|thunder:\/\/|ftp:\/\/|\.torrent|pan\.baidu\.com|alipan\.com|aliyundrive|quark\.cn|115\.com|lanzou|pan\.xunlei|123pan|123684|cloud\.189|wopan|caiyun|\.m3u8|\.mkv)/i;
var DETAIL_HREF = /\/(video|vod|detail|movie|film|tv|p|s|dya|dy|dl|down)[\/?]/i;
var SEARCHISH_HREF = /([?&](q|kw|keyword|wd|s|searchword|query|search|t|ihq)=|\/search|\/s\?|\/so\?|\/tags?\/)/i;
var NO_RESULT = /(没有找到|未找到|暂无相关|没有相关|搜索不到|无搜索结果|没有结果|未搜索到|暂无结果|no\s*results?\s*(found)?|nothing\s*found|0\s*(results?|条|个结果))/i;
function ddmNorm(v) {
if (v == null) return '';
var s = String(v);
try { s = s.normalize('NFKC'); } catch (e) {}
s = s.toLowerCase().replace(/&[a-z#0-9]+;/gi, ' ');
return s.replace(/[^0-9a-z一-鿿]/g, '');
}
function ddmDecode(v) { try { return decodeURIComponent(String(v || '')); } catch (e) { return String(v || ''); } }
function elText(el, limit) {
var t = (el && (el.textContent || el.getAttribute('title'))) || '';
return t.length > limit ? t.slice(0, limit) : t;
}
function buildNeedles(movie) {
var out = [], seen = {};
function push(v, cutColon) {
if (!v) return;
var list = String(v).split(/[/||]/);
for (var i = 0; i < list.length; i++) {
var piece = list[i];
if (cutColon) piece = String(piece).split(/[::]/)[0];
var n = ddmNorm(piece);
if (n.length >= 2 && !seen[n]) { seen[n] = 1; out.push({ n: n, strong: n.length >= 4 }); }
}
}
push(movie.cnTitle, 1); push(movie.enTitle, 1);
push(movie.kw, 0); push(movie.kwEn, 0);
return out;
}
function inNoise(el) {
var n = el, hops = 0;
while (n && n.nodeType === 1 && hops++ < 12 && !/^(BODY|HTML)$/.test(n.tagName)) {
if (NOISE_TAG[n.tagName]) return true;
if (n.className && typeof n.className === 'string' && NOISE_HINT.test(n.className)) return true;
if (n.id && NOISE_HINT.test(n.id)) return true;
n = n.parentElement;
}
return false;
}
function findRow(a) {
var best = a.parentElement || a, n = a.parentElement, hops = 0;
while (n && n.nodeType === 1 && hops++ < 8 && !/^(BODY|HTML)$/.test(n.tagName)) {
var t = elText(n, 1500);
if (t.length > 1400) break;
var strong = /^(LI|TR|ARTICLE|DD)$/.test(n.tagName) ||
ROW_CLASS.test(n.className || '') || ROW_CLASS.test(n.id || '');
if (strong && t.length >= 8) { best = n; if (t.length > 90) break; }
else if (t.length > 90) break;
else best = n;
n = n.parentElement;
}
return best;
}
function rowEvidence(row, movie, needles) {
var txt = elText(row, 1500), inner = row.innerHTML || '', ev = 0, why = [];
if (RESOURCE_HREF.test(inner)) { ev += 3; why.push('resource-link'); }
var an = row.getElementsByTagName('a');
for (var i = 0; i < an.length && i < 25; i++) {
var at = ddmNorm(elText(an[i], 400));
var got = false;
for (var k = 0; k < needles.length; k++) {
var nd = needles[k].n;
if (at.indexOf(nd) === -1) continue;
var extra = at.split(nd).join('');
// v5.4.4: 年份限 1930-2039 —— 旧版 (19|20)\d\d 会把新闻社名 '1905电影网'
// 当成发布年份,让新闻卡片通过结果行校验(影猫导航误报根因之二)
if (extra.length > 1 && (RELEASE.test(extra) || /(19[3-9]\d|20[0-3]\d)/.test(extra))) {
ev += 2; why.push('titled-entry'); got = true; break;
}
}
if (got) break;
}
if (RELEASE.test(txt) || RELEASE.test(inner)) { ev += 2; why.push('release-token'); }
var yr = String(movie.year || '');
if (yr.length === 4 && new RegExp('(^|[^0-9])' + yr + '([^0-9]|$)').test(txt)) { ev += 2; why.push('year'); }
if (DETAIL_HREF.test(inner)) { ev += 1; why.push('detail-link'); }
return { ev: ev, why: why };
}
function verifyResults(htmlText, movie) {
var none = { available:false, hits:0, ev:0, why:[], sample:'', reason:'rejected' };
if (!htmlText || htmlText.length < 300) return none;
var doc = null;
try { doc = new DOMParser().parseFromString(htmlText, 'text/html'); } catch (e) { return none; }
if (!doc || !doc.body) return none;
var needles = buildNeedles(movie);
if (!needles.length) return none;
var anchors = doc.body.getElementsByTagName('a');
var rows = [], hits = 0, best = 0, bestWhy = [], sample = '';
var n = Math.min(anchors.length, 3000);
for (var i = 0; i < n; i++) {
var a = anchors[i];
var at = ddmNorm(elText(a, 400));
var rawHref = a.getAttribute('href') || '';
var href = ddmNorm(ddmDecode(rawHref));
var matched = null, strong = false, hrefOnly = true;
for (var k = 0; k < needles.length; k++) {
var nd = needles[k].n;
var inText = at.indexOf(nd) !== -1;
// a nav / filter tab also carries the query, but only inside a search URL
// (?s=X, /search/X) - that is an echo, never a result entry
if (!inText && href.indexOf(nd) === -1) continue;
if (!inText && SEARCHISH_HREF.test(rawHref)) continue;
matched = nd;
if (inText) hrefOnly = false;
if (needles[k].strong) strong = true;
}
if (!matched) continue;
if (inNoise(a)) continue;
var row = findRow(a);
if (rows.indexOf(row) !== -1) continue;
rows.push(row);
if (rows.length > 400) break;
if (NO_RESULT.test(elText(row, 900))) continue;
var e = rowEvidence(row, movie, needles);
// an href-only hit is normally a sort/tab link repeating the query; it
// counts only when the row itself carries a magnet or netdisk link
if (hrefOnly && e.why.indexOf('resource-link') === -1) continue;
if (!strong && e.ev < 4) continue;
if (e.ev < 2) continue;
hits++;
if (e.ev > best) { best = e.ev; bestWhy = e.why; sample = elText(a, 60); }
}
return { available: hits >= 1 && best >= 2, hits: hits, ev: best,
why: bestWhy, sample: sample, reason: hits ? 'rows' : 'no-rows' };
}
// ============================================================
// v4.8.0 智能关键词选择 (按站点类型选最优候选;v5.4.4: 上限 3→4, 中文站补英文名兜底)
// ============================================================
function pickCandidates(site, movie) {
var base = movie.kwCandidates || [];
var list = [];
function push(s) { if (s && list.indexOf(s) === -1 && base.indexOf(s) !== -1) list.push(s); }
var cat = site._cat || 'online';
if (site.useEn || cat === 'bt_en') {
// 磁力/BT国际站:优先英文
push(movie.enTitle);
push(movie.enTitle ? (movie.enTitle + (movie.year ? ' ' + movie.year : '')) : '');
push(movie.cnTitle);
push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : '');
} else if (cat === 'dl_zh') {
// 中文下载站:中文标题+年份优先
push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : '');
push(movie.cnTitle);
var mp = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : '';
if (mp && mp !== movie.cnTitle) push(mp);
// v5.4.4: 中文名全败后英文原名兜底(部分国内站收录英文名)
push(movie.enTitle);
} else if (cat === 'pan') {
// 网盘搜索站:纯中文标题不带年份
push(movie.cnTitle);
var mp2 = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : '';
if (mp2 && mp2 !== movie.cnTitle) push(mp2);
push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : '');
push(movie.enTitle);
} else {
// 在线影视站 / 默认:中文标题
push(movie.cnTitle);
var mp3 = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : '';
if (mp3 && mp3 !== movie.cnTitle) push(mp3);
push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : '');
push(movie.enTitle);
}
// 剧集季数拼接补充
if (movie.season && movie.cnTitle) push(movie.cnTitle + ' ' + movie.season);
var out = [];
for (var i = 0; i < list.length && out.length < 4; i++) { if (list[i]) out.push(list[i]); }
return out;
}
// ============================================================
// v4.8.0 多关键词检测 (无结果自动切换, 最多4个)
// ============================================================
function testSite(site, movie, cb) {
// v4.7.0: 健康度缓存命中(24h 内不可用)直接跳过
if (isHealthSkipped(site.name)) { cb({accessible:false, time:0, available:false, cachedSkip:true}); return; }
var candidates = pickCandidates(site, movie);
if (!candidates || candidates.length === 0) { cb({accessible:false, time:999999, available:false, skip:true}); return; }
tryKeywords(site, movie, candidates, 0, cb);
}
function tryKeywords(site, movie, cands, idx, cb) {
var kw = cands[idx];
var startTime = Date.now();
var timeout = 8000;
function finish(extra) {
extra = extra || {};
var r = { accessible:true, time: Date.now() - startTime, available:false };
for (var k in extra) { if (hasOwn(extra, k)) r[k] = extra[k]; }
cb(r);
}
function nextOrFail() {
if (idx + 1 < cands.length) {
setTimeout(function() { tryKeywords(site, movie, cands, idx + 1, cb); }, 20);
} else {
// 站点答了但本片无资源 ≠ 站点坏了:不写健康度缓存,
// 否则一部冷门票就会把该站拉黑 24h,换部片子也被误判为不可用
finish();
}
}
var url = site.url + encodeURIComponent(kw) + (site.suffix || '');
gmXhr({url:url, timeout:timeout, retries:1, noCache:true}, function(err, res) {
if (err || !res || !res.responseText) { setHealth(site.name, false); finish({accessible:false, time:999999, error:true}); return; }
var text = res.responseText;
try {
if (isBlocked(text)) { finish({ blocked:true }); return; }
// 所有站点统一走结果条目校验:能证明该片在该站有结果才算「有资源」
var v = verifyResults(text, movie);
if (v.available) { setHealth(site.name, true); finish({ available:true, hits:v.hits, ev:v.ev }); return; }
nextOrFail();
} catch(e) {
logErr('tryKeywords parse error', e, site.name);
setHealth(site.name, true);
// v5.4.4: 解析异常 ≠ 有资源。旧版在此 finish({available:true}),
// 任何异常都会把站点标绿,与「绿=已证明有结果行」的承诺相悖
finish({ available:false, error:true });
}
});
}
// ============================================================
// v4.7.0 分区渲染 (支持子区域定位);v5.2.0 Tier3 全自动检测
// ============================================================
function renderSectionTiered(opts) {
var sitesGroups = opts.sitesGroups;
var movie = opts.movie;
var sectionId = opts.sectionId;
var section = document.getElementById(sectionId);
if (!section) return;
var totalSites = (sitesGroups[1]||[]).length + (sitesGroups[2]||[]).length + (sitesGroups[3]||[]).length;
var totalFound = 0;
var totalDone = 0;
var allResults = {};
var sectionExpanded = false;
var toastShown = false;
// v4.7.0: 支持通过 bodyId/arrowId 精确定位子区域(如「更多网盘搜索站」)
var sectionBody = opts.bodyId ? document.getElementById(opts.bodyId) : section.querySelector('.ddm-section-body');
var sectionArrow = opts.arrowId ? document.getElementById(opts.arrowId) : section.querySelector('.ddm-section-header .ddm-arrow');
// v5.3.0: 无资源站点检测完成即隐藏,仅显示可用站点;
// 梯队可见性用增量计数维护(替代 v5.2.1 每次完成后的全量重扫)
var tierState = {};
function initTierWraps() {
for (var t = 1; t <= 3; t++) {
var cont = document.getElementById(opts.containerPrefix + '-t' + t + '-links');
if (!cont) continue;
var wrap = cont.parentNode;
if (!wrap || wrap.className.indexOf('ddm-tier-block') === -1) continue;
tierState[t] = { wrap: wrap, pending: 0, avail: 0 };
wrap.style.display = 'none';
}
}
function reportTier(tier) {
var st = tierState[tier];
if (st) st.wrap.style.display = (st.pending > 0 || st.avail > 0) ? '' : 'none';
}
initTierWraps();
function autoExpandSection() {
if (sectionExpanded) return;
sectionExpanded = true;
if (sectionBody) {
sectionBody.style.display = 'block';
// v4.9.0: 展开内容区淡入动画
animateBodyIn(sectionBody);
}
if (sectionArrow) sectionArrow.textContent = '▾';
updateSummaryCount();
}
function updateSummaryCount() {
var summaryEl = document.getElementById('ddm-summary-' + opts.summaryKey);
if (summaryEl) {
summaryEl.textContent = totalFound;
if (totalFound > 0) summaryEl.className = 'ddm-summary-num has-val';
}
}
function updateProgress() {
if (opts.barEl) opts.barEl.style.width = Math.round((totalDone/totalSites)*100) + '%';
if (opts.textEl) opts.textEl.textContent = totalDone + '/' + totalSites;
if (opts.countEl) {
opts.countEl.textContent = totalFound + '/' + totalSites;
if (totalFound > 0) opts.countEl.className = 'ddm-section-count has-result';
}
updateSummaryCount();
}
function finalizeSection() {
if (opts.barEl) { opts.barEl.style.width = '100%'; opts.barEl.classList.add('done'); }
if (opts.textEl) opts.textEl.textContent = totalDone + '/' + totalSites;
if (opts.countEl) {
opts.countEl.textContent = totalFound + '/' + totalSites;
if (totalFound > 0) opts.countEl.className = 'ddm-section-count has-result';
}
if (section) {
section.classList.add('ddm-section-completed');
if (totalFound > 0) section.classList.add('ddm-section-has-result');
}
// v5.0.0: 无结果 → 折叠分区为一行居中次级文案「暂未找到相关资源」
var emptyTag = opts.emptyElId ? document.getElementById(opts.emptyElId) : null;
if (totalFound === 0) {
if (sectionBody) sectionBody.style.display = 'none';
if (sectionArrow) sectionArrow.textContent = '▸';
if (emptyTag) {
emptyTag.style.display = 'inline';
var hdrTitle = section ? section.querySelector('.ddm-section-title') : null;
var hdrCount = section ? section.querySelector('.ddm-section-count') : null;
if (hdrTitle) hdrTitle.style.display = 'none';
if (hdrCount) hdrCount.style.display = 'none';
}
} else {
if (emptyTag) emptyTag.style.display = 'none';
}
for (var t = 1; t <= 3; t++) reportTier(t);
updateSummaryCount();
// v4.9.0: 完成有结果 → Toast 通知
if (totalFound > 0 && !toastShown && opts.toastMsg) {
toastShown = true;
showToast(opts.toastMsg + ':找到 ' + totalFound + ' 个可用站点');
}
for (var t in allResults) {
if (!hasOwn(allResults, t)) continue;
var arr = allResults[t];
arr.sort(function(a, b) {
var ra = a.result || {}, rb = b.result || {};
if (ra.available && !rb.available) return -1;
if (!ra.available && rb.available) return 1;
if (ra.accessible && !rb.accessible) return -1;
if (!ra.accessible && rb.accessible) return 1;
return ra.time - rb.time;
});
var cont = document.getElementById(opts.containerPrefix + '-t' + t + '-links');
if (cont) {
for (var i = 0; i < arr.length; i++) cont.appendChild(arr[i].a);
}
}
}
function runTier(tier) {
var sites = sitesGroups[tier] || [];
var containerId = opts.containerPrefix + '-t' + tier + '-links';
var container = document.getElementById(containerId);
if (!container || sites.length === 0) {
if (tier < 3) runTier(tier + 1);
else finalizeSection();
return;
}
container.innerHTML = '';
var tierResults = [];
allResults[tier] = tierResults;
var linkItems = [];
for (var i = 0; i < sites.length; i++) {
var site = sites[i];
var kw = site.useEn ? movie.kwEn : movie.kw;
var url = kw ? (site.url + encodeURIComponent(kw) + (site.suffix || '')) : 'javascript:void(0)';
var a = document.createElement('a');
a.href = url; a.target = '_blank'; a.rel = 'noopener';
a.textContent = site.name;
if (site.desc) a.title = site.desc;
else if (!kw) a.title = site.name;
a.className = 'checking';
// v4.9.0: 逐个弹入瀑布动画 (30ms 递增延迟)
a.style.animation = 'ddm-link-pop .3s cubic-bezier(0.34,1.56,0.64,1) both';
a.style.animationDelay = (i * 30) + 'ms';
// v4.9.0: Tooltip 数据
if (site.desc) a.setAttribute('data-desc', site.desc);
a.setAttribute('data-tier', TIER_CONFIG[site.tier || 2].label);
(function(link) {
link.addEventListener('mouseenter', function(ev) { showTooltipFor(link, ev); }, false);
link.addEventListener('mouseleave', hideTooltip, false);
link.addEventListener('click', hideTooltip, false);
})(a);
container.appendChild(a);
linkItems.push({ site:site, a:a, kw:kw, result:null });
}
// Tier3 国际站与普通站点同样自动检测,仅按速度顺位最后执行
var _token = container._ddmToken || (container._ddmToken = '_ddm_token_' + Math.random().toString(36).slice(2, 9));
var tierFound = 0;
var st = tierState[tier];
if (st) { st.pending = linkItems.length; reportTier(tier); }
pool(linkItems, 0, function(item, idx, workerDone) {
setTimeout(function() {
testSite(item.site, movie, function(r) {
if (container._ddmToken !== _token) { workerDone(); return; }
item.result = r;
item.a.textContent = item.site.name;
// v4.7.0: 记录响应时间用于动态并发调节
if (r.time > 0) updateConcurrency(r.time);
if (r.available) {
item.a.className = 'available';
item.a.title = item.site.name + ' ✅ 有资源 (' + r.time + 'ms)';
if (item.site.desc) item.a.title += ' - ' + item.site.desc;
tierFound++; totalFound++;
autoExpandSection();
if (opts.countEl) {
opts.countEl.textContent = totalFound + '/' + totalSites;
opts.countEl.className = 'ddm-section-count has-result';
}
} else if (r.cachedSkip) {
item.a.className = 'skipped';
item.a.title = item.site.name + ' ⏭ 24h内检测不可用,已跳过';
} else if (r.blocked) {
item.a.className = 'blocked';
item.a.title = item.site.name + ' 🚧 被拦截 (CF挑战/验证码)';
} else if (r.accessible) {
if (r.error) {
item.a.className = 'error';
item.a.title = item.site.name + ' ⚠ 解析异常 (' + r.time + 'ms)';
} else {
item.a.className = 'unavailable';
item.a.title = item.site.name + ' ❌ 无资源 (' + r.time + 'ms)';
}
} else {
item.a.className = 'inaccessible';
item.a.title = item.site.name + ' 🚫 无法访问';
}
tierResults.push(item);
if (st) {
st.pending--;
if (r.available) st.avail++; else item.a.classList.add('ddm-filtered');
reportTier(tier);
}
workerDone();
});
}, randomInt(20, 150));
}, function(finished, total) {
if (container._ddmToken !== _token) return;
var done = 0;
for (var t = 1; t <= 3; t++) {
if (t < tier) done += (sitesGroups[t]||[]).length;
else if (t === tier) done += finished;
}
totalDone = done;
updateProgress();
}, function() {
if (container._ddmToken !== _token) return;
totalDone = 0;
for (var t = 1; t <= tier; t++) totalDone += (sitesGroups[t]||[]).length;
updateProgress();
log('tier ' + tier + ' done, found=' + tierFound + ', section=' + sectionId);
if (tier < 3) {
setTimeout(function() { runTier(tier + 1); }, 80);
} else {
finalizeSection();
}
});
}
runTier(1);
}
// ============================================================
// v5.0.0 构建面板 — 三大类顺序:下载→网盘→在线
// ============================================================
function removePanel() {
var p = document.getElementById('ddm-panel');
if (!p || !p.parentNode) return;
var wrap = p.parentNode;
if (wrap.id === 'ddm-float-wrap') wrap.parentNode.removeChild(wrap); else wrap.removeChild(p);
}
function resetCaches() { _xhrCache = {}; _requestLastTime = {}; }
function buildPanel(movie) {
log('buildPanel v'+VER);
var target = document.querySelector('.aside');
if (!target) target = document.body;
if (document.getElementById('ddm-panel')) return document.getElementById('ddm-panel');
var dlN = DOWNLOAD_SITES.length;
var panN = PAN_SEARCH_SITES.length;
var onlineN = ONLINE_SITES.length;
var tDl = countTier(DOWNLOAD_SITES);
var tPan = countTier(PAN_SEARCH_SITES);
var tOnline = countTier(ONLINE_SITES);
var t1Total = tPan[1] + tOnline[1] + tDl[1];
var t2Total = tPan[2] + tOnline[2] + tDl[2];
var t3Total = tPan[3] + tOnline[3] + tDl[3];
var totalAll = t1Total + t2Total + t3Total;
function tierBlock(prefix, tier, count) {
if (count === 0) return '';
return '' +
'
' + TIER_CONFIG[tier].label + ' · ' + TIER_CONFIG[tier].desc + '' +
'
' +
'
';
}
var panel = document.createElement('div');
panel.id = 'ddm-panel';
panel.className = 'ddm-panel';
panel.innerHTML =
'' +
'' +
'
0下载
' +
'
0网盘直链
' +
'
0网盘搜索
' +
'
0在线
' +
'
' +
// v4.9.0: 实时过滤搜索框
'' +
'🔎' +
'' +
'
' +
// 1. 下载资源(首位)
'' +
'' +
'
' +
'
' +
'
0/' + dlN + '' +
'
' +
'
' +
tierBlock('ddm-dl', 1, tDl[1]) +
tierBlock('ddm-dl', 2, tDl[2]) +
tierBlock('ddm-dl', 3, tDl[3]) +
'
' +
'
' +
// 2. 网盘资源(含API+搜索站)
'' +
'' +
'
搜索中
' +
'
' +
'' +
'
' +
'
' +
'
0/' + panN + '' +
'
' +
'
' +
tierBlock('ddm-pansearch', 1, tPan[1]) +
tierBlock('ddm-pansearch', 2, tPan[2]) +
tierBlock('ddm-pansearch', 3, tPan[3]) +
'
' +
'
' +
// 3. 在线观看(末位)
'' +
'' +
'
' +
'
' +
'
0/' + onlineN + '' +
'
' +
'
' +
tierBlock('ddm-online', 1, tOnline[1]) +
tierBlock('ddm-online', 2, tOnline[2]) +
tierBlock('ddm-online', 3, tOnline[3]) +
'
' +
'
' +
'' +
'总计 ' + totalAll + ' 站点' +
'快 ' + t1Total + '' +
'中 ' + t2Total + '' +
'慢 ' + t3Total + '' +
'
';
try {
if (target === document.body) {
var fw = document.createElement('div');
fw.id = 'ddm-float-wrap';
fw.style.cssText = 'position:fixed;right:18px;top:78px;width:384px;max-height:86vh;overflow-y:auto;z-index:99998;background:transparent;border-radius:20px;';
fw.appendChild(panel);
document.body.appendChild(fw);
} else {
target.insertBefore(panel, target.firstChild);
}
log('panel injected v'+VER);
} catch(e) {
logErr('panel inject failed', e);
try { document.body.appendChild(panel); } catch(e2) {}
}
var headers = panel.querySelectorAll('.ddm-section-header[data-sec]');
for (var hi = 0; hi < headers.length; hi++) {
headers[hi].addEventListener('click', function(e) {
var key = this.getAttribute('data-sec');
var body = document.getElementById('ddm-' + key + '-body');
var arrow = this.querySelector('.ddm-arrow');
if (!body || !arrow) return;
var hid = body.style.display === 'none';
body.style.display = hid ? 'block' : 'none';
arrow.textContent = hid ? '▾' : '▸';
// v4.9.0: 展开时内容区淡入动画
if (hid) {
animateBodyIn(body);
// v5.0.0: 展开后隐藏空结果提示并恢复标题/计数
var et = document.getElementById('ddm-' + key + '-empty');
if (et) et.style.display = 'none';
var ht = this.querySelector('.ddm-section-title');
if (ht) ht.style.display = '';
var hc = this.querySelector('.ddm-section-count');
if (hc) hc.style.display = '';
}
}, false);
}
// v4.9.0: 实时过滤搜索框
var searchInput = document.getElementById('ddm-search');
if (searchInput) {
searchInput.addEventListener('input', function() {
var q = (this.value || '').trim().toLowerCase();
var links = panel.querySelectorAll('.ddm-links a');
for (var li = 0; li < links.length; li++) {
var nm = (links[li].textContent || '').toLowerCase();
var show = !q || nm.indexOf(q) !== -1;
if (show) links[li].classList.remove('ddm-search-hide');
else links[li].classList.add('ddm-search-hide');
}
}, false);
}
var reloadBtn = document.getElementById('ddm-reload');
if (reloadBtn) {
reloadBtn.addEventListener('click', function(e) {
e.preventDefault();
removePanel(); resetCaches();
setTimeout(function(){ initAll(movie); }, 100);
}, false);
}
return panel;
}
// ============================================================
// v4.7.0 DNS预解析 (Tier1 站点域名, 最多6个)
// ============================================================
function addDnsPrefetch() {
try {
var head = document.head || document.documentElement;
if (!head) return;
var seen = {}, domains = [];
var all = DOWNLOAD_SITES.concat(PAN_SEARCH_SITES, ONLINE_SITES);
for (var i = 0; i < all.length && domains.length < 6; i++) {
if (all[i].tier !== 1) continue;
var host = (all[i].url || '').replace(/^https?:\/\//, '').split('/')[0];
if (!host || seen[host]) continue;
seen[host] = 1; domains.push(host);
var link = document.createElement('link');
link.rel = 'dns-prefetch'; link.href = '//' + host;
head.appendChild(link);
}
if (domains.length > 0) log('dns-prefetch: ' + domains.join(', '));
} catch(e) { logErr('addDnsPrefetch error', e); }
}
// ============================================================
// 初始化
// ============================================================
function initAll(movie) {
// v5.4.5: 刷新按钮 removePanel 后 window._ddm_initialized 仍为 true,旧版在此直接
// return → 面板消失不再出现(表现为「点刷新就闪退」)。改以「面板是否已在 DOM」为唯一
// 判据:面板在就跳过,面板被移除过就能正常重建,两条重载路径行为一致。
if (document.getElementById('ddm-panel')) return;
window._ddm_initialized = true;
try {
addCSS(CSS);
addDnsPrefetch();
tagSiteCategories();
var panel = buildPanel(movie);
if (!panel) { window._ddm_initialized = false; return; }
// 分区检测:三大类共用同一渲染流程(网盘直链 API 与「更多网盘搜索站」同属第二分区)
function runSection(key, sites, toast, sectionId, bodyId, arrowId) {
var pre = 'ddm-' + key;
renderSectionTiered({
sitesGroups: groupByTier(sites), movie: movie,
sectionId: sectionId || 'ddm-sec-' + key,
containerPrefix: pre, bodyId: bodyId, arrowId: arrowId,
summaryKey: key, toastMsg: toast, emptyElId: pre + '-empty',
barEl: document.getElementById(pre + '-bar'),
textEl: document.getElementById(pre + '-prog'),
countEl: document.getElementById(pre + '-cnt')
});
}
runSection('dl', DOWNLOAD_SITES, '🧲 下载资源');
// 网盘直链 API(第二分区上半部)
var panBody = document.getElementById('ddm-pan-body');
var panBadge = document.getElementById('ddm-pan-badge');
var panCnt = document.getElementById('ddm-pan-cnt');
var panSec = document.getElementById('ddm-sec-pan');
if (panBody) {
searchPanApi(movie.kw, function(items) {
if (!panBody) return;
if (items && items.length > 0) {
var html = '';
panBody.innerHTML = html;
if (panCnt) { panCnt.textContent = items.length + '条'; panCnt.className = 'ddm-section-count has-result'; }
if (panBadge) { panBadge.className='ddm-progress-badge done'; panBadge.textContent='✓ 找到' + items.length + '条'; }
if (panSec) { panSec.classList.add('ddm-section-completed'); panSec.classList.add('ddm-section-has-result'); }
// v4.9.0: 网盘直链 Toast
showToast('☁️ 网盘直链:找到 ' + items.length + ' 条资源');
var summaryEl = document.getElementById('ddm-summary-pan');
if (summaryEl) { summaryEl.textContent = items.length; summaryEl.className = 'ddm-summary-num has-val'; }
var pwds = panBody.querySelectorAll('.ddm-pan-pwd');
for (var p = 0; p < pwds.length; p++) {
pwds[p].addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
var pwd = this.getAttribute('data-pwd');
var ok = copyText(pwd);
var old = this.innerHTML;
var self = this;
this.className = 'ddm-pan-pwd copied';
this.innerHTML = ok ? '✅已复制' : '失败';
setTimeout(function(){ self.className='ddm-pan-pwd'; self.innerHTML = old; }, 1500);
}, false);
}
} else {
panBody.innerHTML = '未找到网盘资源,可查看下方搜索站
';
if (panBadge) { panBadge.className='ddm-progress-badge done-none'; panBadge.textContent='无结果'; }
if (panSec) panSec.classList.add('ddm-section-completed');
if (panCnt) panCnt.textContent = '0条';
}
});
}
runSection('pansearch', PAN_SEARCH_SITES, '☁️ 网盘搜索', 'ddm-sec-pan', 'ddm-pansearch-body', 'ddm-pansearch-arrow');
runSection('online', ONLINE_SITES, '📺 在线观看');
// IMDb评分注入
if (movie.imdbId) {
try {
gmXhr({url:'https://www.imdb.com/title/'+movie.imdbId+'/',timeout:12000,retries:2,headers:{'Accept-Language':'en-US,en;q=0.9','Referer':'https://www.google.com/'}}, function(err, res) {
if (err || !res || !res.responseText) return;
try {
// v5.4.1: 三级回退取分(参考 4.1.1 版思路)——IMDb 页面结构多变,
// 单一正则经常取不到值。优先级:__NEXT_DATA__ > ld+json > 正则
var rating = '';
try {
var nd = res.responseText.match(/