您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一站式体验,JavBus & JavDB 兼容
当前为
// ==UserScript== // @name JavScript // @namespace JavScript@blc // @version 3.5.0 // @author blc // @description 一站式体验,JavBus & JavDB 兼容 // @icon https://s1.ax1x.com/2022/04/01/q5lzYn.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 play https://s4.ax1x.com/2022/01/12/7nYuKe.png // @resource success https://s1.ax1x.com/2022/04/01/q5l2LD.png // @resource info https://s1.ax1x.com/2022/04/01/q5lyz6.png // @resource warn https://s1.ax1x.com/2022/04/01/q5lgsO.png // @resource error https://s1.ax1x.com/2022/04/01/q5lcQK.png // @supportURL https://t.me/+bAWrOoIqs3xmMjll // @connect * // @run-at document-start // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @grant GM_getResourceURL // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_notification // @grant GM_deleteValue // @grant GM_listValues // @grant GM_addElement // @grant GM_openInTab // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @license GPL-3.0-only // @compatible chrome ≥ 88 & Tampermonkey // @compatible edge ≥ 88 & Tampermonkey // ==/UserScript== /** * TODO: * ❓ 网盘 - 一键离线 自动/手动 */ (function () { // match const MatchDomains = [ { domain: "JavBus", regex: /(jav|bus|dmm|see|cdn|fan){2}\./g }, { domain: "JavDB", regex: /javdb\d*\.com/g }, { domain: "Drive115", regex: /captchaapi\.115\.com/g }, ]; const Domain = MatchDomains.find(({ regex }) => regex.test(location.host))?.domain; if (!Domain) return; // document const DOC = document; DOC.create = (tag, attrs = {}, child) => { const element = DOC.createElement(tag); Object.keys(attrs).forEach(name => element.setAttribute(name, attrs[name])); typeof child === "string" && element.appendChild(DOC.createTextNode(child)); typeof child === "object" && element.appendChild(child); return element; }; // request const request = (url, data = {}, method = "GET", params = {}) => { method = method ? method.toUpperCase().trim() : "GET"; if (!url || !["GET", "POST"].includes(method)) return; if (Object.prototype.toString.call(data) === "[object Object]") { data = Object.keys(data).reduce((pre, cur) => { return `${pre ? `${pre}&` : pre}${cur}=${encodeURIComponent(data[cur])}`; }, ""); } if (method === "GET") { params.responseType = params.responseType ?? "document"; if (data) { if (url.includes("?")) { url = `${url}${url.endsWith("&") ? "" : "&"}${data}`; } else { url = `${url}?${data}`; } } } if (method === "POST") { params.responseType = params.responseType ?? "json"; const headers = params.headers ?? {}; params.headers = { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", ...headers }; } // const headers = params.headers ?? {}; // params.headers = { Referer: "", "Cache-Control": "no-cache", ...headers }; return new Promise(resolve => { GM_xmlhttpRequest({ url, data, method, timeout: 10000, onload: ({ status, response }) => { if (response?.errcode === 911) verify(); if (status === 404) response = false; if (response && ["", "text"].includes(params.responseType)) { if (/<\/?[a-z][\s\S]*>/i.test(response)) { response = new DOMParser().parseFromString(response, "text/html"); } else if (/^{.*}$/.test(response)) { response = JSON.parse(response); } } resolve(response); }, ...params, }); }); }; // utils const getDate = (timestamp, separator = "-") => { const date = timestamp ? new Date(timestamp) : new Date(); const Y = date.getFullYear(); const M = `${date.getMonth() + 1}`.padStart(2, "0"); const D = `${date.getDate()}`.padStart(2, "0"); return `${Y}${separator}${M}${separator}${D}`; }; const addCopyTarget = (selectors, attrs = {}) => { const node = DOC.querySelector(selectors); const _attrs = { "data-copy": node?.textContent ?? "", class: "x-ml", href: "javascript:void(0);" }; const target = DOC.create("a", { ..._attrs, ...attrs }, "复制"); target.addEventListener("click", handleCopyTxt); node.appendChild(target); }; const handleCopyTxt = (e, tip = "成功") => { const { target } = e; const copy = target?.dataset?.copy?.trim() ?? ""; if (!copy) return; e.preventDefault(); e.stopPropagation(); GM_setClipboard(copy); const { textContent = "" } = target; target.textContent = tip; const timer = setTimeout(() => { target.textContent = textContent; clearTimeout(timer); }, 400); return 1; }; const transToBytes = sizeStr => { const sizeNum = sizeStr.replace(/[a-zA-Z\s]/g, ""); if (sizeNum <= 0) return 0; const matchList = [ { unit: /byte/gi, transform: size => size }, { unit: /kb/gi, transform: size => size * 1000 }, { unit: /mb/gi, transform: size => size * Math.pow(1000, 2) }, { unit: /gb/gi, transform: size => size * Math.pow(1000, 3) }, { unit: /kib/gi, transform: size => size * 1024 }, { unit: /mib/gi, transform: size => size * Math.pow(1024, 2) }, { unit: /gib/gi, transform: size => size * Math.pow(1024, 3) }, ]; return ( matchList .find(({ unit }) => unit.test(sizeStr)) ?.transform(sizeNum) ?.toFixed(2) ?? 0 ); }; const unique = (arr, key) => { if (!key) return Array.from(new Set(arr)); arr = arr.map(item => { item[key] = item[key]?.toLowerCase(); return item; }); return Array.from(new Set(arr.map(e => e[key]))).map(e => arr.find(x => x[key] === e)); }; const openInTab = (url, active = true, params = {}) => { GM_openInTab(url, { active: !!active, insert: true, setParent: true, incognito: false, ...params }); }; const notify = msg => { GM_notification({ highlight: true, silent: true, timeout: 5000, ...msg, text: msg?.text || GM_info.script.name, image: GM_getResourceURL(msg?.image ?? "info"), onclick: msg?.clickUrl ? () => openInTab(msg.clickUrl) : () => {}, }); }; 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 delay = n => new Promise(r => setTimeout(r, n * 1000)); const codeParse = code => { const codes = code .split(/-|_/) .filter(Boolean) .map(item => (item.startsWith("0") ? item.slice(1) : item)); return { prefix: codes[0], regex: new RegExp(codes.join("(0|-|_){0,4}"), "i"), }; }; const fadeInImg = nodeList => { const loaded = node => node.classList.add("x-in"); nodeList.forEach(node => { const img = node.querySelector("img"); if (img) img.onload = () => loaded(img); }); }; const addMeta = () => { GM_addElement(DOC.head, "meta", { "http-equiv": "Content-Security-Policy", content: "upgrade-insecure-requests", }); }; // store class Store { static init() { const date = getDate(); const cdKey = "CD"; if (GM_getValue(cdKey, "") === date) return; GM_setValue(cdKey, date); GM_setValue("DETAILS", {}); GM_setValue("RESOURCE", []); GM_setValue("TEMPORARY_OBS", []); } static getDetail(key) { const details = GM_getValue("DETAILS", {}); return details[key] ?? {}; } static upDetail(key, val = {}) { const details = GM_getValue("DETAILS", {}); details[key] = { ...(details[key] ?? {}), ...val }; GM_setValue("DETAILS", details); } static addTemporaryOb(val) { if (!val) return; const obs = GM_getValue("TEMPORARY_OBS", []); obs.push(val); GM_setValue("TEMPORARY_OBS", obs); } static reduceTemporaryOb(val) { if (!val) return; const obs = GM_getValue("TEMPORARY_OBS", []).filter(item => item !== val); GM_setValue("TEMPORARY_OBS", obs); } } // apis class Apis { // movie static async movieImg(code) { code = code.toUpperCase(); const { regex } = codeParse(code); let [blogJav, javStore] = await Promise.all([ request(`https://www.google.com/search?q=${code} site:blogjav.net`), request(`https://javstore.net/search/${code}.html`), ]); blogJav = Array.from(blogJav?.querySelectorAll("#rso .g .yuRUbf a") ?? []).find(item => { return regex.test(item?.querySelector("h3")?.textContent ?? ""); }); javStore = Array.from(javStore?.querySelectorAll("#content_news li a") ?? []).find(item => { return regex.test(item?.title ?? ""); }); const [bjRes, jsRes] = await Promise.all([ request( `${blogJav?.href ? `http://webcache.googleusercontent.com/search?q=cache:${blogJav.href}` : ""}` ), request(javStore?.href ?? ""), ]); const bjImg = bjRes ? bjRes ?.querySelector("#page .entry-content a img") ?.getAttribute("data-lazy-src") .replace("//t", "//img") .replace("thumbs", "images") : ""; const jsImg = jsRes ? jsRes?.querySelector(".news a img[alt*='.th']")?.src.replace(".th", "") : ""; return bjImg || jsImg || ""; } static async movieVideo(code, studio) { code = code.toLowerCase(); const { regex } = codeParse(code); if (studio) { const matchList = [ { 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", trans: code => code.replace(/HEYZO\-/gi, ""), match: "https://www.heyzo.com/contents/3000/%s/heyzo_hd_%s_sample.mp4", }, ]; const matched = matchList.find(({ name }) => name === studio); if (matched) return matched.match.replace(/%s/g, matched.trans ? matched.trans(code) : code); } let [r18, xrmoo] = await Promise.all([ request(`https://www.r18.com/common/search/order=match/searchword=${code}/`), request(`http://dmm.xrmoo.com/sindex.php?searchstr=${code}`), ]); r18 = Array.from(r18?.querySelectorAll("a.js-view-sample") ?? []).find(item => { return regex.test(item?.dataset?.id ?? ""); }); xrmoo = xrmoo?.querySelector(".card .card-footer a.viewVideo")?.getAttribute("data-link"); const type = "video/mp4"; let res = []; if (r18) { const { videoHigh, videoMed, videoLow } = r18.dataset; if (videoHigh) res.push({ src: videoHigh, title: "720p", type }); if (videoMed) res.push({ src: videoMed, title: "480p", type }); if (videoLow) res.push({ src: videoLow, title: "360p", type }); res = res.map(item => { return { ...item, src: item.src.replace("awscc3001.r18.com", "cc3001.dmm.co.jp") }; }); } if (xrmoo) { xrmoo = xrmoo.replace("http://", "https://"); res.push({ src: xrmoo.replace("_sm_w", "_dmb_w"), title: "720p", type }); res.push({ src: xrmoo.replace("_sm_w", "_dm_w"), title: "480p", type }); res.push({ src: xrmoo, title: "360p", type }); } if (res.length) return res.sort((cur, next) => parseInt(next.title, 10) - parseInt(cur.title, 10)); } static async moviePlayer(code) { code = code.toUpperCase(); const { regex } = codeParse(code); const site = "https://netflav.com"; let netflav = await request(`${site}/search?type=title&keyword=${code}`); netflav = Array.from(netflav?.querySelectorAll(".grid_root .grid_cell") ?? []).find(item => { return regex.test(item?.querySelector(".grid_title")?.textContent ?? ""); }); netflav = netflav?.querySelector("a")?.getAttribute("href"); if (!netflav) return; netflav = await request(`${site}${netflav}`); netflav = netflav?.querySelector("script#__NEXT_DATA__")?.textContent ?? "{}"; netflav = JSON.parse(netflav)?.props?.initialState?.video?.data?.srcs ?? []; if (!netflav?.length) return; const matchList = [ { regex: /\/\/(mm9842\.com|www\.avple\.video|asianclub\.tv)/, parse: async url => { const [protocol, href] = url.split("//"); const [host, ...pathname] = href.split("/"); const res = await request( `${protocol}//${host}/api/source/${pathname.pop()}`, { r: "", d: host }, "POST" ); if (!res?.success) return []; return (res?.data ?? []).map(({ file, label, type }) => { return { src: file, title: label, type: `video/${type}` }; }); }, }, { regex: /\/\/(embedgram\.com|vidoza\.net)/, parse: async url => { const res = await request(url); return Array.from(res?.querySelectorAll("video source") ?? []).map( ({ src, title = "", type }) => { return { src, title, type }; } ); }, }, ]; netflav = await Promise.all( netflav .filter(url => matchList.find(({ regex }) => regex.test(url))) .map(url => matchList.find(({ regex }) => regex.test(url)).parse(url)) ); netflav = netflav.reduce((pre, cur) => [...pre, ...cur], []); if (!netflav?.length) return; return netflav.sort((cur, next) => parseInt(next.title || 360, 10) - parseInt(cur.title || 360, 10)); } static async movieTitle(sentence) { const st = encodeURIComponent(sentence.trim()); const data = { async: `translate,sl:auto,tl:zh-CN,st:${st},id:1650701080679,qc:true,ac:false,_id:tw-async-translate,_pms:s,_fmt:pc`, }; const res = await request( "https://www.google.com/async/translate?vet=12ahUKEwixq63V3Kn3AhUCJUQIHdMJDpkQqDh6BAgCECw..i&ei=CbNjYvGCPYLKkPIP05O4yAk&yv=3", data, "POST", { responseType: "" } ); return res?.querySelector("#tw-answ-target-text")?.textContent ?? ""; } static async movieStar(code) { code = code.toUpperCase(); const { regex } = codeParse(code); const site = "https://javdb.com"; let res = await request(`${site}/search?q=${code}&sb=0`); res = Array.from(res?.querySelectorAll(".movie-list .item a") ?? []).find(item => { return regex.test(item?.querySelector(".video-title strong")?.textContent ?? ""); }); if (!res) return; res = await request(`${site}${res.getAttribute("href")}`); res = res?.querySelectorAll(".movie-panel-info > .panel-block"); if (!res?.length) return; res = Array.from(res).find(item => { return /(演員|Actor\(s\)):/.test(item?.querySelector("strong")?.textContent ?? ""); }); if (!res) return; res = res?.querySelector(".value").textContent.trim(); return res .split(/\n/) .filter(item => item.indexOf("♀") !== -1) .map(item => item.replace("♀", "").trim()); } static async movieMagnet(code) { code = code.toUpperCase(); 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"), }, }, { site: "BTSOW", host: "https://btsow.com/", 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"), }, }, // { // site: "BTDigg", // host: "https://btdig.com/", // search: "search?order=0&q=%s", // selectors: ".one_result", // filter: { // name: e => e?.querySelector(".torrent_name").textContent, // link: e => e?.querySelector(".torrent_magnet a").href, // size: e => e?.querySelector(".torrent_size").textContent, // date: e => e?.querySelector(".torrent_age").textContent, // href: e => e?.querySelector(".torrent_name a").href, // }, // }, ]; 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, filter, host } = matchList[index]; node = node?.querySelectorAll(selectors); if (!node?.length) continue; for (const item of node) { const magnet = { from: site }; Object.keys(filter).forEach(key => { magnet[key] = filter[key](item)?.trim(); }); magnet.bytes = transToBytes(magnet.size); magnet.zh = /中文/g.test(magnet.name); magnet.link = magnet.link.split("&")[0]; const { href } = magnet; if (href && !href.includes("//")) magnet.href = `${host}${href.replace(/^\//, "")}`; magnets.push(magnet); } } return magnets; } // drive static async searchVideo(params = { search_value: "" } | "") { if (typeof params === "string") params = { search_value: params }; if (!params.search_value.trim()) return []; const res = await request( "https://webapi.115.com/files/search", { offset: 0, limit: 10000, date: "", aid: 1, cid: 0, pick_code: "", type: 4, source: "", format: "json", o: "user_ptime", asc: 0, star: "", suffix: "", ...params, }, "GET", { responseType: "json" } ); return (res.data ?? []).map(({ cid, fid, n, pc, t }) => { return { cid, fid, n, pc, t }; }); } static async getFile(params = {}) { const res = await request( "https://webapi.115.com/files", { aid: 1, cid: 0, o: "user_ptime", asc: 0, offset: 0, show_dir: 1, limit: 115, code: "", scid: "", snap: 0, natsort: 1, record_open_time: 1, source: "", format: "json", ...params, }, "GET", { responseType: "json" } ); return res?.data ?? []; } static async getSign() { const { state, sign, time } = (await request("http://115.com/", { ct: "offline", ac: "space", _: new Date().getTime() }, "GET", { responseType: "json", })) ?? {}; if (state) return { sign, time }; notify({ title: "请求失败,115未登录", text: "点击跳转登录", image: "error", clickUrl: "http://115.com/?mode=login", }); } static async addTaskUrl({ url, wp_path_id = "", sign, time }) { const _sign = sign && time ? { sign, time } : await this.getSign(); if (!_sign) return; return await request( "https://115.com/web/lixian/?ct=lixian&ac=add_task_url", { url, wp_path_id, ..._sign }, "POST" ); } static getVideo(cid = "") { return this.getFile({ cid, custom_order: 0, star: "", suffix: "", type: 4 }); } static driveClear({ pid, fids }) { const data = { pid, ignore_warn: 1 }; fids.forEach((fid, index) => { data[`fid[${index}]`] = fid; }); return request("https://webapi.115.com/rb/delete", data, "POST"); } static driveRename(res) { const data = {}; for (const { fid, file_name } of res) data[`files_new_name[${fid}]`] = file_name; return request("https://webapi.115.com/files/batch_rename", data, "POST"); } } // common class Common { menus = { tabs: [ { title: "全站", key: "global", prefix: "G" }, { title: "列表页", key: "list", prefix: "L" }, { title: "详情页", key: "movie", prefix: "M" }, { title: "115 相关", key: "drive", prefix: "D" }, ], commands: [ "G_DARK", "G_SEARCH", "G_CLICK", "L_MIT", "L_MTH", "L_MTL", "L_SCROLL", "L_MERGE", "M_IMG", "M_VIDEO", "M_PLAYER", "M_TITLE", "M_STAR", "M_JUMP", "M_SUB", "M_SORT", "M_MAGNET", "D_MATCH", "D_CID", "D_VERIFY", "D_CLEAR", "D_RENAME", ], details: [ { name: "暗黑模式", key: "G_DARK", type: "switch", info: "常见页面暗黑模式", defaultVal: window.matchMedia("(prefers-color-scheme: dark)").matches, hotkey: "d", }, { name: "快捷搜索", key: "G_SEARCH", type: "switch", info: "快捷键 <kbd>/</kbd> 获取焦点,<kbd>ctrl</kbd> + <kbd>/</kbd> 快速搜索粘贴板第一项", defaultVal: true, hotkey: "k", }, { name: "点击事件", key: "G_CLICK", type: "switch", info: "<code>影片</code> / <code>演员</code> 卡片点击新窗口 (左键前台,右键后台) 打开", defaultVal: true, hotkey: "c", }, { name: "预览图替换", key: "L_MIT", type: "switch", info: "替换为封面大图", defaultVal: true, }, { name: "标题等高", key: "L_MTH", type: "switch", info: "影片标题强制等高", defaultVal: true, }, { name: "标题最大行", key: "L_MTL", type: "number", info: "影片标题最大显示行数,超出省略。0 不限制 (等高模式下最小有效值 1)", placeholder: "仅支持整数 ≥ 0", defaultVal: 2, }, { name: "滚动加载", key: "L_SCROLL", type: "switch", info: "滚动加载下一页", defaultVal: true, }, { name: "合并列表", key: "L_MERGE", type: "textarea", info: "列表名不可重复,列表内重复地址自动过滤 (仅支持影片列表相对地址)<br>列表每页按 <code>影片日期</code> > <code>填写顺序</code> 综合排序,并自动去重<br>支持多列表,以空行分隔", placeholder: "[列表1]\n/genre/28\n/star/okq\n\n[列表2]\n/\n/uncensored", defaultVal: "", }, { name: "预览大图", key: "M_IMG", type: "switch", info: `获取自 <a href="https://blogjav.net/" class="link-primary">BlogJav</a>, <a href="https://javstore.net/" class="link-primary">JavStore</a>`, defaultVal: true, }, { name: "预览视频", key: "M_VIDEO", type: "switch", info: `获取自 <a href="https://www.r18.com/" class="link-primary">R18</a>, <a href="http://dmm.xrmoo.com/" class="link-primary">闲人吧</a>`, defaultVal: true, }, { name: "在线播放", key: "M_PLAYER", type: "switch", info: `获取自 <a href="https://netflav.com/" class="link-primary">Netflav</a>`, defaultVal: true, }, { name: "标题机翻", key: "M_TITLE", type: "switch", info: `翻自 <a href="https://google.com/" class="link-primary">Google</a>`, defaultVal: true, }, { name: "演员匹配", key: "M_STAR", type: "switch", info: `如无,获取自 <a href="https://javdb.com/" class="link-primary">JavDB</a>`, defaultVal: true, }, { name: "站点跳转", key: "M_JUMP", type: "switch", info: `在 <a href="https://www.javbus.com/" class="link-primary">JavBus</a> & <a href="https://javdb.com/" class="link-primary">JavDB</a> 间跳转`, defaultVal: true, }, { name: "字幕筛选", key: "M_SUB", type: "switch", info: "针对名称规则为 <code>大写字母</code> + <code>-C</code> 的磁链的额外过滤", defaultVal: false, }, { name: "磁力排序", key: "M_SORT", type: "switch", info: "综合排序 <code>字幕</code> > <code>大小</code> > <code>日期</code>", defaultVal: true, }, { name: "磁力搜索", key: "M_MAGNET", type: "switch", info: `自动去重,获取自 <a href="https://sukebei.nyaa.si/" class="link-primary">Sukebei</a>, <a href="https://btsow.com/" class="link-primary">BTSOW</a>`, defaultVal: true, }, { name: "网盘资源", key: "D_MATCH", type: "switch", info: "资源匹配 & 离线开关 (<strong>请确保网盘已登录</strong>)", defaultVal: true, }, { name: "下载目录", key: "D_CID", type: "input", info: "设置离线下载目录 <strong>cid</strong> 或 <strong>动态参数</strong>,建议 <strong>cid</strong> 效率更高<br><strong>动态参数</strong> 支持网盘 <strong>根目录</strong> 下文件夹名称<br>默认动态参数 <code>${云下载}</code>", placeholder: "cid 或 动态参数", defaultVal: "${云下载}", }, { name: "离线验证", key: "D_VERIFY", type: "number", info: "『<strong>一键离线</strong>』后执行,查询以验证离线下载结果,每次间隔一秒<br>设置验证次数上限,上限次数越多验证越精准<br>建议 3 ~ 5,默认 3", placeholder: "仅支持整数 ≥ 0", defaultVal: 3, }, { name: "离线清理", key: "D_CLEAR", type: "switch", info: "『<strong>一键离线</strong>』&『<strong>离线验证</strong>』后执行,匹配番号清理无关文件", defaultVal: true, }, { name: "文件重命名", key: "D_RENAME", type: "input", info: '『<strong>一键离线</strong>』&『<strong>离线验证</strong>』后执行,支持动态参数:<br><code>${字幕}</code> "【中文字幕】",非字幕资源则为空<br><code>${番号}</code> 页面番号,字母自动转大写 (番号为必须值,如重命名未包含将自动追加前缀)<br><code>${标题}</code> 页面标题,标题可能已包含番号,需自行判断<br><code>${序号}</code> 仅作用于视频文件,数字 1 起', placeholder: "勿填写后缀,可能导致资源不可用", defaultVal: "${字幕}${标题}", }, ], }; route = null; pcUrl = "https://v.anxia.com/?pickcode="; listener = { id: null, list: [], }; init() { Store.init(); this.route = Object.keys(this.routes).find(key => this.routes[key].test(location.pathname)); this.createMenu(); return { ...this, ...this[this.route] }; } createMenu() { GM_addStyle(` .x-scrollbar-hide body::-webkit-scrollbar { display: none; } .x-mask { position: fixed; top: 0; left: 0; z-index: 9999; display: none; box-sizing: border-box; width: 100vw; height: 100vh; margin: 0; padding: 0; background: transparent; border: none; backdrop-filter: blur(50px); } iframe.x-mask { backdrop-filter: none; } .x-hide { display: none; } .x-show { display: block !important; } `); let { tabs, commands, details } = this.menus; const exclude = this.excludeMenu ?? []; if (exclude?.length) { const regex = new RegExp(`^(?!${exclude.join("|")})`); commands = commands.filter(command => regex.test(command)); tabs = tabs.filter(tab => commands.find(command => command.startsWith(tab.prefix))); } if (!commands.length) return; const active = tabs.find(({ key }) => key === this.route) ?? tabs[0]; let tabStr = ""; let panelStr = ""; for (let index = 0; index < tabs.length; index++) { const { title, key, prefix } = tabs[index]; const curCommands = commands.filter(command => command.startsWith(prefix)); const curLen = curCommands.length; if (!curLen) continue; const isActive = key === active.key; tabStr += ` <a class="nav-link${isActive ? " active" : ""}" id="${key}-tab" data-bs-toggle="pill" href="#${key}" role="tab" aria-controls="${key}" aria-selected="${isActive}" > ${title}设置 </a>`; panelStr += ` <div class="tab-pane fade${isActive ? " show active" : ""}" id="${key}" role="tabpanel" aria-labelledby="${key}-tab" > `; for (let curIdx = 0; curIdx < curLen; curIdx++) { const { key: curKey, defaultVal, name, type, hotkey = "", placeholder = "", info, } = details.find(item => item.key === curCommands[curIdx]); const uniKey = `${Domain}_${curKey}`; const val = GM_getValue(uniKey, defaultVal); this[curKey] = val; panelStr += `<div${curIdx + 1 === curLen ? "" : ` class="mb-3"`}>`; if (type === "switch") { if (curKey.startsWith("G")) { GM_registerMenuCommand( `${val ? "关闭" : "开启"}${name}`, () => { GM_setValue(uniKey, !val); location.reload(); }, hotkey ); } panelStr += ` <div class="form-check form-switch"> <input type="checkbox" class="form-check-input" id="${curKey}" aria-describedby="${curKey}_Help" ${val ? "checked" : ""} name="${curKey}" > <label class="form-check-label" for="${curKey}">${name}</label> </div> `; } else if (type === "textarea") { panelStr += ` <label class="form-label" for="${curKey}">${name}</label> <textarea rows="3" class="form-control" id="${curKey}" aria-describedby="${curKey}_Help" placeholder="${placeholder}" name="${curKey}" >${val ?? ""}</textarea> `; } else { panelStr += ` <label class="form-label" for="${curKey}">${name}</label> <input type="${type}" class="form-control" id="${curKey}" aria-describedby="${curKey}_Help" value="${val ?? ""}" placeholder="${placeholder}" name="${curKey}" > `; } if (info) panelStr += `<div id="${curKey}_Help" class="form-text">${info}</div>`; panelStr += `</div>`; } panelStr += `</div>`; } if (!tabStr || !panelStr) return; DOC.addEventListener( "DOMContentLoaded", () => { DOC.body.insertAdjacentHTML( "beforeend", `<iframe class="x-mask" id="control-panel" name="control-panel" src="about:blank" title="控制面板" ></iframe>` ); const iframe = DOC.querySelector("iframe#control-panel"); const _DOC = iframe.contentWindow.document; _DOC.querySelector("head").insertAdjacentHTML( "beforeend", `<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" > <style>${this.style}</style> <base target="_blank">` ); const body = _DOC.querySelector("body"); body.classList.add("bg-transparent"); GM_addElement(body, "script", { src: "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js", }); body.insertAdjacentHTML( "afterbegin", `<button type="button" class="d-none" id="openModal" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#controlPanel" > open </button> <div class="modal fade" id="controlPanel" tabindex="-1" aria-labelledby="controlPanelLabel" aria-hidden="true" > <div class="modal-dialog modal-lg modal-fullscreen-lg-down modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="controlPanelLabel"> 控制面板 - <a href="https://sleazyfork.org/zh-CN/scripts/435360-javscript" class="link-secondary text-decoration-none" > ${GM_info.script.name} v${GM_info.script.version} </a> </h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" > </button> </div> <div class="modal-body"> <form class="mb-0"> <div class="d-flex align-items-start"> <div class="nav flex-column nav-pills me-3 sticky-top" id="v-pills-tab" role="tablist" aria-orientation="vertical" > ${tabStr} </div> <div class="tab-content flex-fill" id="v-pills-tabContent"> ${panelStr} </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-danger" data-bs-dismiss="modal" data-action="restart" > 重置脚本 </button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-action="reset" > 恢复所有默认 </button> <button type="button" class="btn btn-primary" data-bs-dismiss="modal" data-action="save" > 保存设置 </button> </div> </div> </div> </div>` ); body.querySelector(".modal-footer").addEventListener("click", e => { const { action } = e.target.dataset; if (!action) return; e.preventDefault(); e.stopPropagation(); if (action === "save") { const data = Object.fromEntries(new FormData(body.querySelector("form")).entries()); commands.forEach(key => GM_setValue(`${Domain}_${key}`, data[key] ?? "")); } if (action === "reset") { GM_listValues().forEach(name => name.startsWith(Domain) && GM_deleteValue(name)); } if (action === "restart") { GM_listValues().forEach(name => GM_deleteValue(name)); } location.reload(); }); const toggleIframe = () => { DOC.body.parentNode.classList.toggle("x-scrollbar-hide"); iframe.classList.toggle("x-show"); }; const openModal = () => { if (iframe.classList.contains("x-show")) return; toggleIframe(); _DOC.querySelector("#openModal").click(); }; GM_registerMenuCommand("控制面板", openModal, "s"); _DOC.querySelector("#controlPanel").addEventListener("hidden.bs.modal", toggleIframe); }, { once: true } ); } // styles variables = ` :root { --x-bgc: #121212; --x-sub-bgc: #202020; --x-ftc: #fffffff2; --x-sub-ftc: #aaa; --x-grey: #313131; --x-blue: #0a84ff; --x-orange: #ff9f0a; --x-green: #30d158; --x-red: #ff453a; --x-line-h: 22px; --x-thumb-w: 190px; --x-cover-w: 295px; --x-thumb-ratio: 334 / 473; --x-cover-ratio: 135 / 91; --x-avatar-ratio: 1; --x-sprite-ratio: 4 / 3; } `; style = ` ::-webkit-scrollbar { width: 8px !important; height: 8px !important; } ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px !important; } * { text-decoration: none !important; text-shadow: none !important; outline: none !important; } `; dmStyle = ` ::-webkit-scrollbar-thumb, button { background: var(--x-grey) !important; } * { box-shadow: none !important; } *:not(span[class]) { border-color: var(--x-grey) !important; } html, body, input, textarea { background: var(--x-bgc) !important; } body, *::placeholder { color: var(--x-sub-ftc) !important; } nav { background: var(--x-sub-bgc) !important; } a, button, h1, h2, h3, h4, h5, h6, input, p, textarea { color: var(--x-ftc) !important; } img { filter: brightness(0.9) contrast(0.9) !important; } `; customStyle = ` #x-status { margin-bottom: 20px; color: var(--x-sub-ftc); font-size: 14px !important; text-align: center; } .x-ml { margin-left: 10px !important; } .x-mr { margin-right: 10px !important; } .x-in { opacity: 1 !important; transition: opacity 0.25s linear; } .x-out { opacity: 0 !important; transition: opacity 0.25s linear; } .x-flex { display: flex !important; } .x-cover { width: var(--x-cover-w) !important; } .x-cover > *:first-child { aspect-ratio: var(--x-cover-ratio) !important; } .x-ellipsis { display: -webkit-box !important; overflow: hidden; white-space: unset !important; text-overflow: ellipsis; -webkit-line-clamp: 1; -webkit-box-orient: vertical; } .x-line { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .x-title { line-height: var(--x-line-h) !important; } .x-matched { color: var(--x-blue) !important; font-weight: bold; } .x-player { position: relative; display: block; overflow: hidden; } .x-player::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgb(0 0 0 / 20%); background-image: url(${GM_getResourceURL("play")}); background-repeat: no-repeat; background-position: center; background-size: 40px; opacity: 0.85; transition: all 0.25s ease-out; content: ""; } .x-player:hover::after { background-color: rgb(0 0 0 / 0%); } .x-player img { filter: none !important; } `; // G_DARK globalDark = (css = "", dark = "") => { if (this.G_DARK) css += dark; if (css) GM_addStyle(css.includes("var(--x-") ? `${this.variables}${css}` : css); }; // G_SEARCH globalSearch = (selectors, pathname) => { if (!this.G_SEARCH) return; DOC.addEventListener("keydown", async e => { if (e.ctrlKey && e.keyCode === 191) { const text = (await navigator.clipboard.readText()).trim(); if (!text) return; openInTab(`${location.origin}${pathname.replace("%s", text)}`); } }); DOC.addEventListener("keyup", e => { if (e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(DOC.activeElement.nodeName)) { DOC.querySelector(selectors).focus(); } }); }; // G_CLICK globalClick = (selectors, node = DOC, callback) => { node = node || DOC; if (this.G_CLICK && this.D_MATCH && !this.listener.id && callback) { this.listener.id = GM_addValueChangeListener("TEMPORARY_OBS", (name, old_value, new_value, remote) => { if (!remote) return; for (const url of unique(this.listener.list)) { if (!new_value.includes(url)) continue; Store.reduceTemporaryOb(url); callback(url); } }); } node.addEventListener("click", e => { let { target } = e; let url = ""; if (target.classList.contains("x-player")) { url = `${this.pcUrl}${target.dataset.code}`; } else if (this.G_CLICK) { target = getTarget(e); if (target) { url = target.href; if (this.D_MATCH && callback) this.listener.list.push(url); } } if (!url) return; e.preventDefault(); e.stopPropagation(); openInTab(url); }); const getTarget = ({ target }) => { const item = target.closest(selectors); return !item?.href || !node.contains(item) ? false : item; }; if (!this.G_CLICK) return; let _event; node.addEventListener("mousedown", e => { if (e.button !== 2) return; const target = getTarget(e); if (!target) return; e.preventDefault(); target.oncontextmenu = e => e.preventDefault(); _event = e; }); node.addEventListener("mouseup", e => { if (e.button !== 2) return; const target = getTarget(e); if (!_event || !target) return; e.preventDefault(); const { clientX, clientY } = e; const { clientX: _clientX, clientY: _clientY } = _event; if (Math.abs(clientX - _clientX) + Math.abs(clientY - _clientY) > 5) return; if (this.D_MATCH && callback) this.listener.list.push(target.href); openInTab(target.href, false); }); }; // L_MIT listMovieImgType = (node, condition) => { const img = node.querySelector("img"); if (!this.L_MIT || !img) return; node.classList.add("x-cover"); img.loading = "lazy"; const { src = "" } = img; img.src = condition.find(({ regex }) => regex.test(src))?.replace(src) ?? src; }; // L_MTL, L_MTH listMovieTitle = () => { let num = parseInt(this.L_MTL ?? 0, 10); if (this.L_MTH && num < 1) num = 1; return ` .x-title { -webkit-line-clamp: ${num <= 0 ? "unset" : num}; ${this.L_MTH ? `height: calc(var(--x-line-h) * ${num}) !important;` : ""} } `; }; // L_SCROLL listScroll = (container, itemSelector, path) => { let msnry = {}; if (itemSelector) { const items = container.querySelectorAll(itemSelector); msnry = new Masonry(container, { itemSelector, columnWidth: items[items.length - 2] ?? items[items.length - 1], fitWidth: true, visibleStyle: { opacity: 1 }, hiddenStyle: { opacity: 0 }, }); container.classList.add("x-in"); } if (!path) return msnry; if (!this.L_SCROLL) return; let nextURL; const updateNextURL = (node = DOC) => { nextURL = node.querySelector(path)?.href; }; updateNextURL(); const infScroll = new InfiniteScroll(container, { path: () => nextURL, checkLastPage: path, outlayer: msnry, history: false, }); infScroll?.on("request", async (_, fetchPromise) => { const { body } = await fetchPromise.then(); if (body) updateNextURL(body); }); const status = DOC.create("div", { id: "x-status" }); container.insertAdjacentElement("afterend", status); let textContent = "加载中..."; const noMore = "没有更多了"; try { const path = infScroll.getPath() ?? ""; if (!path) textContent = noMore; } catch (err) { textContent = noMore; } status.textContent = textContent; infScroll?.once("last", () => { status.textContent = noMore; }); return infScroll; }; // L_MERGE listMerge = () => { const mergeStr = this.L_MERGE.trim(); if (!mergeStr) return; const mergeGrp = mergeStr .split("\n\n") .map(item => item.trim()) .filter(item => /^\[.+\]/.test(item)); const res = []; mergeGrp.forEach(item => { const list = item .split("\n") .map(item => item.trim()) .filter(Boolean); if (list?.length > 1) res.push(list[0].replaceAll(/\[|\]/g, "")); }); return unique(res.map(item => item?.trim()).filter(Boolean)); }; getMerge = key => { if (!key || !this.L_MERGE) return; let list = []; for (const item of this.L_MERGE.split("\n\n")) { const [_key, ...urls] = item.split("\n"); if (_key !== `[${key}]`) continue; list = urls; break; } return unique(list.map(item => item?.trim()).filter(Boolean)); }; // M_IMG movieImg = async ({ code }, start) => { if (!this.M_IMG) return; start && start(); let img = Store.getDetail(code)?.img; if (!img?.length) { img = await Apis.movieImg(code); if (img?.length) Store.upDetail(code, { img }); } return img; }; // M_VIDEO movieVideo = async ({ code, studio }, start) => { if (!this.M_VIDEO) return; start && start(); let video = Store.getDetail(code)?.video; if (!video?.length) { video = await Apis.movieVideo(code, studio); if (video?.length) Store.upDetail(code, { video }); } return video; }; // M_PLAYER moviePlayer = async ({ code }, start) => { if (!this.M_PLAYER) return; start && start(); let player = Store.getDetail(code)?.player; if (!player?.length) { player = await Apis.moviePlayer(code); if (player?.length) Store.upDetail(code, { player }); } return player; }; // M_TITLE movieTitle = async ({ code, title }, start) => { if (!this.M_TITLE) return; start && start(); let transTitle = Store.getDetail(code)?.transTitle; if (!transTitle?.length) { transTitle = await Apis.movieTitle(title); if (transTitle?.length) Store.upDetail(code, { transTitle }); } return transTitle; }; // M_STAR movieStar = async ({ code, star: hasStar }, start) => { if (!this.M_STAR || hasStar) return; start && start(); let star = Store.getDetail(code)?.star; if (!star?.length) { star = await Apis.movieStar(code); if (star?.length) Store.upDetail(code, { star }); } return star; }; // M_JUMP movieJump = start => { if (!this.M_JUMP) return; start && start(); }; // M_SUB movieSub = (magnets, start) => { if (!this.M_SUB) return magnets; start && start(); const regex = /[A-Z]+.*-C/; return magnets.map(item => { item.zh = item.zh && !regex.test(item.name); return item; }); }; // M_SORT movieSort = (magnets, start) => { if (!this.M_SORT) return magnets; start && start(); return magnets.length <= 1 ? magnets : magnets.sort((pre, next) => { if (pre.zh === next.zh) { if (pre.bytes === next.bytes) return next.date - pre.date; return next.bytes - pre.bytes; } else { return pre.zh > next.zh ? -1 : 1; } }); }; // M_MAGNET movieMagnet = async ({ code }, start) => { if (!this.M_MAGNET) return; start && start(); let magnet = Store.getDetail(code)?.magnet; if (!magnet?.length) { magnet = unique(await Apis.movieMagnet(code), "link"); if (magnet?.length) Store.upDetail(code, { magnet }); } return magnet; }; // D_MATCH driveMatch = async ({ code, res }, start) => { if (!this.D_MATCH) return; start && start(); code = code.toUpperCase(); const { prefix, regex } = codeParse(code); if (res === "list") { res = Store.getDetail(code)?.res; if (!res?.length) { const RESOURCE = GM_getValue("RESOURCE", []); let item = RESOURCE.find(item => item.prefix === prefix); if (!item) { item = { prefix, res: await Apis.searchVideo(prefix) }; RESOURCE.push(item); GM_setValue("RESOURCE", RESOURCE); } res = await this.driveMatch({ ...item, code }); } } else { let _res = res ?? (await Apis.searchVideo(prefix)); if (_res?.length) _res = _res.filter(({ n }) => regex.test(n)); if (!res) { if (!_res?.length) { const cid = await this.driveCid(); _res = await Apis.getVideo(cid); } if (_res?.length) _res = _res.filter(({ n }) => regex.test(n)); Store.upDetail(code, { res: _res }); if (this.G_CLICK) Store.addTemporaryOb(location.href); } res = _res; } return res; }; // D_CID driveCid = async () => { let cid = this.D_CID?.trim() === "" ? "${云下载}" : this.D_CID; if (/^\$\{.+\}$/.test(cid)) { cid = cid.replace(/\$|\{|\}/g, "").trim(); const res = await Apis.getFile(); cid = res.find(({ n }) => n === cid)?.cid ?? ""; } return cid; }; // D_VERIFY driveVerify = async ({ code, cid = "" }) => { let verify = this.D_VERIFY <= 0; for (let idx = 0; idx < this.D_VERIFY; idx++) { await delay(1); let res = await Apis.getVideo(cid); res = await this.driveMatch({ code, res }); res = res.filter(({ t }) => t.startsWith(getDate())); if (!res.length) continue; const fids = (Store.getDetail(code)?.res ?? []).map(({ fid }) => fid); res = res.filter(({ fid }) => !fids.includes(fid)); if (!res.length) continue; verify = res; break; } return verify; }; // D_CLEAR driveClear = async ({ cid, res, code }) => { if (!this.D_CLEAR) return; const { regex } = codeParse(code); unique(res.map(item => item.cid).filter(item => item !== cid)).forEach(async item => { let data = await Apis.getFile({ cid: item }); if (data?.length <= 1) data = []; data = data.filter(({ n }) => !regex.test(n)); if (data?.length) Apis.driveClear({ pid: item, fids: data.map(({ fid }) => fid) }); }); }; // D_RENAME driveRename = ({ cid, res, zh, code, title }) => { let file_name = this.D_RENAME?.trim(); if (!file_name) return; code = code.toUpperCase(); file_name = file_name .replace(/\$\{字幕\}/g, zh ? "【中文字幕】" : "") .replace(/\$\{番号\}/g, code) .replace(/\$\{标题\}/g, title); if (!codeParse(code).regex.test(file_name)) file_name = `${code} - ${file_name}`; res = res.filter(item => item.ico); const numRegex = /\$\{序号\}/g; const data = []; unique(res.map(item => `${item.cid}/${item.ico}`)).forEach(key => { const [_cid, _ico] = key.split("/"); res.filter(item => item.cid === _cid && item.ico === _ico).forEach((item, index) => { data.push({ ...item, file_name: `${file_name.replace(numRegex, index + 1)}.${_ico}` }); }); }); unique(res.map(item => item.cid).filter(item => item !== cid)).forEach(fid => { data.push({ fid, file_name: file_name.replace(numRegex, "") }); }); return Apis.driveRename(data); }; // OFFLINE driveOffline = async (e, { magnets, code, title }) => { const { target } = e; const { magnet: type } = target.dataset; if (!type) return; e.preventDefault(); e.stopPropagation(); const { classList } = target; if (classList.contains("active")) return; classList.add("active"); const originText = target.textContent; target.textContent = "请求中..."; target.setAttribute("disabled", "disabled"); let wp_path_id = await this.driveCid(); if (!/^\d+$/.test(wp_path_id)) wp_path_id = ""; if (type === "all") { const warnMsg = { title: `${code} 一键离线任务失败`, image: "warn" }; const successMsg = { title: `${code} 一键离线任务成功`, image: "success" }; const magnetLen = magnets.length; for (let index = 0; index < magnetLen; index++) { const sign = await Apis.getSign(); if (!sign) break; const isLast = index + 1 === magnetLen; const { link: url, zh } = magnets[index]; let res = await Apis.addTaskUrl({ url, wp_path_id, ...sign }); if (!res?.state) { if (res?.errcode === 911) { notify({ title: `${code} 一键离线任务中断`, text: res.error_msg, image: "warn", highlight: false, }); break; } if (!isLast) continue; notify(warnMsg); break; } const cid = wp_path_id; res = await this.driveVerify({ code, cid }); if (!res) { if (!isLast) continue; notify(warnMsg); break; } if (res?.length) { successMsg.text = "点击跳转目录"; successMsg.clickUrl = `https://115.com/?cid=${res[0].cid}&offset=0&mode=wangpan`; await this.driveRename({ cid, res, zh, code, title }); this.driveClear({ cid, res, code }); } notify(successMsg); break; } } else if (type) { const res = await Apis.addTaskUrl({ url: type, wp_path_id }); if (res) { notify({ title: `${code} 离线任务添加${res.state ? "成功" : "失败"}`, text: res.error_msg ?? "", image: res.state ? "success" : "warn", highlight: false, }); } } classList.remove("active"); target.textContent = originText; target.removeAttribute("disabled"); }; } // javbus class JavBus extends Common { constructor() { super(); return super.init(); } routes = { list: /^\/((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, movie: /^\/[\w]+(-|_)?[\d]*.*$/i, }; // styles _style = ` body { overflow-y: overlay; } .ad-box { display: none !important; } footer { display: none !important; } `; _dmStyle = ` .nav > li > a:hover, .nav > li > a:focus, .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { background: var(--x-grey) !important; } .nav > li.active > a, .nav > .open > a, .nav > .open > a:hover, .nav > .open > a:focus, .dropdown-menu { background: var(--x-bgc) !important; } .modal-content, .alert { background: var(--x-sub-bgc) !important; } .btn-primary { background: var(--x-blue) !important; border-color: var(--x-blue) !important; } .btn-success { background: var(--x-green) !important; border-color: var(--x-green) !important; } .btn-warning { background: var(--x-orange) !important; border-color: var(--x-orange) !important; } .btn-danger { background: var(--x-red) !important; border-color: var(--x-red) !important; } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .8 !important; } `; boxStyle = ` .movie-box, .avatar-box, .sample-box { width: var(--x-thumb-w) !important; margin: 10px !important; border: none !important; } .movie-box .photo-frame, .avatar-box .photo-frame, .sample-box .photo-frame { height: auto !important; margin: 10px !important; border: none !important; } .movie-box .photo-frame { aspect-ratio: var(--x-thumb-ratio); } .avatar-box .photo-frame { aspect-ratio: var(--x-avatar-ratio); } .sample-box .photo-frame { aspect-ratio: var(--x-sprite-ratio); } .movie-box img, .avatar-box img, .sample-box img { width: 100% !important; min-width: unset !important; max-width: none !important; height: 100% !important; min-height: unset !important; max-height: none !important; margin: 0 !important; object-fit: cover !important; } .movie-box > *, .avatar-box > *, .sample-box > * { background: unset !important; } .movie-box > *:nth-child(2), .avatar-box > *:nth-child(2), .sample-box > *:nth-child(2) { height: auto !important; padding: 0 10px 10px !important; line-height: var(--x-line-h) !important; border: none !important; } `; dmBoxStyle = ` .movie-box, .avatar-box, .sample-box { background: var(--x-sub-bgc) !important; } .movie-box > *:nth-child(2), .avatar-box > *:nth-child(2), .sample-box > *:nth-child(2) { color: unset; } .movie-box date { color: var(--x-sub-ftc) !important; } `; // methods _globalSearch = () => { this.globalSearch("#search-input", "/search/%s"); }; _globalClick = callback => { this.globalClick([".movie-box", ".avatar-box"], "", callback); }; modifyMovieBox = (node = DOC) => { const items = node.querySelectorAll(".movie-box"); for (const item of items) { const info = item.querySelector(".photo-info span"); info.innerHTML = info.innerHTML.replace(/<\/?span.*>|<br>/g, ""); const title = info.firstChild; if (!title) continue; const titleText = title.textContent.trim(); const _title = DOC.create("div", { title: titleText, class: "x-ellipsis x-title" }, titleText); info.replaceChild(_title, title); } }; _listMerge = call => { const nav = this.listMerge(); if (!nav?.length) return; if (call) return call(nav); DOC.querySelector("#navbar > ul.nav.navbar-nav")?.insertAdjacentHTML( "beforeend", `<li id="merge" class="dropdown hidden-sm"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" data-hover="dropdown" role="button" aria-expanded="false" > 合并列表 <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> ${nav.reduce((prev, curr) => `${prev}<li><a href="/?merge=${curr}">${curr}</a></li>`, "")} </ul> </li>` ); }; // modules list = { docStart() { const style = ` #waterfall { display: none; opacity: 0; } #waterfall .item { float: unset !important; } #waterfall img { opacity: 0; } .search-header { padding: 0 !important; background: none !important; box-shadow: none !important; } .search-header .nav-tabs { display: none !important; } .alert-common { margin: 20px 20px 0 !important; } .alert-page { margin: 20px !important; } .text-center.hidden-xs { display: none; line-height: 0; } ul.pagination { margin-bottom: 40px; } .movie-box .x-title + div { height: var(--x-line-h) !important; margin: 4px 0; } .avatar-box .pb10 { padding: 0 !important; } .avatar-box .pb10:not(:last-child) { margin-bottom: 4px !important; } .avatar-box p { margin: 0 0 6px !important; } .mleft { display: flex !important; align-items: center; } .mleft .btn-xs { margin: 0 6px 0 0 !important; } `; const dmStyle = ` .pagination > li > a { color: var(--x-ftc) !important; background-color: var(--x-sub-bgc) !important; } .nav-pills > li.active > a { background-color: var(--x-blue) !important; } .pagination > li:not(.active) > a:hover { background-color: var(--x-grey) !important; } `; this.globalDark( `${this.style}${this._style}${this.boxStyle}${this.customStyle}${style}${this.listMovieTitle()}`, `${this.dmStyle}${this._dmStyle}${this.dmBoxStyle}${dmStyle}` ); }, contentLoaded() { this._listMerge(); this._globalSearch(); this._globalClick(url => { const node = DOC.querySelector(`a.movie-box[href="${url}"]`); if (node) this.updateMatchStatus(node); }); const { search } = location; if (location.pathname === "/" && search.startsWith("?merge=")) { const title = search.split("=").pop(); const list = this.getMerge(title); if (!list?.length) return location.replace(location.origin); DOC.title = `${title} - 合并列表 - ${Domain}`; const merge = DOC.querySelector("#merge"); if (merge) { DOC.querySelector("#navbar > ul.nav.navbar-nav > .active").classList.remove("active"); merge.classList.add("active"); } return this.fetchMerge(list); } const nav = DOC.querySelector(".search-header .nav"); if (nav) nav.classList.replace("nav-tabs", "nav-pills"); this.modifyLayout(); }, async fetchMerge(list) { const parseDate = node => node.querySelector("date:last-child").textContent.replaceAll("-", "").trim(); const mergeItem = nodeList => { let items = []; nodeList.forEach(dom => items.push( ...Array.from(this.modifyItem(dom) ?? []).filter(item => item.querySelector(".movie-box")) ) ); items = items.reduce((total, item) => { const [code, date] = item.querySelectorAll("date"); const index = total.findIndex(t => { const [_code, _date] = t.querySelectorAll("date"); return code.textContent === _code.textContent && date.textContent === _date.textContent; }); if (index === -1) total.push(item); return total; }, []); items.sort((first, second) => parseDate(second) - parseDate(first)); return items; }; const _waterfall = DOC.create("div", { id: "waterfall", class: "x-show" }); list = await Promise.all(list.map(item => request(`${location.origin}${item}`))); const items = mergeItem(list); if (items.length) items.forEach(item => _waterfall.appendChild(item)); const waterfall = DOC.querySelector("#waterfall"); waterfall.parentElement.replaceChild(_waterfall, waterfall); const status = DOC.create("div", { id: "x-status" }, items.length ? "加载中..." : "没有更多了"); _waterfall.insertAdjacentElement("afterend", status); if (!items.length) return; const msnry = this.listScroll(_waterfall, ".item"); let isLoading = false; const noMore = () => { window.onscroll = null; status.textContent = "没有更多了"; }; window.onscroll = async () => { if (isLoading) return; const scrollHeight = Math.max(DOC.documentElement.scrollHeight, DOC.body.scrollHeight); const scrollTop = window.pageYOffset || DOC.documentElement.scrollTop || DOC.body.scrollTop; const clientHeight = window.innerHeight || Math.min(DOC.documentElement.clientHeight, DOC.body.clientHeight); if (clientHeight + scrollTop + 40 < scrollHeight) return; isLoading = true; list = list.map(dom => dom.querySelector("#next")?.href ?? "").filter(Boolean); if (!list.length) return noMore(); list = await Promise.all(list.map(item => request(item))); const _items = mergeItem(list); if (!_items.length) return noMore(); _items.forEach(item => _waterfall.appendChild(item)); msnry.appended(_items); isLoading = false; }; }, modifyLayout() { const waterfall = DOC.querySelector("#waterfall"); if (!waterfall) return; const isStarDetail = /^\/(uncensored\/)?star\/\w+/i.test(location.pathname); const _waterfall = waterfall.cloneNode(true); _waterfall.removeAttribute("style"); _waterfall.setAttribute("class", "x-show"); const items = this.modifyItem(_waterfall); const itemsLen = items?.length; if (itemsLen) { _waterfall.innerHTML = ""; for (let index = 0; index < itemsLen; index++) { if (isStarDetail && !index) continue; _waterfall.appendChild(items[index]); } } waterfall.parentElement.replaceChild(_waterfall, waterfall); const infScroll = this.listScroll(_waterfall, ".item", "#next"); if (!infScroll) return DOC.querySelector(".text-center.hidden-xs")?.classList.add("x-show"); infScroll?.on("request", async (_, fetchPromise) => { const { body } = await fetchPromise.then(); if (!body) return; let items = this.modifyItem(body); if (isStarDetail) [_, ...items] = items; infScroll.appendItems(items); infScroll.options.outlayer.appended(items); }); }, modifyItem(container) { const items = container.querySelectorAll(".item"); for (const item of items) { item.removeAttribute("style"); item.setAttribute("class", "item"); this._listMovieImgType(item); this.modifyAvatarBox(item); this.modifyMovieBox(item); } fadeInImg(items); this._driveMatch(container); return items; }, _listMovieImgType(node) { const item = node.querySelector(".movie-box"); if (!item) return; const condition = [ { regex: /\/thumb(s)?\//gi, replace: val => val.replace(/\/thumb(s)?\//gi, "/cover/").replace(/\.jpg/gi, "_b.jpg"), }, { regex: /pics\.dmm\.co\.jp/gi, replace: val => val.replace("ps.jpg", "pl.jpg"), }, ]; this.listMovieImgType(item, condition); }, modifyAvatarBox(node = DOC) { const items = node.querySelectorAll(".avatar-box"); for (const item of items) { const span = item.querySelector("span"); if (span.classList.contains("mleft")) { const title = span.firstChild; const titleText = title.textContent.trim(); const _title = DOC.create("div", { title: titleText, class: "x-line" }, titleText); title.parentElement.replaceChild(_title, title); span.insertAdjacentElement("afterbegin", span.querySelector("button")); continue; } span.classList.add("x-line"); } }, async _driveMatch(node = DOC) { const items = node.querySelectorAll(".movie-box"); for (const item of items) await this.updateMatchStatus(item); }, async updateMatchStatus(node) { const code = node.querySelector("date")?.textContent?.trim(); if (!code) return; const res = await this.driveMatch({ code, res: "list" }); if (!res?.length) return; const frame = node.querySelector(".photo-frame"); frame.classList.add("x-player"); frame.setAttribute("title", "点击播放"); frame.setAttribute("data-code", res[0].pc); node.querySelector(".x-title").classList.add("x-matched"); }, }; genre = { docStart() { const style = ` .alert-common { margin: 20px 0 0 !important; } .container-fluid { padding: 0 20px !important; } h4 { margin: 20px 0 10px 0 !important; } .genre-box { margin: 10px 0 20px 0 !important; padding: 20px !important; } .genre-box a { text-align: left !important; cursor: pointer !important; user-select: none !important; } .genre-box input { margin: 0 !important; vertical-align: middle !important; } .x-last-box { margin-bottom: 70px !important; } button.btn.btn-danger.btn-block.btn-genre { position: fixed !important; bottom: 0 !important; left: 0 !important; margin: 0 !important; border: none !important; border-radius: 0 !important; } `; const dmStyle = ` .genre-box { background: var(--x-sub-bgc) !important; } `; this.globalDark(`${this.style}${this._style}${style}`, `${this.dmStyle}${this._dmStyle}${dmStyle}`); }, contentLoaded() { this._listMerge(); this._globalSearch(); if (!DOC.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return; const box = DOC.querySelectorAll(".genre-box"); box[box.length - 1].classList.add("x-last-box"); DOC.querySelector(".container-fluid.pt10").addEventListener("click", ({ target }) => { if (target.nodeName !== "A" || !target.classList.contains("text-center")) return; const checkbox = target.querySelector("input"); checkbox.checked = !checkbox.checked; }); }, }; forum = { docStart() { const style = ` .bcpic, .banner728, .banner300, .jav-footer { display: none !important; } #toptb { position: fixed !important; top: 0 !important; right: 0 !important; left: 0 !important; z-index: 999 !important; border-color: #e7e7e7; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); } #search-input { border-right: none !important; } .jav-button { margin-top: -3px !important; margin-left: -4px !important; } #wp { margin-top: 55px !important; } .biaoqicn_show a { width: 20px !important; height: 20px !important; line-height: 20px !important; opacity: .7; } #online .bm_h { border-bottom: none !important; } #online .bm_h .o img { margin-top: 48%; } #moquu_top { right: 20px; bottom: 20px; } `; this.globalDark(`${this.style}${this._style}${style}`); }, contentLoaded() { this._listMerge(nav => { DOC.querySelector("#toptb ul")?.insertAdjacentHTML( "beforeend", `<li class="nav-title nav-inactive"><a href="/?merge=${nav[0]}">合并列表</a></li>` ); }); this._globalSearch(); }, }; movie = { params: {}, magnets: null, docStart() { const style = ` #mag-submit-show, #mag-submit, #magnet-table, h4[style="position:relative"], h4[style="position:relative"] + .row, .info span.glyphicon-info-sign { display: none !important; } html { padding-right: 0 !important; } @media (min-width: 1270px) { .container { width: 1270px; } } .container { margin-bottom: 40px; } .row.movie { padding: 0 !important; } #magneturlpost + .movie { margin-top: 20px !important; padding: 10px !important; } .screencap, .info { padding: 10px !important; border: none !important; } .bigImage { position: relative; display: block; overflow: hidden; opacity: 0; aspect-ratio: var(--x-cover-ratio); } .info p { line-height: var(--x-line-h) !important; } .star-box img { width: 100% !important; height: auto !important; margin: 0 !important; } .star-box .star-name { padding: 6px 0 !important; background: unset !important; border: none !important; } #avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -10px !important; word-spacing: -20px; } .avatar-box, .sample-box, .movie-box { vertical-align: top !important; word-spacing: 0 !important; } .movie-box > *:nth-child(2) { min-height: 32px !important; text-align: left !important; } .x-grass-img { object-fit: cover; } .x-grass-mask, .x-contain { position: absolute; top: 0; left: 0; width: 100% !important; height: 100% !important; } .x-grass-mask { backdrop-filter: blur(50px); } .x-contain { z-index: -1; object-fit: contain; border: none; opacity: 0; } video.x-contain { background-color: #000; } .x-contain.x-in { z-index: 9 !important; } .mfp-img { max-height: unset !important; } .btn-group button { border-width: .5px !important; } .x-res * { color: #CC0000 !important; } .x-table { margin: 0 !important; } .x-caption { display: flex; align-items: center; } .x-caption .label { position: unset !important; } .x-table tr { display: table; width: 100%; table-layout: fixed; } .x-table tr > * { vertical-align: middle !important; border-left: none !important; } .x-table tr > *:first-child { width: 50px; } .x-table tr > *:nth-child(2) { width: 33.3%; } .x-table tr > *:last-child, .x-table tfoot tr > th:not(:nth-child(3)) { border-right: none !important; } .x-table tbody { display: block; max-height: calc(37px * 10 - 1px); overflow: overlay; table-layout: fixed; } .x-table tbody tr > * { border-top: none !important; } .x-table tbody tr:last-child > *, .x-table tfoot tr > * { border-bottom: none !important; } `; const dmStyle = ` .movie, .btn-group button[disabled], .star-box-up li, .table-striped > tbody > tr:nth-of-type(odd) { background: var(--x-sub-bgc) !important; } .btn-group button.active, .x-offline.active { background: var(--x-bgc) !important; } tbody tr:hover, .table-striped > tbody > tr:nth-of-type(odd):hover { background: var(--x-grey) !important; } `; this.globalDark( `${this.style}${this._style}${this.boxStyle}${this.customStyle}${style}`, `${this.dmStyle}${this._dmStyle}${this.dmBoxStyle}${dmStyle}` ); }, contentLoaded() { addMeta(); this._listMerge(); this._globalSearch(); this._globalClick(); this.params = this.getParams(); addCopyTarget("h3", { title: "复制标题" }); this.initSwitch(); this.updateSwitch({ key: "img", title: "大图" }); this.updateSwitch({ key: "video", title: "预览" }); this.updateSwitch({ key: "player", title: "视频", type: "video" }); this._movieTitle(); addCopyTarget("span[style='color:#CC0000;']", { title: "复制番号" }); this._movieJump(); this._movieStar(); this._driveMatch(); DOC.querySelector(".x-offline")?.addEventListener("click", e => this._driveOffline(e)); const tableObs = new MutationObserver((_, obs) => { obs.disconnect(); this.refactorTable(); }); tableObs.observe(DOC.querySelector("#movie-loading"), { attributes: true, attributeFilter: ["style"] }); this.modifyMovieBox(); }, getParams() { const info = DOC.querySelector(".info"); const { textContent } = info; return { title: DOC.querySelector("h3").textContent, code: info.querySelector("span[style='color:#CC0000;']").textContent, date: info.querySelector("p:nth-child(2)").childNodes[1].textContent.trim(), studio: textContent.match(/(?<=製作商: ).+/g)?.pop(0), star: !/暫無出演者資訊/g.test(textContent), }; }, _movieJump() { const { code } = this.params; if (!code) return; const start = () => { DOC.querySelector("span[style='color:#CC0000;']")?.insertAdjacentHTML( "beforeend", `<a class="x-ml" href="https://javdb.com/search?q=${code}#jump" title="跳转 JavDB">跳转</a>` ); }; this.movieJump(start); }, initSwitch() { const bigImage = DOC.querySelector(".bigImage"); const img = bigImage.querySelector("img"); img.classList.add("x-grass-img"); bigImage.insertAdjacentHTML( "beforeend", `<div class="x-grass-mask"></div><img src="${img.src}" id="x-switch-cover" class="x-contain x-in">` ); bigImage.classList.add("x-in"); const info = DOC.querySelector(".info"); info.insertAdjacentHTML( "afterbegin", `<div class="btn-group btn-group-justified mb10" hidden id="x-switch" role="group"> <div class="btn-group btn-group-sm" role="group" title="点击放大"> <button type="button" class="btn btn-default active" for="x-switch-cover">封面</button> </div> </div>` ); const switcher = info.querySelector("#x-switch"); switcher.addEventListener("click", ({ target }) => { const id = target.getAttribute("for"); if (!id) return; const { classList } = target; const curActive = bigImage.querySelector(".x-contain.x-in"); if (classList.contains("active")) { curActive.nodeName === "VIDEO" ? (curActive.muted = !curActive.muted) : bigImage.click(); return; } curActive.classList.toggle("x-in"); curActive?.pause && curActive.pause(); const active = bigImage.querySelector(`#${id}`); active.classList.toggle("x-in"); if (active.nodeName === "IMG") { const { src } = active; bigImage.href = src; bigImage.querySelector(".x-grass-img").src = src; } active?.play && active.play(); active?.focus && active.focus(); const curTarget = switcher.querySelector("button.active"); curTarget.classList.toggle("active"); curTarget.setAttribute("title", "点击切换"); classList.toggle("active"); target.removeAttribute("title"); }); }, async updateSwitch({ key, title, type }) { if (!type) type = key; const id = `x-switch-${key}`; const switcher = DOC.querySelector("#x-switch"); const start = () => { if (!switcher.classList.contains("x-show")) switcher.classList.add("x-show"); switcher.insertAdjacentHTML( "beforeend", `<div class="btn-group btn-group-sm" role="group"> <button type="button" class="btn btn-default" for="${id}" disabled>查询中...</button> </div>` ); }; const src = await this[`movie${key[0].toUpperCase()}${key.slice(1)}`](this.params, start); const node = switcher.querySelector(`button[for="${id}"]`); if (!node) return; node.textContent = `${src?.length ? "查看" : "暂无"}${title}`; if (!src?.length) return; node.parentNode.setAttribute("title", "点击放大或切换静音"); node.removeAttribute("disabled"); node.setAttribute("title", "点击切换"); const item = DOC.create(type, { id, class: "x-contain" }); if (typeof src === "string") item.src = src; if (type === "video") { if (Object.prototype.toString.call(src) === "[object Array]") { src.forEach(params => { const source = DOC.create("source", params); item.appendChild(source); }); } item.controls = true; item.currentTime = 3; item.muted = true; item.preload = "metadata"; item.addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); const { target: video } = e; video.paused ? video.play() : video.pause(); }); } DOC.querySelector(".bigImage").insertAdjacentElement("beforeend", item); }, async _movieTitle() { const start = () => { DOC.querySelector("#x-switch").insertAdjacentHTML( "afterend", `<p><span class="header">机翻标题: </span><span class="x-transTitle">查询中...</span></p>` ); }; const transTitle = await this.movieTitle(this.params, start); const transTitleNode = DOC.querySelector(".x-transTitle"); if (transTitleNode) transTitleNode.textContent = transTitle ?? "查询失败"; }, async _movieStar() { const start = () => { const starShow = DOC.querySelector("p.star-show"); starShow.nextElementSibling.nextSibling.remove(); starShow.insertAdjacentHTML("afterend", `<p class="x-star">查询中...</p>`); }; const star = await this.movieStar(this.params, start); const starNode = DOC.querySelector(".x-star"); if (!starNode) return; starNode.innerHTML = !star?.length ? "暂无演员数据" : star.reduce( (acc, cur) => `${acc}<span class="genre"><label><a href="/search/${cur}">${cur}</a></label></span>`, "" ); }, async _driveMatch() { const start = () => { if (DOC.querySelector(".x-res")) return; GM_addStyle(`tbody a[data-magnet] { display: inline !important; }`); DOC.querySelector(".info").insertAdjacentHTML( "beforeend", `<p class="header">网盘资源:</p><p class="x-res">查询中...</p><button type="button" class="btn btn-default btn-sm btn-block x-offline" data-magnet="all">一键离线</button>` ); }; const res = await this.driveMatch(this.params, start); const resNode = DOC.querySelector(".x-res"); if (!resNode) return; resNode.innerHTML = !res?.length ? "暂无网盘资源" : res.reduce( (acc, { pc, t, n }) => `${acc}<div class="x-line"><a href="${this.pcUrl}${pc}" target="_blank" title="${t} / ${n}">${n}</a></div>`, "" ); }, refactorTable() { const table = DOC.querySelector("#magnet-table"); table.parentElement.innerHTML = ` <table class="table table-striped table-hover table-bordered x-table"> <caption><div class="x-caption">重构的表格</div></caption> <thead> <tr> <th scope="col">#</th> <th scope="col">磁力名称</th> <th scope="col">档案大小</th> <th scope="col" class="text-center">分享日期</th> <th scope="col" class="text-center">来源</th> <th scope="col" class="text-center">字幕</th> <th scope="col">操作</th> </tr> </thead> <tbody> <tr><th scope="row" colspan="7" class="text-center text-muted">暂无数据</th></tr> </tbody> <tfoot> <tr> <th scope="row"></th> <th></th> <th colspan="4" class="text-right">总数</th> <td>0</td> </tr> </tfoot> </table> `; DOC.querySelector(".x-table tbody").addEventListener("click", e => { !handleCopyTxt(e, "复制成功") && this._driveOffline(e); }); const magnets = []; for (const tr of table.querySelectorAll("tr")) { const [link, size, date] = tr.querySelectorAll("td"); const _link = link?.querySelector("a"); const _size = size?.textContent.trim(); if (!_link || !_size || !date) continue; magnets.push({ name: _link.textContent.trim(), link: _link.href.split("&")[0], zh: !!link.querySelector("a.btn.btn-mini-new.btn-warning.disabled"), size: _size, bytes: transToBytes(_size), date: date.textContent.trim(), }); } this.refactorTd(magnets); this._movieMagnet(); }, async _movieMagnet() { const start = () => { DOC.querySelector(".x-caption").insertAdjacentHTML( "beforeend", `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 磁力搜索</span><span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 自动去重</span>` ); }; const magnets = await this.movieMagnet(this.params, start); if (magnets?.length) this.refactorTd(magnets); }, refactorTd(magnets) { const table = DOC.querySelector(".x-table"); const caption = table.querySelector(".x-caption"); let subStart = () => { caption.insertAdjacentHTML( "beforeend", `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 字幕筛选</span>` ); }; let sortStart = () => { caption.insertAdjacentHTML( "beforeend", `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 磁力排序</span>` ); }; if (this.magnets) { subStart = null; sortStart = null; magnets = unique([...this.magnets, ...magnets], "link"); } table.querySelector("tfoot td").textContent = magnets.length; magnets = this.movieSub(magnets, subStart); magnets = this.movieSort(magnets, sortStart); this.magnets = magnets; magnets = this.createTd(magnets); if (!magnets) return; table.querySelector("tbody").innerHTML = magnets; if (!subStart || !sortStart) return; const node = table.querySelector("thead th:last-child"); node.innerHTML = `<a href="javascript:void(0);" title="复制所有磁力链接">全部复制</a>`; node.querySelector("a").addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); GM_setClipboard(this.magnets.map(({ link }) => link).join("\n")); const { target } = e; target.textContent = "复制成功"; const timer = setTimeout(() => { target.textContent = "全部复制"; clearTimeout(timer); }, 300); }); }, createTd(magnets) { if (!magnets.length) return; return magnets.reduce( (acc, { name, link, size, date, from, href, zh }, index) => ` ${acc} <tr> <th scope="row">${index + 1}</th> <th class="x-line" title="${name}"> <a href="${link}">${name}</a> </th> <td>${size}</td> <td class="text-center">${date}</td> <td class="text-center"> <a${href ? ` href="${href}" target="_blank" title="查看详情"` : ""}> <code>${from ?? Domain}</code> </a> </td> <td class="text-center"> <span class="glyphicon ${ zh ? "glyphicon-ok-circle text-success" : "glyphicon-remove-circle text-danger" }" > </span> </td> <td> <a href="javascript:void(0);" data-copy="${link}" class="x-mr" title="复制磁力链接" > 复制链接 </a> <a hidden href="javascript:void(0);" data-magnet="${link}" class="text-success" title="仅添加离线任务" > 添加离线 </a> </td> </tr>`, "" ); }, async _driveOffline(e) { await this.driveOffline(e, { ...this.params, magnets: this.magnets }); await delay(1); this._driveMatch(); }, }; } // javdb class JavDB extends Common { constructor() { super(); return super.init(); } excludeMenu = ["G_DARK", "L_MIT", "M_STAR", "M_SUB"]; routes = { list: /^\/$|^\/(guess|censored|uncensored|western|fc2|anime|search|video_codes|tags|rankings|actors|series|makers|directors|publishers)/i, movie: /^\/v\//i, others: /.*/i, }; // styles _style = ` html { padding: 0 !important; overflow: overlay; } body { padding-top: 3.25rem; } .section { padding: 0; } section.section { padding: 20px; } #search-type, #video-search { border: none; } #video-search:hover, #video-search:focus { z-index: auto; } .float-buttons { right: 8px; } #footer, nav.app-desktop-banner { display: none !important; } [data-theme="dark"] ::-webkit-scrollbar-thumb { background: #313131 !important; } [data-theme="dark"] img { filter: brightness(0.9) contrast(0.9); } `; // methods _globalSearch = () => { this.globalSearch("#video-search", "/search?q=%s"); }; _listMerge = () => { const nav = this.listMerge(); if (!nav?.length) return; DOC.querySelector("#navbar-menu-hero .navbar-start")?.insertAdjacentHTML( "beforeend", `<div class="navbar-item has-dropdown is-hoverable"> <a class="navbar-link" href="/?merge=${nav[0]}">合并列表</a> <div class="navbar-dropdown is-boxed"> ${nav.reduce( (prev, curr) => `${prev}<a class="navbar-item" href="/?merge=${curr}">${curr}</a>`, "" )} </div> </div>` ); }; // modules list = { docStart() { const style = ` @media (max-width: 575.98px) { .movie-list.v, .actors { grid-template-columns: repeat(2, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(1, minmax(0, 1fr)); } } @media (min-width: 576px) { .movie-list.v, .actors { grid-template-columns: repeat(3, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (min-width: 768px) { .movie-list.v, .actors { grid-template-columns: repeat(4, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (min-width: 992px) { .movie-list.v, .actors { grid-template-columns: repeat(5, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (min-width: 1200px) { .movie-list.v, .actors { grid-template-columns: repeat(6, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (min-width: 1400px) { .movie-list.v, .actors { grid-template-columns: repeat(7, minmax(0, 1fr)); } .movie-list.h { grid-template-columns: repeat(4, minmax(0, 1fr)); } } .movie-list, .actors, .section-container { display: none; gap: 20px; margin: 0 !important; padding: 0 0 20px; } .movie-list img, .actors img, .section-container img { opacity: 0; transition: opacity 0.25s linear !important; } .movie-list .box { padding: 0 0 10px; } a.box:focus, a.box:hover, [data-theme="dark"] a.box:focus, [data-theme="dark"] a.box:hover { box-shadow: none !important; } [data-theme="dark"] .box:focus, [data-theme="dark"] .box:hover { background-color: #0a0a0a !important; } .movie-list .item .cover { padding: 0 !important; } .movie-list.v .item .cover { aspect-ratio: var(--x-thumb-ratio); } .movie-list.h .item .cover { aspect-ratio: var(--x-cover-ratio); } .movie-list .item .cover img { width: 100%; height: 100%; object-fit: contain; } .movie-list .item .cover:hover img { z-index: 0; transform: none; } .movie-list .item .video-title { margin: 10px 10px 0; padding: 0; font-size: 14px; line-height: var(--x-line-h); } .movie-list .item .score { padding: 10px 10px 0; } .movie-list .item .meta { padding: 4px 10px 0; } .movie-list .box .tags { min-height: 36px; margin-bottom: -8px; padding: 4px 10px 0; } .movie-list .box .tags .tag { margin-bottom: 8px; } .actors .box { margin-bottom: 0; padding-bottom: 10px; font-size: 14px; } .actor-box a strong { padding: 10px 10px 0; line-height: unset; } .section-container .box { font-size: 14px; } nav.pagination { display: none; margin: 0 -4px !important; padding: 20px 0 40px; } nav.pagination, :root[data-theme="dark"] nav.pagination { border-top: none !important; } .awards { padding-bottom: 20px; } .awards:last-child { padding-bottom: 0; } `; this.globalDark(`${this.style}${this.customStyle}${this._style}${style}${this.listMovieTitle()}`); }, contentLoaded() { this.captureJump(); this._listMerge(); this._globalSearch(); this.globalClick([".movie-list .box", ".actors .box a", ".section-container .box"], "", url => { url = url.replace(location.origin, ""); const node = DOC.querySelector(`.movie-list .box[href="${url}"]`); if (node) this.updateMatchStatus(node); }); const { search } = location; if (location.pathname === "/" && search.startsWith("?merge=")) { const title = search.split("=").pop(); const list = this.getMerge(title); if (!list?.length) return location.replace(location.origin); DOC.title = `${title} - 合并列表 - ${Domain}`; return this.fetchMerge(list); } const selectors = [".movie-list", ".actors", ".section-container"]; if (DOC.querySelectorAll(selectors).length === 1) { return selectors.forEach(item => this.modifyLayout(item)); } GM_addStyle(` .movie-list, .actors, .section-container { display: grid; } .movie-list img, .actors img, .section-container img { opacity: 1; } nav.pagination { display: flex; } `); }, captureJump() { let { pathname, hash, search } = location; if (pathname !== "/search" || hash !== "#jump") return; let res = {}; search .replace("?", "") .split("&") .forEach(item => { const [key, val] = item.split("="); res[key] = val; }); res = res["q"]; if (!res) return; const { regex } = codeParse(res); const node = Array.from(DOC.querySelectorAll(".movie-list .item a") ?? []).find(item => { return regex.test(item.querySelector(".video-title strong")?.textContent ?? ""); }); if (node?.href) location.replace(node.href); }, async fetchMerge(list) { GM_addStyle(` .tabs.main-tabs.is-boxed, .toolbar { display: none; } section.section { padding-bottom: 0; } `); const selectors = ".movie-list"; const parseDate = node => node.querySelector(".meta").textContent.replaceAll("-", "").trim(); const mergeItem = nodeList => { let items = []; nodeList.forEach(dom => items.push(...Array.from(this.modifyItem(dom, selectors) ?? []))); items = items.reduce((total, item) => { const code = item.querySelector(".video-title strong"); const date = item.querySelector(".meta"); const index = total.findIndex(t => { const _code = t.querySelector(".video-title strong"); const _date = t.querySelector(".meta"); return code.textContent === _code.textContent && date.textContent === _date.textContent; }); if (index === -1) total.push(item); return total; }, []); items.sort((first, second) => parseDate(second) - parseDate(first)); return items; }; const container = DOC.querySelector(selectors); const _container = container.cloneNode(true); _container.style.cssText += "display:grid"; _container.innerHTML = ""; list = await Promise.all(list.map(item => request(`${location.origin}${item}`))); const items = mergeItem(list); if (items.length) items.forEach(item => _container.appendChild(item)); container.parentElement.replaceChild(_container, container); const status = DOC.create("div", { id: "x-status" }, items.length ? "加载中..." : "没有更多了"); _container.insertAdjacentElement("afterend", status); if (!items.length) return; let isLoading = false; const noMore = () => { window.onscroll = null; status.textContent = "没有更多了"; }; window.onscroll = async () => { if (isLoading) return; const scrollHeight = Math.max(DOC.documentElement.scrollHeight, DOC.body.scrollHeight); const scrollTop = window.pageYOffset || DOC.documentElement.scrollTop || DOC.body.scrollTop; const clientHeight = window.innerHeight || Math.min(DOC.documentElement.clientHeight, DOC.body.clientHeight); if (clientHeight + scrollTop + 40 < scrollHeight) return; isLoading = true; list = list.map(dom => dom.querySelector(".pagination-next")?.href ?? "").filter(Boolean); if (!list.length) return noMore(); list = await Promise.all(list.map(item => request(item))); const _items = mergeItem(list); if (!_items.length) return noMore(); _items.forEach(item => _container.appendChild(item)); isLoading = false; }; }, modifyLayout(selectors) { const container = DOC.querySelector(selectors); if (!container) return; const _container = container.cloneNode(true); this.modifyItem(_container, selectors); container.parentElement.replaceChild(_container, container); _container.style.cssText += "display:grid"; const setSection = () => GM_addStyle(`section.section { padding-bottom: 0; }`); const infScroll = this.listScroll(_container, "", ".pagination-next"); if (!infScroll) { const pagination = DOC.querySelector("nav.pagination"); if (pagination) { pagination.classList.add("x-flex"); setSection(); } return; } setSection(); infScroll?.on("request", async (_, fetchPromise) => { const { body } = await fetchPromise.then(); if (!body) return; const items = this.modifyItem(body, selectors); infScroll.appendItems(items); }); }, modifyItem(container, selectors) { const items = []; container.querySelectorAll(`${selectors} a`).forEach(item => { const _item = item.closest(`${selectors} > *`); if (_item) { this.modifyMovieBox(_item); items.push(_item); } }); fadeInImg(items); this._driveMatch(container); return items; }, modifyMovieBox(node = DOC) { const items = node.querySelectorAll(".box"); for (const item of items) { item?.querySelector(".video-title")?.classList.add("x-ellipsis", "x-title"); } }, async _driveMatch(node = DOC) { const items = node.querySelectorAll(".movie-list .box"); for (const item of items) await this.updateMatchStatus(item); }, async updateMatchStatus(node) { const code = node.querySelector(".video-title strong")?.textContent?.trim(); if (!code) return; const res = await this.driveMatch({ code, res: "list" }); if (!res?.length) return; const frame = node.querySelector(".cover"); frame.classList.add("x-player"); frame.setAttribute("title", "点击播放"); frame.setAttribute("data-code", res[0].pc); node.querySelector(".x-title").classList.add("x-matched"); }, }; movie = { params: {}, magnets: [], docStart() { const style = ` .first-block .copy-to-clipboard, .review-buttons .panel-block:nth-child(2), .top-meta > *:not(span.tag) { display: none; } img { width: 100% !important; vertical-align: middle; } h2.title { margin-bottom: 10px !important; } .video-meta-panel { margin-bottom: 20px; padding: 0; } .video-meta-panel > .columns { position: relative; margin: 0; overflow: hidden; } @media screen and (min-width: 1024px) { .video-meta-panel > .columns { align-items: start; } } .video-meta-panel > .columns > .column { padding: 10px; } .column-video-cover { position: relative; margin: 10px; padding: 0 !important; overflow: hidden; background-color: #000; aspect-ratio: var(--x-cover-ratio); } @media only screen and (max-width: 1024px) { .video-meta-panel .column-video-cover { width: auto !important; margin-bottom: 0; } #magnets-content > .columns { padding: 0; } } .column-video-cover .cover-container { position: static !important; display: inline !important; } .column-video-cover .cover-container::after { height: 100%; } .column-video-cover .cover-container .play-button { z-index: -1; } .x-contain.x-in + .play-button { z-index: auto; } .preview-images img { height: 100% !important; object-fit: cover; } .column-video-cover a > img { max-height: unset; opacity: 0; } .x-contain { position: absolute; top: 0; left: 0; z-index: -1; width: 100% !important; height: 100% !important; object-fit: contain !important; border: none; opacity: 0; } .x-contain.x-in { z-index: auto; display: block !important; } .movie-panel-info div.panel-block { padding: 10px 0; font-size: 14px; } .movie-panel-info > div.panel-block:first-child { padding-top: 0; } .movie-panel-info > div.panel-block:last-child { padding-bottom: 0; } .video-detail > .columns { margin: 0 0 20px; } .video-detail > .columns > .column { padding: 0; } .message-body { padding: 10px; } .video-panel .tile-images { gap: 10px; } @media (max-width: 575.98px) { .video-panel .tile-images { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (min-width: 576px) { .video-panel .tile-images { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (min-width: 768px) { .video-panel .tile-images { grid-template-columns: repeat(4, minmax(0, 1fr)); } } @media (min-width: 992px) { .video-panel .tile-images { grid-template-columns: repeat(5, minmax(0, 1fr)); } } @media (min-width: 1200px) { .video-panel .tile-images { grid-template-columns: repeat(6, minmax(0, 1fr)); } } @media (min-width: 1400px) { .video-panel .tile-images { grid-template-columns: repeat(7, minmax(0, 1fr)); } } .preview-video-container::after { height: 100%; } .preview-images > a { aspect-ratio: var(--x-sprite-ratio); } #magnets > .message { margin-bottom: 0; } .top-meta { padding: 0 !important; } .top-meta > span.tag { margin: 0 5px 10px 0; } #magnets-content { max-height: 400px; overflow: auto; } #magnets-content > .columns { margin: 0; padding: 5px 0; } #magnets-content > .columns > .column { display: flex; align-items: center; margin: 0; padding: 5px 10px; } #magnets-content .tag, #magnets-content .button { margin: 0; } .review-items .review-item { padding: 10px 0; } .review-items .review-item:first-child { padding-top: 0; } .review-items .review-item:last-child { padding-bottom: 0; } .message-header { padding: 8px 10px; } .tile-images.tile-small .tile-item { padding-bottom: 10px; background-color: #fff; } [data-theme="dark"] .tile-images.tile-small .tile-item { background-color: #0a0a0a; } .tile-images.tile-small .tile-item img { margin-bottom: 10px; } .tile-images.tile-small .tile-item > div { padding: 0 10px !important; } #x-switch { display: none; } #x-switch > * { flex: 1; } .x-from { min-width: 70px; } .x-offline { width: 100%; } `; this.globalDark(`${this.style}${this.customStyle}${this._style}${style}`); }, contentLoaded() { addMeta(); this._listMerge(); this._globalSearch(); this.globalClick([".tile-images.tile-small a.tile-item"]); const preview = DOC.querySelector(".preview-images"); if (preview && !preview.querySelector("a")) preview.closest(".columns").remove(); this.params = this.getParams(); addCopyTarget("h2.title", { title: "复制标题" }); addCopyTarget(".first-block .value", { title: "复制番号" }); this._movieJump(); this.initSwitch(); this.updateSwitch({ key: "img", title: "大图" }); this.updateSwitch({ key: "video", title: "预览" }); this.updateSwitch({ key: "player", title: "视频", type: "video" }); this._movieTitle(); this._movieMagnet(); this._driveMatch(); DOC.querySelector(".x-offline")?.addEventListener("click", e => this._driveOffline(e)); }, getParams() { const infos = Array.from(DOC.querySelectorAll(".movie-panel-info > .panel-block") ?? []); const findInfos = label => { return ( infos .find(info => info.querySelector("strong")?.textContent === label) ?.querySelector(".value") ?.textContent?.trim() ?? "" ); }; return { title: DOC.querySelector("h2.title").textContent.trim(), code: DOC.querySelector(".first-block .value").textContent.trim(), date: findInfos("日期:"), studio: findInfos("片商:"), }; }, _movieJump() { const { code } = this.params; if (code.startsWith("FC2")) return; const node = DOC.querySelector(".first-block .value"); const prefix = node.querySelector("a")?.textContent ?? ""; if (!prefix || prefix === "复制") return; const start = () => { node.insertAdjacentHTML( "beforeend", `<a class="x-ml" href="https://www.javbus.com/${code}" title="跳转 JavBus">跳转</a>` ); }; this.movieJump(start); }, initSwitch() { const info = DOC.querySelector(".movie-panel-info"); info.insertAdjacentHTML( "afterbegin", `<div class="panel-block" id="x-switch"> <a class="button is-small is-light is-active" for="x-switch-cover" title="点击放大或切换静音">封面</a> </div>` ); const cover = DOC.querySelector(".column-video-cover a > img"); cover.id = "x-switch-cover"; cover.classList.add("x-contain", "x-in"); DOC.querySelector("#x-switch").addEventListener("click", ({ target }) => { const { classList } = target; if ( target.nodeName !== "A" || target.getAttribute("disabled") === "" || classList.contains("is-loading") ) { return; } const id = target.getAttribute("for"); const item = DOC.querySelector(`#${id}`); if (classList.contains("is-active")) { item.parentNode.click(); item.muted = !item.muted; } else { const preItem = DOC.querySelector(".column-video-cover .x-contain.x-in"); preItem?.pause && preItem.pause(); preItem.classList.toggle("x-in"); item.classList.toggle("x-in"); item?.play && item.play(); item?.focus && item.focus(); const preTarget = DOC.querySelector("#x-switch a.is-active"); preTarget.removeAttribute("title"); preTarget.classList.toggle("is-active"); target.classList.toggle("is-active"); target.setAttribute("title", "点击放大或切换静音"); } }); }, async updateSwitch({ key, title, type }) { if (!type) type = key; const id = `x-switch-${key}`; const switcher = DOC.querySelector("#x-switch"); const start = () => { if (!switcher.classList.contains("x-flex")) switcher.classList.add("x-flex"); switcher.insertAdjacentHTML( "beforeend", `<a class="button is-small is-light is-loading" for="${id}">查看${title}</a>` ); }; const src = await this[`movie${key[0].toUpperCase()}${key.slice(1)}`](this.params, start); const node = switcher.querySelector(`a[for="${id}"]`); if (!node) return; node.classList.remove("is-loading"); if (!src?.length) { node.setAttribute("disabled", ""); node.textContent = `暂无${title}`; return; } let item = DOC.create(type, { id, class: "x-contain" }); if (typeof src === "string") item.src = src; if (type === "video") { if (Object.prototype.toString.call(src) === "[object Array]") { src.forEach(params => { const source = DOC.create("source", params); item.appendChild(source); }); } item.controls = true; item.currentTime = 3; item.muted = true; item.preload = "metadata"; item.addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); const { target: video } = e; video.paused ? video.play() : video.pause(); }); } else { item = DOC.create("a", { "data-fancybox": "gallery", href: item.src }, item); } DOC.querySelector(".column-video-cover").insertAdjacentElement("beforeend", item); }, async _movieTitle() { const start = () => { DOC.querySelector("#x-switch").insertAdjacentHTML( "afterend", `<div class="panel-block"><strong>机翻:</strong> <span class="value x-transTitle">查询中...</span></div>` ); }; const transTitle = await this.movieTitle(this.params, start); const transTitleNode = DOC.querySelector(".x-transTitle"); if (transTitleNode) transTitleNode.textContent = transTitle ?? "查询失败"; }, async _driveMatch() { const start = () => { if (DOC.querySelector(".x-res")) return; GM_addStyle(`#magnets-content button.button.x-hide{ display: flex; }`); DOC.querySelector(".movie-panel-info").insertAdjacentHTML( "beforeend", `<div class="panel-block"><strong>资源:</strong> <span class="value x-res">查询中...</span></div><div class="panel-block"><button class="button is-info is-small x-offline" data-magnet="all">一键离线</button></div>` ); }; const res = await this.driveMatch(this.params, start); const resNode = DOC.querySelector(".x-res"); if (!resNode) return; resNode.innerHTML = !res?.length ? "暂无网盘资源" : res.reduce( (acc, { pc, t, n }) => `${acc}<div class="x-ellipsis"><a href="${this.pcUrl}${pc}" target="_blank" title="${t} / ${n}">${n}</a></div>`, "" ); }, async _movieMagnet() { const start = () => { const node = DOC.querySelector(".top-meta"); if (!node) return; node.insertAdjacentHTML( "beforeend", `<span class="tag is-success">磁力搜索</span><span class="tag is-success">自动去重</span>` ); }; let magnets = (await this.movieMagnet(this.params, start)) ?? []; const curMagnets = Array.from(DOC.querySelectorAll("#magnets-content .item") ?? []).map(item => { const name = item.querySelector(".magnet-name"); const size = name.querySelector(".meta")?.textContent.split(",")[0].trim() ?? ""; return { bytes: transToBytes(size), date: item.querySelector(".date .time").textContent, from: Domain, link: name.querySelector("a").href.split("&")[0], name: name.querySelector(".name").textContent, size, zh: !!name?.querySelector(".tags .tag.is-warning.is-small.is-light"), }; }); magnets = unique([...curMagnets, ...magnets], "link"); this._movieSort(magnets); }, _movieSort(magnets) { const start = () => { const node = DOC.querySelector(".top-meta"); if (node) node.insertAdjacentHTML("beforeend", `<span class="tag is-success">磁力排序</span>`); }; magnets = this.movieSort(magnets, start); if (!magnets.length) return; this.magnets = magnets; magnets = magnets.map( ({ link, name, size, zh, date, from, href }, index) => ` <div class="item columns is-desktop${(index + 1) % 2 === 0 ? "" : " odd"}"> <div class="magnet-name column is-four-fifths" title="${name}"> <a href="${link}" class="x-ellipsis"> <span class="name">${name}</span> </a> </div> <div class="column"> <span class="tag is-warning is-small is-light ${zh ? "" : " x-out"}">字幕</span> </div> <div class="date column"> <span class="meta">${size}</span> </div> <div class="date column"> <span class="time">${date}</span> </div> <div class="column"> <a class="tag is-danger is-small is-light x-from" ${href ? `href="${href}" target="_blank" title="查看详情"` : ""} > ${from} </a> </div> <div class="buttons column"> <button class="button is-info is-small" data-copy="${link}" title="复制磁力链接" type="button">复制链接</button><button class="button is-info is-small x-ml x-hide" data-magnet="${link}" title="仅添加离线任务" type="button">添加离线</button> </div> </div> ` ); magnets = magnets.join(""); const node = DOC.querySelector("#magnets-content"); node.innerHTML = magnets; DOC.querySelector("#magnets-content").addEventListener("click", e => { !handleCopyTxt(e, "复制成功") && this._driveOffline(e); }); }, async _driveOffline(e) { await this.driveOffline(e, { ...this.params, magnets: this.magnets }); await delay(1); this._driveMatch(); }, }; others = { docStart() { GM_addStyle(`${this.style}${this._style}`); }, contentLoaded() { this._listMerge(); this._globalSearch(); }, }; } // 115 class Drive115 { contentLoaded() { window.focus(); DOC.querySelector(`#js_ver_code_box button[rel="verify"]`).addEventListener("click", () => { const interval = setInterval(() => { if (DOC.querySelector(".vcode-hint").getAttribute("style").indexOf("none") !== -1) { clearTimer(); window.open("", "_self"); window.close(); } }, 300); const timeout = setTimeout(() => clearTimer(), 600); const clearTimer = () => { clearInterval(interval); clearTimeout(timeout); }; }); } } try { const Process = eval(`new ${Domain}()`); Process.docStart && Process.docStart(); Process.contentLoaded && DOC.addEventListener("DOMContentLoaded", () => Process.contentLoaded()); Process.load && window.addEventListener("load", () => Process.load()); } catch (err) { console.error(`${GM_info.script.name}: 无匹配模块`); } })();