// ==UserScript== // @name 豆瓣下载助手 // @namespace https://github.com/Shshuaicc/douban-download-master // @version 5.4.1 // @description 豆瓣电影详情页下载/网盘/在线 三大类资源聚合搜索 v5.4.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.4.1 (原名「豆瓣电影下载大师」) * 更新日志: * v5.4.1 - 结果条目级核验,误报清零(本版核心) * 【问题】旧版把「站点能打开」当成「该站有这部片」: * - 96/160 站点标记为「仅可达性判定」,只要返回 200 且页面 >500 字节即标绿 * - 其余走整页文本打分,而搜索页必然回显查询词(、搜索框 value、 * 面包屑、甚至「没有找到 X」这句话本身),标题命中即 +4,零结果页也能过阈值 * - 实测:用一部根本不存在的影片检索 160 站,旧逻辑误报 150 站(94%) * 【重做】新增结果条目校验器 verifyResults(),只认「结果行」: * - DOMParser 解析响应,锚点文本或 href 命中片名 * - 命中若只出现在搜索型链接(?s=X、/search/X、?wd=X)中 → 判为回显,丢弃 * - 该行必须另带资源证据:磁力/网盘直链、发布标记(1080p/WEB-DL/x264/中字)、 * 四位年份、或详情链接;仅「片名+Download」这类模板回显不计分 * - 行内出现「没有找到/暂无相关/no results」→ 整行否决 * - 所有站点统一走此校验,删除 detect 开关(不再有「仅可达性」这一档) * 【站点集】按新标准重扫 1360 个可达域名 + 374 个历史端点,只保留能真正出结果的: * 下载 9 · 网盘 6 · 在线 28 = 共 43 站,全部逐站复测 * (不存在的片名在最终站点集上误报 0) * 【吸纳参考脚本 4.1.1 的优点】 * - 逐条结果解析(DOMParser + 真实条目计数)替代整页关键词打分 —— 本版核心 * - IMDb 评分三级回退解析:__NEXT_DATA__ → application/ld+json → 正则 * - 「站点答了但无结果」不再写 24h 健康度黑名单(旧版一部冷门票会把站点拉黑 * 一整天,换部片子也被误判跳过);仅传输层失败才缓存不可用 * - HTTP 404 视为站点的有效应答(进入结果校验)而非站点故障 * 【未采纳】withCredentials(公开搜索页无需 Cookie,且会触发带凭据 CORS 失败)、 * 防机器人「去验证→回来重试」引导与结果条目列表(均需新增界面元素, * 与「不改 UI」冲突);网盘站自有 JSON 接口实测仅 2/62 可用,不成规模。 * 【界面】面板 DOM 结构、类名、层级与 v5.3.1 完全一致,未增删任何元素。 * * v5.31 - 站点扩容(三类各新增实测可用站,按实测延迟分层) * 【采集】新增候选 2,292 个域名:FMHY 国际清单(video/torrenting/downloading 三页) * + 14 篇中文影视/网盘/磁力推荐榜单,全部来自可抓取来源,不凭记忆编造网址 * 【验证】可达性初筛 → 首页指纹(maccms/WordPress/Next/表单 action)→ 定向搜索端点 * → 真实片名请求 + 结果页特征判定,两轮(严格 / 前端渲染宽松)互补 * 【相关性】结果页须含发布标记(1080p/BluRay/x264/磁力)或影视/网盘字样, * 并剔除过期域名被抢注后的跳转站(丹麦高校、工具箱、Object Cache 等 8 例) * 【新增】下载 +27 · 网盘 +22 · 在线 +30 = 共 79 站, * 每类内部按实测延迟三分位划 快速/中速/慢速;站点总数 81 → 160 * 【说明】在线类目标 30 已达成;下载 27、网盘 22 为「实测可出结果」的上限—— * 中文网盘搜索生态可枚举站点已基本收齐,其余候选已停放/失效/需代理 * * v5.3.0 - 更名·站点实测重排·代码精简 * 【更名】程序名统一为「豆瓣下载助手」:脚本名、面板标题、启动标记、日志前缀 * 【站点校准】246 站全量实测(真实关键词请求 ×3 轮重试 + 根域二次探测 + 响应体特征判定): * - 删除 165 个连不上的站点:域名解析失败 29、连接超时 51、拒绝连接 8、 * HTTP 4xx/5xx 40、Cloudflare 挑战页 8,以及返回「域名出售/跳转/人机验证」 * 空壳却仍被标绿的站点(人人影视·深影字幕组·MK影视·YaPan·云盘精灵 等) * - 修复 9 个「站点存活但搜索路径写错」的网盘站端点(搜百度盘·影猫导航·夸克资源· * 天天云搜·海搜·知了搜·盘友圈·盘库吧·盘搜搜·云盘4K,原 /search?keyword= 全为 404) * - 新增 6 个实测可用站:蓝光电影(下载)·盘搜Pro·天天搜索·皮卡搜索·夸父资源社(网盘)· * 影猫仓库(在线)·东京图书馆(国际BT) * - 国际站按类收敛:下载 38→7、在线 46→17,仅保留实测可达者 * - 结果:246 → 81 站,全部复测可达;快速层实测可用 12 → 43 * (下载快速 0→4、在线快速 0→10、网盘快速 12→29) * - 梯队按实测延迟重排:快 <1s · 中 1-3s · 慢=国际站(需代理); * 原快速层中已连不上的站点全部剔除而非降级 * 【代码精简】界面元素与 DOM 结构逐节点校验为完全一致: * - 移除 GBK 码表模块(仅服务于已失效的 4 个 GBK-POST 站点)与 POST 检测分支 * - 梯队可见性由「每个站点完成后全量重扫」改为增量计数,消除 O(n²) 扫描 * - 删除未被消费的字段(导演/主演/片长/类型、noResult、badgeEl、useImdb)、 * 站点专用解析器表(10 项对应站点已全部下线)、旧版 GM.xmlHttpRequest 兼容分支 * - 合并三段同构的分区初始化、面板卸载与缓存清理逻辑;定时器 5 路收敛为 1 路轮询 * - 修正 v5.2.1 遗留并发缺陷:pool 的 done 回调在同帧多站点收尾时会重复触发, * 导致下一梯队被并发重复启动、可用站点计数翻倍(如显示 133/87) * - 体积 1927 行/114KB → 1578 行/89KB(-22%) * v5.2.1 - 界面精简(删除「复制全部可用链接」按钮) * v5.2.0 - 纯白实底化·结果纯化·国际站全自动 * v5.1.0 - 配色可读性重构·去荧光·全量淡字强化 * v5.0.0 - 视觉降噪重构·信息提纯·双模适配 * v4.x - 分层加载 / FMHY 国际站集成 / 加权评分 / iOS 毛玻璃 * 注:CSS 类名与存储键沿用 ddm- 前缀(内部标识,不显示于界面)。 * ============================================================ */ (function() { var NAME = '豆瓣下载助手', VER = '5.4.1'; // ============================================================ // 【0号优先级】立即注入可见标记 // ============================================================ try { var _ddm_mark = document.createElement('div'); _ddm_mark.id = 'ddm-boot-mark'; _ddm_mark.innerHTML = '<span class="ddm-boot-dot"></span>' + NAME + ' ' + VER; _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 = NAME + ' v' + VER + ' 已加载 - 点击关闭'; _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) { setTimeout(_ddm_addMark, 50); return; } document.body.appendChild(_ddm_mark); setTimeout(function() { if (_ddm_mark.parentNode) { _ddm_mark.style.background = 'rgba(26,127,55,0.07)'; _ddm_mark.innerHTML = '<span class="ddm-boot-dot done"></span>' + NAME + ' ' + VER + ' ✓'; } }, 1500); } _ddm_addMark(); console.log('%c[' + NAME + ' v' + VER + '] 已启动', 'color:#1c1c1e;font-size:14px;font-weight:bold;'); } catch(e) { try { alert(NAME + ' v' + VER + '启动: ' + e.message); } catch(e2) {} } // ============================================================ // 工具函数 (纯ES5) // ============================================================ var LOGP = '[DDA' + VER + ']'; function log() { try { var a=[LOGP]; for(var i=0;i<arguments.length;i++)a.push(arguments[i]); console.log.apply(console,a); } catch(e){} } function logErr() { try { var a=[LOGP + ' ERR']; for(var i=0;i<arguments.length;i++)a.push(arguments[i]); console.error.apply(console,a); } catch(e){} } function hasOwn(o,k) { return Object.prototype.hasOwnProperty.call(o,k); } function esc(s) { if(s==null)return''; return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/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 = '<div class="ddm-tip-name">' + esc(nm) + '</div>'; if (desc) html += '<div class="ddm-tip-desc">' + esc(desc) + '</div>'; if (tier) html += '<span class="ddm-tip-tier">' + esc(tier) + '</span>'; 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 url = opts.url; var headers = opts.headers || {}; var timeout = opts.timeout || 8000; var maxRetries = opts.retries || 1; var retryCount = 0; var cacheKey = url; 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 wt = Math.max(0, MIN_REQUEST_INTERVAL-(now-(_requestLastTime[host]||0))); _requestLastTime[host] = now+wt; function retry(msg) { if (retryCount++ < maxRetries) { setTimeout(doRequest, 500*Math.pow(2,retryCount-1)+randomInt(100,300)); return; } cb(new Error(msg)); } function exec() { if (typeof GM_xmlhttpRequest === 'undefined') { cb(new Error('no GM_xmlhttpRequest')); return; } GM_xmlhttpRequest({ method:'GET', url:url, headers:fh, timeout:timeout, onload: function(res) { if(!opts.noCache) _xhrCache[cacheKey]={r:res,t:Date.now()}; cb(null,res); }, onerror: function() { retry('network error'); }, ontimeout: function() { retry('timeout'); } }); } wt > 0 ? setTimeout(exec,wt) : exec(); } catch(e) { cb(e); } } doRequest(); } // ============================================================ // 中文数字转换 // ============================================================ function cn2num(s) { if(/^\d+$/.test(s))return s; var m={'零':0,'一':1,'二':2,'两':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9,'十':10}; if(s.length===1)return String(m[s]||0); if(s.charAt(0)==='十')return String(10+(m[s.charAt(1)]||0)); if(s.charAt(s.length-1)==='十')return String((m[s.charAt(0)]||0)*10); if(s.indexOf('十')!==-1){var p=s.split('十');return String((m[p[0]]||0)*10+(m[p[1]]||0));} return s; } function pad2(n){n=String(n);return n.length<2?'0'+n:n;} // ============================================================ // 提取电影信息 // ============================================================ function getMovieInfo() { var info = {cnTitle:'',enTitle:'',year:'',imdbId:'',isTV:false,season:'',episode:'',kw:'',kwEn:'',kwCandidates:[]}; try { var h1 = document.querySelector('h1 span[property="v:itemreviewed"]') || document.querySelector('h1'); if (!h1) return info; var rawTitle = (h1.textContent||'').trim(); var yearEl = document.querySelector('h1 .year'); if (yearEl) { var ym=(yearEl.textContent||'').match(/\((\d{4})\)/); if(ym)info.year=ym[1]; } if (!info.year) { var tym=rawTitle.match(/\((\d{4})\)/); if(tym){info.year=tym[1]; rawTitle=rawTitle.replace(/\s*\(\d{4}\)\s*$/,'').trim();} } // 韩文/日文/西里尔/泰文等外语字种出现处截断,仅保留其前的中文片名 var fm = rawTitle.match(/[\u3040-\u30ff\uac00-\ud7af\u0400-\u04ff\u0e00-\u0e7f]/); var cnTitle = fm ? rawTitle.slice(0, fm.index) : rawTitle; if (cnTitle !== rawTitle && cnTitle.trim()) { 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<akas.length;ai++){var s=akas[ai].trim(); if(s&&/^[A-Za-z]/.test(s)){info.enTitle=s;break;}}} } if (info.isTV) { var sm=rawTitle.match(/第\s*([一二三四五六七八九十两\d]+)\s*季/)||rawTitle.match(/Season\s*(\d+)/i)||rawTitle.match(/\bS(\d{1,2})\b/); if(sm)info.season='S'+pad2(cn2num(sm[1])); var em=rawTitle.match(/第\s*([一二三四五六七八九十百两\d]+)\s*集/)||rawTitle.match(/Episode\s*(\d+)/i)||rawTitle.match(/\bE(\d{1,3})\b/); if(em)info.episode='E'+pad2(cn2num(em[1])); } } info.kw = info.cnTitle; if(info.season)info.kw+=' '+info.season; if(info.episode)info.kw+=info.episode; info.kwEn = info.enTitle || info.cnTitle; // v4.8.0: 构建候选关键词数组(去重) var cands = []; function cpush(s) { if (s && cands.indexOf(s) === -1) cands.push(s); } if (info.cnTitle) { cpush(info.cnTitle); if (info.year) cpush(info.cnTitle + ' ' + info.year); var mainPart = info.cnTitle.split(/[::]/)[0].trim(); if (mainPart && mainPart !== info.cnTitle) cpush(mainPart); } if (info.enTitle) { cpush(info.enTitle); if (info.year) cpush(info.enTitle + ' ' + info.year); } if (info.season && info.cnTitle) cpush(info.cnTitle + ' ' + info.season); if (info.kw && info.kw !== info.cnTitle) cpush(info.kw); info.kwCandidates = cands; log('movie info:', info); } catch(e) { logErr('getMovieInfo error', e); } return info; } // ============================================================ // v4.6.1 三大类站点配置 // 顺序:下载资源 → 网盘资源 → 在线观看 // tier: 1=快 2=中 3=慢 // v4.6.1: 移除已知失效站点,三类各新增5个可用站点 // ============================================================ // ============================================================ // v5.3.0 站点配置(246 站全量实测后重排) // 梯队依据实测响应时间:快<1s · 中1-3s · 慢=国际站(需代理) // detect:true 走内容评分;缺省仅做可达性判定 // ============================================================ // === 第一大类:🧲 下载资源 (中文高清 + 国际BT) === var DOWNLOAD_SITES = [ { name:'Nyaa中文', url:'https://cn.nyaa.net/?q=', useEn:true, desc:'磁力搜索', tier:1 }, { name:'Eyedex', url:'https://www.eyedex.org/search/?q=', useEn:true, desc:'需代理·BT磁力', tier:1 }, { name:'X磁搜', url:'https://search.cisoux.com/search?q=', useEn:true, desc:'磁力搜索', tier:1 }, { name:'NNM', url:'https://nnmclub.to/?q=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'Rutor', url:'https://rutor.is/b.php?search=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'Binsearch', url:'https://binsearch.info/search?q=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'Knaben', url:'https://knaben.org/search?q=', useEn:true, desc:'需代理·BT磁力', tier:3 }, { name:'Didicili', url:'https://nongdie.sbs/search?kw=', useEn:true, desc:'需代理·BT磁力', tier:3 }, { name:'RLSBB', url:'https://log.rlsbb.ru/?s=', useEn:true, desc:'需代理·BT磁力', tier:3 }, ]; // === 第二大类 API:网盘直链聚合源 === var PAN_API_URL = 'https://so.252035.xyz/api/search?kw='; // === 第二大类:☁️ 网盘资源 (网盘搜索站) === var PAN_SEARCH_SITES = [ { name:'盘搜Pro', url:'https://panso.vip/search?q=', useEn:true, desc:'网盘搜索', tier:1 }, { name:'皮卡搜索', url:'https://www.pikasoo.top/search/?q=', useEn:true, desc:'网盘搜索', tier:1 }, { name:'短剧搜', url:'https://www.duanjuso.cc/search?q=', useEn:true, desc:'网盘搜索', tier:2 }, { name:'大盘搜', url:'https://dapanso.com/search?keyword=', useEn:true, desc:'网盘搜索', tier:2 }, { name:'盘搜搜', url:'https://pansoso.com/?q=', useEn:true, desc:'网盘搜索', tier:3 }, { name:'盘库吧', url:'https://panku8.com/s?q=', useEn:true, desc:'网盘搜索', tier:3 }, ]; // === 第三大类:📺 在线观看 (国内正版平台 + FMHY国际站) === var ONLINE_SITES = [ { name:'PP视频', url:'https://search.pptv.com/s_video?kw=', desc:'在线影视', tier:1 }, { name:'哔哩哔哩', url:'https://www.bilibili.com/search?keyword=', useEn:true, desc:'在线影视', tier:1 }, { name:'LeVidia', url:'https://www.levidia.ch/search.php?q=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'Cinezo', url:'https://cinezo.st/search?q=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'EmnexMovies', url:'https://emnexmovies.tech/search?q=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'AniCine', url:'https://anicine.xyz/?s=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'StreamVaults', url:'https://streamvaults.ru/?s=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'Hdencode', url:'https://hdencode.org/?s=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'ArrowTV', url:'https://arrowtv.net/?s=', desc:'需代理·在线影视', tier:1 }, { name:'Movy', url:'https://www.movy.sx/?s=', useEn:true, desc:'需代理·在线影视', tier:1 }, { name:'WATCHOTT', url:'https://watchott.org/search?q=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'ReelStream', url:'https://rreelstream.live/?s=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'Latestmovies', url:'https://latestmovies.net/?s=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'CorsFlix', url:'https://watch.corsflix.net/?s=', desc:'需代理·在线影视', tier:2 }, { name:'Anixtv', url:'https://anixx.fun/search?q=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'Popwatch', url:'https://popwatch.to/search?q=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'Chillflix', url:'https://www.chillflix.lol/search?q=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'WorldFree4u', url:'https://worldfree4u.expert/?s=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'JustWatch', url:'https://www.justwatch.com/us/search?q=', useEn:true, desc:'需代理·在线影视', tier:2 }, { name:'Cinemove', url:'https://cinemove.cc/?s=', desc:'需代理·在线影视', tier:3 }, { name:'Toflix', url:'https://toflix.co/search?q=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'FilmGo', url:'https://www.filmgo.live/search/?q=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'PhonoFilm', url:'https://phonofilm.cc/?s=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'Kofi', url:'https://kofi.mov/search?q=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'Vuflix', url:'https://vuflix.co/search?keyword=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'M2V', url:'https://m2v.ru/search?q=', useEn:true, desc:'需代理·在线影视', tier:3 }, { name:'新电影天堂', url:'https://www.xl720.com/?s=', useEn:true, desc:'在线影视', tier:3 }, { name:'Pahe', url:'https://pahe.ink/?s=', useEn:true, desc:'需代理·在线影视', tier:3 }, ]; // ============================================================ // v4.6.1 梯队配置 // ============================================================ var TIER_CONFIG = { 1: { label:'快速', desc:'1-3s·搜索站', color:'#4a7c59' }, 2: { label:'中速', desc:'3-8s·资源站', color:'#8a6d3b' }, 3: { label:'慢速', desc:'5-15s·国际站', color:'#9c4a4a' } }; function groupByTier(sites) { var groups = { 1: [], 2: [], 3: [] }; for (var i = 0; i < sites.length; i++) { var s = sites[i]; var t = s.tier || 2; if (!groups[t]) groups[t] = []; groups[t].push(s); } return groups; } function countTier(arr) { var c = {1:0, 2:0, 3:0}; for (var i=0;i<arr.length;i++){var t=arr[i].tier||2; if(c[t]!==undefined)c[t]++;} return c; } // ============================================================ // v4.6.1 CSS样式 — iOS 毛玻璃 (Liquid Glass) // ============================================================ var CSS = [ // iOS 字体栈 — v5.0.0 系统字体栈 ':root{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC",sans-serif;}', '.ddm-panel{margin-bottom:30px;background:#ffffff;border-radius:20px;border:1px solid rgba(120,120,128,0.16);font-size:13px;line-height:1.55;box-shadow:0 8px 30px rgba(0,0,0,0.10),0 2px 8px rgba(0,0,0,0.05);overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC",sans-serif;color:#1c1c1e;-webkit-font-smoothing:antialiased;}', // 顶部头 — 半透明玻璃,深色标题 '.ddm-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px 12px;background:transparent;}', '.ddm-header h2{margin:0;font-size:15px;font-weight:600;letter-spacing:-0.2px;display:flex;align-items:center;gap:8px;color:rgba(0,0,0,0.9);}', '.ddm-header h2 .ddm-icon{font-size:17px;filter:saturate(1);}', '.ddm-actions a{color:#007AFF;text-decoration:none;font-size:14px;cursor:pointer;transition:all .25s cubic-bezier(0.34,1.56,0.64,1);display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:rgba(0,122,255,0.12);}', '.ddm-actions a:hover{background:rgba(0,122,255,0.2);transform:rotate(180deg) scale(1.05);}', '.ddm-actions a:active{transform:rotate(180deg) scale(0.92);}', // 摘要统计条 — iOS 分段控制风格 '.ddm-summary-bar{display:flex;gap:6px;padding:4px 14px 14px;background:transparent;}', '.ddm-summary-item{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;cursor:default;transition:all .3s cubic-bezier(0.34,1.56,0.64,1);padding:9px 4px;border-radius:12px;background:rgba(120,120,128,0.08);}', '.ddm-summary-item:hover{background:rgba(120,120,128,0.14);transform:translateY(-1px);}', '.ddm-summary-num{font-size:19px;font-weight:600;color:#8E8E93;min-width:24px;text-align:center;font-variant-numeric:tabular-nums;letter-spacing:-0.5px;transition:color .3s;}', '.ddm-summary-num.has-val{color:#1A7F37;font-weight:500;}', '.ddm-summary-label{font-size:10px;color:rgba(0,0,0,0.5);font-weight:500;letter-spacing:0.2px;}', // 分区 — iOS 内嵌分组卡片 '.ddm-section{margin:0 12px 6px;border-radius:14px;background:#f6f6f7;border:1px solid rgba(120,120,128,0.12);transition:all .35s cubic-bezier(0.34,1.56,0.64,1);position:relative;overflow:hidden;}', '.ddm-section:last-child{margin-bottom:8px;}', // 完成态 — 系统绿微描边 '.ddm-section.ddm-section-completed{border-color:rgba(26,127,55,0.30)!important;}', '.ddm-section.ddm-section-has-result{border-color:rgba(26,127,55,0.30)!important;background:rgba(26,127,55,0.04);box-shadow:none;}', // 分区标题 — iOS 列表行 '.ddm-section-header{display:flex;align-items:center;padding:11px 12px;cursor:pointer;user-select:none;transition:background .2s;gap:10px;}', '.ddm-section-header:hover{background:rgba(120,120,128,0.08);}', '.ddm-arrow{color:#8E8E93;font-size:11px;transition:transform .3s cubic-bezier(0.34,1.56,0.64,1);width:10px;display:inline-block;flex-shrink:0;font-weight:600;}', '.ddm-section-icon{font-size:16px;flex-shrink:0;filter:saturate(1);}', '.ddm-section-title{color:rgba(0,0,0,0.9);font-size:14px;font-weight:600;flex:1;letter-spacing:-0.1px;}', '.ddm-section-count{font-size:11px;color:rgba(0,0,0,0.5);background:rgba(120,120,128,0.12);padding:2px 9px;border-radius:9px;font-weight:600;font-variant-numeric:tabular-nums;transition:all .3s;}', '.ddm-section-count.has-result{background:rgba(26,127,55,0.07);color:#1A7F37;}', // v5.0.0: 空结果折叠提示 — 居中次级文案 '.ddm-empty-tag{flex:1;text-align:center;font-size:11px;color:rgba(0,0,0,0.5);font-weight:400;letter-spacing:0.2px;}', // 进度条 — v5.0.0 2px 中性灰底+蓝填充 '.ddm-progress-line{display:flex;align-items:center;gap:10px;padding:0 12px 9px;}', '.ddm-progress-line.empty{display:none;}', '.ddm-progress-bar{flex:1;height:2px;background:rgba(120,120,128,0.16);border-radius:1px;overflow:hidden;}', '.ddm-progress-fill{height:100%;background:#007AFF;border-radius:1px;transition:width .5s cubic-bezier(0.4,0,0.2,1);width:0;}', '.ddm-progress-text{color:rgba(0,0,0,0.5);font-size:11px;min-width:48px;text-align:right;font-variant-numeric:tabular-nums;font-weight:500;}', '.ddm-progress-badge{font-size:10px;padding:3px 9px;border-radius:9px;font-weight:600;white-space:nowrap;transition:all .3s;letter-spacing:0.2px;}', '.ddm-progress-badge.loading{background:rgba(120,120,128,0.12);color:#8E8E93;animation:none;}', '.ddm-progress-badge.done{background:rgba(26,127,55,0.10);color:#1A7F37;}', '.ddm-progress-badge.done-none{background:rgba(255,59,48,0.1);color:rgba(194,30,18,0.7);}', // 分区内容 '.ddm-section-body{padding:8px 12px 12px;}', // 梯队标签 — v4.10.0 纯文字 + 左侧3px竖线色条 '.ddm-tier-tag{display:inline-block;font-size:10px;font-weight:600;padding:0 0 0 8px;background:none!important;border-left:3px solid transparent;line-height:1.35;margin-bottom:7px;margin-right:6px;letter-spacing:0.2px;}', '.ddm-tier-tag.t1{border-left-color:#2DA44E;color:#1A7F37;}', '.ddm-tier-tag.t2{border-left-color:#FF9500;color:#C93400;}', '.ddm-tier-tag.t3{border-left-color:#FF3B30;color:#C21E12;}', '.ddm-tier-block{margin-bottom:8px;}', '.ddm-tier-block:last-child{margin-bottom:0;}', // 站点链接 — v5.0.0 紧凑通透胶囊(8px/3px 10px/11px) '.ddm-links{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:7px;}', '.ddm-links:last-child{margin-bottom:0;}', '.ddm-links a{display:inline-flex;align-items:center;padding:3px 10px;border-radius:8px;background:transparent;color:rgba(0,0,0,0.9);text-decoration:none;font-size:11px;transition:all .2s;cursor:pointer;border:1px solid rgba(120,120,128,0.15);font-weight:500;}', '.ddm-links a:hover{background:rgba(120,120,128,0.12);color:#007AFF;transform:translateY(-1px);box-shadow:none;}', '.ddm-links a:active{transform:scale(0.95);}', // v5.0.0: checking 静态胶囊 + 尾随4px灰点呼吸 '.ddm-links a.checking{background:rgba(120,120,128,0.08)!important;color:rgba(0,0,0,0.55)!important;border-color:transparent!important;transition:all .2s;}', '.ddm-links a.checking::after{content:"";display:inline-block;width:4px;height:4px;border-radius:50%;background:#8E8E93;margin-left:6px;animation:ddm-check-dot 1s ease-in-out infinite;vertical-align:middle;flex-shrink:0;opacity:0.7;}', '@keyframes ddm-check-dot{0%,100%{opacity:1;}50%{opacity:0.2;}}', // v5.0.0: 可用站点 — 极淡底 + 文字着色 + 左侧3px绿竖条,无发光 '.ddm-links a.available{background:rgba(26,127,55,0.07)!important;color:#1A7F37!important;font-weight:500;border:1px solid rgba(26,127,55,0.22)!important;border-left:3px solid #2DA44E!important;box-shadow:none!important;}', '.ddm-links a.available:hover{background:rgba(26,127,55,0.12)!important;color:#1A7F37!important;transform:translateY(-1px);box-shadow:none!important;}', '.ddm-links a.unavailable{background:rgba(120,120,128,0.08)!important;color:rgba(60,60,67,0.4)!important;text-decoration:line-through;cursor:not-allowed;opacity:0.6;}', '.ddm-links a.unavailable:hover{transform:none;box-shadow:none;}', '.ddm-links a.error{background:rgba(255,59,48,0.1)!important;color:rgba(194,30,18,0.7)!important;opacity:0.75;}', '.ddm-links a.error:hover{transform:none;}', // v4.10.0: 无法访问 — 低透明删除线,无背景 '.ddm-links a.inaccessible{background:transparent!important;color:rgba(120,120,128,0.7)!important;opacity:0.55;text-decoration:line-through;cursor:not-allowed;border-color:transparent!important;}', '.ddm-links a.ddm-filtered{display:none!important;}', '.ddm-links a.skipped{background:rgba(120,120,128,0.08)!important;color:rgba(120,120,128,0.6)!important;text-decoration:line-through;cursor:not-allowed;opacity:0.55;border:1px dashed rgba(120,120,128,0.35)!important;}', '.ddm-links a.skipped:hover{transform:none;box-shadow:none;}', // v4.8.0 被拦截态 — 系统紫虚线 (CF挑战/验证码) '.ddm-links a.blocked{background:rgba(88,86,214,0.14)!important;color:#5856D6!important;border:1px dashed rgba(88,86,214,0.45)!important;opacity:0.85;font-weight:500;}', '.ddm-links a.blocked:hover{transform:none;box-shadow:none;background:rgba(88,86,214,0.2)!important;}', // 网盘资源列表 — iOS 卡片 '.ddm-pan-list{display:flex;flex-direction:column;gap:6px;}', '.ddm-pan-res{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:11px;background:#f6f6f7;color:#1c1c1e;font-size:12px;transition:all .22s;cursor:pointer;border:1px solid rgba(120,120,128,0.12);text-decoration:none;}', '.ddm-pan-res:hover{background:#ffffff;transform:translateX(2px);border-color:rgba(0,122,255,0.3);box-shadow:0 2px 8px rgba(0,0,0,0.06);}', '.ddm-pan-tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:6px;font-size:10px;color:#fff;font-weight:600;flex-shrink:0;letter-spacing:0.2px;}', '.ddm-pan-tag.ali{background:#FF9500;}', '.ddm-pan-tag.quark{background:#007AFF;}', '.ddm-pan-tag.baidu{background:#5856D6;}', '.ddm-pan-tag.xunlei{background:#8E8E93;}', '.ddm-pan-tag.other{background:#8E8E93;}', '.ddm-pan-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#1c1c1e;text-decoration:none;max-width:none;font-weight:500;}', '.ddm-pan-pwd{display:inline-flex;align-items:center;gap:4px;color:#FF3B30;font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;font-size:11px;cursor:pointer;padding:3px 8px;background:rgba(255,59,48,0.1);border-radius:7px;transition:all .2s;flex-shrink:0;font-weight:600;}', '.ddm-pan-pwd:hover{background:#FF3B30;color:#fff;}', '.ddm-pan-pwd.copied{background:#2DA44E;color:#fff;}', // 加载/空状态 '.ddm-loading{color:#8E8E93;font-size:12px;padding:10px 0;display:flex;align-items:center;gap:8px;font-weight:500;}', '.ddm-loading::before{content:"";display:inline-block;width:13px;height:13px;border:2px solid rgba(142,142,147,0.3);border-top-color:#007AFF;border-radius:50%;animation:ddm-spin 0.7s linear infinite;}', '@keyframes ddm-spin{to{transform:rotate(360deg);}}', '.ddm-empty{color:#8E8E93;font-size:12px;padding:11px 13px;display:flex;align-items:center;gap:6px;background:rgba(120,120,128,0.08);border-radius:11px;border:1px solid rgba(120,120,128,0.16);}', // 底部统计 — v5.0.0 最弱信息层 10px opacity 0.35 '.ddm-stats{padding:11px 18px;background:transparent;border-top:1px solid rgba(120,120,128,0.12);font-size:10px;color:rgba(0,0,0,0.6);opacity:0.75;display:flex;gap:12px;flex-wrap:wrap;justify-content:center;align-items:center;}', '.ddm-stat-item{display:flex;align-items:center;gap:4px;}', '.ddm-stat-num{color:rgba(0,0,0,0.6);font-weight:600;font-size:12px;}', '.ddm-stat-tier{display:flex;align-items:center;gap:4px;padding:2px 8px;border-radius:8px;font-size:10px;font-weight:600;}', '.ddm-stat-tier.t1{background:rgba(26,127,55,0.10);color:#1A7F37;}', '.ddm-stat-tier.t2{background:rgba(255,149,0,0.12);color:#C93400;}', '.ddm-stat-tier.t3{background:rgba(255,59,48,0.1);color:#C21E12;}', // iOS 风格细滚动条 '#ddm-float-wrap::-webkit-scrollbar{width:6px;}', '#ddm-float-wrap::-webkit-scrollbar-track{background:transparent;}', '#ddm-float-wrap::-webkit-scrollbar-thumb{background:rgba(120,120,128,0.3);border-radius:3px;}', '#ddm-float-wrap::-webkit-scrollbar-thumb:hover{background:rgba(120,120,128,0.5);}', '#ddm-float-wrap{scrollbar-width:thin;scrollbar-color:rgba(120,120,128,0.3) transparent;}', // ===== v4.9.0 动画系统 ===== '@keyframes ddm-panel-in{0%{opacity:0;transform:translateY(20px) scale(0.96);}55%{opacity:1;transform:translateY(-2px) scale(1.006);}100%{opacity:1;transform:translateY(0) scale(1);}}', '.ddm-panel{animation:ddm-panel-in .5s cubic-bezier(0.34,1.56,0.64,1) both;}', '@keyframes ddm-body-in{from{opacity:0;transform:translateY(-8px);}to{opacity:1;transform:translateY(0);}}', '.ddm-body-in{animation:ddm-body-in .3s ease-out both;}', '@keyframes ddm-link-pop{0%{opacity:0;transform:scale(0.8);}100%{opacity:1;transform:scale(1);}}', // v5.0.0: 进度完成态 — 纯绿实色,无渐变流动 '.ddm-progress-fill.done{background:#2DA44E!important;transition:width .5s cubic-bezier(0.4,0,0.2,1);}', // ===== v4.9.0 Tooltip ===== '@keyframes ddm-tip-in{from{opacity:0;transform:scale(0.85);}to{opacity:1;transform:scale(1);}}', '.ddm-tip{position:fixed;z-index:2147483646;background:rgba(30,30,34,0.92);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);color:#fff;font-size:11px;padding:8px 12px;border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,0.3);pointer-events:none;max-width:240px;animation:ddm-tip-in .2s cubic-bezier(0.34,1.56,0.64,1) both;border:1px solid rgba(255,255,255,0.12);font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC",sans-serif;}', '.ddm-tip-name{font-weight:600;font-size:12px;margin-bottom:2px;}', '.ddm-tip-desc{color:rgba(255,255,255,0.75);margin-top:1px;}', '.ddm-tip-tier{display:inline-block;margin-top:5px;font-size:10px;padding:1px 8px;border-radius:6px;background:rgba(255,255,255,0.16);color:rgba(255,255,255,0.85);}', // ===== v4.9.0 Toast ===== '@keyframes ddm-toast-in{from{opacity:0;transform:translate(-50%,14px);}to{opacity:1;transform:translate(-50%,0);}}', '@keyframes ddm-toast-out{from{opacity:1;transform:translate(-50%,0);}to{opacity:0;transform:translate(-50%,-10px);}}', '.ddm-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:2147483647;background:rgba(40,40,45,0.78);-webkit-backdrop-filter:blur(18px) saturate(150%);backdrop-filter:blur(18px) saturate(150%);color:#fff;font-size:13px;font-weight:500;padding:10px 20px;border-radius:14px;box-shadow:0 8px 28px rgba(0,0,0,0.25);border:1px solid rgba(255,255,255,0.15);font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC",sans-serif;animation:ddm-toast-in .35s cubic-bezier(0.34,1.56,0.64,1) both;letter-spacing:0.2px;}', '.ddm-toast-hide{animation:ddm-toast-out .3s ease-in both!important;}', // ===== v4.9.0 实时过滤搜索框 ===== '.ddm-search-box{margin:0 14px 12px;position:relative;}', '.ddm-search-input{width:100%;box-sizing:border-box;padding:8px 14px 8px 34px;border-radius:11px;border:1px solid transparent;background:rgba(120,120,128,0.12);color:#1c1c1e;font-size:12px;font-family:inherit;outline:none;transition:all .2s;font-weight:500;}', '.ddm-search-input::-webkit-input-placeholder{color:rgba(0,0,0,0.3);}', '.ddm-search-input:focus{border-color:rgba(0,122,255,0.4);background:rgba(120,120,128,0.16);box-shadow:none;}', '.ddm-search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:13px;opacity:0.55;pointer-events:none;}', '.ddm-links a.ddm-search-hide{display:none!important;}', // ===== v4.9.0 启动标记状态灯 ===== '.ddm-boot-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#FF9500;margin-right:7px;flex-shrink:0;animation:ddm-dot-breathe 1.2s ease-in-out infinite;}', '@keyframes ddm-dot-breathe{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.4;transform:scale(0.72);}}', '.ddm-boot-dot.done{background:#2DA44E;animation:none;transform:scale(1);}', // ===== v5.0.0 深色模式(通透融合)===== '@media (prefers-color-scheme: dark){' + '.ddm-panel{background:#1c1c1e;border-color:rgba(120,120,128,0.16);color:#f2f2f7;box-shadow:0 12px 40px rgba(0,0,0,0.45);}' + '.ddm-header h2{color:rgba(255,255,255,0.9);}' + '.ddm-actions a{color:#0A84FF;background:rgba(10,132,255,0.2);}' + '.ddm-summary-item{background:rgba(255,255,255,0.08);}' + '.ddm-summary-num{color:#98989f;}' + '.ddm-summary-num.has-val{color:#4CC38A;font-weight:500;}' + '.ddm-summary-label{color:rgba(255,255,255,0.5);}' + '.ddm-section{background:rgba(255,255,255,0.05);border-color:rgba(120,120,128,0.12);}' + '.ddm-section.ddm-section-has-result{background:rgba(76,195,138,0.07);border-color:rgba(76,195,138,0.35)!important;}' + '.ddm-section-header:hover{background:rgba(255,255,255,0.08);}' + '.ddm-section-title{color:rgba(255,255,255,0.9);}' + '.ddm-section-count{color:rgba(255,255,255,0.5);background:rgba(255,255,255,0.12);}' + '.ddm-section-count.has-result{background:rgba(76,195,138,0.12);color:#4CC38A;}' + '.ddm-empty-tag{color:rgba(255,255,255,0.5);}' + '.ddm-arrow{color:rgba(255,255,255,0.4);}' + '.ddm-progress-bar{background:rgba(255,255,255,0.14);}' + '.ddm-progress-fill{background:#0A84FF;}' + '.ddm-progress-fill.done{background:#4CC38A!important;}' + '.ddm-progress-text{color:rgba(255,255,255,0.5);}' + '.ddm-progress-badge.done{background:rgba(76,195,138,0.12);color:#4CC38A;}' + '.ddm-progress-badge.done-none{background:rgba(255,69,58,0.16);color:#FF453A;}' + '.ddm-progress-badge.loading{background:rgba(255,255,255,0.1);color:#98989f;animation:none;}' + '.ddm-links a{background:transparent;color:rgba(255,255,255,0.9);border-color:rgba(120,120,128,0.15);}' + '.ddm-links a:hover{background:rgba(255,255,255,0.12);color:#0A84FF;}' + '.ddm-links a.checking{background:rgba(255,255,255,0.06)!important;color:rgba(255,255,255,0.5)!important;border-color:transparent!important;}' + '.ddm-links a.checking::after{background:rgba(255,255,255,0.5);}' + '.ddm-links a.available{background:rgba(76,195,138,0.12)!important;color:#4CC38A!important;border:1px solid rgba(76,195,138,0.30)!important;border-left:3px solid #4CC38A!important;box-shadow:none!important;}' + '.ddm-links a.available:hover{background:rgba(76,195,138,0.18)!important;color:#4CC38A!important;box-shadow:none!important;}' + '.ddm-links a.unavailable{color:rgba(142,142,147,0.5)!important;}' + '.ddm-links a.inaccessible{color:rgba(142,142,147,0.55)!important;opacity:0.55!important;}' + '.ddm-links a.blocked{background:rgba(94,92,230,0.16)!important;color:#BF5AF2!important;}' + '.ddm-tier-tag.t1{border-left-color:#4CC38A;color:#4CC38A;}' + '.ddm-tier-tag.t2{border-left-color:#FF9F0A;color:#FF9F0A;}' + '.ddm-tier-tag.t3{border-left-color:#FF453A;color:#FF453A;}' + '.ddm-pan-res{background:rgba(255,255,255,0.05);color:#f2f2f7;border-color:rgba(120,120,128,0.12);}' + '.ddm-pan-res:hover{background:rgba(255,255,255,0.1);}' + '.ddm-pan-name{color:#f2f2f7;}' + '.ddm-pan-pwd{color:#FF453A;background:rgba(255,69,58,0.16);}' + '.ddm-loading{color:#98989f;}' + '.ddm-empty{background:rgba(255,255,255,0.05);color:#98989f;border-color:rgba(120,120,128,0.12);}' + '.ddm-stats{background:transparent;border-top-color:rgba(120,120,128,0.12);color:rgba(255,255,255,0.6);opacity:0.75;}' + '.ddm-stat-num{color:rgba(255,255,255,0.6);}' + '.ddm-stat-tier.t1{background:rgba(76,195,138,0.16);color:#4CC38A;}' + '.ddm-stat-tier.t2{background:rgba(255,159,10,0.14);color:#FF9F0A;}' + '.ddm-stat-tier.t3{background:rgba(255,69,58,0.14);color:#FF453A;}' + '.ddm-search-input{background:rgba(120,120,128,0.12);color:#f2f2f7;border-color:transparent;}' + '.ddm-search-input::-webkit-input-placeholder{color:rgba(255,255,255,0.25);}' + '.ddm-search-input:focus{border-color:rgba(10,132,255,0.4);background:rgba(120,120,128,0.18);}' + '.ddm-pan-tag.baidu{background:#5E5CE6;}' + '#ddm-float-wrap::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.25);}' + '#ddm-boot-mark{background:#2c2c2e!important;color:#f2f2f7!important;border-color:rgba(120,120,128,0.16)!important;}' + '.ddm-toast{background:rgba(44,44,46,0.85);}' + '.ddm-tip{background:rgba(60,60,64,0.94);}' + '}' ].join('\n'); // ============================================================ // 并发控制池 (v4.7.0 动态并发 6~12) // ============================================================ var CONCUR = { min: 6, max: 12, current: 8 }; var _respTimes = []; function getConcurrency() { return CONCUR.current; } // 根据最近请求的响应时间分布,在 6~12 之间自适应调整并发上限 function updateConcurrency(elapsed) { if (!(elapsed > 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, doneFired = false; function getLim() { return (limit > 0) ? limit : getConcurrency(); } function next() { // 多个 worker 同帧收尾时 next() 会被重复调度,done 只允许触发一次, // 否则下一梯队被并发重复启动、可用站点计数翻倍(v5.2.1 遗留缺陷) if (finished >= items.length) { if (done && !doneFired) { doneFired = true; 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_API_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 反拦截检测 (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; } // ============================================================ // v5.4.1 结果条目校验器 // 旧算法在整页文本上打分,而搜索页必然回显查询词(<title>、搜索框 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; var RELEASE = /(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*[集话])/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(''); if (extra.length > 1 && (RELEASE.test(extra) || /(19|20)\d\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 智能关键词选择 (按站点类型选最优候选, 最多3个) // ============================================================ 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); } 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 { // 站点答了但本片无资源 ≠ 站点坏了:不写健康度缓存, // 否则一部冷门票就会把该站拉黑 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); 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.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 '<div class="ddm-tier-block">' + '<span class="ddm-tier-tag t' + tier + '">' + TIER_CONFIG[tier].label + ' · ' + TIER_CONFIG[tier].desc + '</span>' + '<div class="ddm-links" id="' + prefix + '-t' + tier + '-links"></div>' + '</div>'; } var panel = document.createElement('div'); panel.id = 'ddm-panel'; panel.className = 'ddm-panel'; panel.innerHTML = '<div class="ddm-header">' + '<h2><i class="ddm-icon">⬇</i> ' + NAME + ' v' + VER + '</h2>' + '<div class="ddm-actions"><a href="javascript:void(0)" id="ddm-reload" title="刷新检测">🔄</a></div>' + '</div>' + '<div class="ddm-summary-bar">' + '<div class="ddm-summary-item"><span class="ddm-summary-num" id="ddm-summary-dl">0</span><span class="ddm-summary-label">下载</span></div>' + '<div class="ddm-summary-item"><span class="ddm-summary-num" id="ddm-summary-pan">0</span><span class="ddm-summary-label">网盘直链</span></div>' + '<div class="ddm-summary-item"><span class="ddm-summary-num" id="ddm-summary-pansearch">0</span><span class="ddm-summary-label">网盘搜索</span></div>' + '<div class="ddm-summary-item"><span class="ddm-summary-num" id="ddm-summary-online">0</span><span class="ddm-summary-label">在线</span></div>' + '</div>' + // v4.9.0: 实时过滤搜索框 '<div class="ddm-search-box">' + '<span class="ddm-search-icon">🔎</span>' + '<input type="text" class="ddm-search-input" id="ddm-search" placeholder="实时过滤站点名称..." autocomplete="off">' + '</div>' + // 1. 下载资源(首位) '<div class="ddm-section" id="ddm-sec-dl">' + '<div class="ddm-section-header" data-sec="dl">' + '<span class="ddm-arrow">▸</span>' + '<span class="ddm-section-icon">🧲</span>' + '<span class="ddm-section-title">下载资源</span>' + '<span class="ddm-empty-tag" id="ddm-dl-empty" style="display:none;">暂未找到相关资源</span>' + '<span class="ddm-section-count" id="ddm-dl-cnt">0/' + dlN + '</span>' + '</div>' + '<div class="ddm-progress-line" id="ddm-dl-progress-line">' + '<div class="ddm-progress-bar"><div class="ddm-progress-fill" id="ddm-dl-bar"></div></div>' + '<span class="ddm-progress-text" id="ddm-dl-prog">0/' + dlN + '</span>' + '</div>' + '<div class="ddm-section-body" id="ddm-dl-body" style="display:none;">' + tierBlock('ddm-dl', 1, tDl[1]) + tierBlock('ddm-dl', 2, tDl[2]) + tierBlock('ddm-dl', 3, tDl[3]) + '</div>' + '</div>' + // 2. 网盘资源(含API+搜索站) '<div class="ddm-section" id="ddm-sec-pan">' + '<div class="ddm-section-header" data-sec="pan">' + '<span class="ddm-arrow">▾</span>' + '<span class="ddm-section-icon">☁️</span>' + '<span class="ddm-section-title">网盘资源</span>' + '<span class="ddm-section-count" id="ddm-pan-cnt">加载中</span>' + '</div>' + '<div class="ddm-progress-line" id="ddm-pan-progress-line"><span class="ddm-progress-badge loading" id="ddm-pan-badge">搜索中</span></div>' + '<div class="ddm-section-body" id="ddm-pan-body">' + '<div class="ddm-loading">正在搜索网盘资源...</div>' + '</div>' + '<div class="ddm-section-header" data-sec="pansearch" style="padding-top:0;padding-bottom:6px;">' + '<span class="ddm-arrow" id="ddm-pansearch-arrow">▸</span>' + '<span class="ddm-section-icon" style="font-size:13px;">🔍</span>' + '<span class="ddm-section-title" style="font-size:12px;font-weight:500;">更多网盘搜索站</span>' + '<span class="ddm-empty-tag" id="ddm-pansearch-empty" style="display:none;font-size:10px;">暂未找到相关资源</span>' + '<span class="ddm-section-count" id="ddm-pansearch-cnt" style="font-size:11px;">0/' + panN + '</span>' + '</div>' + '<div class="ddm-progress-line" id="ddm-pansearch-progress-line" style="padding-top:0;">' + '<div class="ddm-progress-bar"><div class="ddm-progress-fill" id="ddm-pansearch-bar"></div></div>' + '<span class="ddm-progress-text" id="ddm-pansearch-prog">0/' + panN + '</span>' + '</div>' + '<div class="ddm-section-body" id="ddm-pansearch-body" style="padding-top:6px;display:none;">' + tierBlock('ddm-pansearch', 1, tPan[1]) + tierBlock('ddm-pansearch', 2, tPan[2]) + tierBlock('ddm-pansearch', 3, tPan[3]) + '</div>' + '</div>' + // 3. 在线观看(末位) '<div class="ddm-section" id="ddm-sec-online">' + '<div class="ddm-section-header" data-sec="online">' + '<span class="ddm-arrow">▸</span>' + '<span class="ddm-section-icon">📺</span>' + '<span class="ddm-section-title">在线观看</span>' + '<span class="ddm-empty-tag" id="ddm-online-empty" style="display:none;">暂未找到相关资源</span>' + '<span class="ddm-section-count" id="ddm-online-cnt">0/' + onlineN + '</span>' + '</div>' + '<div class="ddm-progress-line" id="ddm-online-progress-line">' + '<div class="ddm-progress-bar"><div class="ddm-progress-fill" id="ddm-online-bar"></div></div>' + '<span class="ddm-progress-text" id="ddm-online-prog">0/' + onlineN + '</span>' + '</div>' + '<div class="ddm-section-body" id="ddm-online-body" style="display:none;">' + tierBlock('ddm-online', 1, tOnline[1]) + tierBlock('ddm-online', 2, tOnline[2]) + tierBlock('ddm-online', 3, tOnline[3]) + '</div>' + '</div>' + '<div class="ddm-stats">' + '<span class="ddm-stat-item">总计 <span class="ddm-stat-num">' + totalAll + '</span> 站点</span>' + '<span class="ddm-stat-tier t1">快 ' + t1Total + '</span>' + '<span class="ddm-stat-tier t2">中 ' + t2Total + '</span>' + '<span class="ddm-stat-tier t3">慢 ' + t3Total + '</span>' + '</div>'; 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) { 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; } // 分区检测:三大类共用同一渲染流程(网盘直链 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 = '<div class="ddm-pan-list">'; for (var i = 0; i < items.length; i++) { var it = items[i]; html += '<a class="ddm-pan-res" href="' + esc(it.url) + '" target="_blank" rel="noopener" title="' + esc(it.name) + '">'; html += '<span class="ddm-pan-tag ' + esc(it.tag) + '">' + esc(it.tagLabel) + '</span>'; html += '<span class="ddm-pan-name">' + esc(it.name) + '</span>'; if (it.pwd) html += '<span class="ddm-pan-pwd" data-pwd="' + esc(it.pwd) + '" title="点击复制提取码">🔑' + esc(it.pwd) + '</span>'; html += '</a>'; } html += '</div>'; 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 = '<div class="ddm-empty">未找到网盘资源,可查看下方搜索站</div>'; 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(/<script[^>]+id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/i); if (nd) { var j = JSON.parse(nd[1]); var sum = j && j.props && j.props.pageProps && j.props.pageProps.aboveTheFoldData && j.props.pageProps.aboveTheFoldData.ratingsSummary; if (sum && sum.aggregateRating) rating = String(sum.aggregateRating); } } catch (e1) {} if (!rating) { var ld = res.responseText.match(/<script[^>]+type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/i); if (ld) { try { var o = JSON.parse(ld[1]); var rr = (o && o.aggregateRating) || (o && o[0] && o[0].aggregateRating); if (rr && rr.ratingValue) rating = String(rr.ratingValue); } catch (e2) {} } } if (!rating) { var m = res.responseText.match(/"ratingValue"\s*:\s*"?(\d+\.?\d*)"?/); if (m) rating = m[1]; } if (rating) { 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 = '<div class="rating_logo">IMDb评分</div><div class="rating_self clearfix" typeof="v:Rating"><strong class="rating_num" property="v:average">' + esc(rating) + '</strong><span class="m-right10">星</span></div><a href="https://www.imdb.com/title/'+esc(movie.imdbId)+'/" target="_blank" style="color:#3377aa;font-size:12px;">查看IMDb页面</a>'; 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 v'+VER); } catch(e) { logErr('initAll error', e); window._ddm_initialized = false; } } // ============================================================ // 主入口 // ============================================================ function main() { log('main() v'+VER); try { var movie = getMovieInfo(); if (!movie.cnTitle) { var bm = document.getElementById('ddm-boot-mark'); if(bm)bm.innerHTML=NAME + ' - 非详情页?'; 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() { removePanel(); resetCaches(); window._ddm_initialized = false; main(); }); GM_registerMenuCommand('🧹 清空缓存', function() { resetCaches(); try { if (typeof GM_listValues !== 'undefined') { var ks=GM_listValues(); for (var i=0;i<ks.length;i++){ if (ks[i].indexOf(HEALTH_KEY_PREFIX)===0) GM_deleteValue(ks[i]); } } } catch(e){} alert('缓存已清空(含站点健康度缓存)'); }); function setAllSections(open) { var bodies = document.querySelectorAll('.ddm-section-body'); for (var i=0;i<bodies.length;i++){ bodies[i].style.display = open ? 'block' : 'none'; } var arrows = document.querySelectorAll('.ddm-section-header .ddm-arrow'); for (var j=0;j<arrows.length;j++){ arrows[j].textContent = open ? '▾' : '▸'; } } GM_registerMenuCommand('🔽 全部展开', function(){ setAllSections(true); }); GM_registerMenuCommand('🔼 全部折叠', function(){ setAllSections(false); }); } } catch(e) {} // startWhenReady 自身按 200ms 轮询直至 DOM 就绪,无需多路定时器 startWhenReady(); window.addEventListener('load', function(){ setTimeout(startWhenReady, 50); }, false); log('v'+VER+' initialized'); })();