// ==UserScript== // @name 🫧404小站 — 🎬VIP追剧神器 | 完全免费 | 支持多平台 | (电脑/手机/平板...自适应) // @namespace https://scriptcat.org/zh-CN/users/162063 // @version 3.5.2 // @description ▶在线VIP视频解析工具 (电脑/手机/平板...自适应) | free | 支持多平台【爱奇艺】【腾讯视频】【优酷土豆】【芒果TV】【乐视视频】【哔哩哔哩】【搜狐视频】等常见平台。✨9条解析接口实测可用 ✨内嵌播放无广告 ✨智能切集追剧 ✨内嵌铺满原播放区 ✨一键自动解析 制作不易,有问题可加微信咨询:Why15236444193 [如果加微信未能及时回复,请多多包涵哈!] // @author yyy404 // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_openInTab // @grant GM_xmlhttpRequest // @resource hlsJs https://cdn.jsdelivr.net/npm/hls.js@1.7.3/dist/hls.min.js // @resource artPlayerJs https://cdn.jsdelivr.net/npm/artplayer@5.4.0/dist/artplayer.js // @grant GM_getResourceText // @connect * // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @run-at document-start // @icon https://cdn.jsdmirror.com/gh/whyyy-404/icon@main/Collection/Cartoon/bear-yes.gif // ==/UserScript== // =========================================================================== // 【致谢与来源】 // // 本项目非常感谢来自「屏幕前的你」大佬(https://github.com/963540817) // 的 Hub 以及各种强势功能,特此致谢。(引用前提:拒绝商业化) // // 「采集源」标签页(含其中的「搜索跳转」分组)所收录的第三方采集接口清单, // 参考自「屏幕前的你」大佬分享的自用脚本: // // https://github.com/963540817/dashu // // 本脚本在该清单基础上所做的工作是:逐条实测、剔除已失效的条目、按可用性重新分组, // 并补充了搜索 / 代理兜底 / 两步型等取数逻辑。 // 但清单的来源是 dashu —— 再次感谢来自「屏幕前的你」大佬的 Hub。 // // 如果「屏幕前的你」大佬本人认为当前的收录或署名方式仍有不妥, // 欢迎提 Issue 或直接联系,我会立即调整或移除相关内容。 // =========================================================================== /*    ┏┓   ┏┓   ┏┛┻━━━━━━┛ ┻┓   ┃      ┃   ┃   ━   ┃   ┃ ┳┛ ┗┳  ┃   ┃      ┃   ┃   ┻   ┃   ┃      ┃   ┗━━━┓   ┏━┛Codes are far away from bugs with the animal protecting     ┃   ┃ 神兽保佑,代码无bug     ┃   ┃     ┃   ┗━━━┓     ┃      ┣┓     ┃     ┏┛     ┗┓┓┏━┳┓┏┛      ┃┫┫ ┃┫┫      ┗┻┛ ┗┻┛ */ (function() { 'use strict'; // ===== 防止在iframe中重复执行 ===== if (window.self !== window.top) { console.log('VIP解析脚本:检测到iframe环境,跳过执行'); return; } // ===== 66dpw 跳板页接管 ===== // https://www.66dpw.vip/?url=<视频页> 不是解析接口,单独打开是没用的: // · 服务端忽略 ?url=(带 / 不带 / 编码过的 ?url= 返回完全相同的首页 HTML) // · 首页自身没有播放能力(HTML 与 12 个外部 JS 里没有任何播放器库的痕迹) // 它的唯一用途是当「授权宿主页」:真播放接口只有当「自己嵌在这个已授权域名下」才放行, // 顶层直接打开同一个接口会被拒(实测提示「您的域名未授权播放」)。 // 授权判定读的是祖先域、不是 Referer 头,所以这里不伪造来源。 // 做法:点击时把「宿主页地址 / 真播放接口 / 要解析的视频页地址」三个值记进 GM 存储; // 宿主页读到后整体替换 body,只留一个容器 + 一个 iframe, // 并用常驻 CSS 把它的其余元素移出屏幕。 // ⚠️ iframe 上这几组属性都要带,少一个都会出问题(都是实测对比出来的): // · sandbox="allow-scripts allow-same-origin" // 缺了它,内层页面(它自己会加载统计脚本和 Cast SDK)能弹窗、能跳顶层; // 带上之后这些都被禁掉。allow-same-origin 必须留,否则内层拿不到 localStorage。 // · loading="eager" importance="high" fetchpriority="high" // 加载优先级。不标的话这个 iframe 是默认优先级, // 会和宿主页那 1 MB 脚本 + 几百张封面图抢带宽,起播明显变慢。 // · allow="autoplay;encrypted-media;picture-in-picture;…" + allowfullscreen // 不给她方权限,起播和全屏都会被挡。 // ⚠️ 不能在这里调 window.stop():它会连「新的 frame」一起停掉, // 会导致新建的 iframe 永远不出画面(表现:整页纯黑,连它自己的 loading 图都看不到)。 const GATE_HOST = 'www.66dpw.vip'; const GATE_PLAY_API = 'https://svip.qlplayer.cyou/?url='; // 宿主页的窗口名。 // ⚠️ 必须是【具名窗口】,不能再用 '_blank': // 具名窗口同名只有一个,第二次点会复用它并导航过去,而不是再开一个新标签。 // 用 _blank 的话每点一次就多留一个宿主页标签,而每个宿主页都在往真播放接口加载 iframe, // 十几个标签同时抢同一个域名的连接 → 新开那个的请求一直排不到 → iframe 永远不出画面。 const GATE_WIN_NAME = 'vip_jx_gate'; // 宿主页的完整前缀。判断「当前这页是不是跳板页」必须用它整串比对: // ⚠️ 只查 hostname + 有没有 ?url= 是不够的 —— 那样 jiexi.html?url=…(66网2)也会被吃掉, // 66网2 就变成 66网1 了。路径必须是 /,参数必须紧跟在 ? 后面。 const GATE_PREFIX = 'https://' + GATE_HOST + '/?url='; // 要解析的目标地址【直接读地址栏的 ?url=】,不经过 GM 存储: // 目标就明明白白写在地址栏上,手动打开或刷新那一页也能用,不存在跨页隐藏状态。 // (原先还写了一份到 GM 存储做兜底,但地址栏永远带着目标 ⇒ 那份兜底从来没被读到过,已删) function gateInfo() { try { if (!String(window.location.href).startsWith(GATE_PREFIX)) return null; // 取「url=」之后的全部内容,不按 & 截断(视频页地址自带 & 也不会被切掉) const m = /[?&]url=(.+)$/.exec(window.location.search); if (!m) return null; let v = m[1]; try { v = decodeURIComponent(v); } catch (e) { } return /^https?:\/\//i.test(v) ? { api: GATE_PLAY_API, target: v } : null; } catch (e) { return null; } } const GATE = gateInfo(); if (GATE) { let gateWrap = null; const buildGate = () => { if (gateWrap) return true; const body = document.body; if (!body) return false; // 常驻规则:把它的元素移出屏幕并压成零尺寸。 // 只写 display:none 不够 —— 它自己的懒加载仍可能把首屏那几 MB 封面图拉下来。 const st = document.createElement('style'); st.textContent = 'html,body{margin:0;padding:0;height:100%;overflow:hidden;}' + 'body>:not(#vip_gate_wrap){display:none !important;max-width:0 !important;max-height:0 !important;' + 'overflow:hidden !important;position:absolute;left:-102030px;}'; (document.head || document.documentElement).appendChild(st); // 建容器 + iframe。 // ⚠️ 顺序很关键:必须【先挂 load 监听 → 再进 DOM → 最后才设 src】。 // 之前用 body.innerHTML 插入,iframe 一进 DOM 就开始导航, // 等我们再回头找它挂监听时已经错过 load,表现就是「永远加载不出来」。 // 容器用 fixed + 视口单位,不吃 body 的高度(宿主页自己有 360 KB 的 CSS,body 高度不可靠) gateWrap = document.createElement('div'); gateWrap.id = 'vip_gate_wrap'; gateWrap.style.cssText = 'visibility:visible!important;opacity:1!important;overflow:visible!important;' + 'position:fixed;top:0;left:0;width:100vw;height:100vh;margin:0;padding:0;' + 'z-index:1;background:#000;display:flex;justify-content:center;align-items:center;flex-direction:column;'; const ifrEl = document.createElement('iframe'); ifrEl.setAttribute('frameborder', '0'); ifrEl.setAttribute('allow', 'autoplay; fullscreen; encrypted-media; picture-in-picture'); ifrEl.setAttribute('allowfullscreen', 'true'); ifrEl.style.cssText = 'display:block;position:absolute;top:0;left:0;width:100%;height:100%;' + 'border:0;margin:0;padding:0;'; const gateSrc = GATE.api + GATE.target; gateWrap.appendChild(ifrEl); body.innerHTML = ''; body.appendChild(gateWrap); // 最后一步才开始加载:容器和样式都就位了,免得加载时布局还没稳 ifrEl.src = gateSrc; if (document.title === '') document.title = 'VIP追剧神器 · 播放'; return true; }; // 后到的元素靠常驻 CSS 挡;这里再移一次,避免它们留在 DOM 里参与布局 // (容器必须保留:把它一起删掉会卸载 iframe,播放得重来) const dropOthers = () => { const body = document.body; if (!body || !gateWrap) return; Array.prototype.slice.call(body.children).forEach((el) => { if (el !== gateWrap) { try { el.remove(); } catch (e) { } } }); }; if (!buildGate()) { const waitBody = setInterval(() => { if (buildGate()) clearInterval(waitBody); }, 1); } document.addEventListener('DOMContentLoaded', dropOthers, { once: true }); window.addEventListener('load', dropOthers, { once: true }); } // 接口 / 条目的底色标记(想换颜色只改这里) // 规律:同一家的条目,不管在哪个标签页,都用同一个 mark —— 这样一眼能认出是同一家。 // special = 邦宁(红):解析结果特殊 // qilin = 66网 / 麒麟(绿):同一家的全部入口 —— // 解析接口 66网1·66网2·麒麟1 + 搜索跳转 66网1片库搜索 + 导航 66大片网 // wsyzy = 无损云(蓝):主站 wsyzy.cc / 采集接口 api.wsyzy.net —— 采集源 + 搜索跳转 + 导航 三处同色 // txnp = txnp.cn 一家(紫):解析接口 TXNQ(bfq.) / 酥皮(art.) + 搜索跳转 cms.txnp.cn + 导航 cms.txnp.cn // lxyy = 洛雪TV(橙):导航页(目前只有这一条) // zip0 = ZIP0(青柠黄绿):导航页第一条(作者的邀请链接) // ⚠️ 这是【文字颜色】,不是底色 —— 只给名字上色,不铺背景(铺背景会"顶眼睛",看久了不舒服)。 // 所以用实色 hex(不要带透明度):文字色一旦半透明就会发灰、看不清。 // 底色是深灰 #2c2e34,所以这些颜色都偏亮 —— 亮色在深底上才读得清。 // ⚠️ 排列规则:同一家(同色)必须挨在一起,而且【三个分区的家族顺序必须一致】—— // 下面这个声明顺序就是家族顺序:导航页 / 搜索跳转 / 解析接口 都按它排。 // 各家的顺序在三个分区里完全相同,看熟一个分区就够了。 // ⚠️ 配色尽量照顾色觉障碍(红绿色弱最常见): // ① 不用纯红+纯绿这一对(红绿色弱下会混成相近的黄褐色)⇒ 66网一家改用【蓝绿 teal】 // ② 靠色相之余也拉开明度(黄最亮、粉次之、朱红/橙居中) // ③ 但要说实话:8 家颜色不可能两两都被所有色觉类型分辨。颜色在这里只是【辅助分组】—— // 名字就写在旁边,不靠颜色也能用,所以不影响功能。 const API_MARK_COLOR = { zip0: '#c6f36b', // ① 青柠黄绿 —— ZIP0(取自它官网「找到就直接播放」那句的颜色,就是站点主色) txnp: '#c9a0f0', // ② 紫红 —— txnp.cn 一家(TXNQ / 酥皮 / txnp搜索 / cms.txnp.cn) qilin: '#3fd9b8', // ③ 蓝绿 —— 66大片网 / 麒麟 一家(原纯绿,色弱考虑换掉) wsyzy: '#5cb8ff', // ④ 天蓝 —— 无水印资源网 / 无损云 eco: '#ffa64d', // ⑤ 橙 —— EcoHub 一家 ikanbot: '#9db8d1', // ⑥ 钢蓝灰 —— 爱看机器人(名字带「机器人」,配金属冷色;也是唯一低饱和色,最好认) lxyy: '#ff8fb0', // ⑦ 粉 —— 洛雪TV special: '#ff6b4a' // ⑧ 朱红 —— 邦宁(原来跟纯绿挨着,现在绿已改蓝绿) }; const parseApis = [ // ===== 解析接口【只保留已实测可用的 9 条】===== // 这 9 条都是逐条实测确认可用的;历史上删掉的失效条目不再收录。 // ⚠️ 排列规则:同一家的(同色)挨在一起,且【家族顺序跟导航页 / 搜索跳转一致】 // (见 API_MARK_COLOR 的声明顺序)。TXNQ 那家排最前,因为它是实测里最好用的。 {"name": "TXNQ", "type": "1,3", "url": "https://bfq.txnp.cn/player?url=", "mark": "txnp"}, {"name": "酥皮", "type": "1,3", "url": "https://art.txnp.cn/?url=", "mark": "txnp"}, // ===== 66大片网 / 麒麟 这一家的三个解析入口(同色 = 同一家)===== // 命名:66网1/2 = 走 66大片网 的两个入口;麒麟1 = 麒麟自己的接口域名。 // 66网1:站内跳转入口(= 授权宿主页)。需要"剥 query + 不编码"的形态,所以打 clean 标记 {"name": "66网1", "type": "3", "url": "https://www.66dpw.vip/?url=", "mark": "qilin", "clean": true, "windowOpen": true}, // 66网2:独立解析页(内部再嵌真正的接口)。内嵌会被域名授权挡住 {"name": "66网2", "type": "3", "url": "https://www.66dpw.vip/88888888/jiexi.html?url=", "mark": "qilin"}, // 麒麟1:麒麟的另一个接口域名。实测 title 是「麒麟视频播放器」。 // ⚠️ type 用 "1,3" 是故意的:66网2 内嵌被域名授权挡所以只敢写 "3";这条静态检查没发现拦截, // 但静态查不出运行时的域名授权 ⇒ 写 "1,3" 让界面上能【一键切内嵌/弹窗】自己试。 {"name": "麒麟1", "type": "1,3", "url": "https://free.maccms.xyz/?url=", "mark": "qilin"}, // 邦宁:解析结果特殊,所以单独一个颜色。放在最后 —— 原来紧跟 66网那组(蓝绿)之后、 // 那时 66网还是纯绿,红绿相邻对红绿色弱不友好;现在绿已换蓝绿,而且它排在家族顺序的末位。 {"name": "邦宁", "type": "1,3", "url": "https://video.isyour.love/player/getplayer?url=", "mark": "special"}, // ===== 以下 3 条没有颜色(不属于上面任何一家)===== {"name": "七哥", "type": "1,3", "url": "https://jx.202617.xyz/tv.php?url="}, {"name": "M1907", "type": "1,2,3", "url": "https://im1907.top/?jx="}, {"name": "Node", "type": "1,3", "url": "https://jx.nodenode.dpdns.org/?url="}, ]; const uniqueApis = []; const seenUrls = new Set(); parseApis.forEach(api => { if (!seenUrls.has(api.url)) { seenUrls.add(api.url); uniqueApis.push(api); } }); let customApis = GM_getValue("custom_parse_apis", []); let allApis = [...uniqueApis, ...customApis]; const vipBoxId = 'vip_jx_box_' + Math.ceil(Math.random() * 100000000); const VIP_ICON_CDN = 'https://cdn.jsdmirror.com/gh/whyyy-404/icon@main/Collection/Cartoon'; const VIP_ICON_GIF = { idle: `${VIP_ICON_CDN}/bear-idle.gif`, drag: `${VIP_ICON_CDN}/bear-drag.gif`, autoOff: `${VIP_ICON_CDN}/bear-auto-off.gif`, autoOn: `${VIP_ICON_CDN}/bear-auto-on.gif`, notice: `${VIP_ICON_CDN}/notice.gif` }; const VIP_MAIN_ICON_SIZE = 72; const VIP_FLOAT_ICON_SIZE = 56; const VIP_USAGE_HTML = `
📖 使用说明:
  1、「自定义设置」改样式 / 快捷键 / 接口(加错的接口可在里面的「管理自定义接口」删掉)
  2、解析视频:点击内嵌接口解析(优先试靠前的「TXNQ」「酥皮」「66网1」等;)
  3、播放模式:点击接口右侧「内嵌/弹窗」可切换
  4、解析切集后:换集后旧播放器会关闭,开自动解析则自动解析新集
  5、自动解析:先在「自动解析设置」选接口,再点发呆熊/跳熊浮标开关
  6、快捷键:Alt+V 呼出/隐藏,Alt+R 刷新接口,Alt+S 样式设置
  7、关闭解析:点击播放器右上角 × 刷新页面恢复原视频(手机端点浮标即可开关面板)
  8、404极阴岛 QQ 群:725752181
`; function updateAutoSwitchIcon(enabled, apiName) { const autoBtn = DOM_CACHE.vipBox && DOM_CACHE.vipBox.querySelector('#vip_auto'); const autoImg = autoBtn && autoBtn.querySelector('#vip_auto_img'); if (autoImg) { autoImg.src = enabled ? VIP_ICON_GIF.autoOn : VIP_ICON_GIF.autoOff; } if (autoBtn) { autoBtn.title = enabled ? (apiName ? `自动解析已开启:${apiName}(点击关闭)` : '自动解析已开启(点击关闭)') : '点击开启自动解析(需先在自动解析设置中选择接口)'; } } // 播放器容器配置 const PLAYER_CONTAINERS = [ { host: "v.qq.com", container: "#mod_player,#player-container,.container-player", displayNodes: ["#mask_layer", ".mod_vip_popup", "#mask_layer", ".panel-tip-pay"] }, { host: "m.v.qq.com", container: ".mod_player,#player", displayNodes: [".mod_vip_popup", "[class^=app_],[class^=app-],[class*=_app_],[class*=-app-],[class$=_app],[class$=-app]", "div[dt-eid=open_app_bottom]", "div.video_function.video_function_new", "a[open-app]", "section.mod_source", "section.mod_box.mod_sideslip_h.mod_multi_figures_h,section.mod_sideslip_privileges,section.mod_game_rec"] }, {host: "w.mgtv.com", container: "#mgtv-player-wrap", displayNodes: []}, {host: "www.mgtv.com", container: "#mgtv-player-wrap", displayNodes: []}, { host: "m.mgtv.com", container: ".video-area", displayNodes: ["div.adFixedContain,div.ad-banner,div.m-list-graphicxcy.fstp-mark", "div[class^=mg-app],div#comment-id.video-comment div.ft,div.bd.clearfix,div.v-follower-info", "div.ht.mgui-btn.mgui-btn-nowelt", "div.personal", "div[data-v-41c9a64e]"] }, {host: "www.bilibili.com", container: "#player_module,#bilibiliPlayer,#bilibili-player", displayNodes: []}, {host: "m.bilibili.com", container: ".player-wrapper,.player-container,.mplayer", displayNodes: []}, { host: "www.iqiyi.com", container: "#outlayer,.iqp-player-videolayer,.m-video-player-wrap", displayNodes: ["#playerPopup", "#vipCoversBox", "div.iqp-player-vipmask", "div.iqp-player-paymask", "div.iqp-player-loginmask", "div[class^=qy-header-login-pop]", ".covers_cloudCover__ILy8R", "#videoContent > div.loading_loading__vzq4j", ".iqp-player-guide", ".defaultController_playCtrl__Smes8", ".tips_textsBackImg__svIhR", "[class*='defaultController']", "[class*='player-buttons']", "[class*='tips_']", ".qy-player-controller", ".qy-player-tips", "[class*='danmu']", "[class*='Danmu']", ".iqp-danmu", ".qy-player-danmu", "[class*='barrage']", ".XPlayer_heatMapContainer__17MIj", ".progressBar_container__0x13u", ".XPlayer_bottom__xzRnb", "div.m-iqyGuide-layer", "a[down-app-android-url]", ".loading_loading__vzq4j", "[name=m-extendBar]", "[class*=ChannelHomeBanner]", "section.m-hotWords-bottom"] }, { host: "m.iqiyi.com", container: ".m-video-player-wrap, .iqp-player-videolayer", displayNodes: ["div.m-iqyGuide-layer", "a[down-app-android-url]", "div.iqp-player-vipmask", ".loading_loading__vzq4j", "[name=m-extendBar]", "[class*=ChannelHomeBanner]", "section.m-hotWords-bottom"] }, {host: "www.iq.com", container: ".intl-video-wrap", displayNodes: []}, { host: "v.youku.com", container: "#ykPlayer,#playerMouseWheel,.h5-detail-player", displayNodes: ["#iframaWrapper", "#video_side_cashier", ".secondary-container.video_side_cashier_wrapper", ".advertise-layer", ".youku-advertise-layer", "#youku-advertise", "#player-advertise", ".advertise-youku-tips", ".preloading-layer", ".preplay-layer", ".kui-preloading-layer-0", ".kui-layer-0", ".kui-preloadinglayer-preloading-animation", ".kui-preplaylayer-preplay-background", ".kui-dashboard-timer-container", ".kui-dashboard-bar-container", ".kui-dashboard-dashboard-panel", "#youku-dashboard > div.kui-dashboard-dashboard-panel", "#youku-dashboard > div.kui-dashboard-dashboard-background", "#youku-dashboard > div.kui-dashboard-bar-container", "#youku-dashboard > div.kui-dashboard-timer-container"] }, {host: "m.youku.com", container: "#playerMouseWheel,.h5-detail-player", displayNodes: []}, {host: "tv.sohu.com", container: "#player", displayNodes: []}, {host: "film.sohu.com", container: "#playerWrap", displayNodes: []}, {host: "www.le.com", container: "#le_playbox", displayNodes: []}, {host: "video.tudou.com", container: ".td-playbox", displayNodes: []}, {host: "v.pptv.com", container: "#pptv_playpage_box", displayNodes: []}, {host: "vip.pptv.com", container: ".w-video", displayNodes: []}, {host: "www.wasu.cn", container: "#flashContent", displayNodes: []}, {host: "www.acfun.cn", container: "#player", displayNodes: []}, {host: "www.1905.com", container: "#player,#vodPlayer", displayNodes: []}, {host: "vip.1905.com", container: "#player,#vodPlayer", displayNodes: []}, ]; const DEFAULT_STYLE = { bgColor: '#3f4149', fontColor: '#DCDCDC', opacity: 0.95, width: '380px' }; const DEFAULT_SHORTCUT = { toggle: 'v', refresh: 'r', style: 's' }; // 样式/快捷键/面板位置/自动解析等全网共用同一套 GM 键;手动解析标记仍按站点隔离,避免多标签页互相干扰 const VIP_STORAGE_GLOBAL = "404vip_jx_global"; const shortcutStorageKey = "vip_custom_shortcut_" + VIP_STORAGE_GLOBAL; const CONFIG = { vipBoxId: vipBoxId, autoPlayerKey: "auto_player_key_" + VIP_STORAGE_GLOBAL, autoPlayerVal: "auto_player_value_" + VIP_STORAGE_GLOBAL, flag: "flag_vip_" + window.location.host.replace(/[^a-z0-9.-]/gi, "_"), panelPosKey: "vip_panel_pos_" + VIP_STORAGE_GLOBAL, customStyleKey: "vip_custom_style_" + VIP_STORAGE_GLOBAL, customShortcutKey: shortcutStorageKey, shortcut: GM_getValue(shortcutStorageKey, DEFAULT_SHORTCUT) }; (function migratePerHostPrefsToGlobal() { const h = window.location.host; const tryCopy = (newKey, oldPrefix) => { const oldKey = oldPrefix + h; if (GM_getValue(newKey) === undefined && GM_getValue(oldKey) !== undefined) { GM_setValue(newKey, GM_getValue(oldKey)); } }; tryCopy(CONFIG.customStyleKey, "vip_custom_style_"); tryCopy(CONFIG.customShortcutKey, "vip_custom_shortcut_"); tryCopy(CONFIG.panelPosKey, "vip_panel_pos_"); tryCopy(CONFIG.autoPlayerKey, "auto_player_key_"); tryCopy(CONFIG.autoPlayerVal, "auto_player_value_"); })(); const DOM_CACHE = { vipBox: null, vipList: null, vipTab: null, donateTab: null, simpleApiList: null, complexApiList: null, addApiForm: null, styleSetPanel: null, shortcutSetPanel: null, autoParseSetPanel: null, noticePanel: null, apiNameInput: null, apiUrlInput: null, apiTypeSelect: null, // ===== 采集源 / 导航 标签页 ===== collectTab: null, navTab: null, collectKeyword: null, collectFilterChk: null, collectStatus: null, collectSources: null, collectEpisodes: null, collectOutput: null, navLinks: null }; // 全局播放器控制 let lastPageUrl = window.location.href; GM_addStyle(` #${CONFIG.vipBoxId} { cursor: pointer; position: fixed; top: 120px; left: 0px; z-index: 2147483647; text-align: left; transition: left 0.3s ease; } #${CONFIG.vipBoxId}.visible { left: 0px; } #${CONFIG.vipBoxId} .img_box { width: 32px; height: 32px; line-height: 32px; text-align: center; background-color: lightgreen; margin: 6px 0px; color: white; font-size: 16px; font-weight: bold; border-radius: 5px; } #${CONFIG.vipBoxId} .vip_icon > .img_box { width: ${VIP_MAIN_ICON_SIZE}px; height: ${VIP_MAIN_ICON_SIZE}px; line-height: 0; background-color: transparent; border-radius: 8px; overflow: hidden; padding: 0; margin-top: 0; touch-action: none; } #${CONFIG.vipBoxId} .vip_float_btn { width: ${VIP_FLOAT_ICON_SIZE}px; height: ${VIP_FLOAT_ICON_SIZE}px; line-height: 0; background-color: transparent; border-radius: 8px; overflow: hidden; padding: 0; touch-action: none; } #${CONFIG.vipBoxId} #vip_icon_img { width: 100%; height: 100%; object-fit: cover; object-position: center 58%; transform: scale(1.14); display: block; pointer-events: none; user-select: none; } #${CONFIG.vipBoxId} #vip_auto_img { width: 100%; height: 100%; object-fit: cover; object-position: 30% center; transform: scale(1.22); display: block; pointer-events: none; user-select: none; } #${CONFIG.vipBoxId} #vip_notice_img { width: 100%; height: 100%; object-fit: cover; object-position: center; transform: scale(1.1); display: block; pointer-events: none; user-select: none; } #${CONFIG.vipBoxId} .vip_list { display: none; position: absolute; border-radius: 5px; left: ${VIP_MAIN_ICON_SIZE}px; top: 0; text-align: center; border: 1px solid white; padding: 10px 0px; max-height: 80vh; overflow-y: auto; opacity: 0; transform: translateX(-10px); transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); } #${CONFIG.vipBoxId} .vip_list.visible { display: block; opacity: 1; transform: translateX(0); } #${CONFIG.vipBoxId} .vip_list ul { padding-left: 10px; margin: 0; } #${CONFIG.vipBoxId} .vip_list li { border-radius: 2px; font-size: 12px; text-align: center; /* 一行 4 个。box-sizing:border-box + 收紧内边距后,每格可用宽度比原来多 6px */ width: calc(25% - 2px); box-sizing: border-box; line-height: 21px; float: left; border: 1px solid gray; padding: 0 2px; margin: 4px 1px; overflow: hidden; /* 改成 flex 行:名字可以被省略号截断,但「内嵌/弹窗」永远不被挤掉(否则切换不了) */ display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(10px); cursor: pointer; } #${CONFIG.vipBoxId} .vip_list li .api-name { flex: 0 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; } #${CONFIG.vipBoxId} .vip_list li .api-mode { flex: 0 0 auto; white-space: nowrap; } /* 解析标签页【底部】的「必看说明」:小字、左对齐、上面一条细分割线,不抢接口列表的注意力 */ #${CONFIG.vipBoxId} .api-must-read { font-size: 11px; line-height: 1.7; text-align: left; padding: 8px 10px 4px 10px; margin-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.12); opacity: 0.9; } #${CONFIG.vipBoxId} .api-must-read b { color: #ffcf6b; } /* ⚠️ 蓝色规则必须连子孙一起写:.api-must-read b(0,1,1)作用在 上时比 .qq(0,2,0)更具体, 只写 .qq 的话里面的 会被染成琥珀色 —— 表现就是"设了蓝色却显示黄色"。 ⚠️ 这段 CSS 在【模板字符串】里,注释里不能写反引号,否则会把字符串截断。 */ #${CONFIG.vipBoxId} .api-must-read .qq, #${CONFIG.vipBoxId} .api-must-read .qq b { color: #7dd3fc; } #${CONFIG.vipBoxId} .api-must-read .mr-title { color: #7dd3fc; font-weight: bold; } /* 标题里那个怒脸单独给红色;「👉必看说明:」继承 .mr-title 的蓝色不变 */ #${CONFIG.vipBoxId} .api-must-read .mr-title .mr-angry { color: #ff4d4d; } #${CONFIG.vipBoxId} .vip_list.visible li { opacity: 1; transform: translateY(0); transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0.1s; } #${CONFIG.vipBoxId} .complex-api-list li { width: calc(50% - 2px); } #${CONFIG.vipBoxId} .vip_list li:hover { background: rgba(28, 132, 198, 0.15) !important; border: 1px solid #1c84c6 !important; } #${CONFIG.vipBoxId} .vip_list::-webkit-scrollbar { width: 5px; height: 1px; } #${CONFIG.vipBoxId} .vip_list::-webkit-scrollbar-thumb { box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); background: #A8A8A8; } #${CONFIG.vipBoxId} .vip_list::-webkit-scrollbar-track { box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); background: #F1F1F1; } #${CONFIG.vipBoxId} li.selected { background: #075985 !important; color: #ffffff !important; border: 1px solid #7dd3fc !important; } @media (max-width: 768px) { #${CONFIG.vipBoxId} .vip_list { width: calc(100vw - 88px) !important; max-width: 380px; max-height: 70vh; box-sizing: border-box; } #${CONFIG.vipBoxId} .vip_list li { width: calc(50% - 2px) !important; font-size: 13px; line-height: 26px; } } #${CONFIG.vipBoxId} #vip_auto { background-color: transparent; } #${CONFIG.vipBoxId} .vip_notice_panel { display: none; position: absolute; left: ${VIP_MAIN_ICON_SIZE}px; bottom: 0; width: 380px; max-width: calc(100vw - ${VIP_MAIN_ICON_SIZE + 20}px); max-height: 70vh; overflow-y: auto; border-radius: 8px; border: 1px solid white; padding: 10px; box-sizing: border-box; z-index: 1; } #${CONFIG.vipBoxId} .vip_notice_panel.visible { display: block; } #${CONFIG.vipBoxId} .vip_notice_panel #donate_section { opacity: 1; transform: none; margin-top: 10px; padding-top: 10px; border-top: 1px solid #555; } #${CONFIG.vipBoxId} #add_api_btn, #${CONFIG.vipBoxId} #manage_api_btn, #${CONFIG.vipBoxId} #open-style-set-btn, #${CONFIG.vipBoxId} #open-shortcut-set-btn, #${CONFIG.vipBoxId} #open-auto-parse-set-btn { background-color: #36383f; color: #ccc; border: 1px solid #5a5a5a; font-size: 12px; width: auto; padding: 6px 12px; margin-top: 5px; border-radius: 3px; cursor: pointer; margin-left: 5px; } #${CONFIG.vipBoxId} #add_api_btn:hover, #${CONFIG.vipBoxId} #manage_api_btn:hover, #${CONFIG.vipBoxId} #open-style-set-btn:hover, #${CONFIG.vipBoxId} #open-shortcut-set-btn:hover, #${CONFIG.vipBoxId} #open-auto-parse-set-btn:hover { background-color: #42444a; } /* 「 | 内嵌 / 弹窗」里那个字。【只有一个类 .mode】—— 能不能点不由类名区分, 由
  • 上有没有 data-modes 决定(访问 togglePlayMode 之前会先查它)。 故意不加 cursor:pointer —— 可切的和不可切的长得完全一样, 不给"看着能点、点了没反应"的假提示。 */ .mode { margin-left: 2px; } .section-title { font-weight: bold; font-size: 14px; padding: 5px 0px; clear: both; opacity: 0; transform: translateY(10px); } #${CONFIG.vipBoxId} .vip_list.visible .section-title { opacity: 1; transform: translateY(0); transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0.05s; } #${CONFIG.vipBoxId} #donate_section { clear: both; margin-top: 10px; padding: 10px; text-align: center !important; border-top: 1px solid #555; opacity: 0; transform: translateY(10px); } #${CONFIG.vipBoxId} .vip_list.visible #donate_section { opacity: 1; transform: translateY(0); transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0.15s; } #${CONFIG.vipBoxId} #donate_section .donate-title { font-size: 12px; margin-bottom: 5px; } #${CONFIG.vipBoxId} #qr-code-img { max-width: 100px; max-height: 100px; margin: 5px auto 0 auto !important; border: 1px solid #ddd; background: white; display: block !important; } #${CONFIG.vipBoxId} .tab-header { display: flex; } #${CONFIG.vipBoxId} .tab-button { flex: 1; padding: 5px 0; border: none; cursor: pointer; outline: none; font-size: 12px; background: none; } #${CONFIG.vipBoxId} .tab-button.active { font-weight: bold; color: #1c84c6 !important; } #${CONFIG.vipBoxId} .tab-divider { width: 1px; background-color: #5a5a5a; margin: 5px 0; } #${CONFIG.vipBoxId} .tab-content { display: none; } #${CONFIG.vipBoxId} .tab-content.active { display: block; } /* ===== 采集源页内嵌播放器(阶段3) ===== */ #${CONFIG.vipBoxId} .collect-player-wrap { margin-top: 6px; } #${CONFIG.vipBoxId} .collect-player-bar { display: flex; align-items: center; gap: 6px; font-size: 11px; margin-bottom: 4px; text-align: left; } /* ===== 共用的表单外观 ===== 输入框(input/select)和主按钮的外观在 4 个标签页里反复出现、属性值一模一样, 这里集中写一份;下面各规则只留自己特有的(padding / font-size / width 等)。 ⚠️ 顺序要求:这条必须在各具体规则【之前】—— 选择器同优先级时,靠后的才盖得住。 ⚠️ .add-api-form .cancel-btn 改的是背景色,它选择器更具体,不受这条影响。 */ #${CONFIG.vipBoxId} .collect-player-bar select, #${CONFIG.vipBoxId} .collect-search input, #${CONFIG.vipBoxId} .collect-url-row input, #${CONFIG.vipBoxId} .add-api-form input, #${CONFIG.vipBoxId} .add-api-form select, #vip-style-set-panel input, #vip-shortcut-set-panel input, #vip-auto-parse-set-panel select { border-radius: 3px; border: 1px solid #5a5a5a; background-color: #2c2e34; color: #ccc; } #${CONFIG.vipBoxId} .collect-player-bar button, #${CONFIG.vipBoxId} .collect-search button, #${CONFIG.vipBoxId} .collect-url-row button, #${CONFIG.vipBoxId} .add-api-form button { border: none; border-radius: 3px; background-color: #1c84c6; color: #fff; cursor: pointer; } #${CONFIG.vipBoxId} .collect-player-bar select { padding: 2px 4px; font-size: 11px; } #${CONFIG.vipBoxId} .collect-player-bar button { padding: 3px 10px; font-size: 11px; } /* ===== 采集源标签页 ===== */ #${CONFIG.vipBoxId} .collect-search { display: flex; gap: 6px; padding: 6px 10px 0 10px; } #${CONFIG.vipBoxId} .collect-search input { flex: 1; min-width: 0; padding: 6px; font-size: 12px; } #${CONFIG.vipBoxId} .collect-search button { padding: 6px 12px; font-size: 12px; white-space: nowrap; } #${CONFIG.vipBoxId} .collect-filter-row { padding: 4px 10px 0 10px; font-size: 11px; text-align: left; line-height: 1.5; } #${CONFIG.vipBoxId} .collect-filter-row label { cursor: pointer; user-select: none; } #${CONFIG.vipBoxId} .collect-status { padding: 4px 10px; font-size: 11px; opacity: 1; text-align: left; line-height: 1.4; } /* 没有内容时整块不占位 —— 否则它会带着 padding 撑出一截空白 */ #${CONFIG.vipBoxId} .collect-status:empty { display: none; } #${CONFIG.vipBoxId} .collect-sources { max-height: 300px; overflow-y: auto; } /* 功能/来源 两级分区标题 */ #${CONFIG.vipBoxId} .collect-sec { font-size: 12px; font-weight: bold; text-align: left; padding: 5px 10px 0 10px; opacity: 1; } /* 「← 返回命中列表」:只在命中多部时出现(见 renderCollectEpisodeList)。 命中列表和剧集列表渲染进同一个容器,展开剧集会把列表覆盖掉 —— 这个按钮让人退回去换一部。 */ #${CONFIG.vipBoxId} .collect-back { display: inline-block; font-size: 11px; padding: 3px 8px; margin: 5px 0 1px 10px; border: 1px solid #1c84c6; border-radius: 2px; color: #7dd3fc; cursor: pointer; opacity: 1; } #${CONFIG.vipBoxId} .collect-back:hover { background: rgba(28, 132, 198, 0.25); } #${CONFIG.vipBoxId} .collect-src-title { font-size: 10px; text-align: left; padding: 4px 10px 2px 10px; opacity: 0.7; } /* 采集源标签页顶部那条「晚上容易卡」的提示:居中、颜色不解淡(opacity:1 且用暖色,别让它糊成一团浅灰) */ #${CONFIG.vipBoxId} .collect-night-note { font-size: 11px; text-align: center; padding: 6px 8px 2px 8px; color: #ffcf6b; opacity: 1; line-height: 1.5; } #${CONFIG.vipBoxId} .collect-src-title.warn { color: #ff6b6b; opacity: 1; font-weight: bold; } #${CONFIG.vipBoxId} .collect-src-list { display: flex; flex-wrap: wrap; gap: 4px; padding: 0 10px 4px 10px; max-height: 150px; overflow-y: auto; } #${CONFIG.vipBoxId} .collect-src-item { font-size: 11px; line-height: 20px; box-sizing: border-box; width: calc(33.33% - 4px); padding: 0 6px; text-align: center; border: 1px solid gray; border-radius: 2px; cursor: pointer; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #${CONFIG.vipBoxId} .collect-src-item:hover { background: rgba(28, 132, 198, 0.15); border-color: #1c84c6; } #${CONFIG.vipBoxId} .collect-src-item.selected { background: #075985 !important; color: #fff; border-color: #7dd3fc; } /* 实测不可用的源:虚线边框 + 变暗,但仍可点击复测 */ #${CONFIG.vipBoxId} .collect-src-item.bad { border-style: dashed; opacity: 0.7; } #${CONFIG.vipBoxId} .collect-hint { font-size: 10px; opacity: 0.85; margin-left: 6px; } #${CONFIG.vipBoxId} .collect-result-list { max-height: 220px; overflow-y: auto; padding: 0 10px; } #${CONFIG.vipBoxId} .collect-result-item { font-size: 12px; text-align: left; padding: 5px 6px; margin: 4px 0; border: 1px solid gray; border-radius: 2px; cursor: pointer; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #${CONFIG.vipBoxId} .collect-result-item:hover { background: rgba(28, 132, 198, 0.15); border-color: #1c84c6; } #${CONFIG.vipBoxId} .collect-result-item.selected { background: #075985; color: #fff; border-color: #7dd3fc; } #${CONFIG.vipBoxId} .collect-result-item .collect-meta { font-size: 10px; opacity: 0.75; margin-left: 4px; } #${CONFIG.vipBoxId} .collect-episodes { max-height: 220px; overflow-y: auto; padding: 0 10px 6px 10px; } #${CONFIG.vipBoxId} .collect-ep-list { display: flex; flex-wrap: wrap; gap: 4px; } #${CONFIG.vipBoxId} .collect-route-name { font-size: 10px; text-align: left; opacity: 0.75; margin: 6px 0 3px 0; } #${CONFIG.vipBoxId} .collect-ep { font-size: 11px; line-height: 20px; box-sizing: border-box; width: calc(33.33% - 4px); padding: 0 4px; text-align: center; border: 1px solid gray; border-radius: 2px; cursor: pointer; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #${CONFIG.vipBoxId} .collect-ep:hover { background: rgba(28, 132, 198, 0.15); border-color: #1c84c6; } #${CONFIG.vipBoxId} .collect-ep.selected { background: #075985; color: #fff; border-color: #7dd3fc; } #${CONFIG.vipBoxId} .collect-output { padding: 0 10px 6px 10px; } #${CONFIG.vipBoxId} .collect-url-row { text-align: left; margin-top: 4px; } #${CONFIG.vipBoxId} .collect-url-src { display: block; font-size: 10px; opacity: 0.75; margin-bottom: 3px; } #${CONFIG.vipBoxId} .collect-url-row input { width: 100%; box-sizing: border-box; padding: 5px; font-size: 11px; } #${CONFIG.vipBoxId} .collect-url-row button { margin-top: 4px; padding: 5px 14px; font-size: 12px; } /* ===== 导航标签页 ===== */ #${CONFIG.vipBoxId} .nav-list { padding: 0 10px; max-height: 300px; overflow-y: auto; } #${CONFIG.vipBoxId} .nav-item { display: block; text-align: left; padding: 7px 8px; margin: 5px 0; border: 1px solid gray; border-radius: 3px; cursor: pointer; font-size: 12px; line-height: 1.5; } #${CONFIG.vipBoxId} .nav-item:hover { background: rgba(28, 132, 198, 0.15); border-color: #1c84c6; } #${CONFIG.vipBoxId} .nav-item .nav-desc { display: block; font-size: 11px; opacity: 0.92; margin-top: 3px; } #${CONFIG.vipBoxId} .nav-item .nav-url { display: block; font-size: 10px; opacity: 0.7; margin-top: 2px; word-break: break-all; } #${CONFIG.vipBoxId} .add-api-form { padding: 10px; border-radius: 4px; margin: 10px; display: none; } #${CONFIG.vipBoxId} .add-api-form input, #${CONFIG.vipBoxId} .add-api-form select, #vip-style-set-panel input, #vip-shortcut-set-panel input, #vip-auto-parse-set-panel select { width: 100%; padding: 6px; margin: 5px 0; } #${CONFIG.vipBoxId} .add-api-form button { padding: 8px 12px; margin: 5px 2px; font-size: 12px; } #${CONFIG.vipBoxId} .add-api-form .cancel-btn { background-color: #72747a; } /* ===== 「管理自定义接口」面板 ===== 为什么要有它:原来 customApis 只增不减(全文件只有 push,没有删除/编辑入口), 加错一个地址就只能去清浏览器数据。这里列出用户加过的每一条 + 一个删除按钮。 它不在 .add-api-form 里面,所以拿不到那条共用输入框样式,这里单独写。 */ #${CONFIG.vipBoxId} .custom-api-manage { display: none; margin: 10px; padding: 8px 10px; border-radius: 4px; border: 1px solid #5a5a5a; text-align: left; } #${CONFIG.vipBoxId} .custom-api-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.08); } #${CONFIG.vipBoxId} .custom-api-row:last-child { border-bottom: none; } #${CONFIG.vipBoxId} .custom-api-info { flex: 1 1 auto; min-width: 0; } #${CONFIG.vipBoxId} .custom-api-name { display: block; font-size: 12px; } #${CONFIG.vipBoxId} .custom-api-url { display: block; font-size: 10px; opacity: 0.65; word-break: break-all; } #${CONFIG.vipBoxId} .custom-api-del { flex: 0 0 auto; padding: 4px 10px; border: none; border-radius: 3px; background-color: #b3352f; color: #fff; font-size: 11px; cursor: pointer; } #${CONFIG.vipBoxId} .custom-api-del:hover { background-color: #d13f38; } #${CONFIG.vipBoxId} .custom-api-empty { font-size: 11px; opacity: 0.7; text-align: center; padding: 2px 0; } /* 自定义接口在解析列表里的记号:中性灰蓝,不占用 7 个家族色 */ #${CONFIG.vipBoxId} .api-custom-mark { color: #9fb3c8; font-weight: bold; margin-right: 1px; } #vip-style-set-panel, #vip-shortcut-set-panel, #vip-auto-parse-set-panel { padding: 10px; margin: 10px; border-top: 1px solid #555; display: none; } #vip-style-set-panel .style-item, #vip-shortcut-set-panel .shortcut-item, #vip-auto-parse-set-panel .auto-parse-item { display: flex; align-items: center; margin: 8px 0; gap: 8px; } #vip-style-set-panel .style-item label, #vip-shortcut-set-panel .shortcut-item label, #vip-auto-parse-set-panel .auto-parse-item label { font-size: 12px; width: 80px; text-align: left; } #vip-style-set-panel .style-item input, #vip-shortcut-set-panel .shortcut-item input, #vip-auto-parse-set-panel .auto-parse-item select { flex: 1; padding: 4px; } #vip-style-set-panel button, #vip-shortcut-set-panel button, #vip-auto-parse-set-panel button { width: 40%; padding: 8px 12px; margin: 5px 1%; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; display: inline-block; } #reset-style-btn, #reset-shortcut-btn, #disable-auto-parse-btn { background: #ff69b4 !important; color: white !important; } #save-auto-parse-btn, #save-shortcut-btn, #save-style-btn { background: #1c84c6 !important; color: white !important; } .shortcut-tip { font-size: 10px; color: #ccc; margin-top: 5px; text-align: left; line-height: 1.4; } /* 播放器控制按钮样式 */ .vip-player-close-btn { position: absolute; top: 15px; right: 15px; width: auto; height: auto; padding: 5px 10px; background: transparent; border: none; cursor: pointer; font-size: 32px; color: rgba(255, 255, 255, 0.8); transition: all 0.3s ease; opacity: 1; text-shadow: 0 2px 4px rgba(0,0,0,0.5); pointer-events: auto; } .vip-player-close-btn:hover { color: rgba(255, 255, 255, 1); transform: scale(1.2); } .vip-player-close-btn.hidden { opacity: 0; pointer-events: none; } `); function findTargetElement(targetContainer) { const body = window.document; let tabContainer; let tryTime = 0; const maxTryTime = 120; let startTimestamp; return new Promise((resolve, reject) => { function tryFindElement(timestamp) { if (!startTimestamp) { startTimestamp = timestamp; } const elapsedTime = timestamp - startTimestamp; if (elapsedTime >= 500) { tabContainer = body.querySelector(targetContainer); if (tabContainer) { resolve(tabContainer); } else if (++tryTime === maxTryTime) { reject(); } else { startTimestamp = timestamp; } } if (!tabContainer && tryTime < maxTryTime) { requestAnimationFrame(tryFindElement); } } requestAnimationFrame(tryFindElement); }); } function encodeVideoUrl(url) { if (!url) return ''; return encodeURIComponent(url).replace(/%20/g, '+'); } // =================================================================== // ===== 采集源页(功能 × 来源 两级分区)===== // 请求方式:接口地址 + ?ac=detail&wd=剧名 → 返回 JSON(vod_play_url 里是 .m3u8) // fmt 说明: // maccms10 = 苹果CMS v10(ac=detail) // maccms8 = 苹果CMS v8(ac=videolist) // custom = 非标准路径,按模板拼({kw} 替换剧名) // search = 不是接口,直接用剧名拼好 URL 打开新标签 // jump = 强制跳转:用【当前视频页地址】拼好 URL 打开新标签 // random = 随机组合源(从 pool 里随机挑一个真源来搜) // link = 不是接口,点开新标签(用于纯外链型条目) // info = 纯展示,不可点(没有可用地址的条目) // 裸域名会由 normalizeCollectApi() 自动补 /api.php/provide/vod/ // 数据来源:公开渠道整理的采集接口清单 + EcoHub + 18+.json // v 字段 = 可用性备注(部分条目在浏览器之外的环境取不到数据,以实际点开的结果为准) // =================================================================== // ⚠️ 以下清单参考自 屏幕前的你 大佬 分享的自用脚本(https://github.com/963540817/dashu), // 经逐条实测筛选后收录;详细来源说明见文件顶部「致谢与来源」。 const COLLECT_GROUPS = [ // 四个分区:采集源 / EcoHub / 搜索跳转 / 其他。 // 每个来源【内部】再按实测结果分子类,从「好用」排到「没用」。 // ⚠️ 各条的 v 记的是【最近一次实测】的状态,不是永久结论: // 采集站的可用性会波动(站长带宽、限流、间歇宕机),同一个源两次测出不同结果是常态。 // 复测后只需改子类归属和 v,顶层分区不用动。 { sec: '采集源', src: '推荐 · 随机挑一个', items: [ { name: '日常', api: '', fmt: 'random', pool: 'auto' } ] }, { sec: '采集源', src: '✅ 可用', items: [ { name: '期颐', api: 'https://iqiyizyapi.com/api.php/provide/vod/from/iqym3u8', fmt: 'maccms10', hit: true }, { name: '巨量', api: 'https://api.juliang.live/api/provide/vod', fmt: 'maccms10', hit: true }, { name: '天堂', api: 'http://caiji.dyttzyapi.com', fmt: 'maccms10', hit: true }, { name: '豪华', api: 'https://hhzyapi.com', fmt: 'maccms10', hit: true }, { name: '西瓜', api: 'https://caiji.xgzyapi.com', fmt: 'maccms10', hit: true }, { name: '极速', api: 'https://jszyapi.com', fmt: 'maccms10', hit: true }, { name: '虎牙', api: 'https://www.huyaapi.com', fmt: 'maccms10', hit: true }, { name: '百度', api: 'https://api.apibdzy.com', fmt: 'maccms10', hit: true }, { name: '量子', api: 'https://cj.lziapi.com', fmt: 'maccms10', hit: true }, { name: '最大', api: 'https://api.zuidapi.com', fmt: 'maccms10', hit: true }, { name: '无忧', api: 'https://www.wyvod.com', fmt: 'maccms10', hit: true }, { name: '魔都', api: 'https://caiji.moduapi.cc', fmt: 'maccms10', hit: true }, { name: '魔都2', api: 'https://caiji.moduapi.cc/api.php/provide/vod/from/modum3u8', fmt: 'maccms10', hit: true }, // 下面两条和 EcoHub 分区里的「速博(SUBO)」「非凡(FF)」同源,两边都保留。 // ⚠️ 与 EcoHub 分区的「速博(SUBO)」「非凡(FF)」同源,这两条的 v 里已写明,悬停可见。 { name: '速播', api: 'https://subocj.com', fmt: 'maccms10', hit: true }, { name: '非凡', api: 'http://api.ffzyapi.com', fmt: 'maccms10', hit: true } ] }, { // 和上面「✅ 可用」是同一类结果(直连就能搜到),单独成组只因为【形状不同】: // 它搜索只返回视频 ID,播放地址要再按 ID 问一次详情 ⇒ 多一次请求,稍慢。 // 这是【格式】差异,不是【策略】:对方没有拒绝我们,只是话不一样。 sec: '采集源', src: '✅ 可用 · 稍慢', items: [ { name: '沃云', api: 'http://zhibo.jishuwo.com/api/so/index.php?wd={kw}', fmt: 'woyun', detail: 'http://zhibo.jishuwo.com/api/so/index.php?ac=detail&id={id}', hit: true } ] }, { sec: '采集源', src: '⚠️ 可用,代理稍慢', items: [ { name: '酷播', api: 'https://api.yzzy-api.com/inc/apijson.php', fmt: 'maccms10' }, { name: '如意', api: 'https://cj.rycjapi.com', fmt: 'maccms10' }, { name: 'U酷', api: 'https://api.ukuapi.com', fmt: 'maccms10' }, { name: '爱坤', api: 'http://www.ikunzy.com', fmt: 'maccms10' } ] }, { // 和上面「✅ 可用」是同一类结果(都能搜到),分开只是因为机制不同: // 这些站点禁用了关键词搜索,脚本识别到之后会自动改走「联想接口 + 按 ID 取详情」 // 识别信号有两种形态,两种都处理: // 一种是把拒绝包成 JSON 错误码返回,另一种是直接回一句纯文本 sec: '采集源', src: '✅ 可用 · 自动换接口', items: [ { name: '茅台', api: 'https://caiji.maotaizy.cc', fmt: 'maccms10' }, { name: '天涯', api: 'https://tyyszyapi.com', fmt: 'maccms10' }, { name: '牛牛', api: 'https://api.niuniuzy.me', fmt: 'maccms10' }, { name: '丫丫', api: 'https://cj.yayazy.net', fmt: 'maccms10' }, { name: 'OK', api: 'http://api.okzyw.net', fmt: 'maccms10' }, { name: '无损云', api: 'https://api.wsyzy.net', fmt: 'maccms10', mark: 'wsyzy' } ] }, { sec: 'EcoHub', src: '✅ 可用', items: [ { name: '默认', api: 'https://jinyingzy.com/api.php/provide/vod', fmt: 'maccms10', hit: true }, { name: '速博(SUBO)', api: 'https://subocaiji.com/api.php/provide/vod', fmt: 'maccms10', hit: true }, { name: '金鹰2(JY)', api: 'https://jyzyapi.com/api.php/provide/vod', fmt: 'maccms10', hit: true }, { name: '非凡(FF)', api: 'http://cj.ffzyapi.com/api.php/provide/vod/', fmt: 'maccms10', hit: true }, { name: 'HD(LY)', api: 'https://360zy.com/api.php/provide/vod/at/json', fmt: 'maccms10', hit: true }, { name: 'U酷(UKU)', api: 'https://api.ukuapi88.com/api.php/provide/vod', fmt: 'maccms10', hit: true }, { name: '光速(GS)', api: 'https://api.guangsuapi.com/api.php/provide/vod/json', fmt: 'maccms10', hit: true }, { name: 'HD(BF)', api: 'https://bfzyapi.com/api.php/provide/vod/', fmt: 'maccms10', hit: true }, { name: '红牛(HN)', api: 'https://www.hongniuzy2.com/api.php/provide/vod/at/json', fmt: 'maccms10', hit: true } ] }, { // 和「✅ 可用」也是同一类结果(都能搜到),单独成组是因为它走的是第三条机制: // 关键词搜被拒 → 联想接口也被拒 → 改翻最近几页列表,在本地按剧名找。 // ⚠️ 这条机制只对「最近更新过」的剧有效 —— 这类站分页总数上万,找老剧翻两页不可能命中。 sec: 'EcoHub', src: '✅ 可用 · 只能找新剧', items: [ { name: '樱花(YH)', api: 'https://m3u8.apiyhzy.com/api.php/provide/vod/', fmt: 'maccms10' } ] }, { sec: 'EcoHub', src: '⚠️ 可用,代理稍慢', items: [ { name: 'HD(IK)', api: 'https://ikunzyapi.com/api.php/provide/vod/at/json', fmt: 'maccms10' } ] }, { sec: '搜索跳转', src: '跳到对方站自己搜', items: [ // 排列:家族顺序跟导航页 / 解析接口【保持一致】(见 API_MARK_COLOR 的声明顺序),无色的沉底 { name: 'txnp搜索', api: 'https://cms.txnp.cn/index.php/vod/search.html?wd={kw}', fmt: 'search', mark: 'txnp' }, { name: '66网1片库搜索', api: 'https://www.66dpw.vip/vodsearch/-------------.html?wd={kw}', fmt: 'search', mark: 'qilin' }, { name: '无损云搜索', api: 'http://wsyzy.cc/index.php/vod/search.html?wd={kw}&submit=search', fmt: 'search', mark: 'wsyzy' }, { name: 'EcoHub站', api: 'https://eco.fe-spark.cn/search?search={kw}', fmt: 'search', mark: 'eco' }, { name: '爱看机器人(主)', api: 'https://www.ikanbot.com/search?q={kw}', fmt: 'search', mark: 'ikanbot' }, { name: '爱看机器人(备)', api: 'http://ikanbot.eu.org/search?q={kw}', fmt: 'search', mark: 'ikanbot' } ] }, ]; // 扁平化:让「渲染顺序」和「点击索引」永远一致,避免漏项/错位 const COLLECT_ALL = []; COLLECT_GROUPS.forEach((g) => { g.items.forEach((it) => { it.sec = g.sec; it.src = g.src; // 成人标记只看条目自己的 adult 字段;分区标题的 warn 只负责样式,不再兼作成人判定 it._idx = COLLECT_ALL.length; COLLECT_ALL.push(it); }); }); // ===== 导航(纯外链,点击用 GM_openInTab 打开)===== const NAV_LINKS = [ // ⚠️ 排列规则: // ① 同色的必须挨在一起; // ② 家族顺序跟搜索跳转 / 解析接口保持一致(见 API_MARK_COLOR 的声明顺序)—— // 在一个分区看熟了顺序,到别的分区不用重新找; // ③ 带颜色的排在前面(它们是"有来头"的站),不带颜色的沉底。 { name: 'ZIP0', url: 'https://zip0.com/?r=0LUAMJ', mark: 'zip0', desc: '在线影视聚合搜索,一次查询多个公开来源 · 覆盖电影 / 短剧 / 电视剧 / 综艺 / 纪录片 / 体育 · 另有看电视 / 听广播 / 音乐 / 小游戏 · (❤️ ω ❤️) 作者力推,非常推荐 · ┏ (゜ω゜)=👉 邀请链接' }, { name: 'cms.txnp.cn', url: 'https://cms.txnp.cn/', mark: 'txnp', desc: '免费短视频分享站,影视 / 短视频 / 动漫 / 综艺 / 学习 / 音乐等分类,自带搜索' }, { name: '66大片网', url: 'https://www.66dpw.vip/', mark: 'qilin', desc: '电影 / 动漫 / 短剧 / 综艺等,各分类上万部,每日更新,手机端流畅播放' }, { name: '无水印资源网', url: 'http://wsyzy.cc/', mark: 'wsyzy', desc: '电影 / 电视剧 / 美剧 / 韩剧 / 综艺 / 动漫 / 短剧 / 纪录片,站内标称 12.5 万条,自带搜索(首次需验证码)· ⚠️ 它自带的播放器会做 P2P 上传、占用上行带宽' }, { name: 'EcoHub 演示站', url: 'https://eco.fe-spark.cn/', mark: 'eco', desc: '开源多播放源聚合站(电视剧 / 电影 / 动漫 / 综艺 / 体育),站点自述「自动采集、多播放源集成」' }, { name: '爱看机器人', url: 'https://www.ikanbot.com/', mark: 'ikanbot', desc: '全网影视资源搜索引擎(爬虫检索免费在线影视),自带搜索' }, { name: '洛雪TV', url: 'https://tv.lxyy.club/', mark: 'lxyy', desc: '影视推荐聚合(电影 / 电视剧 / 动漫 / 综艺 / 短剧),多来源聚合搜索;搜索在前端做,URL 带不了关键词' }, // 以下三条不带颜色(不属于任何一个"家族") { name: '网盘资源导航', url: 'https://wangpanziyuan.pages.dev/', desc: '电子书 / 漫画 / 影视 / 游戏 / 音乐 / 学习资料 / 办公软件的网盘合集,站内标称 10 万+ 资源' }, { name: '动漫共和国', url: 'https://666.oneghg.com/', desc: '日漫 / 国创 / 经典番剧 / 剧场版免费在线追番,另有安卓 / iOS / Windows 客户端' }, { name: '小小漫迷', url: 'https://xxmanmi.com/', desc: '动漫在线观看,另有电影 / 综艺 / 美剧 / 韩剧 / 短剧分类,自带搜索' } ]; const COLLECT_TIMEOUT = 15000; // 单请求超时(ms) // ===== 采集代理兜底(方案 A)===== // 直连失败时,把目标地址拼在代理后面重试(前缀式:代理地址 + 目标URL) // 直连失败的源走这个代理重试(第三方服务,可用性与安全性自负) // ⚠️ 这是第三方服务器:只在【直连失败】时才用;想彻底关掉就把下面这一行改成空字符串 '' const COLLECT_PROXY = 'https://p.xbta.cc/'; // 已经在代理 / 跨域服务上的地址,不再重复套代理 const COLLECT_PROXY_SKIP = /^\s*?https?:\/\/(?:[^\/]+?\.)?(?:cors|p\.xbta|seep\.eu|codetabs|similarsites)\./i; const COLLECT_PROXY_MAXFAIL = 3; // 代理连续失败几次,就本次会话停用代理 const COLLECT_LIST_MAXPAGE = 2; // 「翻最近列表」兜底最多翻几页(每页 20 条,写死防止无限翻) const COLLECT_TWOSTEP_MAX = 3; // 「两步型」源(沃云)搜索命中很多时,最多跟几条去取详情。 // 实测搜「斗破」沃云一次返回 20 条,而每条都要一次详情请求 —— 不封顶就会连打 20 次。 let collectProxyUsed = 0; // 统计:本次会话成功走代理几次 let collectProxyFail = 0; // 连续失败计数 // ===== 采集源内嵌播放器(阶段3)===== // collectPlayerMode:art = Artplayer;native = 原生