// ==UserScript== // @name 我只想好好观影 // @namespace liuser.betterworld.love // @match https://movie.douban.com/subject/* // @match https://m.douban.com/movie/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @connect * // @run-at document-end // @require https://cdnjs.cloudflare.com/ajax/libs/artplayer/5.1.0/artplayer.min.js // @require https://unpkg.com/artplayer-plugin-control@2.0.0/dist/artplayer-plugin-control.js // @require https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.12/hls.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.9/vue.min.js // @version 3.9.6 // @author liuser, collaborated with ray // @description 为了想看就看 // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/459540/%E6%88%91%E5%8F%AA%E6%83%B3%E5%A5%BD%E5%A5%BD%E8%A7%82%E5%BD%B1.user.js // @updateURL https://update.greasyfork.icu/scripts/459540/%E6%88%91%E5%8F%AA%E6%83%B3%E5%A5%BD%E5%A5%BD%E8%A7%82%E5%BD%B1.meta.js // ==/UserScript== //vue production https://cdn.jsdelivr.net/npm/vue@2.7.14 //vue dev https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js //按钮样式 // @note 按钮样式 GM_addStyle( ` .liu-btn{ cursor:pointer; font-size:1rem; padding: 0.6rem 1.2rem; border: 1px solid transparent; border-radius: 3px; max-height:50px; } .play-btn { border-radius: 8px; cursor: pointer; font-weight: bolder; background-color:#e8f5e9; } .play-btn:hover { background-color:#c8e6c9; } .play-btn:active{ background-color: #81c784; } .source-selector{ background-color: #141414; color: #99a2aa; padding:0.6rem 0.8rem; margin:0.5rem 0.875rem; border-radius:4px; } .source-selector:hover{ background-color: #1f1f1f; } .series-selector{ background-color: #141414; border-radius:3px; color: #99a2aa; font-size:16px; padding: 12px 16px; } .series-selector:hover{ background-color: #153a1d; } .playing{ border:2px solid #007011; } .selected{ border:2px solid #007011; } .liu-closePlayer{ border-radius:3px; background-color: #141414; float:right; color: #99a2aa; width:2rem; height:2rem; line-height:2rem; padding:0; margin:0.5rem 1rem; } .liu-closePlayer:hover{ background-color:#1f1f1f; color:white; } .love-support{ color:#99a2aa; background-color:tranparent; margin-right:32px; } a:visited{ color:#99a2aa; } a:hover{ font-weight:bold; color:#A8DB39; background:none; } ` ); //@note 剧集选择器布局 GM_addStyle( ` .series-contianer{ display:grid; grid-template-columns: repeat(4,1fr); grid-auto-rows:50px; grid-column-gap:16px; grid-row-gap:16px; margin-top:16px; height:524px; overflow-y:scroll; } .series-contianer::-webkit-scrollbar { display: none; } @media screen and (max-width: 1025px) { .series-contianer{ display:grid; grid-template-columns: repeat(6,1fr); } } ` ); //布局@note 整体布局 GM_addStyle( ` :root{ font-size:16px; font-family: BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif !important; } :root::-webkit-scrollbar { display: none; } .TalionNav{ z-index:10; } .speed-slow{ color:#9e9e9e; } .speed-fast{ color:#4aa150; } .mannul{ margin:16px 0px 64px 14px; font-size:16px; display:flex; flex-wrap:wrap; } .authoralert{ font-size:16px; margin-left:14px; color:#F76965; } .liu-playContainer{ width:100%; height:100%; background-color:#1c2022; position:fixed; top:0; z-index:11; overflow:auto; } .liu-playContainer::-webkit-scrollbar { display: none; } .video-selector{ display:flex; flex-wrap:wrap; margin-top:1rem; } .liu-selector:hover{ color:#aed0ee; background-color:none; } .liu-selector{ color:black; cursor:pointer; padding:3px; margin:5px; border-radius:2px; } .liu-rapidPlay{ color: #007722; } .liu-light{ background-color:#7bed9f; } .artplayer-app{ height:600px; } .playSpace{ display: grid; /* height:400px; */ margin:1rem; grid-template-columns: 2fr 1fr; grid-row-gap:0px; grid-column-gap:1rem; margin-top:2rem; clear: both; } @media screen and (max-width: 1025px) { .playSpace{ display: grid; /* height:600px; */ grid-template-rows: 1fr 0.5fr; grid-template-columns:1fr; grid-row-gap:10px; grid-column-gap:0px; } } .seletor-title{ height:60px; line-height:3rem; background-color: #141414; color:#fafafa; font-size:1.25rem; padding: 0 1rem; } ` ); // 上传额外源的信息 const sourceupload = () => { let sourceAdded = prompt( "请输入自定义源,名称与链接用|隔开,每一项用英文逗号隔开,例子:XX资源|https://xx.com/,YY资源|https://yy.com/" ); GM_setValue("sourceAdded", sourceAdded); }; // 注册菜单按钮 GM_registerMenuCommand("自定义源", sourceupload); (function () { const _debug = 0; //@note debug const searchSource = [ //@note 内置搜索源 { name: "红牛资源", searchUrl: "https://www.hongniuzy2.com/api.php/provide/vod/from/hnm3u8/", }, { name: "暴风资源", searchUrl: "https://bfzyapi.com/api.php/provide/vod/", }, // { // name: "快帆资源", // searchUrl: "https://api.kuaifan.tv/api.php/provide/vod/", // }, 失效 { name: "非凡资源", searchUrl: "http://cj.ffzyapi.com/api.php/provide/vod/", }, { name: "量子资源", searchUrl: "https://cj.lziapi.com/api.php/provide/vod/", }, { name: "ikun资源", searchUrl: "https://ikunzyapi.com/api.php/provide/vod/from/ikm3u8/at/json/", }, { name: "光速资源", searchUrl: "https://api.guangsuapi.com/api.php/provide/vod/from/gsm3u8/", }, { name: "高清资源", searchUrl: "https://api.1080zyku.com/inc/apijson.php/", }, { name: "天空资源", searchUrl: "https://m3u8.tiankongapi.com/api.php/provide/vod/from/tkm3u8/", }, //有防火墙,垃圾 { name: "闪电资源", searchUrl: "https://sdzyapi.com/api.php/provide/vod/", }, //不太好,格式经常有错 { name: "索尼资源", searchUrl: "https://suoniapi.com/api.php/provide/vod/", }, { name: "飞速资源", searchUrl: "https://www.feisuzyapi.com/api.php/provide/vod/", }, //经常作妖或者没有资源 { name: "卧龙资源", searchUrl: "https://collect.wolongzyw.com/api.php/provide/vod/", }, //非常恶心的广告 // { "name": "8090资源", "searchUrl": "https://api.yparse.com/api/json/m3u8/" },垃圾 可能有墙 { name: "百度云资源", searchUrl: "https://api.apibdzy.com/api.php/provide/vod/", }, // { "name": "酷点资源", "searchUrl": "https://kudian10.com/api.php/provide/vod/" }, { name: "淘片资源", searchUrl: "https://taopianapi.com/cjapi/mc/vod/json/m3u8.html", }, // { "name": "ck资源", "searchUrl": "https://ckzy.me/api.php/provide/vod/" }, { name: "快播资源", searchUrl: "https://caiji.kczyapi.com/api.php/provide/vod/", }, { name: "乐视资源", searchUrl: "https://leshiapi.com/api.php/provide/vod/at/json/", }, { name: "优质资源", searchUrl: "https://api.1080zyku.com/inc/apijson.php", }, { name: "丫丫资源", searchUrl: "https://cj.yayazy.net/api.php/provide/vod/", }, { name: "金鹰资源", searchUrl: "https://jyzyapi.com/provide/vod/from/jinyingm3u8/at/json", }, { name: "快播资源", searchUrl: "https://caiji.kczyapi.com/api.php/provide/vod/", }, // { "name": "海外看资源", "searchUrl": "http://api.haiwaikan.com/v1/vod/" }, // 说是屏蔽了所有中国的IP,所以如果你有外国的ip可能比较好 // { "name": "68资源", "searchUrl": "https://caiji.68zyapi.com/api.php/provide/vod/" }, // {"name":"鱼乐资源","searchUrl":"https://api.yulecj.com/api.php/provide/vod/"},//速度太慢 { name: "无尽资源", searchUrl: "https://api.wujinapi.me/api.php/provide/vod/", }, //资源少 ]; const { query: $, queryAll: $$, isMobile } = Artplayer.utils; //工具函数 const tip = (message) => alert(message); // 判断是否为 Edge 浏览器 const isEdge = /Edge\/\d+/.test(navigator.userAgent); // 判断是否为 Chrome 浏览器 const isChrome = /Chrome\/\d+/.test(navigator.userAgent) && !isEdge; // 判断是否为 Safari 浏览器 const isSafari = /Safari\/\d+/.test(navigator.userAgent) && !isChrome; //--------------------------全局方法 //获取豆瓣影片名称 const videoName = isMobile ? $(".sub-title").innerText : document.title.slice(0, -5).replace(" ", ""); // debug方法 const log = (function () { if (_debug) return console.log.bind(console); return function () {}; })(); const htmlToElement = function (html) { //将html字符串转为element const template = document.createElement("template"); template.innerHTML = html.trim(); return template.content.firstChild; }; //速度为0不一定是无法播放,可能是源的防火墙阻止了测速,也可以试试。 const handleResponse = function (response) { // log("正在处理搜索的结果"); if (!response) { // log("返回结果错误,response is undefined"); return { r: false }; } if (response.list.length == 0) { // log("没有搜索到结果"); return { r: false }; } let video, found = false; for (let item of response.list) { // 对比名称、发行年、演员,只要有一个一样就算成功 let nameEqual = item.vod_name == videoName; let yearEqual = getVideoYear(item.vod_year); let actorContain = videoActor(item.vod_actor.split(",")[0]); if (yearEqual === true || actorContain === true || nameEqual === true) { video = item; found = true; // log(`资源匹配成功`); break; } } if (found == false) { return { r: false }; } let vod_name = video.vod_name; let playList = video.vod_play_url .split("$$$") .filter((str) => str.includes("m3u8")); if (playList.length == 0) { throw new Error("没有m3u8资源, 无法测速, 无法播放"); return { r: false }; } playList = playList[0].split("#"); playList = playList.map((str) => { let index = str.indexOf("$"); return { name: str.slice(0, index), url: str.slice(index + 1), speed: -1, }; }); return { r: true, content: playList, vod_name: vod_name }; }; //播放按钮 class PlayBtn { constructor() { const e = htmlToElement( `` ); $(isMobile ? ".sub-original-title" : "h1").appendChild(e); e.onclick = function () { initVue(); }; } } const playM3u8 = function (video, url, art) { if (Hls.isSupported()) { if (art.hls) art.hls.destroy(); const hls = new Hls(); hls.loadSource(url); hls.attachMedia(video); art.hls = hls; art.on("destroy", () => hls.destroy()); } else if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = url; } else { art.notice.show = "Unsupported playback format: m3u8"; } }; //获取电影的年份 const getVideoYear = function (outYear) { const e = $(isMobile ? ".sub-original-title" : ".year"); if (!e) { // log("获取年份失败,请检查!"); return 0; } return e.innerText.includes(outYear); }; //对比电影演员 const videoActor = function (outActor) { const e = $(isMobile ? ".bd" : ".actor"); if (!e) { // log("获取演员失败,请检查!"); return 0; } //log(`${outActor}:匹配结果${e.innerText.includes(outActor)}`) return e.innerText.includes(outActor); }; //下载 const get = function (detail) { //@note get log("正在请求:"); log(detail); return new Promise((resolve, reject) => { let timer = setTimeout(() => { resolve({ r: false }); }, 3000); let defaultConfig = { method: "GET", timeout: 3000, onload: (r) => { clearTimeout(timer); resolve({ r: true, content: r.response }); }, onerror: () => { log("get请求error " + detail.url); resolve({ r: false }); }, onabort: () => { log("get请求abort " + detail.url); resolve({ r: false }); }, ontimeout: () => { log("get请求timeout " + detail.url); resolve({ r: false }); }, }; let config = Object.assign(defaultConfig, detail); GM_xmlhttpRequest(config); }); }; //下载m3u8的内容,返回片段列表 const downloadtsList = async function (url) { let domain = url.split("/")[0]; let baseUrl = url.split("/")[2]; let result = await get({ url: encodeURI(url), }); if (!result.r) { return { r: false }; } let downloadContent = result.content; if (!downloadContent.includes("#EXTM3U")) { log("无法获取m3u8内容,请求网址为:" + url); log("下载的内容为"); log(downloadContent); return { r: false }; } let tsList = []; if (downloadContent.includes(".m3u8")) { //如果还是m3u8地址 let lines = downloadContent.split("\n"); for (let item of lines) { if (/^[#\s]/.test(item)) continue; //跳过注释和空白行 if (item == "") continue; if (/^\//.test(item)) { //如果是相对链接的话 let result = await downloadtsList(domain + "//" + baseUrl + item); if (!result.r) { return { r: false }; } tsList = result.content; } else if (/^https?:\/\//i.test(item)) { //如果是绝对链接的话 let result = await downloadtsList(item); if (!result.r) { return { r: false }; } tsList = result.content; } else { //那就只剩下替代链接的情况了 log("m3u8替代情况"); log(item); let contents = url.split("/"); contents[contents.length - 1] = item; log(contents); url = contents.join("/"); let result = await downloadtsList(url); if (!result.r) { return { r: false }; } tsList = result.content; } } return { r: true, content: tsList }; } if (downloadContent.includes(".ts")) { //如果是ts地址 let lines = downloadContent.split("\n"); for (let item of lines) { if (/^[#\s]/.test(item)) continue; //跳过注释和空白行 if (item == "") continue; if (/^https?:\/\//i.test(item)) { //如果是http直链 tsList.push(item); } else if (/^\//.test(item)) { //如果是相对链接 tsList.push(domain + "//" + baseUrl + item); } else { //如果不是相对链接就把index.m3u8替换掉就行 let contents = url.split("/"); contents[contents.length - 1] = item; url = contents.join("/"); tsList.push(url); } } log(`测试列表为:`); log(tsList); return { r: true, content: tsList }; } log("未知状况"); log(downloadContent); return { r: false }; }; //app的整体结构,作为vue的渲染模板 //@note vueAppTemplate const vueAppTemplate = `