您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
暗黑模式、滚动加载、预览视频、预览大图、离线下载(115)...
当前为
// ==UserScript== // @name JavBus工具 // @namespace https://greasyfork.org/users/175514 // @description 暗黑模式、滚动加载、预览视频、预览大图、离线下载(115)... // @version 0.2.3 // @icon https://z3.ax1x.com/2021/10/15/53gMFS.png // @include *://*.javbus.com/* // @include *://*.115.com/* // @require https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js // @require https://unpkg.com/infinite-scroll@4/dist/infinite-scroll.pkgd.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js // @resource fail https://z3.ax1x.com/2021/10/15/53gcex.png // @resource info https://z3.ax1x.com/2021/10/15/53g2TK.png // @resource success https://z3.ax1x.com/2021/10/15/53gqTf.png // @run-at document-start // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_notification // @grant GM_setClipboard // @grant GM_getResourceURL // @grant GM_openInTab // @grant GM_info // @grant GM_registerMenuCommand // @connect * // @license MIT // ==/UserScript== /** * 非完全测试,自用Edge * TODO: * ✔️ 暗黑模式 * ✔️ 点击事件(新窗口打开,左键前台,右键后台) * ✔️ 滚动加载 * ✔️ 获取预览大图(javstore.net) * ✔️ 获取预览视频(r18.com),如有资源点击封面图或样品图像处进行预览 * ✔️ 获取演员名单(javdb.com) * ✔️ 离线下载(请求离线成功2秒后查询结果汇报),自动修改文件名称,(如已设置操作目录cid,自动移动视频文件至对应目录并删除原目录) * ✔️ 一键离线(字幕>尺寸>日期)排队执行离线下载,成功一结束排队 * ✔️ 115账号验证弹窗 * ✔️ 查询115是否已有离线资源 * ✔️ 查询115是否已有离线资源并缓存(默认3天有效),3天后重新查询,支持手动刷新“资源刷新” * ✔️ 根据缓存数据匹配列表页已有资源状态,添加已有资源列表页 */ (function () { "use strict"; // params let mousePoint = 0; const dm = GM_getValue("dm") ?? matchMedia("(prefers-color-scheme: dark)").matches; const lm = GM_getValue("lm") ?? false; const ck = GM_getValue("ck") ?? true; const rootId = GM_getValue("rid") ?? ""; const SUFFIX = " !important"; const { host, pathname } = location; // document const doc = document; doc.create = (tag, attr = {}, child = "") => { if (!tag) return null; tag = doc.createElement(tag); Object.keys(attr).forEach(name => tag.setAttribute(name, attr[name])); typeof child === "string" && tag.appendChild(doc.createTextNode(child)); typeof child === "object" && tag.appendChild(child); return tag; }; const insertBefore = (el, htmlString) => el.insertAdjacentHTML("beforebegin", htmlString); const insertAfter = (el, htmlString) => el.insertAdjacentHTML("afterend", htmlString); const appendBefore = (el, htmlString) => el.insertAdjacentHTML("afterbegin", htmlString); const appendAfter = (el, htmlString) => el.insertAdjacentHTML("beforeend", htmlString); // localforage const lf = localforage; lf.upItem = async (key, val) => { const original = (await lf.getItem(key)) ?? {}; await lf.setItem(key, Object.assign(original, val)); }; // request const request = (url, method = "GET", data = {} | "", params = {}) => { if (!url) return; method = method?.toLocaleUpperCase() ?? "GET"; if (method === "POST") { const cType = { "Content-Type": "application/x-www-form-urlencoded" }; params.headers = Object.assign(params.headers ?? {}, cType); } if (typeof data === "object") { const keys = Object.keys(data); if (keys.length) { data = keys.reduce((pre, cur) => `${pre}${pre ? "&" : ""}${cur}=${encodeURIComponent(data[cur])}`, ""); if (method === "GET") url = `${url}?${data}`; } } return new Promise(resolve => { GM_xmlhttpRequest({ url, method, data, timeout: 20000, onload: ({ status, responseText }) => { if (status === 404) resolve(false); if (/<\/?[a-z][\s\S]*>/i.test(responseText)) { responseText = new DOMParser().parseFromString(responseText, "text/html"); } if (/^{.*}$/.test(responseText)) { responseText = JSON.parse(responseText); } if (`${responseText?.errcode}` === "911") verify(); resolve(responseText); }, ...params, }); }); }; // notifiy const notifiy = (title = "" | {}, text = "", image = "info", clickUrl = "") => { let params = { title, text: text ?? GM_info.script.name, image, highlight: true, timeout: 3000, clickUrl, }; if (typeof title === "object") params = { ...params, ...title }; if (params.clickUrl) params.onclick = () => GM_openInTab(clickUrl, { active: true }); GM_notification({ ...params, image: GM_getResourceURL(params?.image ?? "info") }); }; // verify const verify = async () => { const time = new Date().getTime(); let h = 667; let w = 375; let t = (window.screen.availHeight - h) / 2; let l = (window.screen.availWidth - w) / 2; window.open( `https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_${time}`, "验证账号", `height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no` ); }; // delay const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // scriptStart const scriptStart = () => { doc.addEventListener("keyup", event => { const e = event || window.event; if (e && e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(doc.activeElement.nodeName)) { doc.querySelector("#search-input").focus(); } }); const nav = `<a href="https://www.javbus.com/resource">资源</a>`; let navbar = doc.querySelector("#navbar .nav.navbar-nav"); if (navbar) return appendAfter(navbar, `<li class="resource">${nav}</li>`); navbar = doc.querySelector("#toptb .wp .z ul"); if (navbar) return appendAfter(navbar, `<li class="nav-title nav-inactive">${nav}</li>`); }; // handleClick const handleClick = (selectors, node = doc) => { for (const item of node.querySelectorAll(selectors)) { const href = item?.href; if (!href) continue; item.addEventListener("contextmenu", e => e.preventDefault()); item.addEventListener("click", e => { e.preventDefault(); GM_openInTab(href, { active: true }); }); item.addEventListener("mousedown", e => { if (e.button !== 2) return; e.preventDefault(); mousePoint = e.screenX + e.screenY; }); item.addEventListener("mouseup", e => { const num = e.screenX + e.screenY - mousePoint; if (e.button !== 2 || num > 5 || num < -5) return; e.preventDefault(); GM_openInTab(href); }); } }; // getDate const getDate = (str = "") => { const date = str ? new Date(str) : new Date(); const Y = date.getFullYear(); const M = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1; const D = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate(); return `${Y}-${M}-${D}`; }; class Common { docStart = () => {}; contentLoaded = () => {}; load = () => {}; } class Waterfall extends Common { isHome = /^(\/(page\/\d+)?|\/uncensored(\/page\/\d+)?)+$/i.test(pathname); docStart = () => { GM_addStyle(` .search-header { padding: 0${SUFFIX}; background: none${SUFFIX}; box-shadow: none${SUFFIX}; } .photo-frame { position: relative; margin: 10px${SUFFIX}; } .photo-frame img { height: 100%${SUFFIX}; width: 100%${SUFFIX}; object-fit: cover${SUFFIX}; margin: 0${SUFFIX}; } .photo-info { padding: 10px${SUFFIX}; } .alert-page { margin: 20px${SUFFIX}; } `); if (!lm) return; const itemSizer = `167px`; const gutterSizer = `20px`; GM_addStyle(` .pagination, footer { display: none${SUFFIX}; } .page-load-status { display: none; padding-bottom: ${gutterSizer}; text-align: center; } body { overflow: hidden; } .scrollBox { height: calc(100vh - 50px); overflow: hidden scroll; } #waterfall { opacity: 0; margin: ${gutterSizer} auto 0 auto${SUFFIX}; } .item-sizer, .item a { width: ${itemSizer}${SUFFIX}; } .gutter-sizer { width: ${gutterSizer}${SUFFIX}; } .item a { margin: 0 0 ${gutterSizer} 0${SUFFIX}; } `); }; contentLoaded = () => { const nav = doc.querySelector(".search-header .nav"); if (nav) nav.setAttribute("class", "nav nav-pills"); if (!lm) return this.modifyItem(); this.handleLoadMore(); if (!this.isHome) this.modifyLayout(); }; load = () => { if (lm && this.isHome) this.modifyLayout(); }; handleLoadMore = () => { const oldWaterfall = doc.querySelector("#waterfall"); if (!oldWaterfall) return GM_addStyle(`#waterfall { opacity: 1; }`); const newWaterfall = doc.querySelector("#waterfall #waterfall"); if (newWaterfall) oldWaterfall.parentNode.replaceChild(newWaterfall, oldWaterfall); const waterfall = doc.querySelector("#waterfall"); appendBefore(waterfall, `<div class="item-sizer"></div><div class="gutter-sizer"></div>`); insertAfter( waterfall, `<div class="page-load-status"><span class="loader-ellips infinite-scroll-request">Loading...</span><span class="infinite-scroll-last">End of content</span><span class="infinite-scroll-error">No more pages to load</span></div>` ); doc.querySelector(".container-fluid .row").classList.add("scrollBox"); }; modifyLayout = () => { const waterfall = doc.querySelector("#waterfall"); if (!waterfall) return; let msnry = new Masonry(waterfall, { itemSelector: "none", columnWidth: ".item-sizer", gutter: ".gutter-sizer", horizontalOrder: true, fitWidth: true, stagger: 30, visibleStyle: { transform: "translateY(0)", opacity: 1 }, hiddenStyle: { transform: "translateY(120px)", opacity: 0 }, }); imagesLoaded(waterfall, () => { msnry.options.itemSelector = ".item"; const elems = waterfall.querySelectorAll(".item"); this.modifyItem(); msnry.appended(elems); GM_addStyle(`#waterfall { opacity: 1; }`); }); // 搜索页滚动加载需特殊处理 const path = !/^\/(uncensored\/)?(search|searchstar)+\//i.test(pathname) ? "#next" : function () { const items = ["search", "searchstar"]; for (const item of items) { if (pathname.indexOf(`${item}/`) < 0) continue; let [prefix, suffix] = pathname.split("&"); suffix = suffix ?? ""; prefix = prefix.split("/"); let pre = ""; for (let index = 0; index <= prefix.indexOf(item) + 1; index++) { pre = `${pre}${prefix[index]}/`; } return `${pre}${this.loadCount + 2}&${suffix}`; } }; let infScroll = new InfiniteScroll(waterfall, { path, append: ".item", outlayer: msnry, elementScroll: ".scrollBox", history: false, historyTitle: false, hideNav: ".pagination", status: ".page-load-status", debug: false, }); infScroll.on("load", this.modifyItem); }; modifyItem = (node = doc) => { if (ck) handleClick("a.movie-box", node); const items = node.querySelectorAll(".item"); for (const item of items) { let [code, date] = item.querySelectorAll("date"); if (code && date) { code = code.textContent; date = date.textContent; const codeKey = `${code.trim().toUpperCase()}/${date.trim().toUpperCase()}`; lf.keys().then(keys => { if (!keys.includes(codeKey)) return; lf.getItem(codeKey).then(val => { const resource = val?.resource; if (!resource?.length) return; const photo = item.querySelector(".photo-frame"); photo.classList.add("playBtn"); photo.setAttribute("title", "点击播放"); photo.addEventListener("click", e => { e.stopPropagation(); e.preventDefault(); GM_openInTab(resource[0].link, { active: true }); }); }); }); } const info = item.querySelector("a .photo-info span:not(.mleft)"); if (!info) continue; const [titleNode, secondaryNode] = info.childNodes; const titleTxt = titleNode.textContent.trim(); const ellipsis = doc.create("div", { class: "ellipsis", title: titleTxt }, titleTxt); if (secondaryNode?.nodeName === "BR") { info.removeChild(secondaryNode); ellipsis.classList.add("line-4"); } info.replaceChild(ellipsis, titleNode); } }; } class Genre extends Common { docStart = () => { GM_addStyle(` footer { display: none${SUFFIX}; } button.btn.btn-danger.btn-block.btn-genre { position: fixed${SUFFIX}; bottom: 0${SUFFIX}; margin: 0${SUFFIX}; left: 0${SUFFIX}; border: 0${SUFFIX}; border-radius: 0${SUFFIX}; } `); }; contentLoaded = () => { if (!doc.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return; const box = doc.querySelectorAll(".genre-box"); box[box.length - 1].setAttribute("style", "margin-bottom: 65px;"); }; } class Forum extends Common { docStart = () => { GM_addStyle(` .bcpic, .banner728, .sd.sd_allbox > div:last-child { display: none${SUFFIX}; } .jav-button { margin-top: -3px${SUFFIX}; } #toptb { position: fixed${SUFFIX}; top: 0${SUFFIX}; left: 0${SUFFIX}; right: 0${SUFFIX}; z-index: 999${SUFFIX}; } #wp { margin-top: 55px${SUFFIX}; } `); }; } class Resource extends Common { docStart = () => { GM_addStyle(` .alert.alert-danger.alert-page.error-page, footer { display: none; } .resItem { margin: 15px 0; border-radius: 4px; overflow: hidden; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1); background-color: ${dm ? SecondaryBackground : "#fff"}; } .resItem a { color: unset; } .thumb { aspect-ratio: 100 / 67; } .thumb img { width: 100%; height: 100%; object-fit: cover; } .info { padding: 15px; } .title { margin-bottom: 6px; } `); }; contentLoaded = async () => { doc.title = "已有资源(0)"; const navbar = doc.querySelector("#navbar .nav.navbar-nav"); navbar.querySelector(".active").classList.remove("active"); navbar.querySelector(".resource").classList.add("active"); const keys = await lf.keys(); let nodes = []; for (const key of keys) { const item = await lf.getItem(key); const resource = item?.resource; if (!resource?.length) continue; nodes.push({ ...item, key: key.split("/")[0] }); } if (!nodes?.length) { const alert = doc.querySelector(".alert.alert-danger.alert-page.error-page"); alert.querySelector("h4").textContent = "暂无本地缓存数据"; const [zh, en] = alert.querySelectorAll("p"); zh.setAttribute("style", "display: none"); en.textContent = "请检查浏览器相关设置或重新获取数据"; alert.setAttribute("style", "display: block"); return; } doc.title = `已有资源(${nodes.length})`; nodes.sort((pre, next) => { let preTime = pre.resource[0].timestamp || Date.parse(pre.resource[0].getDate) / 1000; let nextTime = next.resource[0].timestamp || Date.parse(next.resource[0].getDate) / 1000; return preTime > nextTime ? -1 : 1; }); nodes = nodes.reduce((pre, { thumb, title, resource, href, key, upDate }) => { return `${pre} <div class="col-lg-3 col-md-4 col-sm-6"> <div class="resItem"> <a href="${href ?? "/" + key}"> <div class="playBtn thumb" data-link="${resource[0].link}" title="点击播放"> <img src="${thumb}" alt="${title}"> </div> <div class="info"> <div class="ellipsis title" title="${title}">${title}</div> <button class="btn btn-xs btn-primary" disabled="disabled"> 更新:${getDate(upDate)} </button> <button class="btn btn-xs btn-danger" disabled="disabled"> 资源:${resource.length} </button> </div> </a> </div> </div>`; }, ""); appendAfter(doc.querySelector(".container-fluid .row"), nodes); for (const item of doc.querySelectorAll(".resItem .thumb")) { item.addEventListener("click", e => { e.stopPropagation(); e.preventDefault(); GM_openInTab(item.getAttribute("data-link"), { active: true }); }); } ck && handleClick(".resItem a"); }; } class Details extends Common { code = ""; codeKey = ""; lfItem = {}; config = async (code, date) => { if (!code || !date) return; const codeKey = `${code}/${date}`; this.code = code; this.codeKey = codeKey; this.lfItem = (await lf.getItem(codeKey)) ?? {}; }; docStart = () => { GM_addStyle(` .info .glyphicon-info-sign, h4[style="position:relative"], h4[style="position:relative"] + .row { display: none${SUFFIX}; } .info ul { margin: 0${SUFFIX}; } .screencap { max-height: 600px; overflow: hidden; } #avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px${SUFFIX}; } .photo-info { height: auto${SUFFIX}; } `); GM_addStyle(` #mask { position: fixed; width: 100%; height: 100%; z-index: 9999; left: 0; top: 0; background: rgba(11,11,11,.8); display: flex; justify-content: center; align-items: center; display: none; } `); GM_addStyle(` #exp { display: none; } #expBtn { position: absolute; top: 0; height: 40px; line-height: 40px; left: 0; right: 0; cursor: pointer; background: rgba(0,0,0,.7); color: #fff; margin: 560px 15px 0 15px; z-index: 99; } #expBtn::after { content: "展开"; } #exp:checked + .screencap { max-height: none; } #exp:checked + .screencap > #expBtn { top: auto; margin: 0 15px; bottom: 0; } #exp:checked + .screencap > #expBtn::after { content: "收起"; } @media screen and (max-width: 480px) { #btnGrp { margin-bottom: 15px; } .screencap { max-height: 280px; } #expBtn { margin-left: 0; margin-right: 0; } } #resBox a { color: #CC0000${SUFFIX}; } `); doc.addEventListener("DOMNodeInserted", e => { const node = e.target; if (node.nodeName.toLowerCase() !== "tr") return; const href = node.querySelector("td a")?.href; if (!href) return; const td = doc.create("td", { style: "text-align:center;white-space:nowrap" }); const copy = doc.create("a", { href, title: href }, "复制"); const offline = doc.create("a", { href, title: href, style: "margin-left:16px" }, "离线下载"); copy.addEventListener("click", this.copyTxt); offline.addEventListener("click", this.offLine); td.appendChild(copy); td.appendChild(offline); node.appendChild(td); }); }; contentLoaded = async () => { ck && handleClick("a.movie-box"); // copy const handleCopy = selectors => { const node = doc.querySelector(selectors); const txt = node?.textContent?.trim(); if (!node || !txt) return; const copy = doc.create("a", { title: txt, href: txt, style: "margin-left:16px" }, "复制"); copy.addEventListener("click", this.copyTxt); node.appendChild(copy); }; handleCopy("h3"); handleCopy("span[style='color:#CC0000;']"); // expBtn const screencap = doc.querySelector(".col-md-9.screencap"); insertBefore(screencap, `<input type="checkbox" id="exp">`); appendAfter(screencap, `<label for="exp" id="expBtn"></label>`); const info = doc.querySelector(".col-md-3.info"); // resource const res = `<p id="resBox"><span class="header">已有资源:</span><span class="genre">查询中...</span></p>`; appendAfter(info, res); // btnGrp const btnGrp = doc.create("div", { id: "btnGrp", class: "btn-group btn-group-justified" }); const btns = { smartRes: "一键离线", refreshRes: "资源刷新" }; Object.keys(btns).forEach(id => { const btn = doc.create("a", { id, class: "btn btn-default" }, btns[id]); btn.addEventListener("click", () => this.handleOpt(id)); btnGrp.appendChild(btn); }); info.appendChild(btnGrp); // table const td = `<td style="text-align:center;white-space:nowrap">操作</td>`; appendAfter(doc.querySelector("#magnet-table tbody tr"), td); // photoinfo for (const item of doc.querySelectorAll(".photo-info span")) item.classList.add("ellipsis"); // config if (!this.codeKey) { let [code, date] = doc.querySelectorAll("div.col-md-3.info p"); code = code.querySelectorAll("span")[1].childNodes[0].textContent.trim().toUpperCase(); date = date.childNodes[1].textContent.trim().toUpperCase(); await this.config(code, date); } this.handleStar(); this.handleResource(); this.handlePreview(); this.handleVideo(); }; load = () => { const maxHei = 600; const styleHei = doc.querySelector(".col-md-9.screencap .bigImage img").height; if (styleHei > maxHei) { GM_addStyle(`.screencap { max-height: ${maxHei}px; } #expBtn { margin-top: ${maxHei - 40}px; }`); } else { GM_addStyle(`.screencap { max-height: ${styleHei + 40}px; } #expBtn { margin-top: ${styleHei}px; }`); } }; copyTxt = e => { e.preventDefault(); e.stopPropagation(); const node = e.target; const txt = node?.title; if (!node || !txt) return; GM_setClipboard(txt); const text = node?.textContent ?? ""; node.textContent = "成功"; setTimeout(() => { node.textContent = text; }, 1000); }; // 预览大图 handlePreview = async () => { let image = this.lfItem?.image; if (!image) { let res = await request(`https://javstore.net/search/${this.code}.html`); const href = res?.querySelector("#content_news li a")?.href; if (!href) return; res = await request(href); image = res?.querySelector(".news a img[alt*='.th']")?.src?.replace(".th", ""); if (!image) return; lf.upItem(this.codeKey, { image }); } if (!(await request(image))) return; const img = doc.create("img", { src: image, title: "点击收起", style: "cursor:pointer" }); img.addEventListener("click", () => { doc.querySelector("#exp").checked = false; }); img.onload = () => doc.querySelector(".col-md-9.screencap").appendChild(img); }; // 预览视频 handleVideo = async () => { let video = this.lfItem?.video; if (!video) { const res = await request(`https://www.r18.com/common/search/searchword=${this.code}/`); video = res?.querySelector("a.js-view-sample")?.getAttribute("data-video-high"); if (!video) return; lf.upItem(this.codeKey, { video }); } const title = "预览视频"; const playVideo = e => { e.preventDefault(); e.stopPropagation(); doc.body.setAttribute("style", "overflow: hidden;"); doc.querySelector("#mask").setAttribute("style", "display: flex;"); const video = doc.querySelector("video"); video.play(); video.focus(); doc.onkeydown = event => { const e = event || window.event; if (e && e.keyCode === 27) pauseVideo(); }; }; const pauseVideo = () => { doc.body.setAttribute("style", "overflow: auto;"); doc.querySelector("#mask").setAttribute("style", "display: none;"); doc.querySelector("video").pause(); doc.onkeydown = null; }; // 视频播放窗口 const videoNode = doc.create("video", { controls: "controls", src: video }); videoNode.currentTime = 5; videoNode.preload = "auto"; videoNode.muted = true; const closeBtn = doc.create("button", { title: "Close (Esc)", type: "button", class: "mfp-close" }, "×"); closeBtn.addEventListener("click", pauseVideo); const mask = doc.create("div", { id: "mask" }); mask.appendChild(closeBtn); mask.appendChild(videoNode); doc.body.appendChild(mask); // 封面点击播放 const bigImage = doc.querySelector(".bigImage"); const bImg = bigImage.querySelector("img"); const playBtn = doc.create("div", { class: "playBtn", title }); playBtn.addEventListener("click", playVideo); playBtn.appendChild(bImg); bigImage.appendChild(playBtn); // 样品图像 const thumb = bImg.src; const box = doc.create("a", { class: "sample-box", href: thumb, title }); box.addEventListener("click", playVideo); appendAfter(box, `<div class="photo-frame playBtn"><img src="${thumb}"></div>`); if (!doc.querySelector("#sample-waterfall")) { insertBefore(doc.querySelector("div.clearfix"), `<div id="sample-waterfall"></div>`); insertBefore(doc.querySelector("div#sample-waterfall"), `<h4>樣品圖像</h4>`); } const waterfall = doc.querySelector("div#sample-waterfall"); const ref = waterfall.querySelector("a"); ref ? waterfall.insertBefore(box, ref) : waterfall.appendChild(box); }; // 演员列表 handleStar = async () => { const info = doc.querySelector(".col-md-3.info"); if (!/暫無出演者資訊/g.test(info.textContent)) return; let star = this.lfItem?.star ?? []; if (!star?.length) { const site = "https://javdb.com"; let res = await request(`${site}/search?q=${this.code}`); const href = res.querySelector("#videos .grid-item a").getAttribute("href"); if (!href) return; res = await request(`${site}${href}`); res = res?.querySelectorAll(".panel-block"); if (!res?.length) return; res = res[res.length - 3]?.querySelector(".value").textContent.trim(); res = res.split(/\n/).filter(item => item.indexOf("♀") !== -1); star = res.map(item => item.trim().replace("♀", "")); if (!star?.length) return; lf.upItem(this.codeKey, { star }); } const p = doc.create("p"); appendAfter( p, star.reduce((acc, cur) => `${acc}<span class="genre"><a href="/search/${cur}">${cur}</a></span>`, "") ); info.replaceChild(p, doc.querySelector("span.glyphicon.glyphicon-info-sign.mb20")?.nextSibling); }; // 已有资源(本地) handleResource = async () => { const lfItem = await lf.getItem(this.codeKey); let resource = lfItem?.resource ?? []; const upDate = lfItem?.upDate; const bool = !upDate || Math.floor((new Date().getTime() - upDate) / 24 / 3600 / 1000) > 3; if (bool) resource = await this.fetchResource(); const resBox = doc.querySelector("#resBox"); for (const old of resBox.querySelectorAll(".genre")) resBox.removeChild(old); if (!resource?.length) return appendAfter(resBox, `<span class="genre">无</span>`); const child = resource.reduce((acc, { link, getDate, name }) => { let thunbName = name.length > 20 ? `${name.substr(0, 20)}...` : name; return `${acc}<span class="genre"><a href="${link}" title="${getDate}/${name}" target="_blank">${thunbName}</a></span>`; }, ""); appendAfter(resBox, child); if (lfItem?.thumb && lfItem?.title && lfItem?.href) return; lf.upItem(this.codeKey, { thumb: doc.querySelector(".screencap .bigImage img").src, title: doc.querySelector("h3").childNodes[0].textContent, href: location.href, }); }; // 已有资源(115) fetchResource = async (lname = "") => { let code = this.code; let codes = [ code, code.replace(/-/g, ""), code.replace(/-/g, "-0"), code.replace(/-/g, "0"), code.replace(/-/g, "00"), code.replace(/-/g, "_"), code.replace(/-/g, "_0"), code.replace(/-0/g, ""), code.replace(/-0/g, "-"), code.replace(/-0/g, "00"), ]; if (lname) codes.unshift(encodeURIComponent(lname)); let { data } = await request("https://webapi.115.com/files/search", "GET", { search_value: encodeURIComponent(codes.join(" ")), format: "json", }); let resource = []; if (data?.length) { const reg = new RegExp(`(${codes.join("|")})`, "gi"); data = data.filter(({ n, play_long }) => n.match(reg) && play_long && play_long > 0); resource = data.map(item => { return { fid: item.fid, cid: item.cid, link: `https://v.anxia.com/?pickcode=${item.pc}`, getDate: item.t, name: item.n, timestamp: item.te || item.tp, }; }); } await lf.upItem(this.codeKey, { upDate: new Date().getTime(), resource }); return resource; }; // 离线下载 offLine = async e => { e.preventDefault(); e.stopPropagation(); const node = e.target; const link = node?.title; if (!node || !link) return; GM_setClipboard(link); const text = node?.textContent ?? ""; node.textContent = "请求中..."; const firstTd = node.parentNode.parentNode.querySelector("td"); const zh = !!firstTd.querySelector("a.btn.btn-mini-new.btn-warning.disabled"); const lname = firstTd.querySelector("a").textContent.trim(); const res = await this.offLineDownload({ link, zh, lname }); node.textContent = text; notifiy(res); }; // 排队离线/资源刷新 handleOpt = async action => { const node = doc.querySelector(`#${action}`); if (!node) return; node.classList.toggle("disabled"); const text = node?.textContent ?? ""; node.textContent = "请求中..."; if (action === "refreshRes") { await lf.upItem(this.codeKey, { upDate: 0 }); await this.handleResource(); } if (action === "smartRes") { const trs = doc.querySelector("#magnet-table").querySelectorAll("tr"); let magnetArr = []; for (let index = 1; index < trs.length; index++) { const item = { link: "", lname: "", zh: false, size: 0, date: 0 }; let [name, size, date] = trs[index].querySelectorAll("td"); if (!name || !size || !date) continue; item.zh = !!name.querySelector("a.btn.btn-mini-new.btn-warning.disabled"); name = name.querySelector("a"); item.link = name.href; item.lname = name.textContent.trim(); size = size.querySelector("a").textContent.trim().replace(/gb/gi, ""); if (/mb/gi.test(size)) size = (parseFloat(size) / 1024).toFixed(2); item.size = Number(size); item.date = date.querySelector("a").textContent.trim().replace(/-/g, ""); magnetArr.push(item); } magnetArr.sort((pre, next) => { if (pre.zh === next.zh) { if (pre.size === next.size) return next.date - pre.date; return next.size - pre.size; } else { return pre.zh > next.zh ? -1 : 1; } }); for (let index = 0; index < magnetArr.length; index++) { const res = await this.offLineDownload(magnetArr[index]); if (res?.image !== "info") { notifiy(res); break; } if (index !== magnetArr.length - 1) continue; notifiy( "一键离线失败", "远程未查找到新增资源,接口失效或资源被审核", "fail", "http://115.com/?tab=offline&mode=wangpan" ); } } node.textContent = text; node.classList.toggle("disabled"); }; // 请求离线&结果查询 offLineDownload = async ({ link, zh, lname }) => { const fname = `${zh ? "【中文字幕】" : ""}${doc.querySelector("h3").textContent.replace("复制", "")}`; let notifiyObj = { title: "操作失败,115未登录", text: "请登录115账户后再离线下载", image: "fail", clickUrl: "http://115.com/?mode=login", }; let res = await request("http://115.com/", "GET", { ct: "offline", ac: "space", _: new Date().getTime(), }); if (!res?.sign) return notifiyObj; const { sign, time } = res; res = await request("http://115.com/web/lixian/?ct=lixian&ac=add_task_url", "POST", { url: link.substr(0, 60), uid: 0, sign, time, }); const { state, errcode, error_msg } = res; notifiyObj = { title: "离线失败", text: error_msg, image: "info", clickUrl: "http://115.com/?tab=offline&mode=wangpan", }; if (`${errcode}` === "911") { notifiyObj.title += ",账号异常"; notifiyObj.text = "验证后使用"; notifiyObj.image = "fail"; } if (!state) return notifiyObj; // 获取旧的本地缓存数据 let lfItem = (await lf.getItem(this.codeKey)) ?? {}; if (!lfItem?.resource) lfItem.resource = []; // 远程获取搜索结果 await delay(2000); let resource = await this.fetchResource(lname); this.handleResource(); const newRes = resource.filter(item => item.getDate === getDate()); // 搜索结果资源数至少比本地缓存多且获取日期为当前日期 if (resource.length <= lfItem.resource.length || newRes.length < 1) { notifiyObj.text = "未找到新增资源,接口失效或资源审核"; return notifiyObj; } for (let index = 0; index < newRes.length; index++) { const { name } = newRes[index]; const suffix = name.split(".").pop().toLowerCase(); newRes[index]["rename"] = `${fname}.${suffix}`; } this.afterAction(newRes.filter(item => item.name.indexOf(fname) === -1)); return { title: "离线成功", text: "点击跳转", image: "success", clickUrl: `https://115.com/?cid=${rootId ?? 0}&offset=0&mode=wangpan`, }; }; // 离线后重命名,移动,移除原目录等 afterAction = async items => { // rename for (const { fid, rename: file_name } of items) { await request("http://webapi.115.com/files/edit", "POST", { fid, file_name }); } if (rootId) { const params = arr => arr.reduce((acc, cur, idx) => `${acc}&fid[${idx}]=${cur}`, ""); const fids = Array.from(new Set(items.map(item => item.fid))); const cids = Array.from(new Set(items.filter(item => item !== rootId).map(item => item.cid))); // move const move_proid = `${new Date()}_${~(100 * Math.random())}_0`; await request( "https://webapi.115.com/files/move", "POST", `pid=${rootId}&move_proid=${move_proid}${params(fids)}` ); // del request("https://webapi.115.com/rb/delete", "POST", `pid=${rootId}&ignore_warn=1${params(cids)}`); } await lf.upItem(this.codeKey, { upDate: 0 }); this.handleResource(); }; } class JavBusScript { waterfall = new Waterfall(); genre = new Genre(); forum = new Forum(); resource = new Resource(); details = new Details(); } const Background = "rgb(18,18,18)"; const SecondaryBackground = "rgb(32,32,32)"; const LabelColor = "rgba(255,255,255,.95)"; const SecondaryLabelColor = "rgb(170,170,170)"; const Grey = "rgb(49,49,49)"; const Blue = "rgb(10,132,255)"; const Orange = "rgb(255,159,10)"; const Green = "rgb(48,209,88)"; const Red = "rgb(255,69,58)"; const darkMode = path => { if (path === "forum") return; GM_addStyle(` ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-thumb { border-radius: 4px; background-color: ${Grey}; } *:not(span) { border-color: ${Grey}${SUFFIX}; outline: ${Grey}${SUFFIX}; text-shadow: none${SUFFIX}; } body, footer { background: ${Background}${SUFFIX}; color: ${SecondaryLabelColor}${SUFFIX}; } img { filter: brightness(90%)${SUFFIX}; } nav { background: ${SecondaryBackground}${SUFFIX}; } input { background: ${Background}${SUFFIX}; } *::placeholder { color: ${SecondaryLabelColor}${SUFFIX}; } button, input, a, h1, h2, h3, h4, h5, h6 { color: ${LabelColor}${SUFFIX}; box-shadow: none${SUFFIX}; outline: none${SUFFIX}; } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .85${SUFFIX}; } button, .btn-default, .input-group-addon { background: ${Grey}${SUFFIX}; color: ${LabelColor}${SUFFIX}; } .btn-primary { background: ${Blue}${SUFFIX}; border-color: ${Blue}${SUFFIX}; } .btn-success { background: ${Green}${SUFFIX}; border-color: ${Green}${SUFFIX}; } .btn-danger { background: ${Red}${SUFFIX}; border-color: ${Red}${SUFFIX}; } .btn-warning { background: ${Orange}${SUFFIX}; border-color: ${Orange}${SUFFIX}; } .navbar-nav>.active>a, .navbar-nav>.active>a:focus, .navbar-nav>.active>a:hover, .navbar-nav>.open>a, .dropdown-menu { background: ${Background}${SUFFIX}; } .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { background: ${Grey}${SUFFIX}; } .pagination .active a { border: none${SUFFIX}; color: ${LabelColor}${SUFFIX}; } .pagination>li>a, .pagination>li>span { background-color: ${Grey}${SUFFIX}; border: none${SUFFIX}; color: ${LabelColor}${SUFFIX}; } tr, .modal-content, .alert { background: ${SecondaryBackground}${SUFFIX}; box-shadow: none${SUFFIX}; } tr:hover { background: ${Grey}${SUFFIX}; } `); if (path === "waterfall") { GM_addStyle(` .item a { background: ${SecondaryBackground}${SUFFIX}; } .photo-info { background: ${SecondaryBackground}${SUFFIX}; color: ${LabelColor}${SUFFIX}; } date { color: ${SecondaryLabelColor}${SUFFIX}; } .nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover, .nav-pills>li>a:focus, .nav-pills>li>a:hover { background-color: ${Grey}${SUFFIX}; } `); } if (path === "genre") GM_addStyle(`.genre-box { background-color: ${SecondaryBackground}${SUFFIX}; }`); if (path === "forum") { GM_addStyle(` #toptb, .mn > div, .biaoqicn_show.slidebar a:not(.on), .new4_list_top .dspanonhover, div[id*="con_NewOne_"], .frame, .frame-tab, .main-right-p15, .bm.bmw.cl, .bm.bmw.cl .bm_h.cl { background: ${SecondaryBackground}${SUFFIX}; } .jav-footer, .nav-title.nav-active a, .menu-body-panel.login-detail-wrap, .menu-body-panel .icon-arrow-t, .new4_list_top, .comment-excerpt { background: ${Background}${SUFFIX}; } .main-right-zuixin .comment-excerpt:before { border-bottom-color: ${Background}${SUFFIX}; } .main-right-tit span { color: ${LabelColor}${SUFFIX}; } `); } if (path === "details") { GM_addStyle(` .movie, .sample-box, .movie-box, .photo-info { background: ${SecondaryBackground}${SUFFIX}; } .photo-info { color: ${LabelColor}${SUFFIX}; } .avatar-box, .avatar-box span, .info ul li, .info .star-name { background: ${SecondaryBackground}${SUFFIX}; border-color: ${Grey}${SUFFIX}; color: ${LabelColor}${SUFFIX}; } `); } }; if (/javbus\.com/g.test(host)) { const menus = [ { title: "点击事件", name: "ck", val: ck, key: "c" }, { title: "暗黑模式", name: "dm", val: dm, key: "d" }, { title: "滚动加载", name: "lm", val: lm, key: "s" }, ]; for (const { title, name, val, key } of menus) { GM_registerMenuCommand( `${val ? "关闭" : "开启"}${title}`, () => { GM_setValue(name, !val); location.reload(); }, key ); } GM_registerMenuCommand( "离线后操作根目录cid", () => { const rid = prompt("用于离线后移动,删除等操作", rootId); GM_setValue("rid", rid); location.reload(); }, "a" ); lf.config({ name: "JBDB", storeName: "AVS" }); GM_addStyle(` .ad-box { display: none${SUFFIX}; } .ellipsis { overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; } .line-4 { -webkit-line-clamp: 4; } .playBtn { position: relative; } .playBtn:after { position: absolute; background: url(https://javdb.com/packs/media/images/btn-play-b414746c.svg) 50% no-repeat; background-color: rgba(0,0,0,.2); background-size: 40px; content: ""; top: 0; right: 0; bottom: 0; left: 0; transition: all .3s ease-out; opacity: .85; } .playBtn:hover::after { background-color: rgba(0,0,0,0); } .playBtn img { filter: none${SUFFIX}; } `); const pathReg = { waterfall: /^\/((uncensored|uncensored\/)?(page\/\d+)?$)|((uncensored\/)?((search|searchstar|actresses|genre|star|studio|label|series|director|member)+\/)|actresses(\/\d+)?)+/i, genre: /^\/(uncensored\/)?genre$/i, forum: /^\/forum\//i, resource: /^\/resource$/i, details: /^\/[\w]+(-|_)?[\d]*.*$/i, }; const path = Object.keys(pathReg).filter(key => pathReg[key].test(pathname))[0]; if (!path) return; dm && darkMode(path); const jav = new JavBusScript()[path]; if (!jav) return; jav.docStart(); doc.addEventListener("DOMContentLoaded", () => { scriptStart(); jav.contentLoaded(); }); window.addEventListener("load", jav.load); } if (/captchaapi\.115\.com/g.test(host)) { doc.addEventListener("DOMContentLoaded", () => { window.focus(); const btn = doc.querySelector("#js_ver_code_box button[rel='verify']"); btn.addEventListener("click", () => { const interval = setInterval(() => { if (doc.querySelector("div[rel='error_box']").getAttribute("style").indexOf("none") !== -1) { window.open("", "_self"); window.close(); } }, 300); setTimeout(() => clearInterval(interval), 600); }); }); } })();