您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一站式体验, JavBus & JavDB 兼容
当前为
// ==UserScript== // @name JavScript // @namespace https://greasyfork.org/users/175514 // @version 2.5.3 // @author blc // @description 一站式体验, JavBus & JavDB 兼容 // @icon https://z3.ax1x.com/2021/10/15/53gMFS.png // @include * // @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 // @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 // @resource play https://s4.ax1x.com/2022/01/12/7nYuKe.png // @resource loading https://via.placeholder.com/824x40 // @connect * // @run-at document-start // @grant GM_registerMenuCommand // @grant GM_getResourceURL // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_notification // @grant GM_openInTab // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @license GPL // @compatible edge // @compatible chrome // @compatible firefox 未测试 // ==/UserScript== (function () { // domains const createReg = items => new RegExp(`(${items.join("|").replace(/\./g, "\\.")})+`, "gi"); const MatchDomains = [ { domain: "JavBus", regex: /(jav|bus|dmm|see|cdn|fan){2}\./g, }, { domain: "JavDB", regex: /javdb\d*\.com/g, }, ]; // 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; }; // request const request = (url, data = {}, method = "GET", params = {}) => { if (typeof data === "object") { data = Object.keys(data).reduce( (pre, cur) => `${pre ? `${pre}&` : ""}${cur}=${encodeURIComponent(data[cur])}`, "" ); } if (method === "GET" && data) url = `${url}?${data}`; return new Promise(resolve => { GM_xmlhttpRequest({ url, data, method, timeout: 20000, onload: ({ status, response }) => { if (status === 404) resolve(false); const htmlReg = /<\/?[a-z][\s\S]*>/i; const jsonReg = /^{.*}$/; if (htmlReg.test(response)) { response = new DOMParser().parseFromString(response, "text/html"); } else if (jsonReg.test(response)) { response = JSON.parse(response); } if (response?.errcode === 911) verify(); resolve(response); }, ...params, }); }); }; const verify = () => { const h = 667; const w = 375; const t = (window.screen.availHeight - h) / 2; const l = (window.screen.availWidth - w) / 2; window.open( `https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_${new Date().getTime()}`, "验证账号", `height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no` ); }; const notify = msg => { GM_notification({ ...msg, text: msg?.text ?? GM_info.script.name, image: GM_getResourceURL(msg?.image ?? "info"), onclick: msg?.clickUrl ? () => GM_openInTab(msg.clickUrl, { active: true }) : () => {}, highlight: true, timeout: 3000, }); }; const getDate = timestamp => { const date = timestamp ? new Date(timestamp) : 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}`; }; const getScrollTop = () => { let scrollTop = 0; if (DOC.documentElement && DOC.documentElement.scrollTop) { scrollTop = DOC.documentElement.scrollTop; } else if (DOC.body) { scrollTop = DOC.body.scrollTop; } return scrollTop; }; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); const getItem = key => { const details = GM_getValue("DETAILS") ?? {}; return details[key] ?? {}; }; const upItem = (key, val) => { const details = GM_getValue("DETAILS") ?? {}; val = { ...getItem(key), ...val }; details[key] = val; GM_setValue("DETAILS", details); }; const transToBytes = sizeStr => { const limit = 1024; const step = 8; const sizer = [ { unit: /gb/gi, transform: size => size * limit * limit * limit, }, { unit: /gib/gi, transform: size => (size / step) * limit * limit * limit, }, { unit: /mb/gi, transform: size => size * limit * limit, }, { unit: /mib/gi, transform: size => (size / step) * limit * limit, }, { unit: /kb/gi, transform: size => size * limit, }, { unit: /kib/gi, transform: size => (size / step) * limit, }, { unit: /byte/gi, transform: size => size, }, { unit: /bit/gi, transform: size => size / step, }, ]; const size = sizeStr.replace(/[a-zA-Z\s]/g, ""); if (size <= 0) return 0; return ( sizer .find(({ unit }) => unit.test(sizeStr)) ?.transform(size) ?.toFixed(2) ?? 0 ); }; const unique = (arr, key) => { if (!arr) return arr; if (key === undefined) return [...new Set(arr)]; const map = { string: e => e[key], function: e => key(e) }; const fn = map[typeof key]; const obj = arr.reduce((o, e) => ((o[fn(e)] = e), o), {}); return Object.values(obj); }; const tooltip = msg => { console.log( `%c ${msg} %c By ${GM_info.script.name} v${GM_info.script.version} %c`, "background:#555555;padding:1px;border-radius:3px 0 0 3px;color:#fff", "background:#1890FF;padding:1px;border-radius:0 3px 3px 0;color:#fff", "background:transparent" ); }; class Common { docStart = () => {}; contentLoaded = () => {}; load = () => {}; mPoint = 0; pcPreview = "https://v.anxia.com/?pickcode="; clickUrl = "http://115.com/?tab=offline&mode=wangpan"; expHei = 640; expBtnHei = 40; menus = [ { title: "点击事件", key: "CK", command: "c", defaultVal: true }, { title: "暗黑模式", key: "DM", command: "d", defaultVal: true }, { title: "滚动加载", key: "LM", command: "s", defaultVal: true }, { title: "离线后操作目录CID", key: "CID", command: "a", cb: uniKey => { const val = prompt("用于离线下载后移动,删除操作", GM_getValue(uniKey) ?? ""); if (!val) return; GM_setValue(uniKey, val); location.reload(); }, defaultVal: "", }, ]; registerMenu = (menus = this.menus) => { for (const menu of menus) { const { title, key, command, defaultVal } = menu; const uniKey = `${this.constructor.name}_${key}`; const val = GM_getValue(`${uniKey}`) ?? defaultVal; GM_registerMenuCommand( `${menu?.cb ? "设置" : val ? "关闭" : "开启"}${title}`, menu?.cb ? () => menu.cb(uniKey) : () => { GM_setValue(uniKey, !val); location.reload(); }, command ); this[key] = val; } }; initDB = () => { const date = getDate(); const rcdKey = `CD`; if (GM_getValue(rcdKey) !== date) { GM_setValue("DETAILS", {}); GM_setValue("RESOURCE", []); GM_setValue(rcdKey, date); } }; customStyle = () => { GM_addStyle(` video, img { vertical-align: middle !important; } .ellipsis { overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; } .playBtn { position: relative; } .playBtn::after { position: absolute; background: url(${GM_getResourceURL("play")}) 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 !important; } .mask { position: fixed; width: 100%; height: 100%; z-index: 9999; left: 0; top: 0; background: rgba(11, 11, 11, .8); justify-content: center; align-items: center; display: none; } .matched { font-weight: bold; color: rgb(10,132,255) !important; } .hidden { display: none !important; } `); }; darkStyle = () => { 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)"; GM_addStyle(` ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-thumb { border-radius: 4px; background-color: ${Grey}; } *:not(span) { border-color: ${Grey} !important; outline: ${Grey} !important; text-shadow: none !important; } body, footer { background: ${Background} !important; color: ${SecondaryLabelColor} !important; } img { filter: brightness(90%) !important; } nav { background: ${SecondaryBackground} !important; } input { background: ${Background} !important; } *::placeholder { color: ${SecondaryLabelColor} !important; } button, input, a, h1, h2, h3, h4, h5, h6, p { color: ${LabelColor} !important; box-shadow: none !important; outline: none !important; } `); return { Background, SecondaryBackground, LabelColor, SecondaryLabelColor, Grey, Blue, Orange, Green, Red, }; }; 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, { setParent: true, active: true }); }); item.addEventListener("mousedown", e => { if (e.button !== 2) return; e.preventDefault(); this.mPoint = e.screenX + e.screenY; }); item.addEventListener("mouseup", e => { const num = e.screenX + e.screenY - this.mPoint; if (e.button !== 2 || num > 5 || num < -5) return; e.preventDefault(); GM_openInTab(href, { setParent: true, active: false }); }); } }; waterfallLayout = (selectors, itemSelectors, mParams = {}, iParams = {}) => { const node = DOC.querySelector(selectors); if (!node) return; const msnry = new Masonry(node, { 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 }, ...mParams, }); imagesLoaded(node, () => { msnry.options.itemSelector = itemSelectors; const items = node.querySelectorAll(itemSelectors); msnry.appended(items); GM_addStyle(`${selectors} { opacity: 1; }`); }); const infScroll = new InfiniteScroll(node, { path: "#next", append: itemSelectors, outlayer: msnry, history: false, historyTitle: false, hideNav: ".pagination", debug: false, ...iParams, }); return infScroll; }; handleCopy = selectors => { const node = DOC.querySelector(selectors); if (!node) return; const copy = node?.textContent.trim(); if (!copy) return; const copyNode = DOC.create( "a", { title: copy, "data-copy": copy, href: "", style: "margin-left:16px" }, "复制" ); copyNode.addEventListener("click", this.copyTxt); node.appendChild(copyNode); }; copyTxt = e => { e.preventDefault(); e.stopPropagation(); const { target: node } = e; if (!node) return; const { copy } = node.dataset; if (!copy) return; GM_setClipboard(copy); const initial = node?.textContent ?? ""; node.textContent = "成功"; setTimeout(() => { node.textContent = initial; }, 600); }; fetchTranslate = async sentence => { const data = { async: `translate,sl:auto,tl:zh-CN,st:${encodeURIComponent( sentence.trim() )},id:1642125176704,qc:true,ac:false,_id:tw-async-translate,_pms:s,_fmt:pc`, }; sentence = await request( "https://www.google.com/async/translate?vet=12ahUKEwi03Jv2kLD1AhWRI0QIHe_TDKAQqDh6BAgCECY..i&ei=ZtfgYbSRO5HHkPIP76ezgAo&yv=3", data, "POST", { headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } } ); return sentence?.querySelector("#tw-answ-target-text").textContent ?? ""; }; // search prefix match list fetchMatch = async code => { let res = getItem(code)?.resource; if (!res) { const resource = GM_getValue("RESOURCE") ?? []; const prefix = code.split(/(-|_)/g)[0]; let item = resource.find(item => item.prefix === prefix); if (!item) { item = { prefix, res: await this.fetchSearch(prefix) }; resource.push(item); GM_setValue("RESOURCE", resource); } res = await this.fetchResource(code, item.res); } return res; }; // 115 files search fetchSearch = async search_value => { const res = await request( "https://webapi.115.com/files/search", { search_value, offset: 0, limit: 10000, date: "", aid: 1, cid: 0, pick_code: "", type: 4, source: "", format: "json", o: "user_ptime", asc: 0, star: "", suffix: "", }, "GET", { responseType: "json" } ); return (res?.data ?? []).map(({ fid, cid, n, pc, t, te, tp, play_long }) => { return { fid, cid, n, pc, t, te, tp, play_long }; }); }; // 115 files search result match fetchResource = async (code = "", res) => { if (!res) res = await this.fetchSearch(code.split(/(-|_)/g)[0]); if (res?.length) { const codes = unique([ code, code.replace(/-/g, ""), code.replace(/-/g, "."), code.replace(/-0/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"), ]); const reg = createReg(codes); res = res .filter(({ n, play_long }) => n.match(reg) && play_long > 0) .map(({ n: name, pc: pickCode, fid, cid, te, tp, t: date }) => { return { name, pickCode, fid, cid, timestamp: Math.max(te, tp), date }; }); } return res; }; fetchStar = async code => { const site = "https://javdb.com"; let res = await request(`${site}/search?q=${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(); return res .split(/\n/) .filter(item => item.indexOf("♀") !== -1) .map(item => item.replace("♀", "").trim()); }; fetchImage = async (code, date) => { date = date.split("-"); const jb = `http://img.japanese-bukkake.net/${date[0]}/${date[1]}/${code}_s.jpg`; const js = `https://javstore.net/search/${code}.html`; let [jbRes, jsRes] = await Promise.all([request(jb), request(js)]); const href = jsRes?.querySelector("#content_news li a")?.href; if (href) { jsRes = await request(href); jsRes = jsRes?.querySelector(".news a img[alt*='.th']").src.replace(".th", ""); if (jsRes && (await request(jsRes))) return jsRes; } if (typeof jbRes === "object") return jb; }; fetchVideo = async ({ code, studio }) => { code = code.toLowerCase(); if (studio) { const fetchVideoByStudio = [ { name: "東京熱", match: "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4" }, { name: "カリビアンコム", match: "https://smovie.caribbeancom.com/sample/movies/%s/1080p.mp4" }, { name: "一本道", match: "http://smovie.1pondo.tv/sample/movies/%s/1080p.mp4" }, { name: "HEYZO", transform: code => code.replace(/HEYZO\-/gi, ""), match: "https://www.heyzo.com/contents/3000/%s/heyzo_hd_%s_sample.mp4", }, ]; const matched = fetchVideoByStudio.find(({ name }) => name === studio); if (matched) return matched.match.replace(/%s/g, matched.transform ? matched.transform(code) : code); } const [r18, xrmoo] = await Promise.all([ request(`https://www.r18.com/common/search/searchword=${code}/`), request(`http://dmm.xrmoo.com/sindex.php?searchstr=${code}`), ]); return ( r18?.querySelector("a.js-view-sample")?.getAttribute("data-video-high") || xrmoo ?.querySelector(".card .card-footer a.viewVideo") ?.getAttribute("data-link") .replace("_sm_w", "_dmb_w") || "" ); }; fetchMagnet = async code => { const matchList = [ { site: "Sukebei", host: "https://sukebei.nyaa.si/", search: "?f=0&c=0_0&q=%s", selectors: ".table-responsive table tbody tr", filter: { name: e => e?.querySelector("td:nth-child(2) a").textContent, link: e => e?.querySelector("td:nth-child(3) a:last-child").href, size: e => e?.querySelector("td:nth-child(4)").textContent, date: e => e?.querySelector("td:nth-child(5)").textContent.split(" ")[0], href: e => e?.querySelector("td:nth-child(2) a").getAttribute("href"), }, styleClass: "primary", }, { site: "BTGG", host: "https://www.btgg.cc/", search: "search?q=%s", selectors: ".el-main .list .item", filter: { name: e => e?.querySelector(".name a").textContent, link: e => e?.querySelector(".meta a").href, size: e => e?.querySelector(".meta span").textContent.split(",")[0], date: e => e?.querySelector(".meta span:nth-child(4)").textContent.split(",")[0], href: e => e?.querySelector(".name a").getAttribute("href"), }, styleClass: "success", }, { site: "BTSOW", host: "https://btsow.rest/", search: "search/%s", selectors: ".data-list .row:not(.hidden-xs)", filter: { name: e => e?.querySelector(".file").textContent, link: e => `magnet:?xt=urn:btih:${e?.querySelector("a").href.split("/").pop()}`, size: e => e?.querySelector(".size").textContent, date: e => e?.querySelector(".date").textContent, href: e => e?.querySelector("a").getAttribute("href"), }, styleClass: "danger", }, ]; const matched = await Promise.all( matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`)) ); const magnets = []; for (let index = 0; index < matchList.length; index++) { let node = matched[index]; if (!node) continue; const { selectors, site, styleClass, filter, host } = matchList[index]; node = node?.querySelectorAll(selectors); if (!node || !node?.length) continue; for (const item of node) { const magnet = { from: site, styleClass }; Object.keys(filter).map(key => { magnet[key] = filter[key](item).trim(); }); if (!("zh" in magnet)) magnet.zh = /中文/g.test(magnet.name); magnet.link = magnet.link.split("&")[0]; const href = magnet?.href ?? ""; if (href && !href.includes("//")) magnet.href = `${host}${href.replace(/^\//, "")}`; magnets.push(magnet); } } return magnets; }; fetchPlayer = async code => { const codeReg = new RegExp(code, "gi"); const matchList = [ { site: "Netflav", host: "https://netflav.com/", search: "search?type=title&keyword=%s", selectors: ".grid_root .grid_cell", filter: { name: e => e?.querySelector(".grid_title").textContent }, }, { site: "BestJavPorn", host: "https://www2.bestjavporn.com/", search: "search/%s/", selectors: "#main article", filter: { name: e => e?.querySelector("a").title, zh: e => /中文/g.test(e?.querySelector(".hd-video")?.textContent ?? ""), }, }, { site: "JavHHH", host: "https://javhhh.com/", search: "v/?wd=%s", selectors: "#wrapper .typelist .i-container", filter: { name: e => e?.querySelector("img.img-responsive").title }, }, { site: "Avgle", host: "https://avgle.com/", search: "search/videos?search_query=%s&search_type=videos", selectors: ".row .well.well-sm", filter: { name: e => e?.querySelector(".video-title")?.textContent }, }, ]; const matched = await Promise.all( matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`)) ); const playList = []; for (let index = 0; index < matchList.length; index++) { let node = matched[index]; if (!node) continue; const { selectors, site, filter, host } = matchList[index]; node = node?.querySelectorAll(selectors); if (!node || !node?.length) continue; for (const item of node) { const player = { from: site }; for (const key of Object.keys(filter)) player[key] = filter[key](item); const name = player?.name ?? ""; if (!name || !name.match(codeReg)?.length) continue; if (!("zh" in player)) player.zh = /中文/g.test(name); if (!player?.link) player.link = item?.querySelector("a").getAttribute("href"); const link = player?.link ?? ""; if (link && !link.includes("//")) player.link = `${host}${link.replace(/^\//, "")}`; playList.push(player); } } return playList; }; fetchSign = async () => { const res = await request("http://115.com/", { ct: "offline", ac: "space", _: new Date().getTime() }); if (res?.sign) return { sign: res.sign, time: res.time }; notify({ title: "请求失败,115未登录", text: "请登录115账户后再离线下载", image: "fail", clickUrl: "http://115.com/?mode=login", }); }; fetchOffLine = async url => { let res = await this.fetchSign(); if (!res) return; return await request( "http://115.com/web/lixian/?ct=lixian&ac=add_task_url", { url, uid: 0, ...res }, "POST", { headers: { "Content-Type": "application/x-www-form-urlencoded" } } ); }; fetchRename = async res => { const data = {}; for (const { fid, file_name } of res) data[`files_new_name[${fid}]`] = file_name; return await request("https://webapi.115.com/files/batch_rename", data, "POST", { headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, responseType: "json", }); }; fetchMove = async res => { const data = { pid: this.CID, move_proid: `${new Date()}_${~(100 * Math.random())}_0` }; for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index].fid; return await request("https://webapi.115.com/files/move", data, "POST", { headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, responseType: "json", }); }; fetchDelDir = async res => { const data = { pid: this.CID, ignore_warn: 1 }; res = unique(res.map(({ cid }) => cid).filter(item => item !== this.CID)); for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index]; return await request("https://webapi.115.com/rb/delete", data, "POST", { headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, responseType: "json", }); }; searchOnKey = selectors => { DOC.addEventListener("DOMContentLoaded", () => { DOC.addEventListener("keyup", event => { const e = event || window.event; if (e && e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(DOC.activeElement.nodeName)) { DOC.querySelector(selectors).focus(); } }); }); }; handlePreviewFold = selectors => { GM_addStyle(` ${selectors} { overflow: hidden; height: fit-content; max-height: ${this.expHei}px; } #expBtn { top: 0; left: 0; right: 0; color: #fff; z-index: 9; cursor: pointer; position: absolute; background: rgba(0,0,0,.7); height: ${this.expBtnHei}px; line-height: ${this.expBtnHei}px; margin-top: ${this.expHei - this.expBtnHei}px; } #expBtn::after { content: "▼ 展开"; } #exp:checked + ${selectors} > #expBtn::after { content: "▲ 收起"; } #exp:checked + ${selectors} { max-height: none; } #exp:checked + ${selectors} > #expBtn { top: auto; bottom: 0; margin-bottom: 0; } `); const modifyMaxHei = () => { const expHei = this.expHei; const img = DOC.querySelector(`${selectors} img`); const styleHei = img.height || img.clientHeight || img.offsetHeight; if (styleHei > expHei) { GM_addStyle(` ${selectors} { max-height: ${expHei}px; } #expBtn { margin-top: ${expHei - this.expBtnHei}px; } `); } else { GM_addStyle(` ${selectors} { max-height: ${styleHei + this.expBtnHei}px; } #expBtn { margin-top: ${styleHei}px; } `); } }; const box = DOC.querySelector(selectors); const img = box.querySelector("img"); if (img.complete) modifyMaxHei(); img.onload = () => modifyMaxHei(); box.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`); box.insertAdjacentHTML("beforeend", `<label for="exp" id="expBtn"></label>`); }; } class JavBus extends Common { constructor() { super(); this.start(); const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname)); return { ...this, ...this[tag] }; } routeReg = { 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, details: /^\/[\w]+(-|_)?[\d]*.*$/i, }; start = () => { this.registerMenu(); this.initDB(); this.customStyle(); this.customMode(); this.searchOnKey("#search-input"); }; customMode = () => { GM_addStyle(`.ad-box { display: none !important; }`); }; darkMode = dStyle => { const { Grey, LabelColor, Blue, Green, Red, Orange, Background, SecondaryBackground } = dStyle; GM_addStyle(` .btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .85 !important; } button, .btn-default, .input-group-addon { background: ${Grey} !important; color: ${LabelColor} !important; } .btn-primary { background: ${Blue} !important; border-color: ${Blue} !important; } .btn-success { background: ${Green} !important; border-color: ${Green} !important; } .btn-danger { background: ${Red} !important; border-color: ${Red} !important; } .btn-warning { background: ${Orange} !important; border-color: ${Orange} !important; } .navbar-nav>.active>a, .navbar-nav>.active>a:focus, .navbar-nav>.active>a:hover, .navbar-nav>.open>a, .dropdown-menu { background: ${Background} !important; } .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { background: ${Grey} !important; } .pagination .active a { border: none !important; color: ${LabelColor} !important; } .pagination>li>a, .pagination>li>span { background-color: ${Grey} !important; border: none !important; color: ${LabelColor} !important; } tr, .modal-content, .alert { background: ${SecondaryBackground} !important; box-shadow: none !important; } tr:hover { background: ${Grey} !important; } `); }; waterfall = { docStart() { GM_addStyle(` .search-header { padding: 0 !important; background: none !important; box-shadow: none !important; } .photo-frame { position: relative; margin: 10px !important; } .photo-frame img { height: 100% !important; width: 100% !important; object-fit: cover !important; margin: 0 !important; } .photo-info { padding: 10px !important; } .alert-page { margin: 20px !important; } `); if (this.LM) { const itemSizer = `167px`; const gutterSizer = `20px`; GM_addStyle(` .pagination, footer { display: none !important; } .page-load-status { display: none; padding-bottom: ${gutterSizer}; text-align: center; } body { overflow: hidden; } .scrollBox { height: calc(100vh - 51px); overflow: hidden scroll; } #waterfall { opacity: 0; margin: ${gutterSizer} auto 0 auto !important; } .item-sizer, .item a { width: ${itemSizer} !important; } .gutter-sizer { width: ${gutterSizer} !important; } .item a { margin: 0 0 ${gutterSizer} 0 !important; } `); } if (this.DM) { const dStyle = this.darkStyle(); this.darkMode(dStyle); const { SecondaryBackground, LabelColor, SecondaryLabelColor, Grey } = dStyle; GM_addStyle(` .item a { background: ${SecondaryBackground} !important; } .photo-info { background: ${SecondaryBackground} !important; color: ${LabelColor} !important; } date { color: ${SecondaryLabelColor} !important; } .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} !important; } `); } }, contentLoaded() { const nav = DOC.querySelector(".search-header .nav"); if (nav) nav.setAttribute("class", "nav nav-pills"); this.modifyItem(); if (!this.LM) return; this.modifyLayout(); this.handleWaterfall(); }, async modifyItem(node = DOC) { if (this.CK) this.handleClick(".item a", node); const items = node.querySelectorAll(".item"); for (const item of items) { const info = item.querySelector("a .photo-info span:not(.mleft)"); if (!info) continue; const [titleNode, secondaryNode] = info.childNodes; const titleTxt = titleNode.textContent.trim(); const title = DOC.create("div", { class: "title", title: titleTxt }, titleTxt); if (this.LM) title.classList.add("ellipsis"); if (secondaryNode?.nodeName === "BR") info.removeChild(secondaryNode); info.replaceChild(title, titleNode); } for (const item of items) { let code = item.querySelector("date"); if (!code) continue; code = code.textContent.trim().toUpperCase(); const resource = await this.fetchMatch(code); if (!resource?.length) continue; item.querySelector(".title").classList.add("matched"); const photo = item.querySelector(".photo-frame"); photo.classList.add("playBtn"); photo.setAttribute("title", "点击播放"); photo.addEventListener("click", e => { e.stopPropagation(); e.preventDefault(); GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true }); }); } }, modifyLayout() { const oldWaterfall = DOC.querySelector("#waterfall"); if (!oldWaterfall) return; const newWaterfall = DOC.querySelector("#waterfall #waterfall"); if (newWaterfall) oldWaterfall.parentNode.replaceChild(newWaterfall, oldWaterfall); const waterfall = DOC.querySelector("#waterfall"); waterfall.insertAdjacentHTML( "afterbegin", `<div class="item-sizer"></div><div class="gutter-sizer"></div>` ); waterfall.insertAdjacentHTML( "afterend", `<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"); }, handleWaterfall() { const infScroll = this.waterfallLayout( "#waterfall", ".item", {}, { path: !/^\/(uncensored\/)?(search|searchstar)+\//i.test(location.pathname) ? "#next" : function () { const { pathname } = location; 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}`; } }, elementScroll: ".scrollBox", status: ".page-load-status", } ); infScroll?.on("load", e => this.modifyItem(e)); }, }; genre = { docStart() { GM_addStyle(` footer { display: none !important; } button.btn.btn-danger.btn-block.btn-genre { position: fixed !important; bottom: 0 !important; margin: 0 !important; left: 0 !important; border: 0 !important; border-radius: 0 !important; } `); if (this.DM) { const dStyle = this.darkStyle(); this.darkMode(dStyle); const { SecondaryBackground } = dStyle; GM_addStyle(`.genre-box { background-color: ${SecondaryBackground} !important; }`); } }, 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"); }, }; forum = { docStart() { GM_addStyle(` .bcpic, .banner728, .sd.sd_allbox > div:last-child { display: none !important; } .jav-button { margin-top: -3px !important; } #toptb { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; z-index: 999 !important; } #wp { margin-top: 55px !important; } `); }, }; details = { docStart() { GM_addStyle(` .info .glyphicon-info-sign, h4[style="position:relative"], h4[style="position:relative"] + .row { display: none !important; } .info ul { margin: 0 !important; } #avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px !important; } .photo-info { height: auto !important; } #resBox a { color: #CC0000 !important; } #smartOff { width: 100%; } #expBtn { margin-left: 15px; margin-right: 15px; } `); GM_addStyle(` #collapseBtn::after { content: "▼ 展开表格"; } #collapse:checked + .movie { max-height: none; } #collapse:checked + .movie #collapseBtn::after { content: "▲ 收起表格"; } `); if (this.DM) { const dStyle = this.darkStyle(); this.darkMode(dStyle); const { SecondaryBackground, LabelColor, Grey } = dStyle; GM_addStyle(` .movie, .sample-box, .movie-box, .photo-info { background: ${SecondaryBackground} !important; } .photo-info { color: ${LabelColor} !important; } .avatar-box, .avatar-box span, .info ul li, .info .star-name { background: ${SecondaryBackground} !important; border-color: ${Grey} !important; color: ${LabelColor} !important; } `); } }, contentLoaded() { if (this.CK) { this.handleClick("a.movie-box"); this.handleClick("a.avatar-box"); } // insert copy this.handleCopy("h3"); this.handleCopy("span[style='color:#CC0000;']"); // expBtn this.handlePreviewFold(".screencap"); const info = DOC.querySelector(".info"); // resource, player info.insertAdjacentHTML( "beforeend", `<p id="playerBox"><span class="header">在线播放:</span><span class="genre">查询中...</span></p> <p id="resBox"><span class="header">网盘资源:</span><span class="genre">查询中...</span></p>` ); // smart offLine const smartRes = DOC.create("button", { class: "btn btn-default", id: "smartOff" }, "一键离线"); smartRes.addEventListener("click", e => this.handleOffLine(e, "smart")); info.insertAdjacentElement("beforeend", smartRes); // table DOC.querySelector("#magnet-table tbody tr").insertAdjacentHTML( "beforeend", `<td style="text-align:center;white-space:nowrap">操作</td>` ); // ellipsis for (const item of DOC.querySelectorAll(".photo-info span")) item.classList.add("ellipsis"); // handleFetch const params = this.getParams(); if (!params) return; this.handleTransTitle(params); this.handleResource(params); this.handleStar(params); this.handleImage(params); this.handleVideo(params); this.handlePlayer(params); const magnetObs = new MutationObserver(() => this.handleMagnet(params)); magnetObs.observe(DOC.querySelector("#movie-loading"), { attributes: true, attributeFilter: ["style"], }); const tableObs = new MutationObserver(mutationsList => { this.modifyTable(); mutationsList.forEach(({ addedNodes }) => { if (addedNodes) { for (const node of addedNodes) { if (node.nodeName === "TR") this.modifyTR(node); if (node.nodeName === "TBODY") { const trs = node?.querySelectorAll("tr"); if (trs.length) for (const tr of trs) this.modifyTR(tr); } } } }); }); tableObs.observe(DOC.querySelector("#magnet-table"), { childList: true }); }, getParams() { // regex const charReg = /[\u4e00-\u9fa5:]/g; const studioReg = /製作商:/g; const starReg = /暫無出演者資訊/g; // dom const info = DOC.querySelector(".info"); const infos = info.querySelectorAll("p"); // params let [code, date] = infos; if (!code || !date) return; code = code.textContent.replace(charReg, "").trim().toUpperCase(); date = date.textContent.replace(charReg, "").trim().toUpperCase(); let studio = ""; for (const { textContent: text } of infos) { if (!studioReg.test(text)) continue; studio = text.replace(studioReg, "").trim(); break; } return { code, date, studio, star: !starReg.test(info.textContent), title: DOC.querySelector("h3").textContent.replace("复制", "").trim(), res: getItem(code), }; }, modifyTR(node) { 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, "data-copy": href, title: "复制磁力链接", style: "margin-right:16px" }, "复制" ); const offline = DOC.create("a", { href, title: "仅添加离线任务" }, "离线下载"); copy.addEventListener("click", this.copyTxt); offline.addEventListener("click", e => this.handleOffLine(e, href)); td.appendChild(copy); td.appendChild(offline); node.appendChild(td); }, modifyTable() { if (DOC.querySelector("#collapse")) return; const trs = DOC.querySelectorAll("#magnet-table tr"); const limit = 7; if (trs?.length <= limit) return; let maxHeight = 12; for (let index = 0; index < limit; index++) { const tr = trs[index]; maxHeight += tr.offsetHeight; if (index) continue; const td = tr.querySelector("td:last-child"); td.textContent = ""; td.insertAdjacentHTML( "beforeend", `<label for="collapse" id="collapseBtn" class="btn btn-mini-new btn-primary" title="点击展开/收起表格" > </label>` ); } DOC.querySelector("#magneturlpost + .movie").insertAdjacentHTML( "beforebegin", `<input type="checkbox" id="collapse" class="hidden">` ); GM_addStyle(`#collapse + .movie { max-height: ${maxHeight}px; }`); }, async handleTransTitle({ code, title, res }) { let transTitle = res?.transTitle ?? ""; if (!transTitle) { transTitle = await this.fetchTranslate(title); if (!transTitle) return; upItem(code, { transTitle }); } DOC.querySelector("h3").setAttribute("title", transTitle); }, async handleResource({ code }) { const resource = await this.fetchResource(code); upItem(code, { resource }); const resBox = DOC.querySelector("#resBox"); for (const old of resBox.querySelectorAll(".genre")) resBox.removeChild(old); if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="genre">无</span>`); resBox.insertAdjacentHTML( "beforeend", resource.reduce( (acc, { name, pickCode, date }) => ` ${acc} <span class="genre"> <a href="${this.pcPreview}${pickCode}" title="${date}/${name}" target="_blank"> ${name.length > 20 ? `${name.substr(0, 20)}...` : name} </a> </span>`, "" ) ); return resource; }, async handleStar({ code, star, res }) { if (star) return; star = res?.star ?? []; if (!star?.length) { star = await this.fetchStar(code); if (!star?.length) return; upItem(code, { star }); } const p = DOC.create("p"); p.insertAdjacentHTML( "beforeend", star.reduce( (acc, cur) => `${acc}<span class="genre"><a href="/search/${cur}">${cur}</a></span>`, "" ) ); DOC.querySelector(".info").replaceChild( p, DOC.querySelector("span.glyphicon.glyphicon-info-sign.mb20")?.nextSibling ); }, async handleImage({ code, res, date }) { let image = res?.image ?? ""; if (!image) { image = await this.fetchImage(code, date); if (!image) return; upItem(code, { image }); } const screencap = DOC.querySelector(".screencap"); const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") }); screencap.appendChild(placeholder); const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" }); img.addEventListener("click", () => { if (getScrollTop() >= this.expHei) window.scrollTo(0, 0); DOC.querySelector("#exp").checked = false; }); img.onload = () => screencap.replaceChild(img, placeholder); }, async handleVideo({ code, studio, res }) { let video = res?.video ?? ""; if (!video) { video = await this.fetchVideo({ code, studio }); if (!video) return; upItem(code, { video }); } // func const title = "预览视频"; const playVideo = e => { e.preventDefault(); e.stopPropagation(); DOC.querySelector("#video-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.querySelector("#video-mask").setAttribute("style", "display:none"); DOC.querySelector("video").pause(); DOC.onkeydown = null; }; // mask const videoNode = DOC.create("video", { controls: "controls", src: video, width: 720 }); videoNode.currentTime = 3; 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: "video-mask", class: "mask" }); mask.appendChild(closeBtn); mask.appendChild(videoNode); DOC.body.appendChild(mask); // screencap const bigImage = DOC.querySelector(".bigImage"); const playBtn = DOC.create("div", { class: "playBtn", title }); const bImg = bigImage.querySelector("img"); playBtn.addEventListener("click", playVideo); playBtn.appendChild(bImg); bigImage.appendChild(playBtn); // sample-waterfall const thumb = bImg.src; const box = DOC.create("a", { class: "sample-box", href: thumb, title }); box.addEventListener("click", playVideo); box.insertAdjacentHTML("beforeend", `<div class="photo-frame playBtn"><img src="${thumb}"></div>`); let waterfall = DOC.querySelector("#sample-waterfall"); if (!waterfall) { DOC.querySelector("div.clearfix").insertAdjacentHTML( "beforebegin", `<div id="sample-waterfall"></div>` ); waterfall = DOC.querySelector("#sample-waterfall"); waterfall.insertAdjacentHTML("beforebegin", `<h4>樣品圖像</h4>`); return waterfall.appendChild(box); } // jump to preview const ref = waterfall.querySelector("a"); waterfall.insertBefore(box, ref); const imgBtn = DOC.create( "button", { title: "樣品圖像", type: "button", class: "mfp-close", style: "right:44px" }, "📷" ); imgBtn.addEventListener("click", () => { pauseVideo(); ref.click(); }); mask.appendChild(imgBtn); }, async handlePlayer({ code, res }) { let player = res?.player ?? []; if (!player?.length) { player = await this.fetchPlayer(code); if (player?.length) upItem(code, { player }); } const box = DOC.querySelector("#playerBox"); const genre = box.querySelector(".genre"); if (!player?.length) return (genre.textContent = "无"); genre.remove(); box.insertAdjacentHTML( "beforeend", player.reduce( (pre, { zh, link, name, from }) => ` ${pre} <button class="btn btn-mini-new ${zh ? "btn-warning" : "btn-primary"}" data-link="${link}" title="${name}" > <strong>▶ ${from}</strong> </button> `, "" ) ); for (const btn of DOC.querySelectorAll("#playerBox button")) { btn.addEventListener("click", () => GM_openInTab(btn.dataset.link, { active: true })); } }, async handleMagnet({ code, res }) { let magnet = res?.magnet ?? []; if (!magnet?.length) { magnet = await this.fetchMagnet(code); if (!magnet?.length) return; upItem(code, { magnet }); } const tdStyle = "text-align:center;white-space:nowrap"; magnet = magnet.reduce( (pre, { link, name, styleClass, href, from, zh, size, date }) => ` ${pre} <tr> <td> <a href="${link}">${name}</a> <a class="btn btn-mini-new btn-${styleClass}" href="${href}" target="_blank" title="磁链详情页" > <strong>★ ${from}</strong> </a> ${zh ? `<a class="btn btn-mini-new btn-warning disabled">字幕</a>` : ""} </td> <td style="${tdStyle}">${size}</td> <td style="${tdStyle}">${date}</td> </tr> `, `<tr style="color:#CC0000"><td colspan="4">以下磁链来自外部</td></tr>` ); DOC.querySelector("#magnet-table").insertAdjacentHTML("beforeend", magnet); }, async handleOffLine(e, link) { e.preventDefault(); e.stopPropagation(); if (!link) return; const node = e.target; const text = node?.textContent ?? ""; node.textContent = "请求中..."; node.setAttribute("disabled", true); node.setAttribute("style", "pointer-events:none"); const clickUrl = this.clickUrl; if (link !== "smart") { let res = await this.fetchOffLine(link); const { state = false, error_msg = "" } = res; notify({ title: `请求${state ? "成功" : "失败"}`, text: `${error_msg ? error_msg : "仅添加离线任务"}`, image: `${state ? "info" : "fail"}`, clickUrl, }); } else { let links = []; const trs = DOC.querySelectorAll("#magnet-table tr"); for (let index = 1; index < trs.length; index++) { let [link, size, date] = trs[index]?.querySelectorAll("td"); if (!link || !size || !date) continue; links.push({ link: link.querySelector("a").href.split("&")[0], zh: !!link.querySelector("a.btn.btn-mini-new.btn-warning.disabled"), size: transToBytes(size.textContent.trim()), date: date.textContent.trim(), }); } if (links.length) { if (links.length > 1) links = unique(links, "link"); if (links.length > 1) { links.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; } }); } tooltip("磁链列表"); console.table(links); const params = this.getParams(); for (let index = 0; index < links.length; index++) { const { link, zh } = links[index]; let res = await this.fetchOffLine(link); if (!res || res?.errcode === 911) break; if (res?.state) { res = await this.checkResource(params); if (res?.length) { this.afterAction({ ...params, zh, res }); notify({ title: "一键离线任务成功", text: "进行后续操作", image: "success", clickUrl, }); break; } } if (index !== links.length - 1) continue; notify({ title: "一键离线任务失败", image: "fail", clickUrl }); } } } node.textContent = text; node.removeAttribute("disabled"); node.removeAttribute("style"); }, async checkResource({ code }) { const old = getItem(code)?.resource ?? []; await delay(2500); const res = await this.handleResource({ code }); if (Array.isArray(res) && res?.length) { const today = res.filter(({ date }) => date === getDate()); if (today.length && res.length > old.length) return today; } return false; }, async afterAction({ zh, res, title, code }) { res = res.filter(({ name }) => name.indexOf(title) === -1); if (!res.length) return; title = `${zh ? "【中文字幕】" : ""}${title}`; res = res.map(item => { const suffix = item.name.split(".").pop().toLowerCase(); return { ...item, file_name: `${title}.${suffix}` }; }); await this.fetchRename(res); if (this.CID) { const moveRes = await this.fetchMove(res); if (moveRes?.state) this.fetchDelDir(res); } this.handleResource({ code }); }, }; } class JavDB extends Common { constructor() { super(); this.start(); const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname)); return { ...this, ...this[tag] }; } routeReg = { waterfall: /^\/$|^\/(censored|uncensored|western|fc2|anime|search|video_codes|tags|rankings|actors|series|makers|directors|publishers)/i, details: /^\/v\//i, }; start = () => { this.registerMenu(this.menus.filter(item => !["DM"].includes(item.key))); this.initDB(); this.customStyle(); this.customMode(); this.searchOnKey("#video-search"); }; customMode = () => { GM_addStyle(`.message-body .moj-content { display: none !important; }`); }; waterfall = { container: {}, containerList: [ { selectors: "#videos .columns", itemSelectors: "#videos .columns .column", item: "#videos .columns .column a", itemSizer: "160px", }, { selectors: "#actors", itemSelectors: "#actors .box", item: "#actors .box", itemSizer: "130px", }, ], docStart() { GM_addStyle(` #tags { display: none; } #expBtn::after { content: "展开"; } #exp:checked + .tabs.is-boxed + #tags { display: block; } #exp:checked + .tabs.is-boxed > #expBtn::after { content: "收起"; } `); if (this.LM) { const gutterSizer = `20px`; GM_addStyle(` nav.pagination, nav#footer { display: none; } .gutter-sizer { width: ${gutterSizer} !important; } #videos .columns .column img { height: 200px !important; object-fit: cover; } ${this.containerList.map(item => item.selectors).join(", ")} { opacity: 0; } ${this.containerList.reduce( (pre, { itemSelectors, item, itemSizer }) => ` ${pre} ${itemSelectors} { padding: 0 !important; margin: 0 0 ${gutterSizer} 0 !important; width: ${itemSizer} !important; } ${item} { width: ${itemSizer} !important; } `, "" )} `); } }, contentLoaded() { this.modifyItem(); if (DOC.querySelector("#tags")) { const tabs = DOC.querySelector(".tabs.is-boxed"); tabs.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`); tabs.insertAdjacentHTML( "beforeend", `<label class="button is-link" for="exp" id="expBtn"></label>` ); } if (!this.LM) return; this.container = this.containerList.find(({ selectors }) => DOC.querySelector(selectors)); if (location.pathname === "/rankings/fanza_award" || !this.container) { return GM_addStyle(`nav.pagination, nav#footer { display: flex; }`); } GM_addStyle(` .item-sizer { width: ${this.container.itemSizer} !important; } ${this.container.selectors} { margin: 0 auto !important; } `); this.modifyLayout(); this.handleWaterfall(); }, async modifyItem(node = DOC) { if (this.CK) { const extra = [ { itemSelectors: "#series .columns .column" }, { itemSelectors: "#makers .columns .column" }, { itemSelectors: "#directors .columns .column" }, ]; for (const { itemSelectors } of unique([...this.containerList, ...extra], "itemSelectors")) { this.handleClick(`${itemSelectors} a`, node); } } const items = node.querySelectorAll("#videos .columns .column"); for (const item of items) { let img = item.querySelector("img"); if (img) img.src = img.dataset.src; } for (const item of items) { let code = item.querySelector(".uid"); if (!code) continue; code = code.textContent.trim().toUpperCase(); const resource = await this.fetchMatch(code); if (!resource?.length) continue; const title = item.querySelector(".video-title"); const titleTxt = title.textContent; title.textContent = ""; title.insertAdjacentHTML("beforeend", `<span class="matched">${titleTxt}</span>`); const photo = item.querySelector(".item-image"); photo.classList.add("playBtn"); photo.setAttribute("title", "点击播放"); photo.addEventListener("click", e => { e.stopPropagation(); e.preventDefault(); GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true }); }); } }, modifyLayout() { const waterfall = DOC.querySelector(this.container.selectors); waterfall.classList.remove("grid"); waterfall.insertAdjacentHTML( "afterbegin", `<div class="item-sizer"></div><div class="gutter-sizer"></div>` ); }, handleWaterfall() { const infScroll = this.waterfallLayout( this.container.selectors, this.container.itemSelectors, {}, { path: ".pagination-next" } ); infScroll?.on("load", e => this.modifyItem(e)); }, }; details = { docStart() { GM_addStyle(` .video-meta-panel { padding-left: 0; padding-right: 0; } .video-meta-panel > .columns { margin-left: 0; margin-right: 0; } .video-meta-panel > .columns > .column { padding: 0; margin: .75rem; } .column-video-cover .cover-container::after { height: 100%; } .column-video-cover img { width: 100%; max-height: none; } .movie-panel-info div.panel-block { padding: 8px 0; } #smartOff { flex: 1; } #resBox .button, #playerBox .button { height: auto; padding: 3px 6px; line-height: unset; } #playerBox { border-bottom: 0; } .preview-video-container::after { height: 100%; } `); GM_addStyle(` #magnets-content { overflow: hidden; } #collapseBtn::after { content: "▼ 展开表格" } #collapse:checked + .message-body > #magnets-content { max-height: none; } #collapse:checked + .message-body > #collapseBtn::after { content: "▲ 收起表格" } `); }, contentLoaded() { if (this.CK) this.handleClick(".tile-small a.tile-item"); // insert copy this.handleCopy("h2"); this.handlePreviewFold(".column-video-cover"); const info = DOC.querySelector("nav.movie-panel-info"); // insert smartOff const smartRes = DOC.create( "a", { class: "button is-info", id: "smartOff", href: "javascript:;" }, "一键离线" ); smartRes.addEventListener("click", e => this.handleOffLine(e, "smart")); const infos = info.querySelectorAll(".panel-block"); const lastPanel = infos[infos.length - 1]; lastPanel.querySelector(".columns").setAttribute("style", "flex:1"); lastPanel.querySelector(".buttons").appendChild(smartRes); // insert player & resource info.insertAdjacentHTML( "beforeend", `<div class="panel-block" id="resBox"> <strong>网盘资源:</strong><span class="value">查询中...</span> </div> <div class="panel-block" id="playerBox"> <strong>在线播放:</strong><span class="value">查询中...</span> </div>` ); // handleFetch const params = this.getParams(); if (!params) return; this.handleTransTitle(params); this.handleResource(params); this.handleImage(params); this.handleVideo(params); this.handlePlayer(params); this.handleMagnet(params); }, getParams() { const charReg = /[\u4e00-\u9fa5:]/g; const studioReg = /片商:/g; const infos = DOC.querySelectorAll("nav.movie-panel-info .panel-block"); let [code, date] = infos; if (!code || !date) return; code = code.textContent.replace(charReg, "").trim().toUpperCase(); date = date.textContent.replace(charReg, "").trim().toUpperCase(); let studio = ""; for (const { textContent: text } of infos) { if (!studioReg.test(text)) continue; studio = text.replace(studioReg, "").trim(); break; } return { code, date, studio, video: !!DOC.querySelector(".preview-video-container"), title: DOC.querySelector("h2").textContent.replace("复制", "").trim(), res: getItem(code), }; }, modifyTable() { const table = DOC.querySelector("#magnets-content table"); if (!table) return; const trs = table.querySelectorAll("tr"); for (const tr of trs) { const href = tr.querySelector(".magnet-name a").href; if (!href || tr.querySelector(".offline")) continue; const td = DOC.create("td", { class: "sub-column offline", width: "94" }); const btn = DOC.create( "button", { class: "button is-info is-small", type: "button", title: "仅添加离线任务" }, "离线下载" ); btn.addEventListener("click", e => this.handleOffLine(e, href)); td.appendChild(btn); tr.appendChild(td); } const limit = 6; if (trs?.length <= limit) return; let maxHeight = 0; for (let index = 0; index < limit; index++) maxHeight += trs[index].offsetHeight; const body = DOC.querySelector("#magnet-links .message-body"); body.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="collapse" class="hidden">`); body.querySelector(".moj-content").insertAdjacentHTML( "beforebegin", `<label for="collapse" id="collapseBtn" class="button is-info is-small mb-2"></label>` ); GM_addStyle(`#magnets-content { max-height: ${maxHeight}px; }`); }, async handleTransTitle({ code, title, res }) { let transTitle = res?.transTitle ?? ""; if (!transTitle) { transTitle = await this.fetchTranslate(title); if (!transTitle) return; upItem(code, { transTitle }); } DOC.querySelector("h2").setAttribute("title", transTitle); }, async handleResource({ code }) { const resource = await this.fetchResource(code); upItem(code, { resource }); const resBox = DOC.querySelector("#resBox"); resBox.querySelector(".value")?.remove(); resBox.querySelector(".columns")?.remove(); if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="value">无</span>`); resBox.insertAdjacentHTML( "beforeend", ` <div class="columns"> <div class="column"> <div class="buttons are-small review-buttons"> ${resource.reduce( (acc, { name, pickCode, date }) => ` ${acc} <a class="button is-small is-danger" href="${this.pcPreview}${pickCode}" title="${date}/${name}" target="_blank" > ${name.length > 20 ? `${name.substr(0, 20)}...` : name} </a> `, "" )} </div> </div> </div> ` ); return resource; }, async handleImage({ code, res, date }) { let image = res?.image ?? ""; if (!image) { image = await this.fetchImage(code, date); if (!image) return; upItem(code, { image }); } const cover = DOC.querySelector(".column-video-cover"); const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") }); cover.appendChild(placeholder); const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" }); img.addEventListener("click", () => { if (getScrollTop() >= this.expHei) window.scrollTo(0, 0); DOC.querySelector("#exp").checked = false; }); img.onload = () => cover.replaceChild(img, placeholder); }, async handleVideo({ video, code, studio, res }) { if (video) { video = DOC.querySelector("#preview-video source").src; } else { video = res?.video ?? ""; if (!video) video = await this.fetchVideo({ code, studio }); } if (!video) return; upItem(code, { video }); let preview = DOC.querySelector(".tile-images.preview-images"); if (!preview) { DOC.querySelector(".video-meta-panel").insertAdjacentHTML( "afterend", `<div class="columns"> <div class="column"> <article class="message video-panel"> <div class="message-body"> <div class="tile-images preview-images"> </div> </div> </article> </div> </div>` ); preview = DOC.querySelector(".tile-images.preview-images"); } if (!preview.querySelector(".preview-video-container")) { preview.querySelector("#preview-video")?.remove(); preview.insertAdjacentHTML( "afterbegin", `<a class="preview-video-container" data-fancybox="gallery" href="#preview-video"> <span>預告片</span> <img src="${DOC.querySelector(".column-video-cover img").src}" class="video-cover" style="width:150px;height:auto" /> </a> <video id="preview-video" playsinline="" controls="" muted="" preload="auto" style="display:none" > <source src="${video}"/> </video>` ); } const playBtn = DOC.create("a", { class: "play-button" }); playBtn.addEventListener("click", e => { e.stopPropagation(); e.preventDefault(); DOC.querySelector(".preview-video-container").click(); }); playBtn.insertAdjacentHTML( "beforeend", `<span class="icon"><img src="/packs/media/images/btn-play-b414746c.svg"></span> <span class="text">播放预览</span>` ); let cover = DOC.querySelector(".column-video-cover .cover-container"); if (!cover) { const gallery = DOC.querySelector(".column-video-cover a"); gallery.removeAttribute("data-fancybox"); gallery.classList.add("cover-container"); cover = DOC.querySelector(".column-video-cover .cover-container"); playBtn.setAttribute("style", "z-index:9"); } else { playBtn.setAttribute("style", "margin-top:60px;z-index:9"); } cover.insertAdjacentElement("beforeend", playBtn); }, async handlePlayer({ code, res }) { let player = res?.player ?? []; if (!player?.length) { player = await this.fetchPlayer(code); if (player?.length) upItem(code, { player }); } const box = DOC.querySelector("#playerBox"); box.querySelector(".value")?.remove(); box.querySelector(".columns")?.remove(); if (!player?.length) return box.insertAdjacentHTML("beforeend", `<span class="value">无</span>`); box.insertAdjacentHTML( "beforeend", ` <div class="columns"> <div class="column"> <div class="buttons are-small review-buttons"> ${player.reduce( (acc, { zh, link, name, from }) => ` ${acc} <a class="button is-small ${zh ? "is-warning" : "is-info"}" href="${link}" title="${name}" target="_blank" > ▶ ${from} </a> `, "" )} </div> </div> </div> ` ); }, async handleMagnet({ code, res }) { let magnet = res?.magnet ?? []; if (!magnet?.length) { magnet = await this.fetchMagnet(code); if (!magnet?.length) return this.modifyTable(); upItem(code, { magnet }); } magnet = magnet.reduce( (pre, { link, name, styleClass, href, from, zh, size, date }) => ` ${pre} <tr> <td class="magnet-name"> <a href="${link}"> <span>${name}</span> <br> ${zh ? `<span class="tag is-warning is-small">字幕</span>` : ""} <span class="tag is-${styleClass} is-small">${from}</span> <span class="meta"> ( ${size} )</span> </a> </td> <td class="sub-column" width="80"> <span class="time">${date}</span> </td> <td class="sub-column" width="70"> <button class="button is-info is-small copy-to-clipboard" data-clipboard-text="${link}" type="button" > 複製 </button> </td> </tr>`, "" ); const magnets = DOC.querySelector("#magnets-content"); let tbody = magnets.querySelector("table tbody"); if (!tbody) { magnets.textContent = ""; magnets.insertAdjacentHTML("beforeend", `<table width="100%"><tbody></tbody></table>`); tbody = magnets.querySelector("table tbody"); } tbody.insertAdjacentHTML("beforeend", magnet); this.modifyTable(); }, async handleOffLine(e, link) { e.preventDefault(); e.stopPropagation(); if (!link) return; const node = e.target; const text = node?.textContent ?? ""; node.textContent = "请求中..."; node.setAttribute("disabled", true); node.setAttribute("style", "pointer-events:none"); const clickUrl = this.clickUrl; if (link !== "smart") { let res = await this.fetchOffLine(link); const { state = false, error_msg = "" } = res; notify({ title: `请求${state ? "成功" : "失败"}`, text: `${error_msg ? error_msg : "仅添加离线任务"}`, image: `${state ? "info" : "fail"}`, clickUrl, }); } else { let links = []; const trs = DOC.querySelectorAll("#magnets-content tr"); for (const tr of trs) { let [info, date] = tr?.querySelectorAll("td"); if (!info || !date) continue; links.push({ link: info.querySelector("a").href.split("&")[0], zh: info.textContent.includes("字幕"), size: (() => { let size = info.querySelector(".meta").textContent.trim(); size = size.split(",")[0].replace(/\(|\)/gi, "").trim(); return transToBytes(size); })(), date: date.textContent.trim(), }); } if (links.length) { if (links.length > 1) links = unique(links, "link"); if (links.length > 1) { links.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; } }); } tooltip("磁链列表"); console.table(links); const params = this.getParams(); for (let index = 0; index < links.length; index++) { const { link, zh } = links[index]; let res = await this.fetchOffLine(link); if (!res || res?.errcode === 911) break; if (res?.state) { res = await this.checkResource(params); if (res?.length) { this.afterAction({ ...params, zh, res }); notify({ title: "一键离线任务成功", text: "进行后续操作", image: "success", clickUrl, }); break; } } if (index !== links.length - 1) continue; notify({ title: "一键离线任务失败", image: "fail", clickUrl }); } } } node.textContent = text; node.removeAttribute("disabled"); node.removeAttribute("style"); }, async checkResource({ code }) { const old = getItem(code)?.resource ?? []; await delay(2500); const res = await this.handleResource({ code }); if (Array.isArray(res) && res?.length) { const today = res.filter(({ date }) => date === getDate()); if (today.length && res.length > old.length) return today; } return false; }, async afterAction({ zh, res, title, code }) { res = res.filter(({ name }) => name.indexOf(title) === -1); if (!res.length) return; title = `${zh ? "【中文字幕】" : ""}${title}`; res = res.map(item => { const suffix = item.name.split(".").pop().toLowerCase(); return { ...item, file_name: `${title}.${suffix}` }; }); await this.fetchRename(res); if (this.CID) { const moveRes = await this.fetchMove(res); if (moveRes?.state) this.fetchDelDir(res); } this.handleResource({ code }); }, }; } let matched = MatchDomains.find(({ regex }) => regex.test(location.host))?.domain; if (matched) matched = eval(`new ${matched}();`); matched?.docStart(); DOC.addEventListener("DOMContentLoaded", () => matched?.contentLoaded()); window.addEventListener("load", () => matched?.load()); if (/captchaapi\.115\.com/g.test(location.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); }); }); } })();