// ==UserScript==
// @name 豆瓣电影下载大师
// @namespace https://github.com/Shshuaicc/douban-download-master
// @version 5.2.1
// @description 豆瓣电影详情页下载/网盘/在线 三大类资源聚合搜索 v5.2.1 - 移除复制按钮·界面精简
// @author Shshuaicc
// @match *://movie.douban.com/subject/*
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// ==/UserScript==
/*
* ============================================================
* 豆瓣电影下载大师 v5.2.1
* 作者: Shshuaicc
* 更新日志:
* v5.2.1 - 界面精简
* 【组件移除】删除「📋 复制全部可用链接」按钮(含 addCopyBtn 逻辑与亮/暗 CSS)
* v5.2.0 - 纯白实底化·结果纯化·国际站全自动
* 【实底配色】面板/启动标记去毛玻璃改纯色底:亮色 #ffffff、暗色 #1c1c1e
* - 分区卡片亮色 rgba(255,255,255,0.5)→#f6f6f7 浅灰分层;边框/阴影重配
* - backdrop-filter 全删(Toast/Tip 除外),饱和度问题从根源消除
* 【结果纯化】移除「显示全部 (N)」开关:无资源站点检测后直接隐藏,仅显示可用站点
* 【国际站全自动】移除「🌐 检测 N 个国际源」手动触发器:Tier3 与普通站点同样自动检测,
* 仅按速度特点 1→2→3 顺序最后执行
* v5.1.0 - 配色可读性重构·去荧光·全量淡字强化
* 【配色体系】「可用绿」统一双模令牌:亮色 #1A7F37/#2DA44E,暗色 #4CC38A
* - 毛玻璃饱和度 180%→150%,去除荧光泛光;图标饱和滤镜归 1.0
* - 修复整体 opacity 叠加导致的人眼不可读文字:
* stats 0.35→0.75 / filter-toggle 0.4→0.75 / inaccessible 0.35→0.55
* - 可用站点字重 600→500,10-11px 小字对比度全部 ≥4.6:1 (WCAG AA)
* v5.0.0 - 视觉降噪重构·信息提纯·双模适配
* 【配色体系】面板基底 rgba(255,255,255,0.82)/rgba(35,35,38,0.78) + blur(40px) saturate(180%)
* - 边框统一 rgba(120,120,128,0.12);可用态去荧光实底,改极淡底+文字着色+微描边
* - 强调色收敛为 iOS Blue #007AFF/#0A84FF;文本三级层级(主0.9/次0.5)
* 【信息密度】默认仅显示有效结果;「显示全部 (N)」低调文字链开关
* - Tier3 国际站完全折叠,仅显示「🌐 检测 N 个国际源」触发器
* - 空结果分区折叠为居中「暂未找到相关资源」,点击展开失败详情
* 【组件打磨】链接胶囊 8px/3px 10px/11px,可用态左侧3px绿竖条;检测中 4px 灰点呼吸
* - 进度条 2px 中性灰底+蓝填充,完成变绿无流动;统计栏 10px opacity 0.35
* v4.10.0 - 配色收敛与层次重构
* 【问题一·荧光绿收敛】
* - 可用站点改为深绿半透明底 rgba(48,209,88,0.18)+绿字#30D158+1px描边
* - 摘要数字、计数徽章统一半透明绿方案,font-weight 降 500
* - 进度条完成态改为半透明绿渐变,去除实色填充
* 【问题二·无资源折叠】
* - 默认开启「仅显示有资源」,unavailable/inaccessible/blocked 实时隐藏
* - 「显示全部」按钮始终可见,改为低调文字链
* - Tier3 待检测链接默认折叠,仅保留开始检测按钮;点击后逐个显示
* - 全无资源的梯队区块整体隐藏,不占空间
* 【问题三·层次重构】
* - 面板自适应背景:blur(40px) saturate(150%),深浅色各自半透明底
* - 链接胶囊紧凑化(圆角10px/padding 4px 11px/字号11.5px/gap 5px)
* - 梯队标签去背景,改纯文字+左侧3px竖线色条
* - 分区间距6px、内部padding 12px;搜索框/统计栏弱化
* 【问题四·状态清晰】
* - checking 改静态灰底+尾随6px呼吸圆点
* - inaccessible 用 opacity 0.35+删除线,无背景
* - 无结果分区折叠为一行「未找到可用资源」,点击展开
* v4.9.0 - UI视觉与交互全面升级
* v4.8.0 - 检索命中率优化
* v4.7.0 - Bug修复与性能优化
* v4.6.1 - iOS毛玻璃界面·站点核验
* v4.6.0 - 配色与分类调整
* v4.5.0 - 分类合并与UI重构
* v4.4.4 - 渲染与计数统一
* v4.4.3 - 折叠交互优化
* v4.4.2 - 按检索加载速度分层顺序加载
* v4.4.1 - 新增FMHY国际站点集成
* ============================================================
*/
(function() {
// ============================================================
// 【0号优先级】立即注入可见标记
// ============================================================
try {
var _ddm_mark = document.createElement('div');
_ddm_mark.id = 'ddm-boot-mark';
_ddm_mark.innerHTML = 'DDM 5.2.1';
_ddm_mark.style.cssText = 'position:fixed;top:14px;left:14px;z-index:2147483647;background:#ffffff;color:#1c1c1e;font-size:12px;font-weight:600;padding:8px 16px;border-radius:18px;box-shadow:0 4px 16px rgba(0,0,0,0.08),inset 0 0 0 1px rgba(120,120,128,0.16);cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC",sans-serif;transition:all .35s cubic-bezier(0.34,1.56,0.64,1);letter-spacing:0.2px;display:flex;align-items:center;';
_ddm_mark.title = '豆瓣电影下载大师 v5.2.1 已加载 - 点击关闭';
_ddm_mark.onclick = function() { if (_ddm_mark.parentNode) _ddm_mark.parentNode.removeChild(_ddm_mark); };
function _ddm_addMark() {
if (document.getElementById('ddm-boot-mark')) return;
if (document.body) {
document.body.appendChild(_ddm_mark);
// v4.9.0: 加载完成后圆点变绿实心
setTimeout(function() { if (_ddm_mark.parentNode) {
var dot = _ddm_mark.querySelector('.ddm-boot-dot');
if (dot) dot.className = 'ddm-boot-dot done';
_ddm_mark.style.background = 'rgba(26,127,55,0.07)';
_ddm_mark.innerHTML = 'DDM 5.2.1 ✓';
} }, 1500);
} else { setTimeout(_ddm_addMark, 50); }
}
_ddm_addMark();
console.log('%c[DDM5.2.1] 豆瓣电影下载大师 v5.2.1 已启动', 'color:#1c1c1e;font-size:14px;font-weight:bold;');
} catch(e) { try { alert('DDM v5.2.1启动: ' + e.message); } catch(e2) {} }
// ============================================================
// 工具函数 (纯ES5)
// ============================================================
function log() { try { var a=['[DDM5.2.1]']; for(var i=0;i/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
function randomInt(a,b) { return Math.floor(Math.random()*(b-a+1))+a; }
function randomChoice(arr) { return arr[Math.floor(Math.random()*arr.length)]; }
function addCSS(css) {
try { if (typeof GM_addStyle !== 'undefined') { GM_addStyle(css); return; } } catch(e){}
try { var s=document.createElement('style'); s.type='text/css'; s.textContent=css; (document.head||document.documentElement).appendChild(s); } catch(e){}
}
function copyText(text) {
try { if (typeof GM_setClipboard !== 'undefined') { GM_setClipboard(text); return true; } } catch(e){}
try { var ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.left='-9999px'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); return true; } catch(e){ return false; }
}
// ============================================================
// v4.9.0 UI辅助: 展开动画 / Toast / Tooltip
// ============================================================
function animateBodyIn(body) {
if (!body) return;
body.classList.remove('ddm-body-in');
void body.offsetWidth;
body.classList.add('ddm-body-in');
}
function showToast(msg) {
try {
var old = document.getElementById('ddm-toast');
if (old && old.parentNode) old.parentNode.removeChild(old);
var t = document.createElement('div');
t.id = 'ddm-toast';
t.className = 'ddm-toast';
t.textContent = msg;
document.body.appendChild(t);
setTimeout(function() { if (t.parentNode) t.classList.add('ddm-toast-hide'); }, 2700);
setTimeout(function() { if (t.parentNode) t.parentNode.removeChild(t); }, 3150);
} catch(e) { logErr('showToast error', e); }
}
function showTooltipFor(a, ev) {
hideTooltip();
try {
var tip = document.createElement('div');
tip.id = 'ddm-tip';
tip.className = 'ddm-tip';
var nm = a.textContent || '';
var desc = a.getAttribute('data-desc') || '';
var tier = a.getAttribute('data-tier') || '';
var html = '
' + esc(nm) + '
';
if (desc) html += '' + esc(desc) + '
';
if (tier) html += '' + esc(tier) + '';
tip.innerHTML = html;
document.body.appendChild(tip);
var x = ev.clientX, y = ev.clientY;
var tw = tip.offsetWidth || 100, th = tip.offsetHeight || 40;
var nx = Math.max(8, Math.min(x - Math.round(tw / 2), window.innerWidth - tw - 8));
var ny = y - th - 14;
if (ny < 8) ny = y + 20;
tip.style.left = nx + 'px';
tip.style.top = ny + 'px';
} catch(e) { logErr('showTooltip error', e); }
}
function hideTooltip() {
try {
var tip = document.getElementById('ddm-tip');
if (tip && tip.parentNode) tip.parentNode.removeChild(tip);
} catch(e) {}
}
// ============================================================
// 反爬模块
// ============================================================
var UA_POOL = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
];
var REFERER_POOL = ['https://www.baidu.com/','https://www.google.com/','https://www.bing.com/','https://movie.douban.com/',''];
var ACCEPT_LANG_POOL = ['zh-CN,zh;q=0.9,en;q=0.8','zh-CN,zh;q=0.9'];
function getRandomHeaders() {
var h = { 'User-Agent':randomChoice(UA_POOL), 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language':randomChoice(ACCEPT_LANG_POOL), 'Accept-Encoding':'gzip, deflate', 'Cache-Control':'max-age=0', 'DNT':'1' };
var r = randomChoice(REFERER_POOL); if (r) h['Referer']=r;
return h;
}
// ============================================================
// XHR封装
// ============================================================
var _xhrCache = {};
var _requestLastTime = {};
var MIN_REQUEST_INTERVAL = 200;
function gmXhr(opts, cb) {
var method = opts.method || 'GET';
var url = opts.url;
var data = opts.data || null;
var headers = opts.headers || {};
var timeout = opts.timeout || 8000;
var maxRetries = opts.retries || 1;
var retryCount = 0;
var cacheKey = method + '|' + url + '|' + (data || '');
if (!opts.noCache && _xhrCache[cacheKey] && (Date.now()-_xhrCache[cacheKey].t < 300000)) { setTimeout(function(){cb(null,_xhrCache[cacheKey].r);},0); return; }
function doRequest() {
try {
var fh = getRandomHeaders();
for (var k in headers) { if (hasOwn(headers,k)) fh[k]=headers[k]; }
var host = url.replace(/^https?:\/\//,'').split('/')[0];
var now = Date.now();
var lt = _requestLastTime[host] || 0;
var wt = 0;
// v4.7.0: 同域名保留 200ms 最小间隔,去掉随机等待;跨域名零等待
if (now-lt < MIN_REQUEST_INTERVAL) wt = MIN_REQUEST_INTERVAL-(now-lt);
_requestLastTime[host] = now+wt;
function exec() {
if (typeof GM_xmlhttpRequest !== 'undefined') {
GM_xmlhttpRequest({
method:method, url:url, data:data, headers:fh, timeout:timeout,
onload: function(res) { if(!opts.noCache) _xhrCache[cacheKey]={r:res,t:Date.now()}; cb(null,res); },
onerror: function() { if(retryCount 0 ? setTimeout(exec,wt) : exec();
} catch(e) { cb(e); }
}
doRequest();
}
// ============================================================
// GBK编码
// ============================================================
var _gbkTable = null;
function getGBKTable() {
if (_gbkTable) return _gbkTable;
_gbkTable = {};
try { var dec=new TextDecoder('gbk'); for(var l=0x81;l<=0xFE;l++){for(var t=0x40;t<=0xFE;t++){if(t===0x7F)continue; var b=new Uint8Array([l,t]); var c=dec.decode(b); if(c&&c.length===1&&!_gbkTable[c])_gbkTable[c]=[l,t];}} } catch(e){}
return _gbkTable;
}
function encodeGBK(str) {
var tb=getGBKTable(), out=[];
for(var i=0;i= 0xac00 && code <= 0xd7af);
var isJapanese = (code >= 0x3040 && code <= 0x30ff);
var isCyrillic = (code >= 0x0400 && code <= 0x04ff);
var isThai = (code >= 0x0e00 && code <= 0x0e7f);
if (isKorean || isJapanese || isCyrillic || isThai) { hitForeign = true; break; }
cnTitle += ch;
}
if (hitForeign && cnTitle.trim().length > 0) {
info.cnTitle = cnTitle.trim();
} else {
var t1 = rawTitle.match(/^([\u4e00-\u9fa5][\s\S]*?)\s+([A-Za-z][\s\S]*)$/);
if (t1) { info.cnTitle=t1[1].trim(); info.enTitle=t1[2].trim(); } else { info.cnTitle=rawTitle; }
}
if (!info.enTitle) {
var enMatch = rawTitle.match(/\s+([A-Za-z][A-Za-z0-9\s\-\.:,'&]*)$/);
if (enMatch) info.enTitle = enMatch[1].trim();
}
var infoEl = document.querySelector('#info');
if (infoEl) {
var infoText = infoEl.textContent||'';
var im = infoText.match(/IMDb:\s*(tt\d+)/i); if(im)info.imdbId=im[1];
if (/集数|首播|电视台/.test(infoText)) info.isTV = true;
if (!info.enTitle) { var ak=infoText.match(/又名:\s*([^\n]+)/); if(ak){var akas=ak[1].split(/[\/,,]/); for(var ai=0;ai 0)) return;
_respTimes.push(elapsed);
if (_respTimes.length > 30) _respTimes.shift();
if (_respTimes.length < 8) return;
var sum = 0;
for (var i = 0; i < _respTimes.length; i++) sum += _respTimes[i];
var avg = sum / _respTimes.length;
var prev = CONCUR.current;
if (avg < 1200) {
CONCUR.current = Math.min(CONCUR.max, CONCUR.current + 2);
} else if (avg > 3500) {
CONCUR.current = Math.max(CONCUR.min, CONCUR.current - 1);
}
if (CONCUR.current !== prev) log('concurrency adjusted: ' + prev + ' -> ' + CONCUR.current + ' (avg=' + Math.round(avg) + 'ms)');
}
function pool(items, limit, worker, progressCb, done) {
var results = new Array(items.length);
var cursor = 0, running = 0, finished = 0;
function getLim() { return (limit > 0) ? limit : getConcurrency(); }
function next() {
if (finished >= items.length) { if (done) try { done(results); } catch(e) { logErr('pool done error', e); } return; }
while (running < getLim() && cursor < items.length) {
var idx = cursor++; running++;
(function(i) {
try {
worker(items[i], i, function(err, r) {
results[i] = err ? null : r;
running--; finished++;
if (progressCb) try { progressCb(finished, items.length); } catch(e) {}
setTimeout(next, randomInt(10, 50));
});
} catch(e) {
logErr('pool worker error', e);
running--; finished++;
results[i] = null;
if (progressCb) try { progressCb(finished, items.length); } catch(e2) {}
setTimeout(next, 10);
}
})(idx);
}
}
if (progressCb) try { progressCb(0, items.length); } catch(e) {}
next();
}
// ============================================================
// 网盘API搜索
// ============================================================
function searchPanApi(kw, cb) {
var apiUrl = PAN_APIS[0].url + encodeURIComponent(kw);
gmXhr({url:apiUrl, timeout:10000, retries:2}, function(err, res) {
if (err || !res || !res.responseText) { cb([]); return; }
try {
var json = JSON.parse(res.responseText);
var items = [];
var seen = {};
if (json.code === 0 || json.code === '0') {
var data = json.data || {};
var groups = data.merged_by_type || data.by_type || data;
var tagMap = {ali:'ali',aliyun:'ali',quark:'quark',baidu:'baidu',baidupan:'baidu',xunlei:'xunlei',thunder:'xunlei'};
var tagName = {ali:'阿里',quark:'夸克',baidu:'百度',xunlei:'迅雷',other:'网盘'};
var totalAdded = 0;
var MAX_TOTAL = 20;
var PER_GROUP = 6;
for (var type in groups) {
if (!hasOwn(groups, type)) continue;
if (totalAdded >= MAX_TOTAL) break;
var arr = groups[type];
var t = tagMap[type.toLowerCase()] || 'other';
if (Object.prototype.toString.call(arr) === '[object Array]') {
var groupCount = 0;
for (var i = 0; i < arr.length && groupCount < PER_GROUP && totalAdded < MAX_TOTAL; i++) {
var it = arr[i];
var name = it.note||it.name||it.title||it.filename||'资源';
var url = it.url||it.link||it.share_url||'';
var pwd = it.password||it.pwd||it.code||it.extract_code||'';
if (url) {
var normUrl = url.replace(/[?].*$/, '').toLowerCase();
if (seen[normUrl]) continue;
seen[normUrl] = true;
items.push({name:name, url:url, pwd:pwd, tag:t, tagLabel:tagName[t]});
groupCount++;
totalAdded++;
}
}
}
}
}
cb(items);
} catch(e) { logErr('pan api parse error', e); cb([]); }
});
}
// ============================================================
// 站点检测
// ============================================================
// v4.7.0: 站点健康度持久化缓存 (GM_setValue/GM_getValue)
var HEALTH_KEY_PREFIX = 'ddm_health_';
var HEALTH_TTL = 24 * 60 * 60 * 1000; // 24小时
function getHealth(name) {
try {
if (typeof GM_getValue === 'undefined') return null;
var v = GM_getValue(HEALTH_KEY_PREFIX + name, '');
if (!v) return null;
var o = JSON.parse(v);
if (o && typeof o.a !== 'undefined' && typeof o.t !== 'undefined') return o;
} catch(e) {}
return null;
}
function setHealth(name, available) {
try {
if (typeof GM_setValue === 'undefined') return;
GM_setValue(HEALTH_KEY_PREFIX + name, JSON.stringify({ a: available ? 1 : 0, t: Date.now() }));
} catch(e) {}
}
// 24h 内标记为不可用的站点 → 直接标灰跳过请求
function isHealthSkipped(name) {
var h = getHealth(name);
return !!(h && h.a === 0 && (Date.now() - h.t) < HEALTH_TTL);
}
// ============================================================
// v4.8.0 站点类型标记 (智能关键词选择用)
// ============================================================
function tagSiteCategories() {
var i;
for (i = 0; i < DOWNLOAD_SITES.length; i++) {
var s = DOWNLOAD_SITES[i];
s._cat = (s.tier === 3) ? 'bt_en' : ((s.tier === 1) ? 'bt_zh' : 'dl_zh');
}
for (i = 0; i < PAN_SEARCH_SITES.length; i++) PAN_SEARCH_SITES[i]._cat = 'pan';
for (i = 0; i < ONLINE_SITES.length; i++) ONLINE_SITES[i]._cat = 'online';
}
// ============================================================
// v4.8.0 站点专用解析器 (优先于通用评分)
// ============================================================
var SITE_PARSERS = {
'两个BT': function(text) { return /\.btbox|class="[^"]*btbox/i.test(text); },
'海盗湾': function(text) { return /id="searchResult"/i.test(text); },
'海盗湾备用': function(text) { return /id="searchResult"/i.test(text); },
'1337x': function(text) { return /class="[^"]*table-list/i.test(text); },
'1377x': function(text) { return /class="[^"]*table-list/i.test(text); },
'YTS官方': function(text) { return /browse-movies-wrap|browse-movies/i.test(text); },
'YTS': function(text) { return /browse-movies/i.test(text); },
'Nyaa动漫': function(text) { return /class="[^"]*torrent-list/i.test(text); },
'BT天堂': function(text) { return /class="[^"]*mlist/i.test(text); },
'小马盘搜': function(text) { return /(pan\.baidu\.com|aliyundrive|alipan\.com|quark\.cn)/i.test(text); }
};
// ============================================================
// v4.8.0 反拦截检测 (Cloudflare 挑战页)
// ============================================================
function isBlocked(text) {
if (!text) return false;
var low = text.toLowerCase();
if (low.indexOf('cf-browser-verification') !== -1) return true;
if (low.indexOf('challenge-platform') !== -1) return true;
if (low.indexOf('attention required') !== -1) return true;
if (text.length < 3000 && (low.indexOf('just a moment') !== -1 || low.indexOf('checking your browser') !== -1)) return true;
return false;
}
// ============================================================
// v4.8.0 加权评分算法
// ============================================================
var NEGATIVE_KW = ['没有找到','暂无相关','未找到','搜索不到','无结果','没有搜索到','验证码','captcha','404','页面不存在','page not found','no results','nothing found','0 results','0个结果','0 条结果','该关键词没有找到'];
function scoreResult(text, movie) {
var score = 0;
var low = text.toLowerCase();
var i;
// 负面信号 -3
for (i = 0; i < NEGATIVE_KW.length; i++) {
if (low.indexOf(NEGATIVE_KW[i].toLowerCase()) !== -1) { score -= 3; break; }
}
// 页面过短 -5
if (text.length < 800) score -= 5;
// 强信号 +3
if (low.indexOf('magnet:?xt=') !== -1) score += 3;
if (/\.torrent/i.test(low)) score += 3;
if (/(pan\.baidu\.com|aliyundrive|alipan\.com|quark\.cn|cloud\.189\.cn|tianyi|weiyun|pan\.quark\.cn)/i.test(low)) score += 3;
// 标题强匹配 +4
if (movie.cnTitle && text.indexOf(movie.cnTitle) !== -1) score += 4;
if (movie.enTitle && low.indexOf(movie.enTitle.toLowerCase()) !== -1) score += 4;
// 标题前4字部分匹配 +2
var pre4 = (movie.cnTitle || '').substring(0, 4);
if (pre4.length >= 2 && text.indexOf(pre4) !== -1) score += 2;
var pre4en = (movie.enTitle || '').substring(0, 4).toLowerCase();
if (pre4en.length >= 3 && low.indexOf(pre4en) !== -1) score += 2;
// 年份出现 +1
if (movie.year && low.indexOf(movie.year) !== -1) score += 1;
// 结果列表HTML结构 +1~2
if (/]+href="[^"]+\.(html|htm|mp4|mkv|torrent|ram|iso|m3u8|rmvb)/i.test(low)) score += 1;
if (/class="[^"]*(item|result|torrent|list|post|article|entry|search-item|movie-item|resource|share|search-list|box-list|video-list)/i.test(low)) score += 1;
if (text.length > 15000 && /]*>.*<\/li>/i.test(text)) score += 1;
// 下载/播放词汇 +1
if (/(下载|磁力|迅雷|thunder|bt下载|在线观看|播放|watch|play|stream|ed2k|bt种子)/i.test(low)) score += 1;
return score;
}
// ============================================================
// v4.8.0 智能关键词选择 (按站点类型选最优候选, 最多3个)
// ============================================================
function pickCandidates(site, movie) {
if (site.useImdb) return movie.imdbId ? [movie.imdbId] : [];
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);
} 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 : '')) : '');
} 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 : '')) : '');
}
// 剧集季数拼接补充
if (movie.season && movie.cnTitle) push(movie.cnTitle + ' ' + movie.season);
var out = [];
for (var i = 0; i < list.length && out.length < 3; i++) { if (list[i]) out.push(list[i]); }
return out;
}
// ============================================================
// v4.8.0 多关键词检测 (无结果自动切换, 最多3个)
// ============================================================
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 {
setHealth(site.name, false);
finish();
}
}
if (site.method === 'POST') {
var body = site.postData(kw);
gmXhr({
url:site.url, method:'POST', data:body, timeout:timeout, retries:1, noCache:true,
headers:{'Content-Type':'application/x-www-form-urlencoded','Referer':site.url,'Origin':site.url.replace(/(https?:\/\/[^\/]+).*/,'$1')}
}, function(err, res) {
if (err || !res) { setHealth(site.name, false); finish({accessible:false, time:999999, error:true}); return; }
var fu = res.finalUrl || site.url;
var available = site.checkUrl ? site.checkUrl(fu) : true;
if (!available) { nextOrFail(); return; }
setHealth(site.name, true);
finish({ available:true });
});
} else {
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 {
// v4.8.0: 反拦截
if (isBlocked(text)) { finish({ blocked:true }); return; }
// v4.8.0: 站点专用解析器优先
var parser = SITE_PARSERS[site.name];
if (parser) {
var pr = parser(text);
var pAvail = (typeof pr === 'object' && pr !== null && typeof pr.available !== 'undefined') ? pr.available : !!pr;
if (pAvail) { setHealth(site.name, true); finish({ available:true }); return; }
nextOrFail();
return;
}
if (!site.detect) {
// 非内容解析站(如网盘跳转站):仅做可访问性判定
if (text.length < 500 || (res.status && res.status >= 400)) { nextOrFail(); return; }
setHealth(site.name, true);
finish({ available:true });
return;
}
// v4.8.0: 通用加权评分
var score = scoreResult(text, movie);
if (score >= 3) { setHealth(site.name, true); finish({ available:true }); return; }
nextOrFail();
} catch(e) {
logErr('tryKeywords parse error', e, site.name);
setHealth(site.name, true);
finish({ available:true, 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.2.0: 无资源站点检测完成后直接隐藏,仅显示可用站点
function applyFilter() {
for (var t in allResults) {
if (!hasOwn(allResults, t)) continue;
var arr = allResults[t];
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item.result && !item.result.available) item.a.classList.add('ddm-filtered');
}
}
updateTierVisibility();
}
// 全无资源的梯队整体隐藏
function updateTierVisibility() {
var t;
for (t = 1; t <= 3; t++) {
var linksCont = document.getElementById(opts.containerPrefix + '-t' + t + '-links');
if (!linksCont) continue;
var wrap = linksCont.parentNode;
if (!wrap || wrap.className.indexOf('ddm-tier-block') === -1) continue;
var tag = wrap.querySelector('.ddm-tier-tag');
if (tag) tag.style.display = '';
var hasPendingCheck = false;
var hasAvail = false;
var arr = allResults[t] || [];
for (var i = 0; i < arr.length; i++) {
if (!arr[i].result) { hasPendingCheck = true; break; }
if (arr[i].result.available) hasAvail = true;
}
// 检测中(结果未定)或存在可用站点 → 显示;全部完成且无可用 → 隐藏
wrap.style.display = (hasPendingCheck || hasAvail) ? '' : 'none';
}
}
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.badgeEl) {
opts.badgeEl.className = 'ddm-progress-badge ' + (totalFound > 0 ? 'done' : 'done-none');
opts.badgeEl.textContent = totalFound > 0 ? ('✓ ' + totalFound + '可用') : '无资源';
}
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';
}
applyFilter();
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);
}
}
// v5.2.1: 「复制全部可用链接」按钮已移除
}
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.useImdb ? movie.imdbId : (site.useEn ? movie.kwEn : movie.kw);
var url = kw ? (site.url + encodeURIComponent(kw) + (site.suffix || '')) : 'javascript:void(0)';
if (site.method === 'POST') url = site.url;
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 || site.method === 'POST') a.title = site.method === 'POST' ? site.name + ' (点击后搜索)' : 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 });
}
// v5.2.0: Tier3 国际站与普通站点同样自动检测,仅按速度顺位最后执行
var _token = container._ddmToken || (container._ddmToken = '_ddm_token_' + Math.random().toString(36).slice(2, 9));
var tierFound = 0;
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);
// v4.10.0: 实时过滤无资源站点 + 梯队可见性
applyFilter();
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 buildPanel(movie) {
log('buildPanel v5.2.1');
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 v5.2.1');
} 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();
var op = document.getElementById('ddm-panel');
if (op && op.parentNode) { var p=op.parentNode; if(p.id==='ddm-float-wrap')p.parentNode.removeChild(p); else p.removeChild(op); }
_xhrCache = {}; _requestLastTime = {};
setTimeout(function(){ initAll(movie); }, 100);
}, false);
}
return panel;
}
// ============================================================
// v4.7.0 DNS预解析 (Tier1 站点域名, 最多6个)
// ============================================================
function addDnsPrefetch() {
try {
var domains = [];
var seen = {};
var lists = [DOWNLOAD_SITES, PAN_SEARCH_SITES, ONLINE_SITES];
for (var i = 0; i < lists.length && domains.length < 6; i++) {
var arr = lists[i];
for (var j = 0; j < arr.length && domains.length < 6; j++) {
var s = arr[j];
if (s.tier !== 1) continue;
var host = (s.url || '').replace(/^https?:\/\//, '').split('/')[0];
if (!host || seen[host]) continue;
seen[host] = true;
domains.push(host);
}
}
var head = document.head || document.documentElement;
if (!head) return;
for (var k = 0; k < domains.length; k++) {
try {
var link = document.createElement('link');
link.rel = 'dns-prefetch';
link.href = '//' + domains[k];
head.appendChild(link);
} catch(e) {}
}
if (domains.length > 0) log('dns-prefetch: ' + domains.join(', '));
} catch(e) { logErr('addDnsPrefetch error', e); }
}
// ============================================================
// 初始化
// ============================================================
function initAll(movie) {
if (window._ddm_initialized) return;
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; }
// 1. 下载资源(首位)
var dlGroups = groupByTier(DOWNLOAD_SITES);
renderSectionTiered({
sitesGroups: dlGroups,
movie: movie,
sectionId: 'ddm-sec-dl',
containerPrefix: 'ddm-dl',
summaryKey: 'dl',
toastMsg: '🧲 下载资源',
emptyElId: 'ddm-dl-empty',
barEl: document.getElementById('ddm-dl-bar'),
textEl: document.getElementById('ddm-dl-prog'),
badgeEl: null,
countEl: document.getElementById('ddm-dl-cnt')
});
// 2. 网盘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条';
}
});
}
// 2.1 网盘搜索站
var panSearchGroups = groupByTier(PAN_SEARCH_SITES);
renderSectionTiered({
sitesGroups: panSearchGroups,
movie: movie,
sectionId: 'ddm-sec-pan',
containerPrefix: 'ddm-pansearch',
bodyId: 'ddm-pansearch-body',
arrowId: 'ddm-pansearch-arrow',
summaryKey: 'pansearch',
toastMsg: '☁️ 网盘搜索',
emptyElId: 'ddm-pansearch-empty',
barEl: document.getElementById('ddm-pansearch-bar'),
textEl: document.getElementById('ddm-pansearch-prog'),
badgeEl: null,
countEl: document.getElementById('ddm-pansearch-cnt')
});
// 3. 在线观看
var onlineGroups = groupByTier(ONLINE_SITES);
renderSectionTiered({
sitesGroups: onlineGroups,
movie: movie,
sectionId: 'ddm-sec-online',
containerPrefix: 'ddm-online',
summaryKey: 'online',
toastMsg: '📺 在线观看',
emptyElId: 'ddm-online-empty',
barEl: document.getElementById('ddm-online-bar'),
textEl: document.getElementById('ddm-online-prog'),
badgeEl: null,
countEl: document.getElementById('ddm-online-cnt')
});
// 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 {
var m = res.responseText.match(/"ratingValue"\s*:\s*"?(\d+\.?\d*)"?/);
if (m) {
var rating = m[1];
var sectl = document.querySelector('#interest_sectl');
if (sectl && !document.getElementById('ddm-imdb')) {
var wrap = document.createElement('div');
wrap.id = 'ddm-imdb';
wrap.className = 'rating_wrap ddm-imdb';
wrap.innerHTML = 'IMDb评分
' + esc(rating) + '星
查看IMDb页面';
try { sectl.appendChild(wrap); } catch(e){}
}
}
} catch(e) { logErr('imdb parse error', e); }
});
} catch(e) {}
}
var bm = document.getElementById('ddm-boot-mark');
if (bm && bm.parentNode) setTimeout(function(){ if(bm.parentNode)bm.parentNode.removeChild(bm); }, 800);
log('initAll done v5.2.1');
} catch(e) {
logErr('initAll error', e);
window._ddm_initialized = false;
}
}
// ============================================================
// 主入口
// ============================================================
function main() {
log('main() v5.2.1');
try {
var movie = getMovieInfo();
if (!movie.cnTitle) { var bm = document.getElementById('ddm-boot-mark'); if(bm)bm.innerHTML='DDM - 非详情页?'; return; }
initAll(movie);
} catch(e) { logErr('main error', e); }
}
function startWhenReady() {
if (document.body && document.querySelector('h1')) {
if (document.querySelector('.aside') || document.readyState === 'complete') { setTimeout(main, 300); }
else { setTimeout(startWhenReady, 200); }
} else { setTimeout(startWhenReady, 200); }
}
try {
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand('🔄 重新加载面板', function() {
var p = document.getElementById('ddm-panel');
if (p && p.parentNode) { var pp=p.parentNode; if(pp.id==='ddm-float-wrap')pp.parentNode.removeChild(pp); else pp.removeChild(p); }
_xhrCache={}; _requestLastTime={}; window._ddm_initialized=false; main();
});
GM_registerMenuCommand('🧹 清空缓存', function() {
_xhrCache={}; _requestLastTime={};
try { if (typeof GM_listValues !== 'undefined') { var ks=GM_listValues(); for (var i=0;i