JavBus工具

暗黑模式、滚动加载、预览视频、预览大图、网盘资源匹配、离线下载(115)...

Versione datata 15/12/2021. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name            JavBus工具
// @namespace       https://greasyfork.org/users/175514
// @description     暗黑模式、滚动加载、预览视频、预览大图、网盘资源匹配、离线下载(115)...
// @version         0.3.0
// @icon            https://z3.ax1x.com/2021/10/15/53gMFS.png
// @include         *://*.javbus.com/*
// @include         *://*.115.com/*
// @require         https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js
// @require         https://unpkg.com/infinite-scroll@4/dist/infinite-scroll.pkgd.min.js
// @require         https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js
// @resource        fail https://z3.ax1x.com/2021/10/15/53gcex.png
// @resource        info https://z3.ax1x.com/2021/10/15/53g2TK.png
// @resource        success https://z3.ax1x.com/2021/10/15/53gqTf.png
// @run-at          document-start
// @grant           GM_registerMenuCommand
// @grant           GM_getResourceURL
// @grant           GM_xmlhttpRequest
// @grant           GM_setClipboard
// @grant           GM_notification
// @grant           GM_openInTab
// @grant           GM_addStyle
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_info
// @connect         *
// @license         MIT
// ==/UserScript==

/**
 * 非完全测试,自用Edge
 * TODO:
 * ✔️ 暗黑模式
 * ✔️ 点击事件(新窗口打开,左键前台,右键后台)
 * ✔️ 滚动加载
 * ✔️ 获取预览大图(javstore.net)
 * ✔️ 获取预览视频(r18.com),如有资源点击封面图或样品图像处进行预览
 * ✔️ 获取演员名单(javdb.com)
 * ✔️ 离线下载(请求离线成功2秒后查询结果汇报),自动修改文件名称,(如已设置操作目录cid,自动移动视频文件至对应目录并删除原目录)
 * ✔️ 一键离线(字幕>尺寸>日期)排队执行离线下载,成功一结束排队
 * ✔️ 115账号验证弹窗
 * ✔️ 查询115是否已有离线资源并缓存(默认3天有效),3天后重新查询,支持手动刷新“资源刷新”
 * ✔️ 根据缓存资源数据匹配列表页已有资源状态,添加已有资源列表页
 * ✔️ 支持手动获取操作目录(需设置)下列表数据,自动过滤(视频文件 & >100m)以缓存,匹配列表页(注:网盘列表数据与资源数据并不叠加)
 */

(function () {
	const dm = GM_getValue("dm") ?? matchMedia("(prefers-color-scheme: dark)").matches;
	const lm = GM_getValue("lm") ?? false;
	const ck = GM_getValue("ck") ?? false;

	const rootId = GM_getValue("rid") ?? "";
	const rDate = GM_getValue("rDate") ?? "Unknown";
	const rSize = GM_getValue("rSize") ?? 0;

	const SUFFIX = " !important";
	const { host, pathname } = location;
	let mousePoint = 0;

	const insertBefore = (el, htmlString) => el.insertAdjacentHTML("beforebegin", htmlString);
	const insertAfter = (el, htmlString) => el.insertAdjacentHTML("afterend", htmlString);
	const appendBefore = (el, htmlString) => el.insertAdjacentHTML("afterbegin", htmlString);
	const appendAfter = (el, htmlString) => el.insertAdjacentHTML("beforeend", htmlString);
	const doc = document;
	doc.create = (tag, attr = {}, child = "") => {
		if (!tag) return null;
		tag = doc.createElement(tag);
		Object.keys(attr).forEach(name => tag.setAttribute(name, attr[name]));
		typeof child === "string" && tag.appendChild(doc.createTextNode(child));
		typeof child === "object" && tag.appendChild(child);
		return tag;
	};

	let rd;
	const lf = localforage;
	lf.upItem = async (key, val) => {
		const original = (await lf.getItem(key)) ?? {};
		await lf.setItem(key, Object.assign(original, val));
	};

	const request = (url, method = "GET", data = {} | "", params = {}) => {
		if (!url) return;
		method = method?.toLocaleUpperCase() ?? "GET";
		if (method === "POST") {
			const cType = { "Content-Type": "application/x-www-form-urlencoded" };
			params.headers = Object.assign(params.headers ?? {}, cType);
		}
		if (typeof data === "object") {
			const keys = Object.keys(data);
			if (keys.length) {
				data = keys.reduce((pre, cur) => `${pre}${pre ? "&" : ""}${cur}=${encodeURIComponent(data[cur])}`, "");
				if (method === "GET") url = `${url}?${data}`;
			}
		}
		return new Promise(resolve => {
			GM_xmlhttpRequest({
				url,
				method,
				data,
				timeout: 20000,
				onload: ({ status, responseText }) => {
					if (status === 404) resolve(false);
					if (/<\/?[a-z][\s\S]*>/i.test(responseText)) {
						responseText = new DOMParser().parseFromString(responseText, "text/html");
					}
					if (/^{.*}$/.test(responseText)) {
						responseText = JSON.parse(responseText);
					}
					if (`${responseText?.errcode}` === "911") verify();
					resolve(responseText);
				},
				...params,
			});
		});
	};
	const notifiy = (title = "" | {}, text = "", image = "info", clickUrl = "") => {
		let params = {
			title,
			text: text ?? GM_info.script.name,
			image,
			highlight: true,
			timeout: 3000,
			clickUrl,
		};
		if (typeof title === "object") params = { ...params, ...title };
		if (params.clickUrl) params.onclick = () => GM_openInTab(clickUrl, { active: true });
		GM_notification({ ...params, image: GM_getResourceURL(params?.image ?? "info") });
	};
	const verify = async () => {
		const time = new Date().getTime();
		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_${time}`,
			"验证账号",
			`height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no`
		);
	};
	const handleGetRes = async () => {
		const mask = doc.querySelector("#res-mask");
		const title = mask.querySelector(".res-title");
		const content = mask.querySelector(".res-content");
		const btn = mask.querySelector("button");
		// start
		title.textContent = "正在获取网盘列表数据";
		content.textContent = "正在获取:1页";
		btn.textContent = "请求中...";
		btn.classList.toggle("disabled");
		// change
		const change = ({ page, pages, data, count }) => {
			content.textContent = `正在获取:${page}/${pages}页,获取数据:${data.length}/${count}`;
		};
		// done
		const res = await getRes(change);
		notifiy(res);
		title.textContent = res.title;
		content.textContent = res.text;
		btn.textContent = res.err ? "重新获取" : "处理中...";
		if (res.err) return btn.classList.toggle("disabled");
		// filter
		const limit = 100;
		const data = res.data
			.filter(({ play_long, s }) => play_long && s && play_long > 0 && s >= limit * 1024 * 1024)
			.map(item => {
				return {
					fid: item.fid,
					cid: item.cid,
					link: `https://v.anxia.com/?pickcode=${item.pc}`,
					getDate: item.t,
					name: item.n,
					timestamp: item.te || item.tp,
				};
			});
		// setValue
		title.textContent = "正在清除旧缓存数据";
		btn.textContent = "清除中...";
		await rd.clear();
		title.textContent = "正在设置缓存数据";
		btn.textContent = "缓存中...";
		const len = data.length;
		for (let index = 0; index < len; index++) {
			const item = data[index];
			await rd.setItem(item.fid, item);
		}
		GM_setValue("rDate", getDate());
		GM_setValue("rSize", len);
		// end
		title.textContent = `数据缓存已完成(${len})`;
		let second = 3;
		btn.textContent = `立即刷新(${second}s)`;
		btn.classList.toggle("disabled");
		btn.removeEventListener("click", handleGetRes);
		btn.addEventListener("click", () => location.reload());
		setInterval(() => {
			second--;
			btn.textContent = `立即刷新(${second}s)`;
			if (second === 0) location.reload();
		}, 1000);
	};
	const getRes = async change => {
		const re = {
			title: "获取失败,请稍后重试",
			text: "接口失效或网盘未登录,及确保网络状态",
			image: "fail",
			err: true,
			data: [],
			page: 1,
			pages: "Unknown",
			count: "Unknown",
		};

		const URL = "https://webapi.115.com/files";
		const limit = 115;
		const params = {
			aid: 1,
			cid: rootId,
			o: "user_ptime",
			asc: 0,
			offset: 0,
			show_dir: 1,
			limit,
			code: "",
			scid: "",
			snap: 0,
			natsort: 1,
			record_open_time: 1,
			source: "",
			format: "json",
			type: 4,
			star: "",
			suffix: "",
		};

		let res = await request(URL, "GET", params);
		if (!res?.count || !res?.data?.length) return re;
		re.err = false;
		let { count, data } = res;
		re.count = count;
		re.data = data;
		const pages = Math.ceil(count / limit);
		re.pages = pages;
		change(re);
		for (let index = 1; index < pages; index++) {
			params.offset = index * limit;
			re.page = index + 1;
			change(re);
			res = await request(URL, "GET", params);
			if (!res?.data?.length) continue;
			data = [...data, ...res.data];
			re.data = data;
			change(re);
		}
		re.title = `获取完成(${((data.length / count) * 100).toFixed(0)}%)`;
		re.text = "将进行数据过滤以缓存,完成后自动刷新页面";
		re.image = "success";
		return re;
	};
	const handleClick = (selectors, node = doc) => {
		for (const item of node.querySelectorAll(selectors)) {
			const href = item?.href;
			if (!href) continue;
			item.addEventListener("contextmenu", e => e.preventDefault());
			item.addEventListener("click", e => {
				e.preventDefault();
				GM_openInTab(href, { active: true });
			});
			item.addEventListener("mousedown", e => {
				if (e.button !== 2) return;
				e.preventDefault();
				mousePoint = e.screenX + e.screenY;
			});
			item.addEventListener("mouseup", e => {
				const num = e.screenX + e.screenY - mousePoint;
				if (e.button !== 2 || num > 5 || num < -5) return;
				e.preventDefault();
				GM_openInTab(href);
			});
		}
	};
	const getDate = (str = "") => {
		const date = str ? new Date(str) : new Date();
		const Y = date.getFullYear();
		const M = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1;
		const D = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate();
		return `${Y}-${M}-${D}`;
	};
	const getCodes = code => [
		code,
		code.replace(/-/g, ""),
		code.replace(/-/g, "-0"),
		code.replace(/-/g, "0"),
		code.replace(/-/g, "00"),
		code.replace(/-/g, "_"),
		code.replace(/-/g, "_0"),
		code.replace(/-0/g, ""),
		code.replace(/-0/g, "-"),
		code.replace(/-0/g, "00"),
	];
	const scriptStart = () => {
		doc.addEventListener("keyup", event => {
			const e = event || window.event;
			if (e && e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(doc.activeElement.nodeName)) {
				doc.querySelector("#search-input").focus();
			}
		});

		if (rootId) {
			const mask = doc.create("div", { id: "res-mask", class: "mask" });
			GM_addStyle(`
            #res-box { color: ${LabelColor}; text-align: center; }
            #res-box span, .res-title { font-size: 24px; }
            .res-desc { margin-bottom: 8px; }
            .res-content { color: ${SecondaryLabelColor}; }
            .res-action { margin-top: 15px; }
            `);
			appendAfter(
				mask,
				`<div id="res-box">
                    <span class="glyphicon glyphicon-cloud" aria-hidden="true"></span>
                    <div class="res-title">获取网盘列表数据</div>
                    <div class="res-desc">(cid: ${rootId})</div>
                    <div class="res-content">上次获取:${rDate},缓存数据:${rSize}</div>
                    <div class="res-action">
                        <button class="btn btn-default">开始获取</button>
                    </div>
                </div>`
			);
			mask.querySelector("button").addEventListener("click", handleGetRes);
			doc.body.appendChild(mask);
		}

		const nav = `<a href="https://www.javbus.com/resource">资源</a>`;
		let navbar = doc.querySelector("#navbar .nav.navbar-nav");
		if (navbar) return appendAfter(navbar, `<li class="resource">${nav}</li>`);
		navbar = doc.querySelector("#toptb .wp .z ul");
		if (navbar) return appendAfter(navbar, `<li class="nav-title nav-inactive">${nav}</li>`);
	};

	const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

	class Common {
		docStart = () => {};
		contentLoaded = () => {};
		load = () => {};
	}
	class Waterfall extends Common {
		isHome = /^(\/(page\/\d+)?|\/uncensored(\/page\/\d+)?)+$/i.test(pathname);
		docStart = () => {
			GM_addStyle(`
            .search-header {
                padding: 0${SUFFIX};
                background: none${SUFFIX};
                box-shadow: none${SUFFIX};
            }
            .photo-frame {
                position: relative;
                margin: 10px${SUFFIX};
            }
            .photo-frame img {
                height: 100%${SUFFIX};
                width: 100%${SUFFIX};
                object-fit: cover${SUFFIX};
                margin: 0${SUFFIX};
            }
            .photo-info { padding: 10px${SUFFIX}; }
            .alert-page { margin: 20px${SUFFIX}; }
            `);
			if (!lm) return;
			const itemSizer = `167px`;
			const gutterSizer = `20px`;
			GM_addStyle(`
            .pagination, footer { display: none${SUFFIX}; }
            .page-load-status {
                display: none;
                padding-bottom: ${gutterSizer};
                text-align: center;
            }
            body { overflow: hidden; }
            .scrollBox {
                height: calc(100vh - 50px);
                overflow: hidden scroll;
            }
			#waterfall {
			    opacity: 0;
			    margin: ${gutterSizer} auto 0 auto${SUFFIX};
			}
			.item-sizer, .item a { width: ${itemSizer}${SUFFIX}; }
			.gutter-sizer { width: ${gutterSizer}${SUFFIX}; }
            .item a { margin: 0 0 ${gutterSizer} 0${SUFFIX}; }
			`);
		};
		contentLoaded = () => {
			const nav = doc.querySelector(".search-header .nav");
			if (nav) nav.setAttribute("class", "nav nav-pills");
			if (!lm) return this.modifyItem();
			this.handleLoadMore();
			if (!this.isHome) this.modifyLayout();
		};
		load = () => {
			if (lm && this.isHome) this.modifyLayout();
		};
		handleLoadMore = () => {
			const oldWaterfall = doc.querySelector("#waterfall");
			if (!oldWaterfall) return GM_addStyle(`#waterfall { opacity: 1; }`);
			const newWaterfall = doc.querySelector("#waterfall #waterfall");
			if (newWaterfall) oldWaterfall.parentNode.replaceChild(newWaterfall, oldWaterfall);

			const waterfall = doc.querySelector("#waterfall");
			appendBefore(waterfall, `<div class="item-sizer"></div><div class="gutter-sizer"></div>`);
			insertAfter(
				waterfall,
				`<div class="page-load-status"><span class="loader-ellips infinite-scroll-request">Loading...</span><span class="infinite-scroll-last">End of content</span><span class="infinite-scroll-error">No more pages to load</span></div>`
			);

			doc.querySelector(".container-fluid .row").classList.add("scrollBox");
		};
		modifyLayout = () => {
			const waterfall = doc.querySelector("#waterfall");
			if (!waterfall) return;
			let msnry = new Masonry(waterfall, {
				itemSelector: "none",
				columnWidth: ".item-sizer",
				gutter: ".gutter-sizer",
				horizontalOrder: true,
				fitWidth: true,
				stagger: 30,
				visibleStyle: { transform: "translateY(0)", opacity: 1 },
				hiddenStyle: { transform: "translateY(120px)", opacity: 0 },
			});
			imagesLoaded(waterfall, () => {
				msnry.options.itemSelector = ".item";
				const elems = waterfall.querySelectorAll(".item");
				this.modifyItem();
				msnry.appended(elems);
				GM_addStyle(`#waterfall { opacity: 1; }`);
			});
			// 搜索页滚动加载需特殊处理
			const path = !/^\/(uncensored\/)?(search|searchstar)+\//i.test(pathname)
				? "#next"
				: function () {
						const items = ["search", "searchstar"];
						for (const item of items) {
							if (pathname.indexOf(`${item}/`) < 0) continue;
							let [prefix, suffix] = pathname.split("&");
							suffix = suffix ?? "";
							prefix = prefix.split("/");
							let pre = "";
							for (let index = 0; index <= prefix.indexOf(item) + 1; index++) {
								pre = `${pre}${prefix[index]}/`;
							}
							return `${pre}${this.loadCount + 2}&${suffix}`;
						}
				  };
			let infScroll = new InfiniteScroll(waterfall, {
				path,
				append: ".item",
				outlayer: msnry,
				elementScroll: ".scrollBox",
				history: false,
				historyTitle: false,
				hideNav: ".pagination",
				status: ".page-load-status",
				debug: false,
			});
			infScroll.on("load", this.modifyItem);
		};
		modifyItem = (node = doc) => {
			if (ck) handleClick("a.movie-box", node);
			const items = node.querySelectorAll(".item");
			for (const item of items) {
				const info = item.querySelector("a .photo-info span:not(.mleft)");
				if (info) {
					const [titleNode, secondaryNode] = info.childNodes;
					const titleTxt = titleNode.textContent.trim();
					const title = doc.create("div", { class: "title", title: titleTxt }, titleTxt);
					if (lm) title.classList.add("ellipsis");
					if (secondaryNode?.nodeName === "BR") {
						info.removeChild(secondaryNode);
						title.classList.add("line-4");
					}
					info.replaceChild(title, titleNode);
				}

				let [code, date] = item.querySelectorAll("date");
				if (code && date) {
					code = code.textContent;
					date = date.textContent;
					const codeKey = `${code.trim().toUpperCase()}/${date.trim().toUpperCase()}`;
					lf.keys().then(keys => {
						if (!keys.includes(codeKey)) return;
						lf.getItem(codeKey).then(val => {
							const resource = val?.resource;
							if (!resource?.length) return;
							const photo = item.querySelector(".photo-frame");
							photo.classList.add("playBtn");
							photo.setAttribute("title", "点击播放");
							photo.addEventListener("click", e => {
								e.stopPropagation();
								e.preventDefault();
								GM_openInTab(resource[0].link, { active: true });
							});
						});
					});

					const codes = Array.from(new Set(getCodes(code)));
					const reg = new RegExp(`(${codes.join("|")})`, "gi");
					rd.iterate(item => {
						const name = item?.name ?? "";
						if (name.match(reg)) return item;
					}).then(res => {
						if (!(res ?? "")) return;
						const title = item.querySelector(".title");
						title.classList.add("resMatch");
						appendBefore(
							title,
							`<button class="btn btn-xs btn-default" title="列表匹配,点击播放">☆</button>`
						);
						title.querySelector("button").addEventListener("click", e => {
							e.stopPropagation();
							e.preventDefault();
							GM_openInTab(res.link, { active: true });
						});
					});
				}
			}
		};
	}
	class Genre extends Common {
		docStart = () => {
			GM_addStyle(`
            footer { display: none${SUFFIX}; }
            button.btn.btn-danger.btn-block.btn-genre {
                position: fixed${SUFFIX};
                bottom: 0${SUFFIX};
                margin: 0${SUFFIX};
                left: 0${SUFFIX};
                border: 0${SUFFIX};
                border-radius: 0${SUFFIX};
            }
            `);
		};
		contentLoaded = () => {
			if (!doc.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return;
			const box = doc.querySelectorAll(".genre-box");
			box[box.length - 1].setAttribute("style", "margin-bottom: 65px;");
		};
	}
	class Forum extends Common {
		docStart = () => {
			GM_addStyle(`
            .bcpic, 
            .banner728,
            .sd.sd_allbox > div:last-child { display: none${SUFFIX}; }
            .jav-button { margin-top: -3px${SUFFIX}; }
            #toptb {
                position: fixed${SUFFIX};
                top: 0${SUFFIX};
                left: 0${SUFFIX};
                right: 0${SUFFIX};
                z-index: 999${SUFFIX};
            }
            #wp { margin-top: 55px${SUFFIX}; }
            `);
		};
	}
	class Resource extends Common {
		docStart = () => {
			GM_addStyle(`
            .alert.alert-danger.alert-page.error-page, footer { display: none; }
            .resItem {
                margin: 15px 0;
                border-radius: 4px;
                overflow: hidden;
                box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
                background-color: ${dm ? SecondaryBackground : "#fff"};
            }
            .resItem a { color: unset; }
            .thumb { aspect-ratio: 100 / 67; }
            .thumb img {
                width: 100%;
                height: 100%;
                object-fit: cover;
            }
            .info { padding: 15px; }
            .title { margin-bottom: 6px; }
            `);
		};
		contentLoaded = async () => {
			doc.title = "已有资源(0)";
			const navbar = doc.querySelector("#navbar .nav.navbar-nav");
			navbar.querySelector(".active").classList.remove("active");
			navbar.querySelector(".resource").classList.add("active");
			const keys = await lf.keys();
			let nodes = [];
			for (const key of keys) {
				const item = await lf.getItem(key);
				const resource = item?.resource;
				if (!resource?.length) continue;
				nodes.push({ ...item, key: key.split("/")[0] });
			}
			if (!nodes?.length) {
				const alert = doc.querySelector(".alert.alert-danger.alert-page.error-page");
				alert.querySelector("h4").textContent = "暂无本地缓存数据";
				const [zh, en] = alert.querySelectorAll("p");
				zh.setAttribute("style", "display: none");
				en.textContent = "请检查浏览器相关设置或重新获取数据";
				alert.setAttribute("style", "display: block");
				return;
			}
			doc.title = `已有资源(${nodes.length})`;
			nodes.sort((pre, next) => {
				let preTime = pre.resource[0].timestamp || Date.parse(pre.resource[0].getDate) / 1000;
				let nextTime = next.resource[0].timestamp || Date.parse(next.resource[0].getDate) / 1000;
				return preTime > nextTime ? -1 : 1;
			});
			nodes = nodes.reduce((pre, { thumb, title, resource, href, key, upDate }) => {
				return `${pre}
                <div class="col-lg-3 col-md-4 col-sm-6">
                    <div class="resItem">
                        <a href="${href ?? "/" + key}">
                            <div class="playBtn thumb" data-link="${resource[0].link}" title="点击播放">
                                <img src="${thumb}" alt="${title}">
                            </div>
                            <div class="info">
                                <div class="ellipsis title" title="${title}">${title}</div>
                                <button class="btn btn-xs btn-primary" disabled="disabled">
                                    更新:${getDate(upDate)}
                                </button>
                                <button class="btn btn-xs btn-danger" disabled="disabled">
                                    资源:${resource.length}
                                </button>
                            </div>
                        </a>
                    </div>
                </div>`;
			}, "");
			appendAfter(doc.querySelector(".container-fluid .row"), nodes);
			for (const item of doc.querySelectorAll(".resItem .thumb")) {
				item.addEventListener("click", e => {
					e.stopPropagation();
					e.preventDefault();
					GM_openInTab(item.getAttribute("data-link"), { active: true });
				});
			}
			ck && handleClick(".resItem a");
		};
	}
	class Details extends Common {
		code = "";
		codeKey = "";
		lfItem = {};
		config = async (code, date) => {
			if (!code || !date) return;
			const codeKey = `${code}/${date}`;
			this.code = code;
			this.codeKey = codeKey;
			this.lfItem = (await lf.getItem(codeKey)) ?? {};
		};
		docStart = () => {
			GM_addStyle(`
            .info .glyphicon-info-sign,
            h4[style="position:relative"],
            h4[style="position:relative"] + .row { display: none${SUFFIX}; }
            .info ul { margin: 0${SUFFIX}; }
            .screencap { max-height: 600px; overflow: hidden; }
            #avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px${SUFFIX}; }
            .photo-info { height: auto${SUFFIX}; }
            `);
			GM_addStyle(`
            #exp { display: none; }
            #expBtn {
                position: absolute;
                top: 0;
                height: 40px;
                line-height: 40px;
                left: 0;
                right: 0;
                cursor: pointer;
                background: rgba(0,0,0,.7);
                color: #fff;
                margin: 560px 15px 0 15px;
                z-index: 99;
            }
            #expBtn::after { content: "展开"; }
            #exp:checked + .screencap  { max-height: none; }
            #exp:checked + .screencap > #expBtn {
                top: auto;
                margin: 0 15px;
                bottom: 0;
            }
            #exp:checked + .screencap > #expBtn::after { content: "收起"; }
            @media screen and (max-width: 480px) {
                #btnGrp { margin-bottom: 15px; }
                .screencap { max-height: 280px; }
                #expBtn { margin-left: 0; margin-right: 0; }
            }
            #resBox a { color: #CC0000${SUFFIX}; }
            `);
			doc.addEventListener("DOMNodeInserted", e => {
				const node = e.target;
				if (node.nodeName.toLowerCase() !== "tr") return;
				const href = node.querySelector("td a")?.href;
				if (!href) return;
				const td = doc.create("td", { style: "text-align:center;white-space:nowrap" });
				const copy = doc.create("a", { href, title: href }, "复制");
				const offline = doc.create("a", { href, title: href, style: "margin-left:16px" }, "离线下载");
				copy.addEventListener("click", this.copyTxt);
				offline.addEventListener("click", this.offLine);
				td.appendChild(copy);
				td.appendChild(offline);
				node.appendChild(td);
			});
		};
		contentLoaded = async () => {
			ck && handleClick("a.movie-box");
			// copy
			const handleCopy = selectors => {
				const node = doc.querySelector(selectors);
				const txt = node?.textContent?.trim();
				if (!node || !txt) return;
				const copy = doc.create("a", { title: txt, href: txt, style: "margin-left:16px" }, "复制");
				copy.addEventListener("click", this.copyTxt);
				node.appendChild(copy);
			};
			handleCopy("h3");
			handleCopy("span[style='color:#CC0000;']");
			// expBtn
			const screencap = doc.querySelector(".col-md-9.screencap");
			insertBefore(screencap, `<input type="checkbox" id="exp">`);
			appendAfter(screencap, `<label for="exp" id="expBtn"></label>`);

			const info = doc.querySelector(".col-md-3.info");
			// resource
			const res = `<p id="resBox"><span class="header">已有资源:</span><span class="genre">查询中...</span></p>`;
			appendAfter(info, res);
			// btnGrp
			const btnGrp = doc.create("div", { id: "btnGrp", class: "btn-group btn-group-justified" });
			const btns = { smartRes: "一键离线", refreshRes: "资源刷新" };
			Object.keys(btns).forEach(id => {
				const btn = doc.create("a", { id, class: "btn btn-default" }, btns[id]);
				btn.addEventListener("click", () => this.handleOpt(id));
				btnGrp.appendChild(btn);
			});
			info.appendChild(btnGrp);
			// table
			const td = `<td style="text-align:center;white-space:nowrap">操作</td>`;
			appendAfter(doc.querySelector("#magnet-table tbody tr"), td);
			// photoinfo
			for (const item of doc.querySelectorAll(".photo-info span")) item.classList.add("ellipsis");
			// config
			if (!this.codeKey) {
				let [code, date] = doc.querySelectorAll("div.col-md-3.info p");
				code = code.querySelectorAll("span")[1].childNodes[0].textContent.trim().toUpperCase();
				date = date.childNodes[1].textContent.trim().toUpperCase();
				await this.config(code, date);
			}

			this.handleStar();
			this.handleResource();
			this.handlePreview();
			this.handleVideo();
		};
		load = () => {
			const maxHei = 600;
			const styleHei = doc.querySelector(".col-md-9.screencap .bigImage img").height;
			if (styleHei > maxHei) {
				GM_addStyle(`.screencap { max-height: ${maxHei}px; } #expBtn { margin-top: ${maxHei - 40}px; }`);
			} else {
				GM_addStyle(`.screencap { max-height: ${styleHei + 40}px; } #expBtn { margin-top: ${styleHei}px; }`);
			}
		};
		copyTxt = e => {
			e.preventDefault();
			e.stopPropagation();
			const node = e.target;
			const txt = node?.title;
			if (!node || !txt) return;
			GM_setClipboard(txt);
			const text = node?.textContent ?? "";
			node.textContent = "成功";
			setTimeout(() => {
				node.textContent = text;
			}, 1000);
		};
		// 预览大图
		handlePreview = async () => {
			let image = this.lfItem?.image;
			if (!image) {
				let res = await request(`https://javstore.net/search/${this.code}.html`);
				const href = res?.querySelector("#content_news li a")?.href;
				if (!href) return;
				res = await request(href);
				image = res?.querySelector(".news a img[alt*='.th']")?.src?.replace(".th", "");
				if (!image) return;
				lf.upItem(this.codeKey, { image });
			}
			if (!(await request(image))) return;
			const img = doc.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
			img.addEventListener("click", () => {
				doc.querySelector("#exp").checked = false;
			});
			img.onload = () => doc.querySelector(".col-md-9.screencap").appendChild(img);
		};
		// 预览视频
		handleVideo = async () => {
			let video = this.lfItem?.video;
			if (!video) {
				const res = await request(`https://www.r18.com/common/search/searchword=${this.code}/`);
				video = res?.querySelector("a.js-view-sample")?.getAttribute("data-video-high");
				if (!video) return;
				lf.upItem(this.codeKey, { video });
			}
			const title = "预览视频";
			const playVideo = e => {
				e.preventDefault();
				e.stopPropagation();
				doc.body.setAttribute("style", "overflow: hidden;");
				doc.querySelector("#video-mask").setAttribute("style", "display: flex;");
				const video = doc.querySelector("video");
				video.play();
				video.focus();
				doc.onkeydown = event => {
					const e = event || window.event;
					if (e && e.keyCode === 27) pauseVideo();
				};
			};
			const pauseVideo = () => {
				doc.body.setAttribute("style", "overflow: auto;");
				doc.querySelector("#video-mask").setAttribute("style", "display: none;");
				doc.querySelector("video").pause();
				doc.onkeydown = null;
			};

			const videoNode = doc.create("video", { controls: "controls", src: video });
			videoNode.currentTime = 5;
			videoNode.preload = "auto";
			videoNode.muted = true;
			const closeBtn = doc.create("button", { title: "Close (Esc)", type: "button", class: "mfp-close" }, "×");
			closeBtn.addEventListener("click", pauseVideo);
			const mask = doc.create("div", { id: "video-mask", class: "mask" });
			mask.appendChild(closeBtn);
			mask.appendChild(videoNode);
			doc.body.appendChild(mask);

			const bigImage = doc.querySelector(".bigImage");
			const bImg = bigImage.querySelector("img");
			const playBtn = doc.create("div", { class: "playBtn", title });
			playBtn.addEventListener("click", playVideo);
			playBtn.appendChild(bImg);
			bigImage.appendChild(playBtn);

			const thumb = bImg.src;
			const box = doc.create("a", { class: "sample-box", href: thumb, title });
			box.addEventListener("click", playVideo);
			appendAfter(box, `<div class="photo-frame playBtn"><img src="${thumb}"></div>`);

			let waterfall = doc.querySelector("#sample-waterfall");
			if (!waterfall) {
				insertBefore(doc.querySelector("div.clearfix"), `<div id="sample-waterfall"></div>`);
				waterfall = doc.querySelector("#sample-waterfall");
				insertBefore(waterfall, `<h4>樣品圖像</h4>`);
				return waterfall.appendChild(box);
			}
			const ref = waterfall.querySelector("a");
			waterfall.insertBefore(box, ref);
			const imgBtn = doc.create(
				"button",
				{ title: "樣品圖像", type: "button", class: "mfp-close", style: "right:44px" },
				"📷"
			);
			imgBtn.addEventListener("click", () => {
				pauseVideo();
				ref.click();
			});
			mask.appendChild(imgBtn);
		};
		// 演员列表
		handleStar = async () => {
			const info = doc.querySelector(".col-md-3.info");
			if (!/暫無出演者資訊/g.test(info.textContent)) return;
			let star = this.lfItem?.star ?? [];
			if (!star?.length) {
				const site = "https://javdb.com";
				let res = await request(`${site}/search?q=${this.code}`);
				const href = res.querySelector("#videos .grid-item a").getAttribute("href");
				if (!href) return;
				res = await request(`${site}${href}`);
				res = res?.querySelectorAll(".panel-block");
				if (!res?.length) return;
				res = res[res.length - 3]?.querySelector(".value").textContent.trim();
				res = res.split(/\n/).filter(item => item.indexOf("♀") !== -1);
				star = res.map(item => item.trim().replace("♀", ""));
				if (!star?.length) return;
				lf.upItem(this.codeKey, { star });
			}
			const p = doc.create("p");
			appendAfter(
				p,
				star.reduce((acc, cur) => `${acc}<span class="genre"><a href="/search/${cur}">${cur}</a></span>`, "")
			);
			info.replaceChild(p, doc.querySelector("span.glyphicon.glyphicon-info-sign.mb20")?.nextSibling);
		};
		// 已有资源(本地)
		handleResource = async () => {
			const lfItem = await lf.getItem(this.codeKey);

			let resource = lfItem?.resource ?? [];
			const upDate = lfItem?.upDate;
			const bool = !upDate || Math.floor((new Date().getTime() - upDate) / 24 / 3600 / 1000) > 3;
			if (bool) resource = await this.fetchResource();

			const resBox = doc.querySelector("#resBox");
			for (const old of resBox.querySelectorAll(".genre")) resBox.removeChild(old);
			if (!resource?.length) return appendAfter(resBox, `<span class="genre">无</span>`);
			const child = resource.reduce((acc, { link, getDate, name }) => {
				let thunbName = name.length > 20 ? `${name.substr(0, 20)}...` : name;
				return `${acc}<span class="genre"><a href="${link}" title="${getDate}/${name}" target="_blank">${thunbName}</a></span>`;
			}, "");
			appendAfter(resBox, child);

			if (lfItem?.thumb && lfItem?.title && lfItem?.href) return;
			lf.upItem(this.codeKey, {
				thumb: doc.querySelector(".screencap .bigImage img").src,
				title: doc.querySelector("h3").childNodes[0].textContent,
				href: location.href,
			});
		};
		// 已有资源(115)
		fetchResource = async (lname = "") => {
			const code = this.code;
			let codes = getCodes(code);
			if (lname) codes.unshift(encodeURIComponent(lname));
			codes = Array.from(new Set(codes));
			let { data } = await request("https://webapi.115.com/files/search", "GET", {
				search_value: encodeURIComponent(codes.join(" ")),
				format: "json",
			});
			let resource = [];
			if (data?.length) {
				const reg = new RegExp(`(${codes.join("|")})`, "gi");
				data = data.filter(({ n, play_long }) => n.match(reg) && play_long && play_long > 0);
				resource = data.map(item => {
					return {
						fid: item.fid,
						cid: item.cid,
						link: `https://v.anxia.com/?pickcode=${item.pc}`,
						getDate: item.t,
						name: item.n,
						timestamp: item.te || item.tp,
					};
				});
			}
			await lf.upItem(this.codeKey, { upDate: new Date().getTime(), resource });
			return resource;
		};
		// 离线下载
		offLine = async e => {
			e.preventDefault();
			e.stopPropagation();
			const node = e.target;
			const link = node?.title;
			if (!node || !link) return;
			GM_setClipboard(link);
			const text = node?.textContent ?? "";
			node.textContent = "请求中...";
			const firstTd = node.parentNode.parentNode.querySelector("td");
			const zh = !!firstTd.querySelector("a.btn.btn-mini-new.btn-warning.disabled");
			const lname = firstTd.querySelector("a").textContent.trim();
			const res = await this.offLineDownload({ link, zh, lname });
			node.textContent = text;
			notifiy(res);
		};
		// 排队离线/资源刷新
		handleOpt = async action => {
			const node = doc.querySelector(`#${action}`);
			if (!node) return;
			node.classList.toggle("disabled");
			const text = node?.textContent ?? "";
			node.textContent = "请求中...";

			if (action === "refreshRes") {
				await lf.upItem(this.codeKey, { upDate: 0 });
				await this.handleResource();
			}
			if (action === "smartRes") {
				const trs = doc.querySelector("#magnet-table").querySelectorAll("tr");
				let magnetArr = [];
				for (let index = 1; index < trs.length; index++) {
					const item = { link: "", lname: "", zh: false, size: 0, date: 0 };
					let [name, size, date] = trs[index].querySelectorAll("td");
					if (!name || !size || !date) continue;
					item.zh = !!name.querySelector("a.btn.btn-mini-new.btn-warning.disabled");
					name = name.querySelector("a");
					item.link = name.href;
					item.lname = name.textContent.trim();
					size = size.querySelector("a").textContent.trim().replace(/gb/gi, "");
					if (/mb/gi.test(size)) size = (parseFloat(size) / 1024).toFixed(2);
					item.size = Number(size);
					item.date = date.querySelector("a").textContent.trim().replace(/-/g, "");
					magnetArr.push(item);
				}
				magnetArr.sort((pre, next) => {
					if (pre.zh === next.zh) {
						if (pre.size === next.size) return next.date - pre.date;
						return next.size - pre.size;
					} else {
						return pre.zh > next.zh ? -1 : 1;
					}
				});
				for (let index = 0; index < magnetArr.length; index++) {
					const res = await this.offLineDownload(magnetArr[index]);
					if (res?.image !== "info") {
						notifiy(res);
						break;
					}
					if (index !== magnetArr.length - 1) continue;
					notifiy(
						"一键离线失败",
						"远程未查找到新增资源,接口失效或资源被审核",
						"fail",
						"http://115.com/?tab=offline&mode=wangpan"
					);
				}
			}

			node.textContent = text;
			node.classList.toggle("disabled");
		};
		// 请求离线&结果查询
		offLineDownload = async ({ link, zh, lname }) => {
			const fname = `${zh ? "【中文字幕】" : ""}${doc.querySelector("h3").textContent.replace("复制", "")}`;

			let notifiyObj = {
				title: "操作失败,115未登录",
				text: "请登录115账户后再离线下载",
				image: "fail",
				clickUrl: "http://115.com/?mode=login",
			};
			let res = await request("http://115.com/", "GET", {
				ct: "offline",
				ac: "space",
				_: new Date().getTime(),
			});
			if (!res?.sign) return notifiyObj;
			const { sign, time } = res;
			res = await request("http://115.com/web/lixian/?ct=lixian&ac=add_task_url", "POST", {
				url: link.substr(0, 60),
				uid: 0,
				sign,
				time,
			});
			const { state, errcode, error_msg } = res;
			notifiyObj = {
				title: "离线失败",
				text: error_msg,
				image: "info",
				clickUrl: "http://115.com/?tab=offline&mode=wangpan",
			};
			if (`${errcode}` === "911") {
				notifiyObj.title += ",账号异常";
				notifiyObj.text = "验证后使用";
				notifiyObj.image = "fail";
			}
			if (!state) return notifiyObj;
			// 获取旧的本地缓存数据
			let lfItem = (await lf.getItem(this.codeKey)) ?? {};
			if (!lfItem?.resource) lfItem.resource = [];
			// 远程获取搜索结果
			await delay(2000);
			let resource = await this.fetchResource(lname);
			this.handleResource();
			const newRes = resource.filter(item => item.getDate === getDate());
			// 搜索结果资源数至少比本地缓存多且获取日期为当前日期
			if (resource.length <= lfItem.resource.length || newRes.length < 1) {
				notifiyObj.text = "未找到新增资源,接口失效或资源审核";
				return notifiyObj;
			}
			for (let index = 0; index < newRes.length; index++) {
				const { name } = newRes[index];
				const suffix = name.split(".").pop().toLowerCase();
				newRes[index]["rename"] = `${fname}.${suffix}`;
			}
			this.afterAction(newRes.filter(item => item.name.indexOf(fname) === -1));
			return {
				title: "离线成功",
				text: "点击跳转",
				image: "success",
				clickUrl: `https://115.com/?cid=${rootId ?? 0}&offset=0&mode=wangpan`,
			};
		};
		// 离线后重命名,移动,移除原目录等
		afterAction = async items => {
			// rename
			for (const { fid, rename: file_name } of items) {
				await request("http://webapi.115.com/files/edit", "POST", { fid, file_name });
			}
			if (rootId) {
				const params = arr => arr.reduce((acc, cur, idx) => `${acc}&fid[${idx}]=${cur}`, "");
				const fids = Array.from(new Set(items.map(item => item.fid)));
				const cids = Array.from(new Set(items.filter(item => item !== rootId).map(item => item.cid)));
				// move
				const move_proid = `${new Date()}_${~(100 * Math.random())}_0`;
				await request(
					"https://webapi.115.com/files/move",
					"POST",
					`pid=${rootId}&move_proid=${move_proid}${params(fids)}`
				);
				// del
				request("https://webapi.115.com/rb/delete", "POST", `pid=${rootId}&ignore_warn=1${params(cids)}`);
			}
			await lf.upItem(this.codeKey, { upDate: 0 });
			this.handleResource();
		};
	}

	class JavBusScript {
		waterfall = new Waterfall();
		genre = new Genre();
		forum = new Forum();
		resource = new Resource();
		details = new Details();
	}

	const Background = "rgb(18,18,18)";
	const SecondaryBackground = "rgb(32,32,32)";
	const LabelColor = "rgba(255,255,255,.95)";
	const SecondaryLabelColor = "rgb(170,170,170)";
	const Grey = "rgb(49,49,49)";
	const Blue = "rgb(10,132,255)";
	const Orange = "rgb(255,159,10)";
	const Green = "rgb(48,209,88)";
	const Red = "rgb(255,69,58)";
	const darkMode = path => {
		if (path === "forum") return;
		GM_addStyle(`
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        ::-webkit-scrollbar-thumb {
            border-radius: 4px;
            background-color: ${Grey};
        }
        *:not(span) {
            border-color: ${Grey}${SUFFIX};
            outline: ${Grey}${SUFFIX};
            text-shadow: none${SUFFIX};
        }
        body, footer {
            background: ${Background}${SUFFIX};
            color: ${SecondaryLabelColor}${SUFFIX};
        }
        img { filter: brightness(90%)${SUFFIX}; }
        nav { background: ${SecondaryBackground}${SUFFIX}; }
        input { background: ${Background}${SUFFIX}; }
        *::placeholder { color: ${SecondaryLabelColor}${SUFFIX}; }
        button, input, a, h1, h2, h3, h4, h5, h6 {
            color: ${LabelColor}${SUFFIX};
            box-shadow: none${SUFFIX};
            outline: none${SUFFIX};
        }
        .btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .85${SUFFIX}; }
        button, .btn-default, .input-group-addon {
            background: ${Grey}${SUFFIX};
            color: ${LabelColor}${SUFFIX};
        }
        .btn-primary {
            background: ${Blue}${SUFFIX};
            border-color: ${Blue}${SUFFIX};
        }
        .btn-success {
            background: ${Green}${SUFFIX};
            border-color: ${Green}${SUFFIX};
        }
        .btn-danger {
            background: ${Red}${SUFFIX};
            border-color: ${Red}${SUFFIX};
        }
        .btn-warning {
            background: ${Orange}${SUFFIX};
            border-color: ${Orange}${SUFFIX};
        }
        .navbar-nav>.active>a, .navbar-nav>.active>a:focus, .navbar-nav>.active>a:hover, .navbar-nav>.open>a, .dropdown-menu { background: ${Background}${SUFFIX}; }
        .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { background: ${Grey}${SUFFIX}; }
        .pagination .active a {
            border: none${SUFFIX};
            color: ${LabelColor}${SUFFIX};
        }
        .pagination>li>a, .pagination>li>span {
            background-color: ${Grey}${SUFFIX};
            border: none${SUFFIX};
            color: ${LabelColor}${SUFFIX};
        }
        tr, .modal-content, .alert {
            background: ${SecondaryBackground}${SUFFIX};
            box-shadow: none${SUFFIX};
        }
        tr:hover { background: ${Grey}${SUFFIX}; }
        `);
		if (path === "waterfall") {
			GM_addStyle(`
            .item a { background: ${SecondaryBackground}${SUFFIX}; }
            .photo-info {
                background: ${SecondaryBackground}${SUFFIX};
                color: ${LabelColor}${SUFFIX};
            }
            date { color: ${SecondaryLabelColor}${SUFFIX}; }
            .nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover, .nav-pills>li>a:focus, .nav-pills>li>a:hover { background-color: ${Grey}${SUFFIX}; }
            `);
		}
		if (path === "genre") GM_addStyle(`.genre-box { background-color: ${SecondaryBackground}${SUFFIX}; }`);
		if (path === "forum") {
			GM_addStyle(`
            #toptb,
            .mn > div,
            .biaoqicn_show.slidebar a:not(.on),
            .new4_list_top .dspanonhover,
            div[id*="con_NewOne_"],
            .frame,
            .frame-tab,
            .main-right-p15,
            .bm.bmw.cl,
            .bm.bmw.cl .bm_h.cl { background: ${SecondaryBackground}${SUFFIX}; }
            .jav-footer,
            .nav-title.nav-active a,
            .menu-body-panel.login-detail-wrap,
            .menu-body-panel .icon-arrow-t,
            .new4_list_top,
            .comment-excerpt { background: ${Background}${SUFFIX}; }
            .main-right-zuixin .comment-excerpt:before { border-bottom-color: ${Background}${SUFFIX}; }
            .main-right-tit span { color: ${LabelColor}${SUFFIX}; }
            `);
		}
		if (path === "details") {
			GM_addStyle(`
            .movie, .sample-box, .movie-box, .photo-info { background: ${SecondaryBackground}${SUFFIX}; }
            .photo-info { color: ${LabelColor}${SUFFIX}; }
            .avatar-box, .avatar-box span, .info ul li, .info .star-name {
                background: ${SecondaryBackground}${SUFFIX};
                border-color: ${Grey}${SUFFIX};
                color: ${LabelColor}${SUFFIX};
            }
            `);
		}
	};

	if (/javbus\.com/g.test(host)) {
		const menus = [
			{ title: "点击事件", name: "ck", val: ck, key: "c" },
			{ title: "暗黑模式", name: "dm", val: dm, key: "d" },
			{ title: "滚动加载", name: "lm", val: lm, key: "s" },
		];
		for (const { title, name, val, key } of menus) {
			GM_registerMenuCommand(
				`${val ? "关闭" : "开启"}${title}`,
				() => {
					GM_setValue(name, !val);
					location.reload();
				},
				key
			);
		}
		GM_registerMenuCommand(
			"离线后操作根目录cid",
			() => {
				const rid = prompt("用于离线后移动,删除等操作", rootId);
				GM_setValue("rid", rid);
				location.reload();
			},
			"a"
		);

		lf.config({ name: "JBDB", storeName: "AVS" });

		if (rootId) {
			rd = lf.createInstance({ name: "JBDB", storeName: "RES" });
			GM_registerMenuCommand(
				"获取网盘列表",
				() => {
					doc.body.setAttribute("style", "overflow: hidden;");
					doc.querySelector("#res-mask").setAttribute("style", "display: flex;");
				},
				"r"
			);
		}

		GM_addStyle(`
        .ad-box { display: none${SUFFIX}; }
        .ellipsis {
            overflow : hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 1;
            -webkit-box-orient: vertical;
        }
        .line-4 { -webkit-line-clamp: 4; }
        .playBtn { position: relative; }
        .playBtn:after {
            position: absolute;
            background: url(https://javdb.com/packs/media/images/btn-play-b414746c.svg) 50% no-repeat;
            background-color: rgba(0,0,0,.2);
            background-size: 40px;
            content: "";
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            transition: all .3s ease-out;
            opacity: .85;
        }
        .playBtn:hover::after { background-color: rgba(0,0,0,0); }
        .playBtn img { filter: none${SUFFIX}; }
        .mask {
            position: fixed;
            width: 100%;
            height: 100%;
            z-index: 9999;
            left: 0;
            top: 0;
            background: rgba(11,11,11,.8);
            justify-content: center;
            align-items: center;
            display: none;
        }
        .resMatch { color: ${Blue}${SUFFIX}; }
        .resMatch button { margin-right: 4px; }
        `);

		const pathReg = {
			waterfall:
				/^\/((uncensored|uncensored\/)?(page\/\d+)?$)|((uncensored\/)?((search|searchstar|actresses|genre|star|studio|label|series|director|member)+\/)|actresses(\/\d+)?)+/i,
			genre: /^\/(uncensored\/)?genre$/i,
			forum: /^\/forum\//i,
			resource: /^\/resource$/i,
			details: /^\/[\w]+(-|_)?[\d]*.*$/i,
		};
		const path = Object.keys(pathReg).filter(key => pathReg[key].test(pathname))[0];
		if (!path) return;
		dm && darkMode(path);
		const jav = new JavBusScript()[path];
		if (!jav) return;
		jav.docStart();
		doc.addEventListener("DOMContentLoaded", () => {
			scriptStart();
			jav.contentLoaded();
		});
		window.addEventListener("load", jav.load);
	}
	if (/captchaapi\.115\.com/g.test(host)) {
		doc.addEventListener("DOMContentLoaded", () => {
			window.focus();
			const btn = doc.querySelector("#js_ver_code_box button[rel='verify']");
			btn.addEventListener("click", () => {
				const interval = setInterval(() => {
					if (doc.querySelector("div[rel='error_box']").getAttribute("style").indexOf("none") !== -1) {
						window.open("", "_self");
						window.close();
					}
				}, 300);
				setTimeout(() => clearInterval(interval), 600);
			});
		});
	}
})();