// ==UserScript== // @name 豆瓣下载助手 // @namespace https://github.com/Shshuaicc/douban-download-master // @version 5.4.6 // @description 豆瓣电影详情页下载/网盘/在线 三大类资源聚合搜索 v5.4.6 - 刷新按钮闪退修复·版本标识自动同步 // @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.6 (原名「豆瓣电影下载大师」) * 更新日志: * v5.4.6 - 刷新按钮闪退修复 + 版本标识同步 * 【修复·交互】面板右上「🔄 刷新检测」按钮一点就「闪退」:按钮先 removePanel 再 * initAll,但 initAll 顶部的一次性标志 `_ddm_initialized` 只有油猴菜单的 * 「重新加载面板」路径会清除,按钮路径下仍为 true → 直接 return,面板消失 * 且不再出现(该缺陷早期版本即存在)。现改以「面板是否已在 DOM」为唯一判据, * 两条重载路径行为一致;只动 initAll 首行判定,不改任何界面元素。 * 已在真实浏览器 DOM 上对照复现:旧逻辑 reload 后 skip(面板缺失), * 新逻辑 built(面板正常重建)。 * 【修复·显示】面板标题与左上角启动角标一直显示「豆瓣下载助手 v5.4.3」:迭代 * 5.4.4 / 5.4.5 时只同步了 @version 与头部更新日志,漏改内部常量 VER * (面板 h2 标题、加载角标、日志前缀都读它)。本次一并同步为 5.4.6, * 后续迭代需同时更新 @version / 头部标题 / VER 三处。 * 【不变】界面元素与显示完全不变:CSS / TIER_CONFIG / buildPanel 三段哈希与 * v5.4.5 逐字节一致(7f60a497 / b04e6ca5 / 8090ff55),98 个 ddm-* 类名全等 * * v5.4.5 - 网盘聚合检索稳定性修复 * 【修复·核心】「实际使用网盘资源一个都搜不到」:实测聚合接口 so.252035.xyz * 响应波动极大——正常 4-5s、302 跳转跟随约 10s、偶发挂起 40s+ 无响应 * (curl 与真实浏览器双通道复测,单次成功率约五成)。旧实现单请求 * 10s 超时 + 2 次重试,连续挂起时直接返回空 →「未找到网盘资源」。 * 现改为三次错峰竞速:t=0 / t=3s / t=7s 各发一枪(超时 12s/12s/15s), * 任一枪拿到合法 JSON 立刻渲染,三枪全落空才报无结果; * 按实测单枪成功率推算,三枪全挂概率从约 50% 降至约 8%。 * 【修复·附带】gmXhr 重试参数的 falsy 缺陷:`retries:0` 会被 `|| 1` 吞掉变成 * 一次重试,显式 0 现在真正生效(网盘核验探针与竞速枪均只发一次)。 * 【复核·核验逻辑】v5.4.3 的失效剔除判据逐条重测:quark 41011「分享地址已失效」、 * 41031「分享者用户封禁链接查看受限」在真实浏览器打开分享页均显示 * 「该分享已失效,不可访问」→ 剔除判据成立;正常链接 code:0 判活无误。 * 网盘「零结果」与核验逻辑无关,纯为接口超时所致。 * 【不变】界面元素与显示完全不变:CSS / TIER_CONFIG / buildPanel 三段哈希与 * v5.4.4 逐字节一致(7f60a497 / b04e6ca5 / 8090ff55),98 个 ddm-* 类名全等 * * v5.4.4 - 假绿灯误报修复 + 检索关键词逻辑明确 + 下载站扩充 * 【修复·核心】「没有检索到资源却显示为绿色」(实例 ymaoo.cn):三处根因已定位并修复: * (1) 发行质量词匹配无词边界,「favicon / navi」里的 'avi' 被当成视频格式命中; * (2) 「标题+年份」证据的年份匹配为 (19|20)\d\d,命中了站脚「1905电影网」这类数字; * (3) 多关键词检索的异常分支曾把「解析失败」当成「有资源」。 * 现:质量词一律要求词边界、年份收窄为 1930-2039、异常一律判无资源。 * 回归:145 个真实页面样本(幽灵片误报样本 89 + 热门片 47)逐站复测, * 幽灵误报 0/89,原有可核验结果 47/47 全保留,仅真实误报翻转。 * 【明确·检索关键词】按站点类型选择候选(v4.8.0 起,本次明确并加固,最多 4 个): * - 中文站:中文全名 → 中文全名+年份 → 冒号前主名 → 英文名兜底 * - 国际站:英文名 → 英文名+年份 → 中文名兜底 * 候选上限 3→4、中文站补上「英文名兜底」,解决中文站只收英文名资源时的漏检 * 【扩充·站点】下载 44 → 64:新增 20 站(网站总数 136 → 156) * - 6 站结果条目级实测可核验:中国高清网(CNmkv)、TorrentProject、TFPDL、 * RLSBB 三镜像(.cc/.in/.to,实测关键词可拿到具体结果行) * - 14 站域名与搜索端点实测存活、结果前端渲染:BT蚂蚁、磁力天堂、种子资源、 * BTSOW、YifyMovies、nekoBT、Youplex、迅雷下载天堂、影猫仓库、种子搜62、 * NZBKing、SeedHub、NZBIndex、磁力王(desc 已标注,未核验即隐藏,不产生假绿) * 【参考】同人脚本(豆瓣侠 v1.0.8 / 豆瓣资源下载大师 v4.1.1)思路比对:其标题清洗、 * 聚合检索等可取点早前已具备;markdown 导出 / TMDB / 翻译按钮等均属 UI 功能, * 与「界面元素不变」要求冲突,不引入。 * 【不变】界面元素与显示完全不变:CSS / TIER_CONFIG / buildPanel 三段哈希与 v5.4.3 * 逐字节一致(7f60a497 / b04e6ca5 / 8090ff55),98 个 ddm-* 类名全等 * * v5.4.3 - 网盘直链失效核验 + 国内站扩充 * 【修复·核心】「网盘检索到但链接已失效却仍旧显示」:聚合接口返回的是历史收录的 * 分享链接,很多早已被取消/过期,旧版不校验直接把死链铺进面板。 * 新增 verifyPanLinks(),展示前按各家匿名接口逐条问一次: * - 夸克 POST drive.quark.cn/1/clouddrive/share/sharepage/token * code:0 存活;41006 分享不存在 / 41012 已取消分享 → 剔除 * - 阿里 POST api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous * NotFound.ShareLink → 剔除;返回 file_infos/share_name → 有效 * - 百度 服务端页面标题「百度网盘-链接不存在」「页面不存在」或 HTTP 404 → 剔除 * - 天翼 HTTP 404 /「该页已…」→ 剔除 * - 123盘·迅雷·115·UC 需签名或设备风控,匿名无法定性 → 一律保留(宁漏不误杀) * 判定结果按链接缓存 12h;8s 预算内未回应的保留显示,不阻塞面板 * 实测(真实聚合结果):流浪地球2 37 条剔除 6 条(16%)、封神第二部 26 条剔除 * 8 条(31%);跨 4 部影片 178 条可判定直链中 65 条为死链(37%) * 【扩充·站点】83 → 136(下载 29→44 · 网盘 26→46 · 在线 28→46),本批 54 站均为国内站: * - 端点发现方式:读站点自己的搜索表单 + 遍历国内常见引擎模板(盘搜系/苹果CMS/WP) * - 5 站结果条目级实测可核验(能拿到具体结果行):爱搜、百盘搜、优酷、 * 麒麟资源、新电影天堂(例:'肖申克的救赎 2160p remux (1994)') * - 其余 49 站为「域名与搜索端点实测存活、结果由前端渲染」,desc 已标注, * 沿用 v5.3.0 起的「未核验即隐藏」行为,不会给出假的有资源标记 * - 下载类国内可达站实测仅 15 个,未凑满 20(其余候选实测为 404/停放/CF 拦截) * - 实测依据:63 个国内网盘搜索引擎逐个探测,仅 1 个暴露可读的匿名 JSON 接口 * 【改动】gmXhr 增加 method/data 支持(供上述 POST 接口使用),并修正缓存键含请求方法 * 【实测·全量复测】136 站逐个用幽灵片《雾港迷案》复测:误报 0(0.0%); * 用真实片《肖申克的救赎》复测:45 站可给出结果行(下载 9 · 网盘 11 · 在线 25), * 117/136 站可连通 * 【不变】界面元素与显示完全不变:CSS 规则、TIER_CONFIG、buildPanel 三段与 v5.4.2 * 逐字节一致(哈希 7f60a497 / b04e6ca5 / 8090ff55),98 个 ddm-* 类名全等 * * v5.4.2 - 站点扩容:下载 +20 · 网盘 +20(本版核心,全部逐站实测,逻辑与界面零改动) * 【方法】下载类候选走双通道验证:本机直连 curl + 服务端抓取,以真实关键词 * (Inception / 阿凡达)实际检索,返回真实结果行才算通过; * 网盘类候选实测 200+ 域名/端点组合,搜索引擎型逐站试 6 种搜索端点 * 变体,响应体须同时含关键词回显与网盘直链标记才判定为可用 * 【新增·下载】+20(均为国际站,需代理): * - 15 站证据级验证(引用到具体结果行):1337x 主站+3 镜像(ST/TW/WS)、 * LimeTorrents 主站+2 镜像(LOL/INFO)、YTS.mx+镜像BZ、Kickass、 * TorrentFunk、SolidTorrents、BitSearch、Torrents-CSV、Knaben 镜像(.net) * - 5 站存活实测(站活、端点实,条目级证据受抓取截断所限): * EZTV、TorrentGalaxy、BT4G、GloTorrents、iBit * - 剔除:apibay(JSON接口,静态解析不出结果行)、nyaa/animetosho(仅动画)、 * torrentproject/idope(被劫持至赌场页)、tgx.rs(停放)、btdig/torlock 等(CF挑战) * 【新增·网盘】+20(useEn 均为 false,中文引擎走中文片名,与 v4.8.0 分类设计一致): * - 5 站「与脚本同款静态解析」实测可出结果(服务端渲染):咔帕搜索、 * 夸克搜(kuakeso.com)、盘搜(panso.pro)、360盘搜、PanSearch(pansearch.me) * - 15 站实测存活但前端渲染型(浏览器内可用,脚本静态核验无法确认, * 运行时会被 v5.3.0 起的「无资源即隐藏」逻辑过滤,desc 以「前端渲染」标注): * 小马盘搜、97盘搜、盘搜吧、聚盘搜、飞猪盘、大力盘、猫狸盘搜、爱盘搜、 * 秒搜、易搜、UP云搜、奇乐搜、趣盘搜、盘他一下、盘友圈 * 【说明】与 v5.3.0/v5.4.1 结论一致:中文网盘搜索生态的可静态核验站点已基本收齐, * 本次对 15 家导航站/推荐榜单/开源项目实例做穷举后仅得上述 5 家 SSR 站; * 其余候选已过期抢注(如 kuakesou.com 停放页)、CF 挑战(秒搜/易搜/奇乐搜 * 直连被拦)或前端渲染。站点总数 43 → 83(下载 9→29 · 网盘 6→26 · 在线 28)。 * * 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 = '豆瓣下载助手'; // v5.4.6: 版本号优先取油猴注入的 GM_info.script.version(与 @version 天然一致),取不到 // 才用字面量兜底。旧写法每次迭代只改 @version 而漏改 VER,面板标题与角标连着两个 // 版本都还显示 v5.4.3。 var VER = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) || '5.4.6'; // ============================================================ // 【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 = (typeof opts.retries === 'number') ? opts.retries : 1; var retryCount = 0; var cacheKey = (opts.method || 'GET') + ' ' + 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: opts.method || 'GET', url:url, headers:fh, timeout:timeout, data: opts.data || null, 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) === // v5.4.2: +20 国际BT/磁力站(15 站证据级验证 + 5 站存活实测,全部需代理), // 主站 tier2 / 镜像与未决站 tier3,端点模板见各 url/suffix 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 }, // ---- v5.4.2 新增 · 证据级验证(真实关键词检索到结果行)---- { name:'SolidTorrents', url:'https://solidtorrents.to/search?q=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'BitSearch', url:'https://bitsearch.to/search?q=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'TorrentsCSV', url:'https://torrents-csv.com/search?q=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'1337x', url:'https://1337x.to/search/', useEn:true, suffix:'/1/', desc:'需代理·BT磁力', tier:2 }, { name:'Kickass', url:'https://kickasstorrents.to/search/', useEn:true, suffix:'/', desc:'需代理·BT磁力', tier:2 }, { name:'TorrentFunk', url:'https://www.torrentfunk.com/all/torrents/', useEn:true, suffix:'-torrent.html', desc:'需代理·BT磁力', tier:2 }, { name:'LimeTorrents', url:'https://www.limetorrents.pro/search/all/', useEn:true, suffix:'/', desc:'需代理·BT磁力', tier:2 }, { name:'YTS.mx', url:'https://yts.mx/browse-movies/', useEn:true, suffix:'/all/0/latest/0/all', desc:'需代理·高清电影BT', tier:2 }, { name:'Knaben镜像', url:'https://knaben.net/search/', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'1337x镜像ST', url:'https://1337x.st/search/', useEn:true, suffix:'/1/', desc:'需代理·BT磁力', tier:3 }, { name:'1337x镜像TW', url:'https://1337x.tw/search/', useEn:true, suffix:'/1/', desc:'需代理·BT磁力', tier:3 }, { name:'1337x镜像WS', url:'https://x1337x.ws/search/', useEn:true, suffix:'/1/', desc:'需代理·BT磁力', tier:3 }, { name:'Lime镜像LOL', url:'https://www.limetorrents.lol/search/all/', useEn:true, suffix:'/', desc:'需代理·BT磁力', tier:3 }, { name:'Lime镜像INFO', url:'https://limetorrents.info/search/all/', useEn:true, suffix:'/', desc:'需代理·BT磁力', tier:3 }, { name:'YTS镜像BZ', url:'https://yts.bz/browse-movies/', useEn:true, suffix:'/all/0/latest/0/all', desc:'需代理·高清电影BT', tier:3 }, // ---- v5.4.2 新增 · 存活实测(条目级证据受抓取截断所限)---- { name:'TorrentGalaxy', url:'https://torrentgalaxy.to/torrents.php?search=', useEn:true, desc:'需代理·BT磁力', tier:2 }, { name:'EZTV', url:'https://eztvx.to/search/', useEn:true, desc:'需代理·BT磁力', tier:3 }, { name:'BT4G', url:'https://bt4gprx.com/search?q=', useEn:true, desc:'需代理·磁力搜索', tier:3 }, { name:'GloTorrents', url:'https://glodls.to/search.php?q=', useEn:true, desc:'需代理·BT磁力', tier:3 }, { name:'iBit', url:'https://ibit.uno/search?q=', useEn:true, desc:'需代理·磁力搜索', tier:3 }, // ---- v5.4.3 新增 · 国内站 15(1 站结果条目级实测可核验,14 站域名与搜索端点实测存活但结果前端渲染)---- { name:'新电影天堂', url:'https://www.xl720.com/?s=', useEn:false, desc:'高清下载', tier:2 }, { name:'影猫导航', url:'https://www.ymaoo.cn/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'电影港', url:'https://www.dygod.net/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'电影仓库', url:'https://www.dycangku.com/search?q=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'音范丝备用', url:'https://www.yinfans.net/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'高清社区', url:'https://www.hdsay.net/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'蓝光电影', url:'https://www.languangdy.com/search?keyword=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'远鉴字幕', url:'https://www.yuanjian.org/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'东京图书馆', url:'https://www.tokyotosho.info/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'4K8K资源', url:'https://4k8k.xyz/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'片源网', url:'https://pianyuan.org/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'HDmoli', url:'https://hdmoli.pro/?s=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'高清888', url:'https://www.gaoqing888.com/search?kw=', useEn:false, desc:'高清下载·前端渲染', tier:1 }, { name:'音范丝', url:'https://www.yinfans.com/?s=', useEn:false, desc:'高清下载·前端渲染', tier:2 }, { name:'番组放送', url:'https://bgmlist.com/?s=', useEn:false, desc:'高清下载·前端渲染', tier:2 }, // ---- v5.4.4 新增 · 下载 20(6 站条目级实测可核验:中国高清网 / TorrentProject / TFPDL / RLSBB 三镜像; // 14 站域名与搜索端点实测存活但结果前端渲染(或 CF 防护),浏览器可用、脚本核验受限,未核验即隐藏)---- { name:'中国高清网', url:'https://www.cnmkv.com/?s=', useEn:false, desc:'高清下载', tier:1 }, { name:'TorrentProject', url:'https://torrentproject.cc/?t=', useEn:true, desc:'磁力搜索', tier:2 }, { name:'TFPDL', url:'https://tfpdl.to/?s=', useEn:true, desc:'直链下载', tier:2 }, { name:'RLSBB镜像CC', url:'https://search.rlsbb.cc/?s=', useEn:true, desc:'直链下载', tier:3 }, { name:'RLSBB镜像IN', url:'https://search.rlsbb.in/?s=', useEn:true, desc:'直链下载', tier:3 }, { name:'RLSBB镜像TO', url:'https://search.rlsbb.to/?s=', useEn:true, desc:'直链下载', tier:3 }, { name:'BT蚂蚁', url:'https://btmayi.cc/search/', useEn:false, desc:'磁力搜索·前端渲染', tier:1 }, { name:'种子资源', url:'https://torrent2.cc/search/', useEn:false, desc:'磁力搜索·前端渲染', tier:1 }, { name:'磁力天堂', url:'https://cilitiantang.club/search/', useEn:false, desc:'磁力搜索·前端渲染', tier:2 }, { name:'BTSOW', url:'https://btsow.com/search/', useEn:false, desc:'磁力搜索·前端渲染', tier:2 }, { name:'迅雷下载天堂', url:'http://xunleis.pro/search?q=', useEn:false, desc:'下载导航·前端渲染', tier:2 }, { name:'影猫仓库', url:'https://ymck.me/search.html?wd=', useEn:false, desc:'影视导航·前端渲染', tier:2 }, { name:'种子搜62', url:'https://zhongziso62.xyz/search?q=', useEn:false, desc:'磁力搜索·前端渲染', tier:2 }, { name:'YifyMovies', url:'https://yifymovies.xyz/?s=', useEn:true, desc:'高清BT·前端渲染', tier:3 }, { name:'nekoBT', url:'https://nekobt.to/search?q=', useEn:true, desc:'BT磁力·前端渲染', tier:3 }, { name:'Youplex', url:'https://torrents.youplex.site/search?q=', useEn:true, desc:'BT聚合·前端渲染', tier:3 }, { name:'NZBKing', url:'https://nzbking.com/search?q=', useEn:true, desc:'NZB索引', tier:3 }, { name:'SeedHub', url:'https://seedhub.cc/search?q=', useEn:false, desc:'影视动漫·CF防护', tier:3 }, { name:'NZBIndex', url:'https://nzbindex.com/search?q=', useEn:true, desc:'NZB索引·前端渲染', tier:3 }, { name:'磁力王', url:'https://movih.com/?search=', useEn:false, desc:'磁力搜索·前端渲染', tier:3 }, ]; // === 第二大类 API:网盘直链聚合源 === var PAN_API_URL = 'https://so.252035.xyz/api/search?kw='; // === 第二大类:☁️ 网盘资源 (网盘搜索站) === // v5.4.2: +20(前 5 站服务端渲染、与脚本同款静态解析实测可出结果;后 15 站实测 // 存活但为前端渲染型,浏览器内可用、脚本核验受限,desc 已标注「前端渲染」) // 新增站 useEn 均为 false:中文网盘引擎按 v4.8.0 设计走纯中文片名 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 }, // ---- v5.4.2 新增 · SSR 实测可核验(关键词回显 + 网盘直链双特征确认)---- { name:'咔帕搜索', url:'https://cuppaso.com/search?keyword=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'夸克搜', url:'https://kuakeso.com/search?q=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'盘搜', url:'https://panso.pro/search?q=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'360盘搜', url:'https://www.360pansou.com/search?q=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'PanSearch', url:'https://pansearch.me/search?keyword=', useEn:false, desc:'网盘搜索', tier:1 }, // ---- v5.4.2 新增 · 存活实测·前端渲染型(脚本核验受限,浏览器直开可用)---- { name:'小马盘搜', url:'https://www.xiaomapan.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'97盘搜', url:'https://pansoo.cn/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'盘搜吧', url:'https://www.panso678.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'聚盘搜', url:'https://www.panso567.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'飞猪盘', url:'https://www.feizhupan.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'大力盘', url:'https://www.dalipan.xyz/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'猫狸盘搜', url:'https://www.alipansou.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'爱盘搜', url:'https://xiongdipan.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'秒搜', url:'https://miaosou.fun/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'易搜', url:'https://yiso.fun/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'UP云搜', url:'https://www.upyunso.com/search.html?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'奇乐搜', url:'https://qileso.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'趣盘搜', url:'https://pan.funletu.com/#/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'盘他一下', url:'https://panothers.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, { name:'盘友圈', url:'https://panyq.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:2 }, // ---- v5.4.3 新增 · 国内站 20(2 站结果条目级实测可核验,18 站域名与搜索端点实测存活但结果前端渲染)---- { name:'爱搜', url:'https://www.esoua.com/search?q=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'百盘搜', url:'https://baipansou.com/search?q=', useEn:false, desc:'网盘搜索', tier:1 }, { name:'云盘4K', url:'https://www.codelicence.cn/?s=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'搜百度盘', url:'https://www.sobaidupan.com/search.php?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'海豚搜索', url:'https://www.haitunsou.com/?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'啊飞分享', url:'https://dagehao889.cn/?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'云盘之家', url:'https://www.wowenda.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'优聚搜', url:'https://v4.jujuso.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'盘搜网', url:'https://pansou.org/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'夸克资源', url:'https://pan.brtl.cn/?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'千帆搜索', url:'https://pan.qianfan.app/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'天天云搜', url:'https://www.ttyunsou.com/search/-------------.html?wd=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'海搜', url:'https://haisou.cc/?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'小鸡窝', url:'https://xiaojiwo.top/?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'迅雷盘搜', url:'https://xlpanso.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'盘大师', url:'https://www.pandashi8.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'盘了个盘', url:'https://www.panlegepan.com/s/', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'微度盘搜', url:'https://www.weidus.com/search?q=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'天天搜索', url:'https://www.daysou.com/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, { name:'爱盘搜', url:'https://aipanso.com/index.php/search?keyword=', useEn:false, desc:'网盘搜索·前端渲染', tier:1 }, ]; // === 第三大类:📺 在线观看 (国内正版平台 + 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:'Pahe', url:'https://pahe.ink/?s=', useEn:true, desc:'需代理·在线影视', tier:3 }, // ---- v5.4.3 新增 · 国内站 19(2 站结果条目级实测可核验,17 站域名与搜索端点实测存活但结果前端渲染)---- { name:'优酷', url:'https://so.youku.com/search/', useEn:false, suffix:'.html', desc:'在线影视', tier:1 }, { name:'麒麟资源', url:'http://www.qilinzyz.com/index.php/vod/search/wd/', useEn:false, suffix:'.html', desc:'在线影视', tier:1 }, { name:'2345影视', url:'https://v.2345.com/search?q=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'腾讯视频', url:'https://v.qq.com/x/search/?q=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'西瓜视频', url:'https://www.ixigua.com/search/?keyword=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'爱奇艺', url:'https://so.iqiyi.com/so/q_', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'欢喜首映', url:'https://www.huanxi.com/search.shtml?sv=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'芒果TV', url:'https://so.mgtv.com/so?k=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'搜狐视频', url:'https://so.tv.sohu.com/mts?wd=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'CCTV视频', url:'https://search.cctv.com/search.php?qtext=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'达达兔影视', url:'https://www.ddtu3.com/search.php?wd=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'咕咕番', url:'https://gugu01.cc/?s=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'思乐影视', url:'https://www.4ltv.com/search/-------------.html?wd=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'AcFun', url:'https://www.acfun.cn/search/?keyword=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'子夜动漫', url:'https://ziyedm.com/?s=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'ZzzFun', url:'https://zzzfun.one/?s=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'樱花动漫', url:'https://www.iyinghua.io/?s=', useEn:false, desc:'在线影视·前端渲染', tier:1 }, { name:'girigiri', url:'https://girigirilove.com/?s=', useEn:false, desc:'在线影视·前端渲染', tier:2 }, { name:'影猫仓库', url:'https://ymck.pro/search?wd=', useEn:false, desc:'在线影视·前端渲染', tier:2 }, ]; // ============================================================ // 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搜索 // v5.4.5: 聚合接口 so.252035.xyz 响应波动大(正常 4-5s / 302 跟随约 10s / // 偶发挂起 40s+ 无响应,实测单次成功率约五成)。旧实现单请求 10s 超时 + // 2 次重试,连续挂起时直接返回空 → 面板显示「未找到网盘资源」。 // 现改为三次错峰竞速:t=0 / t=3s / t=7s 各发一枪,超时 12s/12s/15s, // 任一枪拿到合法 JSON 即渲染,三枪全落空才报无结果(最坏等待约 22s)。 // ============================================================ function panParseItems(txt) { var json = JSON.parse(txt); 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++; } } } } } return items; } function searchPanApi(kw, cb) { var apiUrl = PAN_API_URL + encodeURIComponent(kw); var settled = false, fails = 0; function finish(items) { if (settled) return; settled = true; verifyPanLinks(items, cb); } function fire(delay, timeout) { setTimeout(function() { if (settled) return; gmXhr({url:apiUrl, timeout:timeout, retries:0}, function(err, res) { if (settled) return; if (err || !res || !res.responseText) { if (++fails >= 3) finish([]); return; } try { finish(panParseItems(res.responseText)); } catch(e) { logErr('pan api parse error', e); if (++fails >= 3) finish([]); } }); }, delay); } fire(0, 12000); fire(3000, 12000); fire(7000, 15000); } // ============================================================ // v5.4.3 网盘直链有效性核验 // 聚合接口给的是历史收录的分享链接,很多早已取消或过期; // 「检索到」不等于「资源还在」,所以展示前先按各家公开接口问一次。 // 判据只在拿到明确的失效答复时剔除,探测失败一律保留(宁漏不误杀)。 // ============================================================ var PAN_CHK_PREFIX = 'ddm_panchk_'; var PAN_CHK_TTL = 12 * 60 * 60 * 1000; var PAN_CHK_LIMIT = 6; var PAN_CHK_BUDGET = 8000; var PAN_DEAD_MSG = /(不存在|已取消|失效|已删除|被删除|已过期|过期|封禁|违规|禁止|链接错误)/; function panProvider(url) { if (!url) return ''; if (/pan\.quark\.cn\/s\//i.test(url)) return 'quark'; if (/(alipan|aliyundrive)\.com\/s\//i.test(url)) return 'ali'; if (/pan\.baidu\.com\/s\//i.test(url)) return 'baidu'; if (/cloud\.189\.cn\/t\//i.test(url)) return 'tianyi'; return ''; } function panPwdOf(url) { var m = url.match(/[?&](?:pwd|password|passcode|extract_code)=([A-Za-z0-9]{4,8})/i); return m ? m[1] : ''; } function panSeg(url) { var m = url.match(/\/[st]\/([A-Za-z0-9_-]+)/); return m ? m[1] : ''; } function panHash(str) { var h = 5381, i; for (i = 0; i < str.length; i++) { h = ((h << 5) + h + str.charCodeAt(i)) | 0; } return (h >>> 0).toString(36) + '_' + str.length; } function panChkGet(url) { try { if (typeof GM_getValue === 'undefined') return ''; var v = GM_getValue(PAN_CHK_PREFIX + panHash(url), ''); if (!v) return ''; var o = JSON.parse(v); if (o && (Date.now() - o.t) < PAN_CHK_TTL) return o.s ? 'ok' : 'dead'; } catch(e) {} return ''; } function panChkPut(url, state) { if (state !== 'ok' && state !== 'dead') return; try { if (typeof GM_setValue === 'undefined') return; GM_setValue(PAN_CHK_PREFIX + panHash(url), JSON.stringify({ s: state === 'ok' ? 1 : 0, t: Date.now() })); } catch(e) {} } function panProbe(url, cb) { var prov = panProvider(url); var settled = false; function once(st) { if (settled) return; settled = true; clearTimeout(guard); cb(st); } if (!prov) { cb('unknown'); return; } var guard = setTimeout(function() { once('unknown'); }, 7000); var id = panSeg(url); if (prov === 'quark') { if (!id) { once('unknown'); return; } gmXhr({ method:'POST', noCache:true, retries:0, timeout:6500, url:'https://drive.quark.cn/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc', headers:{'Content-Type':'application/json'}, data:JSON.stringify({ pwd_id:id, passcode:panPwdOf(url), support_visit_limit_private_share:true }) }, function(err, res) { var t = (res && res.responseText) || ''; if (err || !t) { once('unknown'); return; } if (/"code"\s*:\s*0\b/.test(t)) { once('ok'); return; } var m = t.match(/"message"\s*:\s*"([^"]{1,40})"/); once(m && PAN_DEAD_MSG.test(m[1]) ? 'dead' : 'unknown'); }); return; } if (prov === 'ali') { if (!id) { once('unknown'); return; } gmXhr({ method:'POST', noCache:true, retries:0, timeout:6500, url:'https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous', headers:{'Content-Type':'application/json'}, data:JSON.stringify({ share_id:id }) }, function(err, res) { var t = (res && res.responseText) || ''; if (err && !t) { once('unknown'); return; } if (/NotFound\.ShareLink|ShareLink\.Invalid|ShareLink\.Expired|share_link cannot be found/i.test(t)) { once('dead'); return; } if (/"file_infos"|"share_name"/.test(t)) { once('ok'); return; } once('unknown'); }); return; } // 百度/天翼的失效页由服务端直接渲染标题,取页面即可判定 gmXhr({ noCache:true, retries:0, timeout:6500, url:url }, function(err, res) { var t = (res && res.responseText) || ''; if (err && !t) { once('unknown'); return; } var m = t.match(/<title[^>]*>([^<]{1,80})<\/title>/i); var title = m ? m[1] : ''; if (res && res.status === 404) { once('dead'); return; } if (/(链接不存在|页面不存在|访问出错|分享不存在|链接出错|该页已)/.test(title) || /"errno"\s*:\s*(115|145|1999)\b/.test(t)) { once('dead'); return; } if (prov === 'baidu' && /百度网盘/.test(title)) { once('ok'); return; } once('unknown'); }); } function verifyPanLinks(items, cb) { var i, checkable = 0; for (i = 0; i < items.length; i++) { if (panProvider(items[i].url)) checkable++; } if (!checkable) { cb(items); return; } var verdict = {}, pending = 0, closed = false; function collect() { if (closed) return; closed = true; clearTimeout(budget); var kept = [], dropped = 0; for (var k = 0; k < items.length; k++) { if (verdict[items[k].url] === 'dead') { dropped++; continue; } kept.push(items[k]); } if (dropped) logErr('pan: dropped ' + dropped + ' invalid share link(s)', null); cb(kept); } var budget = setTimeout(collect, PAN_CHK_BUDGET); // 缓存命中的直接定性,未命中的排队探测 var queue = [], seen = {}; for (i = 0; i < items.length; i++) { var u = items[i].url; if (!panProvider(u) || seen[u]) continue; seen[u] = 1; var cached = panChkGet(u); if (cached) { verdict[u] = cached; continue; } queue.push(u); } var idx = 0; function pump() { if (closed) return; while (pending < PAN_CHK_LIMIT && idx < queue.length) { (function(url) { pending++; panProbe(url, function(st) { pending--; verdict[url] = st; panChkPut(url, st); pump(); }); })(queue[idx++]); } if (!pending && idx >= queue.length) collect(); } pump(); } // ============================================================ // 站点检测 // ============================================================ // 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; // v5.4.4: 左右加词边界。旧版无边界,'favicon'/'comments-navi' 里的 'avi' 会 // 让新闻卡片凑出发布标记,把无资源站点标绿(影猫导航误报根因之一) var RELEASE = /(^|[^a-z])(1080p|720p|2160p|4k|uhd|blu[\s-]?ray|bd[\s-]?(rip|remux)|web[\s-]?dl|webrip|x26[45]|h[\s.]?26[45]|hevc|av1|mkv|mp4|avi|rmvb|dvdrip|hdtv|remux|aac|ac-?3|eac3|dts|atmos|hdr|10bit|国语|粤语|中文字幕|中字|中英双字|英字|无修|高清|超清|蓝光|杜比|原版|精校|s\d{1,2}e\d{1,2}|第\s*\d+\s*集|全\s*\d+\s*[集话])(?![a-z])/i; var RESOURCE_HREF = /(magnet:|ed2k:\/\/|thunder:\/\/|ftp:\/\/|\.torrent|pan\.baidu\.com|alipan\.com|aliyundrive|quark\.cn|115\.com|lanzou|pan\.xunlei|123pan|123684|cloud\.189|wopan|caiyun|\.m3u8|\.mkv)/i; var DETAIL_HREF = /\/(video|vod|detail|movie|film|tv|p|s|dya|dy|dl|down)[\/?]/i; var SEARCHISH_HREF = /([?&](q|kw|keyword|wd|s|searchword|query|search|t|ihq)=|\/search|\/s\?|\/so\?|\/tags?\/)/i; var NO_RESULT = /(没有找到|未找到|暂无相关|没有相关|搜索不到|无搜索结果|没有结果|未搜索到|暂无结果|no\s*results?\s*(found)?|nothing\s*found|0\s*(results?|条|个结果))/i; function ddmNorm(v) { if (v == null) return ''; var s = String(v); try { s = s.normalize('NFKC'); } catch (e) {} s = s.toLowerCase().replace(/&[a-z#0-9]+;/gi, ' '); return s.replace(/[^0-9a-z一-鿿]/g, ''); } function ddmDecode(v) { try { return decodeURIComponent(String(v || '')); } catch (e) { return String(v || ''); } } function elText(el, limit) { var t = (el && (el.textContent || el.getAttribute('title'))) || ''; return t.length > limit ? t.slice(0, limit) : t; } function buildNeedles(movie) { var out = [], seen = {}; function push(v, cutColon) { if (!v) return; var list = String(v).split(/[/||]/); for (var i = 0; i < list.length; i++) { var piece = list[i]; if (cutColon) piece = String(piece).split(/[::]/)[0]; var n = ddmNorm(piece); if (n.length >= 2 && !seen[n]) { seen[n] = 1; out.push({ n: n, strong: n.length >= 4 }); } } } push(movie.cnTitle, 1); push(movie.enTitle, 1); push(movie.kw, 0); push(movie.kwEn, 0); return out; } function inNoise(el) { var n = el, hops = 0; while (n && n.nodeType === 1 && hops++ < 12 && !/^(BODY|HTML)$/.test(n.tagName)) { if (NOISE_TAG[n.tagName]) return true; if (n.className && typeof n.className === 'string' && NOISE_HINT.test(n.className)) return true; if (n.id && NOISE_HINT.test(n.id)) return true; n = n.parentElement; } return false; } function findRow(a) { var best = a.parentElement || a, n = a.parentElement, hops = 0; while (n && n.nodeType === 1 && hops++ < 8 && !/^(BODY|HTML)$/.test(n.tagName)) { var t = elText(n, 1500); if (t.length > 1400) break; var strong = /^(LI|TR|ARTICLE|DD)$/.test(n.tagName) || ROW_CLASS.test(n.className || '') || ROW_CLASS.test(n.id || ''); if (strong && t.length >= 8) { best = n; if (t.length > 90) break; } else if (t.length > 90) break; else best = n; n = n.parentElement; } return best; } function rowEvidence(row, movie, needles) { var txt = elText(row, 1500), inner = row.innerHTML || '', ev = 0, why = []; if (RESOURCE_HREF.test(inner)) { ev += 3; why.push('resource-link'); } var an = row.getElementsByTagName('a'); for (var i = 0; i < an.length && i < 25; i++) { var at = ddmNorm(elText(an[i], 400)); var got = false; for (var k = 0; k < needles.length; k++) { var nd = needles[k].n; if (at.indexOf(nd) === -1) continue; var extra = at.split(nd).join(''); // v5.4.4: 年份限 1930-2039 —— 旧版 (19|20)\d\d 会把新闻社名 '1905电影网' // 当成发布年份,让新闻卡片通过结果行校验(影猫导航误报根因之二) if (extra.length > 1 && (RELEASE.test(extra) || /(19[3-9]\d|20[0-3]\d)/.test(extra))) { ev += 2; why.push('titled-entry'); got = true; break; } } if (got) break; } if (RELEASE.test(txt) || RELEASE.test(inner)) { ev += 2; why.push('release-token'); } var yr = String(movie.year || ''); if (yr.length === 4 && new RegExp('(^|[^0-9])' + yr + '([^0-9]|$)').test(txt)) { ev += 2; why.push('year'); } if (DETAIL_HREF.test(inner)) { ev += 1; why.push('detail-link'); } return { ev: ev, why: why }; } function verifyResults(htmlText, movie) { var none = { available:false, hits:0, ev:0, why:[], sample:'', reason:'rejected' }; if (!htmlText || htmlText.length < 300) return none; var doc = null; try { doc = new DOMParser().parseFromString(htmlText, 'text/html'); } catch (e) { return none; } if (!doc || !doc.body) return none; var needles = buildNeedles(movie); if (!needles.length) return none; var anchors = doc.body.getElementsByTagName('a'); var rows = [], hits = 0, best = 0, bestWhy = [], sample = ''; var n = Math.min(anchors.length, 3000); for (var i = 0; i < n; i++) { var a = anchors[i]; var at = ddmNorm(elText(a, 400)); var rawHref = a.getAttribute('href') || ''; var href = ddmNorm(ddmDecode(rawHref)); var matched = null, strong = false, hrefOnly = true; for (var k = 0; k < needles.length; k++) { var nd = needles[k].n; var inText = at.indexOf(nd) !== -1; // a nav / filter tab also carries the query, but only inside a search URL // (?s=X, /search/X) - that is an echo, never a result entry if (!inText && href.indexOf(nd) === -1) continue; if (!inText && SEARCHISH_HREF.test(rawHref)) continue; matched = nd; if (inText) hrefOnly = false; if (needles[k].strong) strong = true; } if (!matched) continue; if (inNoise(a)) continue; var row = findRow(a); if (rows.indexOf(row) !== -1) continue; rows.push(row); if (rows.length > 400) break; if (NO_RESULT.test(elText(row, 900))) continue; var e = rowEvidence(row, movie, needles); // an href-only hit is normally a sort/tab link repeating the query; it // counts only when the row itself carries a magnet or netdisk link if (hrefOnly && e.why.indexOf('resource-link') === -1) continue; if (!strong && e.ev < 4) continue; if (e.ev < 2) continue; hits++; if (e.ev > best) { best = e.ev; bestWhy = e.why; sample = elText(a, 60); } } return { available: hits >= 1 && best >= 2, hits: hits, ev: best, why: bestWhy, sample: sample, reason: hits ? 'rows' : 'no-rows' }; } // ============================================================ // v4.8.0 智能关键词选择 (按站点类型选最优候选;v5.4.4: 上限 3→4, 中文站补英文名兜底) // ============================================================ function pickCandidates(site, movie) { var base = movie.kwCandidates || []; var list = []; function push(s) { if (s && list.indexOf(s) === -1 && base.indexOf(s) !== -1) list.push(s); } var cat = site._cat || 'online'; if (site.useEn || cat === 'bt_en') { // 磁力/BT国际站:优先英文 push(movie.enTitle); push(movie.enTitle ? (movie.enTitle + (movie.year ? ' ' + movie.year : '')) : ''); push(movie.cnTitle); push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : ''); } else if (cat === 'dl_zh') { // 中文下载站:中文标题+年份优先 push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : ''); push(movie.cnTitle); var mp = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : ''; if (mp && mp !== movie.cnTitle) push(mp); // v5.4.4: 中文名全败后英文原名兜底(部分国内站收录英文名) push(movie.enTitle); } else if (cat === 'pan') { // 网盘搜索站:纯中文标题不带年份 push(movie.cnTitle); var mp2 = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : ''; if (mp2 && mp2 !== movie.cnTitle) push(mp2); push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : ''); push(movie.enTitle); } else { // 在线影视站 / 默认:中文标题 push(movie.cnTitle); var mp3 = movie.cnTitle ? movie.cnTitle.split(/[::]/)[0].trim() : ''; if (mp3 && mp3 !== movie.cnTitle) push(mp3); push(movie.cnTitle ? (movie.cnTitle + (movie.year ? ' ' + movie.year : '')) : ''); push(movie.enTitle); } // 剧集季数拼接补充 if (movie.season && movie.cnTitle) push(movie.cnTitle + ' ' + movie.season); var out = []; for (var i = 0; i < list.length && out.length < 4; i++) { if (list[i]) out.push(list[i]); } return out; } // ============================================================ // v4.8.0 多关键词检测 (无结果自动切换, 最多4个) // ============================================================ function testSite(site, movie, cb) { // v4.7.0: 健康度缓存命中(24h 内不可用)直接跳过 if (isHealthSkipped(site.name)) { cb({accessible:false, time:0, available:false, cachedSkip:true}); return; } var candidates = pickCandidates(site, movie); if (!candidates || candidates.length === 0) { cb({accessible:false, time:999999, available:false, skip:true}); return; } tryKeywords(site, movie, candidates, 0, cb); } function tryKeywords(site, movie, cands, idx, cb) { var kw = cands[idx]; var startTime = Date.now(); var timeout = 8000; function finish(extra) { extra = extra || {}; var r = { accessible:true, time: Date.now() - startTime, available:false }; for (var k in extra) { if (hasOwn(extra, k)) r[k] = extra[k]; } cb(r); } function nextOrFail() { if (idx + 1 < cands.length) { setTimeout(function() { tryKeywords(site, movie, cands, idx + 1, cb); }, 20); } else { // 站点答了但本片无资源 ≠ 站点坏了:不写健康度缓存, // 否则一部冷门票就会把该站拉黑 24h,换部片子也被误判为不可用 finish(); } } var url = site.url + encodeURIComponent(kw) + (site.suffix || ''); gmXhr({url:url, timeout:timeout, retries:1, noCache:true}, function(err, res) { if (err || !res || !res.responseText) { setHealth(site.name, false); finish({accessible:false, time:999999, error:true}); return; } var text = res.responseText; try { if (isBlocked(text)) { finish({ blocked:true }); return; } // 所有站点统一走结果条目校验:能证明该片在该站有结果才算「有资源」 var v = verifyResults(text, movie); if (v.available) { setHealth(site.name, true); finish({ available:true, hits:v.hits, ev:v.ev }); return; } nextOrFail(); } catch(e) { logErr('tryKeywords parse error', e, site.name); setHealth(site.name, true); // v5.4.4: 解析异常 ≠ 有资源。旧版在此 finish({available:true}), // 任何异常都会把站点标绿,与「绿=已证明有结果行」的承诺相悖 finish({ available:false, error:true }); } }); } // ============================================================ // v4.7.0 分区渲染 (支持子区域定位);v5.2.0 Tier3 全自动检测 // ============================================================ function renderSectionTiered(opts) { var sitesGroups = opts.sitesGroups; var movie = opts.movie; var sectionId = opts.sectionId; var section = document.getElementById(sectionId); if (!section) return; var totalSites = (sitesGroups[1]||[]).length + (sitesGroups[2]||[]).length + (sitesGroups[3]||[]).length; var totalFound = 0; var totalDone = 0; var allResults = {}; var sectionExpanded = false; var toastShown = false; // v4.7.0: 支持通过 bodyId/arrowId 精确定位子区域(如「更多网盘搜索站」) var sectionBody = opts.bodyId ? document.getElementById(opts.bodyId) : section.querySelector('.ddm-section-body'); var sectionArrow = opts.arrowId ? document.getElementById(opts.arrowId) : section.querySelector('.ddm-section-header .ddm-arrow'); // v5.3.0: 无资源站点检测完成即隐藏,仅显示可用站点; // 梯队可见性用增量计数维护(替代 v5.2.1 每次完成后的全量重扫) var tierState = {}; function initTierWraps() { for (var t = 1; t <= 3; t++) { var cont = document.getElementById(opts.containerPrefix + '-t' + t + '-links'); if (!cont) continue; var wrap = cont.parentNode; if (!wrap || wrap.className.indexOf('ddm-tier-block') === -1) continue; tierState[t] = { wrap: wrap, pending: 0, avail: 0 }; wrap.style.display = 'none'; } } function reportTier(tier) { var st = tierState[tier]; if (st) st.wrap.style.display = (st.pending > 0 || st.avail > 0) ? '' : 'none'; } initTierWraps(); function autoExpandSection() { if (sectionExpanded) return; sectionExpanded = true; if (sectionBody) { sectionBody.style.display = 'block'; // v4.9.0: 展开内容区淡入动画 animateBodyIn(sectionBody); } if (sectionArrow) sectionArrow.textContent = '▾'; updateSummaryCount(); } function updateSummaryCount() { var summaryEl = document.getElementById('ddm-summary-' + opts.summaryKey); if (summaryEl) { summaryEl.textContent = totalFound; if (totalFound > 0) summaryEl.className = 'ddm-summary-num has-val'; } } function updateProgress() { if (opts.barEl) opts.barEl.style.width = Math.round((totalDone/totalSites)*100) + '%'; if (opts.textEl) opts.textEl.textContent = totalDone + '/' + totalSites; if (opts.countEl) { opts.countEl.textContent = totalFound + '/' + totalSites; if (totalFound > 0) opts.countEl.className = 'ddm-section-count has-result'; } updateSummaryCount(); } function finalizeSection() { if (opts.barEl) { opts.barEl.style.width = '100%'; opts.barEl.classList.add('done'); } if (opts.textEl) opts.textEl.textContent = totalDone + '/' + totalSites; if (opts.countEl) { opts.countEl.textContent = totalFound + '/' + totalSites; if (totalFound > 0) opts.countEl.className = 'ddm-section-count has-result'; } if (section) { section.classList.add('ddm-section-completed'); if (totalFound > 0) section.classList.add('ddm-section-has-result'); } // v5.0.0: 无结果 → 折叠分区为一行居中次级文案「暂未找到相关资源」 var emptyTag = opts.emptyElId ? document.getElementById(opts.emptyElId) : null; if (totalFound === 0) { if (sectionBody) sectionBody.style.display = 'none'; if (sectionArrow) sectionArrow.textContent = '▸'; if (emptyTag) { emptyTag.style.display = 'inline'; var hdrTitle = section ? section.querySelector('.ddm-section-title') : null; var hdrCount = section ? section.querySelector('.ddm-section-count') : null; if (hdrTitle) hdrTitle.style.display = 'none'; if (hdrCount) hdrCount.style.display = 'none'; } } else { if (emptyTag) emptyTag.style.display = 'none'; } for (var t = 1; t <= 3; t++) reportTier(t); updateSummaryCount(); // v4.9.0: 完成有结果 → Toast 通知 if (totalFound > 0 && !toastShown && opts.toastMsg) { toastShown = true; showToast(opts.toastMsg + ':找到 ' + totalFound + ' 个可用站点'); } for (var t in allResults) { if (!hasOwn(allResults, t)) continue; var arr = allResults[t]; arr.sort(function(a, b) { var ra = a.result || {}, rb = b.result || {}; if (ra.available && !rb.available) return -1; if (!ra.available && rb.available) return 1; if (ra.accessible && !rb.accessible) return -1; if (!ra.accessible && rb.accessible) return 1; return ra.time - rb.time; }); var cont = document.getElementById(opts.containerPrefix + '-t' + t + '-links'); if (cont) { for (var i = 0; i < arr.length; i++) cont.appendChild(arr[i].a); } } } function runTier(tier) { var sites = sitesGroups[tier] || []; var containerId = opts.containerPrefix + '-t' + tier + '-links'; var container = document.getElementById(containerId); if (!container || sites.length === 0) { if (tier < 3) runTier(tier + 1); else finalizeSection(); return; } container.innerHTML = ''; var tierResults = []; allResults[tier] = tierResults; var linkItems = []; for (var i = 0; i < sites.length; i++) { var site = sites[i]; var kw = site.useEn ? movie.kwEn : movie.kw; var url = kw ? (site.url + encodeURIComponent(kw) + (site.suffix || '')) : 'javascript:void(0)'; var a = document.createElement('a'); a.href = url; a.target = '_blank'; a.rel = 'noopener'; a.textContent = site.name; if (site.desc) a.title = site.desc; else if (!kw) a.title = site.name; a.className = 'checking'; // v4.9.0: 逐个弹入瀑布动画 (30ms 递增延迟) a.style.animation = 'ddm-link-pop .3s cubic-bezier(0.34,1.56,0.64,1) both'; a.style.animationDelay = (i * 30) + 'ms'; // v4.9.0: Tooltip 数据 if (site.desc) a.setAttribute('data-desc', site.desc); a.setAttribute('data-tier', TIER_CONFIG[site.tier || 2].label); (function(link) { link.addEventListener('mouseenter', function(ev) { showTooltipFor(link, ev); }, false); link.addEventListener('mouseleave', hideTooltip, false); link.addEventListener('click', hideTooltip, false); })(a); container.appendChild(a); linkItems.push({ site:site, a:a, kw:kw, result:null }); } // Tier3 国际站与普通站点同样自动检测,仅按速度顺位最后执行 var _token = container._ddmToken || (container._ddmToken = '_ddm_token_' + Math.random().toString(36).slice(2, 9)); var tierFound = 0; var st = tierState[tier]; if (st) { st.pending = linkItems.length; reportTier(tier); } pool(linkItems, 0, function(item, idx, workerDone) { setTimeout(function() { testSite(item.site, movie, function(r) { if (container._ddmToken !== _token) { workerDone(); return; } item.result = r; item.a.textContent = item.site.name; // v4.7.0: 记录响应时间用于动态并发调节 if (r.time > 0) updateConcurrency(r.time); if (r.available) { item.a.className = 'available'; item.a.title = item.site.name + ' ✅ 有资源 (' + r.time + 'ms)'; if (item.site.desc) item.a.title += ' - ' + item.site.desc; tierFound++; totalFound++; autoExpandSection(); if (opts.countEl) { opts.countEl.textContent = totalFound + '/' + totalSites; opts.countEl.className = 'ddm-section-count has-result'; } } else if (r.cachedSkip) { item.a.className = 'skipped'; item.a.title = item.site.name + ' ⏭ 24h内检测不可用,已跳过'; } else if (r.blocked) { item.a.className = 'blocked'; item.a.title = item.site.name + ' 🚧 被拦截 (CF挑战/验证码)'; } else if (r.accessible) { if (r.error) { item.a.className = 'error'; item.a.title = item.site.name + ' ⚠ 解析异常 (' + r.time + 'ms)'; } else { item.a.className = 'unavailable'; item.a.title = item.site.name + ' ❌ 无资源 (' + r.time + 'ms)'; } } else { item.a.className = 'inaccessible'; item.a.title = item.site.name + ' 🚫 无法访问'; } tierResults.push(item); if (st) { st.pending--; if (r.available) st.avail++; else item.a.classList.add('ddm-filtered'); reportTier(tier); } workerDone(); }); }, randomInt(20, 150)); }, function(finished, total) { if (container._ddmToken !== _token) return; var done = 0; for (var t = 1; t <= 3; t++) { if (t < tier) done += (sitesGroups[t]||[]).length; else if (t === tier) done += finished; } totalDone = done; updateProgress(); }, function() { if (container._ddmToken !== _token) return; totalDone = 0; for (var t = 1; t <= tier; t++) totalDone += (sitesGroups[t]||[]).length; updateProgress(); log('tier ' + tier + ' done, found=' + tierFound + ', section=' + sectionId); if (tier < 3) { setTimeout(function() { runTier(tier + 1); }, 80); } else { finalizeSection(); } }); } runTier(1); } // ============================================================ // v5.0.0 构建面板 — 三大类顺序:下载→网盘→在线 // ============================================================ function removePanel() { var p = document.getElementById('ddm-panel'); if (!p || !p.parentNode) return; var wrap = p.parentNode; if (wrap.id === 'ddm-float-wrap') wrap.parentNode.removeChild(wrap); else wrap.removeChild(p); } function resetCaches() { _xhrCache = {}; _requestLastTime = {}; } function buildPanel(movie) { log('buildPanel v'+VER); var target = document.querySelector('.aside'); if (!target) target = document.body; if (document.getElementById('ddm-panel')) return document.getElementById('ddm-panel'); var dlN = DOWNLOAD_SITES.length; var panN = PAN_SEARCH_SITES.length; var onlineN = ONLINE_SITES.length; var tDl = countTier(DOWNLOAD_SITES); var tPan = countTier(PAN_SEARCH_SITES); var tOnline = countTier(ONLINE_SITES); var t1Total = tPan[1] + tOnline[1] + tDl[1]; var t2Total = tPan[2] + tOnline[2] + tDl[2]; var t3Total = tPan[3] + tOnline[3] + tDl[3]; var totalAll = t1Total + t2Total + t3Total; function tierBlock(prefix, tier, count) { if (count === 0) return ''; return '<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) { // v5.4.5: 刷新按钮 removePanel 后 window._ddm_initialized 仍为 true,旧版在此直接 // return → 面板消失不再出现(表现为「点刷新就闪退」)。改以「面板是否已在 DOM」为唯一 // 判据:面板在就跳过,面板被移除过就能正常重建,两条重载路径行为一致。 if (document.getElementById('ddm-panel')) return; window._ddm_initialized = true; try { addCSS(CSS); addDnsPrefetch(); tagSiteCategories(); var panel = buildPanel(movie); if (!panel) { window._ddm_initialized = false; return; } // 分区检测:三大类共用同一渲染流程(网盘直链 API 与「更多网盘搜索站」同属第二分区) function runSection(key, sites, toast, sectionId, bodyId, arrowId) { var pre = 'ddm-' + key; renderSectionTiered({ sitesGroups: groupByTier(sites), movie: movie, sectionId: sectionId || 'ddm-sec-' + key, containerPrefix: pre, bodyId: bodyId, arrowId: arrowId, summaryKey: key, toastMsg: toast, emptyElId: pre + '-empty', barEl: document.getElementById(pre + '-bar'), textEl: document.getElementById(pre + '-prog'), countEl: document.getElementById(pre + '-cnt') }); } runSection('dl', DOWNLOAD_SITES, '🧲 下载资源'); // 网盘直链 API(第二分区上半部) var panBody = document.getElementById('ddm-pan-body'); var panBadge = document.getElementById('ddm-pan-badge'); var panCnt = document.getElementById('ddm-pan-cnt'); var panSec = document.getElementById('ddm-sec-pan'); if (panBody) { searchPanApi(movie.kw, function(items) { if (!panBody) return; if (items && items.length > 0) { var html = '<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'); })();