JavScript

暗黑模式、滚动加载、标题机翻、搜索快捷键、在线播放、磁力搜索、115资源匹配 & 离线下载、预览视频、预览大图、演员匹配...

От 19.01.2022. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name            JavScript
// @namespace       https://greasyfork.org/users/175514
// @description     暗黑模式、滚动加载、标题机翻、搜索快捷键、在线播放、磁力搜索、115资源匹配 & 离线下载、预览视频、预览大图、演员匹配...
// @version         2.5.1
// @icon            https://z3.ax1x.com/2021/10/15/53gMFS.png
// @include         *
// @require         https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js
// @require         https://unpkg.com/infinite-scroll@4/dist/infinite-scroll.pkgd.min.js
// @resource        fail https://z3.ax1x.com/2021/10/15/53gcex.png
// @resource        info https://z3.ax1x.com/2021/10/15/53g2TK.png
// @resource        success https://z3.ax1x.com/2021/10/15/53gqTf.png
// @resource        play https://s4.ax1x.com/2022/01/12/7nYuKe.png
// @resource        loading https://via.placeholder.com/824x40
// @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==

(function () {
	// domains
	const createReg = items => new RegExp(`(${items.join("|").replace(/\./g, "\\.")})+`, "gi");
	const MatchDomains = [
		{
			domain: "JavBus",
			regex: createReg([
				"javbus.com",
				"dmmbus.fun",
				"dmmsee.fun",
				"busjav.fun",
				"busjav.bar",
				"cdnbus.fun",
				"busfan.fun",
				"seedmm.fun",
				"busdmm.fun",
				"buscdn.fun",
			]),
		},
		{
			domain: "JavDB",
			regex: /javdb[\d]*\.com/gi,
		},
	];
	// document
	const DOC = document;
	DOC.create = (tag, attr = {}, child = "") => {
		if (!tag) return null;
		tag = DOC.createElement(tag);
		Object.keys(attr).forEach(name => tag.setAttribute(name, attr[name]));
		typeof child === "string" && tag.appendChild(DOC.createTextNode(child));
		typeof child === "object" && tag.appendChild(child);
		return tag;
	};
	// request
	const request = (url, data = {}, method = "GET", params = {}) => {
		if (typeof data === "object") {
			data = Object.keys(data).reduce(
				(pre, cur) => `${pre ? `${pre}&` : ""}${cur}=${encodeURIComponent(data[cur])}`,
				""
			);
		}
		if (method === "GET" && data) url = `${url}?${data}`;
		return new Promise(resolve => {
			GM_xmlhttpRequest({
				url,
				data,
				method,
				timeout: 20000,
				onload: ({ status, response }) => {
					if (status === 404) resolve(false);
					const htmlReg = /<\/?[a-z][\s\S]*>/i;
					const jsonReg = /^{.*}$/;
					if (htmlReg.test(response)) {
						response = new DOMParser().parseFromString(response, "text/html");
					} else if (jsonReg.test(response)) {
						response = JSON.parse(response);
					}
					if (response?.errcode === 911) verify();
					resolve(response);
				},
				...params,
			});
		});
	};
	const verify = () => {
		const h = 667;
		const w = 375;
		const t = (window.screen.availHeight - h) / 2;
		const l = (window.screen.availWidth - w) / 2;
		window.open(
			`https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_${new Date().getTime()}`,
			"验证账号",
			`height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no`
		);
	};
	const notify = msg => {
		GM_notification({
			...msg,
			text: msg?.text ?? GM_info.script.name,
			image: GM_getResourceURL(msg?.image ?? "info"),
			onclick: msg?.clickUrl ? () => GM_openInTab(msg.clickUrl, { active: true }) : () => {},
			highlight: true,
			timeout: 3000,
		});
	};
	const getDate = timestamp => {
		const date = timestamp ? new Date(timestamp) : new Date();
		const Y = date.getFullYear();
		const M = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1;
		const D = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate();
		return `${Y}-${M}-${D}`;
	};
	const getScrollTop = () => {
		let scrollTop = 0;
		if (DOC.documentElement && DOC.documentElement.scrollTop) {
			scrollTop = DOC.documentElement.scrollTop;
		} else if (DOC.body) {
			scrollTop = DOC.body.scrollTop;
		}
		return scrollTop;
	};
	const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
	const getItem = key => {
		const details = GM_getValue("DETAILS") ?? {};
		return details[key] ?? {};
	};
	const upItem = (key, val) => {
		const details = GM_getValue("DETAILS") ?? {};
		val = { ...getItem(key), ...val };
		details[key] = val;
		GM_setValue("DETAILS", details);
	};
	const transToBytes = sizeStr => {
		const limit = 1024;
		const step = 8;
		const sizer = [
			{
				unit: /gb/gi,
				transform: size => size * limit * limit * limit,
			},
			{
				unit: /gib/gi,
				transform: size => (size / step) * limit * limit * limit,
			},
			{
				unit: /mb/gi,
				transform: size => size * limit * limit,
			},
			{
				unit: /mib/gi,
				transform: size => (size / step) * limit * limit,
			},
			{
				unit: /kb/gi,
				transform: size => size * limit,
			},
			{
				unit: /kib/gi,
				transform: size => (size / step) * limit,
			},
			{
				unit: /byte/gi,
				transform: size => size,
			},
			{
				unit: /bit/gi,
				transform: size => size / step,
			},
		];
		const size = sizeStr.replace(/[a-zA-Z\s]/g, "");
		if (size <= 0) return 0;
		return (
			sizer
				.find(({ unit }) => unit.test(sizeStr))
				?.transform(size)
				?.toFixed(2) ?? 0
		);
	};
	const unique = (arr, key) => {
		if (!arr) return arr;
		if (key === undefined) return [...new Set(arr)];
		const map = { string: e => e[key], function: e => key(e) };
		const fn = map[typeof key];
		const obj = arr.reduce((o, e) => ((o[fn(e)] = e), o), {});
		return Object.values(obj);
	};
	const tooltip = msg => {
		console.log(
			`%c ${msg} %c By ${GM_info.script.name} v${GM_info.script.version} %c`,
			"background:#1890FF;padding:1px;border-radius:3px 0 0 3px;color:#fff",
			"background:#555555;padding:1px;border-radius:0 3px 3px 0;color:#fff",
			"background:transparent"
		);
	};

	class Common {
		docStart = () => {};
		contentLoaded = () => {};
		load = () => {};

		mPoint = 0;
		pcPreview = "https://v.anxia.com/?pickcode=";
		clickUrl = "http://115.com/?tab=offline&mode=wangpan";
		expHei = 640;
		expBtnHei = 40;
		menus = [
			{ title: "点击事件", key: "CK", command: "c", defaultVal: true },
			{ title: "暗黑模式", key: "DM", command: "d", defaultVal: true },
			{ title: "滚动加载", key: "LM", command: "s", defaultVal: true },
			{
				title: "离线后操作目录CID",
				key: "CID",
				command: "a",
				cb: uniKey => {
					const val = prompt("用于离线下载后移动,删除操作", GM_getValue(uniKey) ?? "");
					if (!val) return;
					GM_setValue(uniKey, val);
					location.reload();
				},
				defaultVal: "",
			},
		];
		registerMenu = (menus = this.menus) => {
			for (const menu of menus) {
				const { title, key, command, defaultVal } = menu;
				const uniKey = `${this.constructor.name}_${key}`;
				const val = GM_getValue(`${uniKey}`) ?? defaultVal;
				GM_registerMenuCommand(
					`${menu?.cb ? "设置" : val ? "关闭" : "开启"}${title}`,
					menu?.cb
						? () => menu.cb(uniKey)
						: () => {
								GM_setValue(uniKey, !val);
								location.reload();
						  },
					command
				);
				this[key] = val;
			}
		};
		initDB = () => {
			const date = getDate();
			const rcdKey = `CD`;
			if (GM_getValue(rcdKey) !== date) {
				GM_setValue("DETAILS", {});
				GM_setValue("RESOURCE", []);
				GM_setValue(rcdKey, date);
			}
		};
		customStyle = () => {
			GM_addStyle(`
            video, img { vertical-align: middle !important; }
            .ellipsis {
                overflow : hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 1;
                -webkit-box-orient: vertical;
            }
            .playBtn { position: relative; }
            .playBtn::after {
                position: absolute;
                background: url(${GM_getResourceURL("play")}) 50% no-repeat;
                background-color: rgba(0,0,0,.2);
                background-size: 40px;
                content: "";
                top: 0;
                right: 0;
                bottom: 0;
                left: 0;
                transition: all .3s ease-out;
                opacity: .85;
            }
            .playBtn:hover::after { background-color: rgba(0,0,0,0); }
            .playBtn img { filter: none !important; }
            .mask {
                position: fixed;
                width: 100%;
                height: 100%;
                z-index: 9999;
                left: 0;
                top: 0;
                background: rgba(11, 11, 11, .8);
                justify-content: center;
                align-items: center;
                display: none;
            }
            .matched {
                font-weight: bold;
                color: rgb(10,132,255) !important;
            }
            .hidden { display: none !important; }
            `);
		};
		darkStyle = () => {
			const Background = "rgb(18,18,18)";
			const SecondaryBackground = "rgb(32,32,32)";
			const LabelColor = "rgba(255,255,255,.95)";
			const SecondaryLabelColor = "rgb(170,170,170)";
			const Grey = "rgb(49,49,49)";
			const Blue = "rgb(10,132,255)";
			const Orange = "rgb(255,159,10)";
			const Green = "rgb(48,209,88)";
			const Red = "rgb(255,69,58)";
			GM_addStyle(`
            ::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }
            ::-webkit-scrollbar-thumb {
                border-radius: 4px;
                background-color: ${Grey};
            }
            *:not(span) {
                border-color: ${Grey} !important;
                outline: ${Grey} !important;
                text-shadow: none !important;
            }
            body, footer {
                background: ${Background} !important;
                color: ${SecondaryLabelColor} !important;
            }
            img { filter: brightness(90%) !important; }
            nav { background: ${SecondaryBackground} !important; }
            input { background: ${Background} !important; }
            *::placeholder { color: ${SecondaryLabelColor} !important; }
            button, input, a, h1, h2, h3, h4, h5, h6, p {
                color: ${LabelColor} !important;
                box-shadow: none !important;
                outline: none !important;
            }
            `);
			return {
				Background,
				SecondaryBackground,
				LabelColor,
				SecondaryLabelColor,
				Grey,
				Blue,
				Orange,
				Green,
				Red,
			};
		};
		handleClick = (selectors, node = DOC) => {
			for (const item of node.querySelectorAll(selectors)) {
				const href = item?.href;
				if (!href) continue;
				item.addEventListener("contextmenu", e => e.preventDefault());
				item.addEventListener("click", e => {
					e.preventDefault();
					GM_openInTab(href, { active: true });
				});
				item.addEventListener("mousedown", e => {
					if (e.button !== 2) return;
					e.preventDefault();
					this.mPoint = e.screenX + e.screenY;
				});
				item.addEventListener("mouseup", e => {
					const num = e.screenX + e.screenY - this.mPoint;
					if (e.button !== 2 || num > 5 || num < -5) return;
					e.preventDefault();
					GM_openInTab(href);
				});
			}
		};
		waterfallLayout = (selectors, itemSelectors, mParams = {}, iParams = {}) => {
			const node = DOC.querySelector(selectors);
			if (!node) return;
			const msnry = new Masonry(node, {
				itemSelector: "none",
				columnWidth: ".item-sizer",
				gutter: ".gutter-sizer",
				horizontalOrder: true,
				fitWidth: true,
				stagger: 30,
				visibleStyle: { transform: "translateY(0)", opacity: 1 },
				hiddenStyle: { transform: "translateY(120px)", opacity: 0 },
				...mParams,
			});
			imagesLoaded(node, () => {
				msnry.options.itemSelector = itemSelectors;
				const items = node.querySelectorAll(itemSelectors);
				msnry.appended(items);
				GM_addStyle(`${selectors} { opacity: 1; }`);
			});
			const infScroll = new InfiniteScroll(node, {
				path: "#next",
				append: itemSelectors,
				outlayer: msnry,
				history: false,
				historyTitle: false,
				hideNav: ".pagination",
				debug: false,
				...iParams,
			});
			return infScroll;
		};
		handleCopy = selectors => {
			const node = DOC.querySelector(selectors);
			if (!node) return;
			const copy = node?.textContent.trim();
			if (!copy) return;
			const copyNode = DOC.create(
				"a",
				{ title: copy, "data-copy": copy, href: "", style: "margin-left:16px" },
				"复制"
			);
			copyNode.addEventListener("click", this.copyTxt);
			node.appendChild(copyNode);
		};
		copyTxt = e => {
			e.preventDefault();
			e.stopPropagation();
			const { target: node } = e;
			if (!node) return;
			const { copy } = node.dataset;
			if (!copy) return;
			GM_setClipboard(copy);
			const initial = node?.textContent ?? "";
			node.textContent = "成功";
			setTimeout(() => {
				node.textContent = initial;
			}, 600);
		};
		fetchTranslate = async sentence => {
			const data = {
				async: `translate,sl:auto,tl:zh-CN,st:${encodeURIComponent(
					sentence.trim()
				)},id:1642125176704,qc:true,ac:false,_id:tw-async-translate,_pms:s,_fmt:pc`,
			};
			sentence = await request(
				"https://www.google.com/async/translate?vet=12ahUKEwi03Jv2kLD1AhWRI0QIHe_TDKAQqDh6BAgCECY..i&ei=ZtfgYbSRO5HHkPIP76ezgAo&yv=3",
				data,
				"POST",
				{ headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } }
			);
			return sentence?.querySelector("#tw-answ-target-text").textContent ?? "";
		};
		// search prefix match list
		fetchMatch = async code => {
			let res = getItem(code)?.resource;
			if (!res) {
				const resource = GM_getValue("RESOURCE") ?? [];
				const prefix = code.split(/(-|_)/g)[0];
				let item = resource.find(item => item.prefix === prefix);
				if (!item) {
					item = { prefix, res: await this.fetchSearch(prefix) };
					resource.push(item);
					GM_setValue("RESOURCE", resource);
				}
				res = await this.fetchResource(code, item.res);
			}
			return res;
		};
		// 115 files search
		fetchSearch = async search_value => {
			const res = await request(
				"https://webapi.115.com/files/search",
				{
					search_value,
					offset: 0,
					limit: 10000,
					date: "",
					aid: 1,
					cid: 0,
					pick_code: "",
					type: 4,
					source: "",
					format: "json",
					o: "user_ptime",
					asc: 0,
					star: "",
					suffix: "",
				},
				"GET",
				{ responseType: "json" }
			);
			return (res?.data ?? []).map(({ fid, cid, n, pc, t, te, tp, play_long }) => {
				return { fid, cid, n, pc, t, te, tp, play_long };
			});
		};
		// 115 files search result match
		fetchResource = async (code = "", res) => {
			if (!res) res = await this.fetchSearch(code.split(/(-|_)/g)[0]);
			if (res?.length) {
				const codes = unique([
					code,
					code.replace(/-/g, ""),
					code.replace(/-/g, "."),
					code.replace(/-0/g, "."),
					code.replace(/-/g, "-0"),
					code.replace(/-/g, "0"),
					code.replace(/-/g, "00"),
					code.replace(/-/g, "_"),
					code.replace(/-/g, "_0"),
					code.replace(/-0/g, ""),
					code.replace(/-0/g, "-"),
					code.replace(/-0/g, "00"),
				]);
				const reg = createReg(codes);
				res = res
					.filter(({ n, play_long }) => n.match(reg) && play_long > 0)
					.map(({ n: name, pc: pickCode, fid, cid, te, tp, t: date }) => {
						return { name, pickCode, fid, cid, timestamp: Math.max(te, tp), date };
					});
			}
			return res;
		};
		fetchStar = async code => {
			const site = "https://javdb.com";
			let res = await request(`${site}/search?q=${code}`);
			const href = res?.querySelector("#videos .grid-item a").getAttribute("href");
			if (!href) return;
			res = await request(`${site}${href}`);
			res = res?.querySelectorAll(".panel-block");
			if (!res?.length) return;
			res = res[res.length - 3]?.querySelector(".value").textContent.trim();
			return res
				.split(/\n/)
				.filter(item => item.indexOf("♀") !== -1)
				.map(item => item.replace("♀", "").trim());
		};
		fetchImage = async (code, date) => {
			date = date.split("-");
			const jb = `http://img.japanese-bukkake.net/${date[0]}/${date[1]}/${code}_s.jpg`;
			const js = `https://javstore.net/search/${code}.html`;
			let [jbRes, jsRes] = await Promise.all([request(jb), request(js)]);
			const href = jsRes?.querySelector("#content_news li a")?.href;
			if (href) {
				jsRes = await request(href);
				jsRes = jsRes?.querySelector(".news a img[alt*='.th']").src.replace(".th", "");
				if (jsRes && (await request(jsRes))) return jsRes;
			}
			if (typeof jbRes === "object") return jb;
		};
		fetchVideo = async ({ code, studio }) => {
			code = code.toLowerCase();
			if (studio) {
				const fetchVideoByStudio = [
					{ name: "東京熱", match: "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4" },
					{ name: "カリビアンコム", match: "https://smovie.caribbeancom.com/sample/movies/%s/1080p.mp4" },
					{ name: "一本道", match: "http://smovie.1pondo.tv/sample/movies/%s/1080p.mp4" },
					{
						name: "HEYZO",
						transform: code => code.replace(/HEYZO\-/gi, ""),
						match: "https://www.heyzo.com/contents/3000/%s/heyzo_hd_%s_sample.mp4",
					},
				];
				const matched = fetchVideoByStudio.find(({ name }) => name === studio);
				if (matched) return matched.match.replace(/%s/g, matched.transform ? matched.transform(code) : code);
			}
			const [r18, xrmoo] = await Promise.all([
				request(`https://www.r18.com/common/search/searchword=${code}/`),
				request(`http://dmm.xrmoo.com/sindex.php?searchstr=${code}`),
			]);
			return (
				r18?.querySelector("a.js-view-sample")?.getAttribute("data-video-high") ||
				xrmoo
					?.querySelector(".card .card-footer a.viewVideo")
					?.getAttribute("data-link")
					.replace("_sm_w", "_dmb_w") ||
				""
			);
		};
		fetchMagnet = async code => {
			const matchList = [
				{
					site: "Sukebei",
					host: "https://sukebei.nyaa.si/",
					search: "?f=0&c=0_0&q=%s",
					selectors: ".table-responsive table tbody tr",
					filter: {
						name: e => e?.querySelector("td:nth-child(2) a").textContent,
						link: e => e?.querySelector("td:nth-child(3) a:last-child").href,
						size: e => e?.querySelector("td:nth-child(4)").textContent,
						date: e => e?.querySelector("td:nth-child(5)").textContent.split(" ")[0],
						href: e => e?.querySelector("td:nth-child(2) a").getAttribute("href"),
					},
					styleClass: "primary",
				},
				{
					site: "BTGG",
					host: "https://www.btgg.cc/",
					search: "search?q=%s",
					selectors: ".el-main .list .item",
					filter: {
						name: e => e?.querySelector(".name a").textContent,
						link: e => e?.querySelector(".meta a").href,
						size: e => e?.querySelector(".meta span").textContent.split(",")[0],
						date: e => e?.querySelector(".meta span:nth-child(4)").textContent.split(",")[0],
						href: e => e?.querySelector(".name a").getAttribute("href"),
					},
					styleClass: "success",
				},
				{
					site: "BTSOW",
					host: "https://btsow.rest/",
					search: "search/%s",
					selectors: ".data-list .row:not(.hidden-xs)",
					filter: {
						name: e => e?.querySelector(".file").textContent,
						link: e => `magnet:?xt=urn:btih:${e?.querySelector("a").href.split("/").pop()}`,
						size: e => e?.querySelector(".size").textContent,
						date: e => e?.querySelector(".date").textContent,
						href: e => e?.querySelector("a").getAttribute("href"),
					},
					styleClass: "danger",
				},
			];
			const matched = await Promise.all(
				matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
			);
			const magnets = [];
			for (let index = 0; index < matchList.length; index++) {
				let node = matched[index];
				if (!node) continue;
				const { selectors, site, styleClass, filter, host } = matchList[index];
				node = node?.querySelectorAll(selectors);
				if (!node || !node?.length) continue;
				for (const item of node) {
					const magnet = { from: site, styleClass };
					Object.keys(filter).map(key => {
						magnet[key] = filter[key](item).trim();
					});
					if (!("zh" in magnet)) magnet.zh = /中文/g.test(magnet.name);
					magnet.link = magnet.link.split("&")[0];
					const href = magnet?.href ?? "";
					if (href && !href.includes("//")) magnet.href = `${host}${href.replace(/^\//, "")}`;
					magnets.push(magnet);
				}
			}
			return magnets;
		};
		fetchPlayer = async code => {
			const codeReg = new RegExp(code, "gi");
			const matchList = [
				{
					site: "Netflav",
					host: "https://netflav.com/",
					search: "search?type=title&keyword=%s",
					selectors: ".grid_root .grid_cell",
					filter: { name: e => e?.querySelector(".grid_title").textContent },
				},
				{
					site: "BestJavPorn",
					host: "https://www2.bestjavporn.com/",
					search: "search/%s/",
					selectors: "#main article",
					filter: {
						name: e => e?.querySelector("a").title,
						zh: e => /中文/g.test(e?.querySelector(".hd-video")?.textContent ?? ""),
					},
				},
				{
					site: "JavHHH",
					host: "https://javhhh.com/",
					search: "v/?wd=%s",
					selectors: "#wrapper .typelist .i-container",
					filter: { name: e => e?.querySelector("img.img-responsive").title },
				},
				{
					site: "Avgle",
					host: "https://avgle.com/",
					search: "search/videos?search_query=%s&search_type=videos",
					selectors: ".row .well.well-sm",
					filter: { name: e => e?.querySelector(".video-title")?.textContent },
				},
			];
			const matched = await Promise.all(
				matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
			);
			const playList = [];
			for (let index = 0; index < matchList.length; index++) {
				let node = matched[index];
				if (!node) continue;
				const { selectors, site, filter, host } = matchList[index];
				node = node?.querySelectorAll(selectors);
				if (!node || !node?.length) continue;
				for (const item of node) {
					const player = { from: site };
					for (const key of Object.keys(filter)) player[key] = filter[key](item);
					const name = player?.name ?? "";
					if (!name || !name.match(codeReg)?.length) continue;
					if (!("zh" in player)) player.zh = /中文/g.test(name);
					if (!player?.link) player.link = item?.querySelector("a").getAttribute("href");
					const link = player?.link ?? "";
					if (link && !link.includes("//")) player.link = `${host}${link.replace(/^\//, "")}`;
					playList.push(player);
				}
			}
			return playList;
		};
		fetchSign = async () => {
			const res = await request("http://115.com/", { ct: "offline", ac: "space", _: new Date().getTime() });
			if (res?.sign) return { sign: res.sign, time: res.time };
			notify({
				title: "请求失败,115未登录",
				text: "请登录115账户后再离线下载",
				image: "fail",
				clickUrl: "http://115.com/?mode=login",
			});
		};
		fetchOffLine = async url => {
			let res = await this.fetchSign();
			if (!res) return;
			return await request(
				"http://115.com/web/lixian/?ct=lixian&ac=add_task_url",
				{ url, uid: 0, ...res },
				"POST",
				{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
			);
		};
		fetchRename = async res => {
			const data = {};
			for (const { fid, file_name } of res) data[`files_new_name[${fid}]`] = file_name;
			return await request("https://webapi.115.com/files/batch_rename", data, "POST", {
				headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
				responseType: "json",
			});
		};
		fetchMove = async res => {
			const data = { pid: this.CID, move_proid: `${new Date()}_${~(100 * Math.random())}_0` };
			for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index].fid;
			return await request("https://webapi.115.com/files/move", data, "POST", {
				headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
				responseType: "json",
			});
		};
		fetchDelDir = async res => {
			const data = { pid: this.CID, ignore_warn: 1 };
			res = unique(res.map(({ cid }) => cid).filter(item => item !== this.CID));
			for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index];
			return await request("https://webapi.115.com/rb/delete", data, "POST", {
				headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
				responseType: "json",
			});
		};
		searchOnKey = selectors => {
			DOC.addEventListener("DOMContentLoaded", () => {
				DOC.addEventListener("keyup", event => {
					const e = event || window.event;
					if (e && e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(DOC.activeElement.nodeName)) {
						DOC.querySelector(selectors).focus();
					}
				});
			});
		};
		handlePreviewFold = selectors => {
			GM_addStyle(`
            ${selectors} {
                overflow: hidden;
                height: fit-content;
                max-height: ${this.expHei}px;
            }
            #expBtn {
                top: 0;
                left: 0;
                right: 0;
                color: #fff;
                z-index: 9;
                cursor: pointer;
                position: absolute;
                background: rgba(0,0,0,.7);
                height: ${this.expBtnHei}px;
                line-height: ${this.expBtnHei}px;
                margin-top: ${this.expHei - this.expBtnHei}px;
            }
            #expBtn::after { content: "▼ 展开"; }
            #exp:checked + ${selectors} > #expBtn::after { content: "▲ 收起"; }
            #exp:checked + ${selectors}  { max-height: none; }
            #exp:checked + ${selectors} > #expBtn {
                top: auto;
                bottom: 0;
                margin-bottom: 0;
            }
            `);
			const modifyMaxHei = () => {
				const expHei = this.expHei;
				const img = DOC.querySelector(`${selectors} img`);
				const styleHei = img.height || img.clientHeight || img.offsetHeight;
				if (styleHei > expHei) {
					GM_addStyle(`
                    ${selectors} { max-height: ${expHei}px; }
                    #expBtn { margin-top: ${expHei - this.expBtnHei}px; }
                    `);
				} else {
					GM_addStyle(`
                    ${selectors} { max-height: ${styleHei + this.expBtnHei}px; }
                    #expBtn { margin-top: ${styleHei}px; }
                    `);
				}
			};
			const box = DOC.querySelector(selectors);
			const img = box.querySelector("img");
			if (img.complete) modifyMaxHei();
			img.onload = () => modifyMaxHei();
			box.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`);
			box.insertAdjacentHTML("beforeend", `<label for="exp" id="expBtn"></label>`);
		};
	}
	class JavBus extends Common {
		constructor() {
			super();
			this.start();
			const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname));
			return { ...this, ...this[tag] };
		}
		routeReg = {
			waterfall:
				/^\/((uncensored|uncensored\/)?(page\/\d+)?$)|((uncensored\/)?((search|searchstar|actresses|genre|star|studio|label|series|director|member)+\/)|actresses(\/\d+)?)+/i,
			genre: /^\/(uncensored\/)?genre$/i,
			forum: /^\/forum\//i,
			details: /^\/[\w]+(-|_)?[\d]*.*$/i,
		};
		start = () => {
			this.registerMenu();
			this.initDB();
			this.customStyle();
			this.customMode();
			this.searchOnKey("#search-input");
		};
		customMode = () => {
			GM_addStyle(`.ad-box { display: none !important; }`);
		};
		darkMode = dStyle => {
			const { Grey, LabelColor, Blue, Green, Red, Orange, Background, SecondaryBackground } = dStyle;
			GM_addStyle(`
            .btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .85 !important; }
            button, .btn-default, .input-group-addon {
                background: ${Grey} !important;
                color: ${LabelColor} !important;
            }
            .btn-primary {
                background: ${Blue} !important;
                border-color: ${Blue} !important;
            }
            .btn-success {
                background: ${Green} !important;
                border-color: ${Green} !important;
            }
            .btn-danger {
                background: ${Red} !important;
                border-color: ${Red} !important;
            }
            .btn-warning {
                background: ${Orange} !important;
                border-color: ${Orange} !important;
            }
            .navbar-nav>.active>a, .navbar-nav>.active>a:focus, .navbar-nav>.active>a:hover, .navbar-nav>.open>a, .dropdown-menu { background: ${Background} !important; }
            .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { background: ${Grey} !important; }
            .pagination .active a {
                border: none !important;
                color: ${LabelColor} !important;
            }
            .pagination>li>a, .pagination>li>span {
                background-color: ${Grey} !important;
                border: none !important;
                color: ${LabelColor} !important;
            }
            tr, .modal-content, .alert {
                background: ${SecondaryBackground} !important;
                box-shadow: none !important;
            }
            tr:hover { background: ${Grey} !important; }
            `);
		};
		waterfall = {
			docStart() {
				GM_addStyle(`
                .search-header {
                    padding: 0 !important;
                    background: none !important;
                    box-shadow: none !important;
                }
                .photo-frame {
                    position: relative;
                    margin: 10px !important;
                }
                .photo-frame img {
                    height: 100% !important;
                    width: 100% !important;
                    object-fit: cover !important;
                    margin: 0 !important;
                }
                .photo-info { padding: 10px !important; }
                .alert-page { margin: 20px !important; }
                `);
				if (this.LM) {
					const itemSizer = `167px`;
					const gutterSizer = `20px`;
					GM_addStyle(`
                    .pagination, footer { display: none !important; }
                    .page-load-status {
                        display: none;
                        padding-bottom: ${gutterSizer};
                        text-align: center;
                    }
                    body { overflow: hidden; }
                    .scrollBox {
                        height: calc(100vh - 51px);
                        overflow: hidden scroll;
                    }
                    #waterfall {
                        opacity: 0;
                        margin: ${gutterSizer} auto 0 auto !important;
                    }
                    .item-sizer, .item a { width: ${itemSizer} !important; }
                    .gutter-sizer { width: ${gutterSizer} !important; }
                    .item a { margin: 0 0 ${gutterSizer} 0 !important; }
                    `);
				}
				if (this.DM) {
					const dStyle = this.darkStyle();
					this.darkMode(dStyle);
					const { SecondaryBackground, LabelColor, SecondaryLabelColor, Grey } = dStyle;
					GM_addStyle(`
                    .item a { background: ${SecondaryBackground} !important; }
                    .photo-info {
                        background: ${SecondaryBackground} !important;
                        color: ${LabelColor} !important;
                    }
                    date { color: ${SecondaryLabelColor} !important; }
                    .nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover, .nav-pills>li>a:focus, .nav-pills>li>a:hover { background-color: ${Grey} !important; }
                    `);
				}
			},
			contentLoaded() {
				const nav = DOC.querySelector(".search-header .nav");
				if (nav) nav.setAttribute("class", "nav nav-pills");
				this.modifyItem();
				if (!this.LM) return;
				this.modifyLayout();
				this.handleWaterfall();
			},
			async modifyItem(node = DOC) {
				if (this.CK) this.handleClick(".item a", node);
				const items = node.querySelectorAll(".item");
				for (const item of items) {
					const info = item.querySelector("a .photo-info span:not(.mleft)");
					if (!info) continue;
					const [titleNode, secondaryNode] = info.childNodes;
					const titleTxt = titleNode.textContent.trim();
					const title = DOC.create("div", { class: "title", title: titleTxt }, titleTxt);
					if (this.LM) title.classList.add("ellipsis");
					if (secondaryNode?.nodeName === "BR") info.removeChild(secondaryNode);
					info.replaceChild(title, titleNode);
				}
				for (const item of items) {
					let code = item.querySelector("date");
					if (!code) continue;
					code = code.textContent.trim().toUpperCase();
					const resource = await this.fetchMatch(code);
					if (!resource?.length) continue;
					item.querySelector(".title").classList.add("matched");
					const photo = item.querySelector(".photo-frame");
					photo.classList.add("playBtn");
					photo.setAttribute("title", "点击播放");
					photo.addEventListener("click", e => {
						e.stopPropagation();
						e.preventDefault();
						GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true });
					});
				}
			},
			modifyLayout() {
				const oldWaterfall = DOC.querySelector("#waterfall");
				if (!oldWaterfall) return;
				const newWaterfall = DOC.querySelector("#waterfall #waterfall");
				if (newWaterfall) oldWaterfall.parentNode.replaceChild(newWaterfall, oldWaterfall);
				const waterfall = DOC.querySelector("#waterfall");
				waterfall.insertAdjacentHTML(
					"afterbegin",
					`<div class="item-sizer"></div><div class="gutter-sizer"></div>`
				);
				waterfall.insertAdjacentHTML(
					"afterend",
					`<div class="page-load-status"><span class="loader-ellips infinite-scroll-request">Loading...</span><span class="infinite-scroll-last">End of content</span><span class="infinite-scroll-error">No more pages to load</span></div>`
				);
				DOC.querySelector(".container-fluid .row")?.classList.add("scrollBox");
			},
			handleWaterfall() {
				const infScroll = this.waterfallLayout(
					"#waterfall",
					".item",
					{},
					{
						path: !/^\/(uncensored\/)?(search|searchstar)+\//i.test(location.pathname)
							? "#next"
							: function () {
									const { pathname } = location;
									const items = ["search", "searchstar"];
									for (const item of items) {
										if (pathname.indexOf(`${item}/`) < 0) continue;
										let [prefix, suffix] = pathname.split("&");
										suffix = suffix ?? "";
										prefix = prefix.split("/");
										let pre = "";
										for (let index = 0; index <= prefix.indexOf(item) + 1; index++) {
											pre = `${pre}${prefix[index]}/`;
										}
										return `${pre}${this.loadCount + 2}&${suffix}`;
									}
							  },
						elementScroll: ".scrollBox",
						status: ".page-load-status",
					}
				);
				infScroll?.on("load", e => this.modifyItem(e));
			},
		};
		genre = {
			docStart() {
				GM_addStyle(`
                footer { display: none !important; }
                button.btn.btn-danger.btn-block.btn-genre {
                    position: fixed !important;
                    bottom: 0 !important;
                    margin: 0 !important;
                    left: 0 !important;
                    border: 0 !important;
                    border-radius: 0 !important;
                }
                `);
				if (this.DM) {
					const dStyle = this.darkStyle();
					this.darkMode(dStyle);
					const { SecondaryBackground } = dStyle;
					GM_addStyle(`.genre-box { background-color: ${SecondaryBackground} !important; }`);
				}
			},
			contentLoaded() {
				if (!DOC.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return;
				const box = DOC.querySelectorAll(".genre-box");
				box[box.length - 1].setAttribute("style", "margin-bottom:65px");
			},
		};
		forum = {
			docStart() {
				GM_addStyle(`
                .bcpic, .banner728, .sd.sd_allbox > div:last-child { display: none !important; }
                .jav-button { margin-top: -3px !important; }
                #toptb {
                    position: fixed !important;
                    top: 0 !important;
                    left: 0 !important;
                    right: 0 !important;
                    z-index: 999 !important;
                }
                #wp { margin-top: 55px !important; }
                `);
			},
		};
		details = {
			docStart() {
				GM_addStyle(`
				.info .glyphicon-info-sign,
				h4[style="position:relative"],
				h4[style="position:relative"] + .row { display: none !important; }
				.info ul { margin: 0 !important; }
				#avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px !important; }
				.photo-info { height: auto !important; }
				#resBox a { color: #CC0000 !important; }
				#smartOff { width: 100%; }
                #expBtn { margin-left: 15px; margin-right: 15px; }
				`);
				GM_addStyle(`
                #collapseBtn::after { content: "▼ 展开表格"; }
                #collapse:checked + .movie { max-height: none; }
                #collapse:checked + .movie #collapseBtn::after { content: "▲ 收起表格"; }
                `);
				if (this.DM) {
					const dStyle = this.darkStyle();
					this.darkMode(dStyle);
					const { SecondaryBackground, LabelColor, Grey } = dStyle;
					GM_addStyle(`
                    .movie, .sample-box, .movie-box, .photo-info { background: ${SecondaryBackground} !important; }
                    .photo-info { color: ${LabelColor} !important; }
                    .avatar-box, .avatar-box span, .info ul li, .info .star-name {
                        background: ${SecondaryBackground} !important;
                        border-color: ${Grey} !important;
                        color: ${LabelColor} !important;
                    }
                    `);
				}
			},
			contentLoaded() {
				if (this.CK) {
					this.handleClick("a.movie-box");
					this.handleClick("a.avatar-box");
				}
				// insert copy
				this.handleCopy("h3");
				this.handleCopy("span[style='color:#CC0000;']");
				// expBtn
				this.handlePreviewFold(".screencap");

				const info = DOC.querySelector(".info");
				// resource, player
				info.insertAdjacentHTML(
					"beforeend",
					`<p id="playerBox"><span class="header">在线播放:</span><span class="genre">查询中...</span></p>
                    <p id="resBox"><span class="header">网盘资源:</span><span class="genre">查询中...</span></p>`
				);
				// smart offLine
				const smartRes = DOC.create("button", { class: "btn btn-default", id: "smartOff" }, "一键离线");
				smartRes.addEventListener("click", e => this.handleOffLine(e, "smart"));
				info.insertAdjacentElement("beforeend", smartRes);
				// table
				DOC.querySelector("#magnet-table tbody tr").insertAdjacentHTML(
					"beforeend",
					`<td style="text-align:center;white-space:nowrap">操作</td>`
				);
				// ellipsis
				for (const item of DOC.querySelectorAll(".photo-info span")) item.classList.add("ellipsis");
				// handleFetch
				const params = this.getParams();
				if (!params) return;
				this.handleTransTitle(params);
				this.handleResource(params);
				this.handleStar(params);
				this.handleImage(params);
				this.handleVideo(params);
				this.handlePlayer(params);

				const magnetObs = new MutationObserver(() => this.handleMagnet(params));
				magnetObs.observe(DOC.querySelector("#movie-loading"), {
					attributes: true,
					attributeFilter: ["style"],
				});

				const tableObs = new MutationObserver(mutationsList => {
					this.modifyTable();
					mutationsList.forEach(({ addedNodes }) => {
						if (addedNodes) {
							for (const node of addedNodes) {
								if (node.nodeName === "TR") this.modifyTR(node);
								if (node.nodeName === "TBODY") {
									const trs = node?.querySelectorAll("tr");
									if (trs.length) for (const tr of trs) this.modifyTR(tr);
								}
							}
						}
					});
				});
				tableObs.observe(DOC.querySelector("#magnet-table"), { childList: true });
			},
			getParams() {
				// regex
				const charReg = /[\u4e00-\u9fa5:]/g;
				const studioReg = /製作商:/g;
				const starReg = /暫無出演者資訊/g;
				// dom
				const info = DOC.querySelector(".info");
				const infos = info.querySelectorAll("p");
				// params
				let [code, date] = infos;
				if (!code || !date) return;
				code = code.textContent.replace(charReg, "").trim().toUpperCase();
				date = date.textContent.replace(charReg, "").trim().toUpperCase();
				let studio = "";
				for (const { textContent: text } of infos) {
					if (!studioReg.test(text)) continue;
					studio = text.replace(studioReg, "").trim();
					break;
				}
				return {
					code,
					date,
					studio,
					star: !starReg.test(info.textContent),
					title: DOC.querySelector("h3").textContent.replace("复制", "").trim(),
					res: getItem(code),
				};
			},
			modifyTR(node) {
				const href = node?.querySelector("td a")?.href;
				if (!href) return;
				const td = DOC.create("td", { style: "text-align:center;white-space:nowrap" });
				const copy = DOC.create(
					"a",
					{ href, "data-copy": href, title: "复制磁力链接", style: "margin-right:16px" },
					"复制"
				);
				const offline = DOC.create("a", { href, title: "仅添加离线任务" }, "离线下载");
				copy.addEventListener("click", this.copyTxt);
				offline.addEventListener("click", e => this.handleOffLine(e, href));
				td.appendChild(copy);
				td.appendChild(offline);
				node.appendChild(td);
			},
			modifyTable() {
				if (DOC.querySelector("#collapse")) return;
				const trs = DOC.querySelectorAll("#magnet-table tr");
				const limit = 7;
				if (trs?.length <= limit) return;
				let maxHeight = 12;
				for (let index = 0; index < limit; index++) {
					const tr = trs[index];
					maxHeight += tr.offsetHeight;
					if (index) continue;
					const td = tr.querySelector("td:last-child");
					td.textContent = "";
					td.insertAdjacentHTML(
						"beforeend",
						`<label
                            for="collapse"
                            id="collapseBtn"
                            class="btn btn-mini-new btn-primary"
                            title="点击展开/收起表格"
                        >
                        </label>`
					);
				}
				DOC.querySelector("#magneturlpost + .movie").insertAdjacentHTML(
					"beforebegin",
					`<input type="checkbox" id="collapse" class="hidden">`
				);
				GM_addStyle(`#collapse + .movie { max-height: ${maxHeight}px; }`);
			},
			async handleTransTitle({ code, title, res }) {
				let transTitle = res?.transTitle ?? "";
				if (!transTitle) {
					transTitle = await this.fetchTranslate(title);
					if (!transTitle) return;
					upItem(code, { transTitle });
				}
				DOC.querySelector("h3").setAttribute("title", transTitle);
			},
			async handleResource({ code }) {
				const resource = await this.fetchResource(code);
				upItem(code, { resource });
				const resBox = DOC.querySelector("#resBox");
				for (const old of resBox.querySelectorAll(".genre")) resBox.removeChild(old);
				if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="genre">无</span>`);
				resBox.insertAdjacentHTML(
					"beforeend",
					resource.reduce(
						(acc, { name, pickCode, date }) => `
                        ${acc}
                        <span class="genre">
                            <a href="${this.pcPreview}${pickCode}" title="${date}/${name}" target="_blank">
                                ${name.length > 20 ? `${name.substr(0, 20)}...` : name}
                            </a>
                        </span>`,
						""
					)
				);
				return resource;
			},
			async handleStar({ code, star, res }) {
				if (star) return;
				star = res?.star ?? [];
				if (!star?.length) {
					star = await this.fetchStar(code);
					if (!star?.length) return;
					upItem(code, { star });
				}
				const p = DOC.create("p");
				p.insertAdjacentHTML(
					"beforeend",
					star.reduce(
						(acc, cur) => `${acc}<span class="genre"><a href="/search/${cur}">${cur}</a></span>`,
						""
					)
				);
				DOC.querySelector(".info").replaceChild(
					p,
					DOC.querySelector("span.glyphicon.glyphicon-info-sign.mb20")?.nextSibling
				);
			},
			async handleImage({ code, res, date }) {
				let image = res?.image ?? "";
				if (!image) {
					image = await this.fetchImage(code, date);
					if (!image) return;
					upItem(code, { image });
				}
				const screencap = DOC.querySelector(".screencap");
				const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") });
				screencap.appendChild(placeholder);
				const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
				img.addEventListener("click", () => {
					if (getScrollTop() >= this.expHei) window.scrollTo(0, 0);
					DOC.querySelector("#exp").checked = false;
				});
				img.onload = () => screencap.replaceChild(img, placeholder);
			},
			async handleVideo({ code, studio, res }) {
				let video = res?.video ?? "";
				if (!video) {
					video = await this.fetchVideo({ code, studio });
					if (!video) return;
					upItem(code, { video });
				}
				// func
				const title = "预览视频";
				const playVideo = e => {
					e.preventDefault();
					e.stopPropagation();
					DOC.querySelector("#video-mask").setAttribute("style", "display:flex");
					const video = DOC.querySelector("video");
					video.play();
					video.focus();
					DOC.onkeydown = event => {
						const e = event || window.event;
						if (e && e.keyCode === 27) pauseVideo();
					};
				};
				const pauseVideo = () => {
					DOC.querySelector("#video-mask").setAttribute("style", "display:none");
					DOC.querySelector("video").pause();
					DOC.onkeydown = null;
				};
				// mask
				const videoNode = DOC.create("video", { controls: "controls", src: video, width: 720 });
				videoNode.currentTime = 3;
				videoNode.preload = "auto";
				videoNode.muted = true;
				const closeBtn = DOC.create(
					"button",
					{ title: "Close (Esc)", type: "button", class: "mfp-close" },
					"×"
				);
				closeBtn.addEventListener("click", pauseVideo);
				const mask = DOC.create("div", { id: "video-mask", class: "mask" });
				mask.appendChild(closeBtn);
				mask.appendChild(videoNode);
				DOC.body.appendChild(mask);
				// screencap
				const bigImage = DOC.querySelector(".bigImage");
				const playBtn = DOC.create("div", { class: "playBtn", title });
				const bImg = bigImage.querySelector("img");
				playBtn.addEventListener("click", playVideo);
				playBtn.appendChild(bImg);
				bigImage.appendChild(playBtn);
				// sample-waterfall
				const thumb = bImg.src;
				const box = DOC.create("a", { class: "sample-box", href: thumb, title });
				box.addEventListener("click", playVideo);
				box.insertAdjacentHTML("beforeend", `<div class="photo-frame playBtn"><img src="${thumb}"></div>`);
				let waterfall = DOC.querySelector("#sample-waterfall");
				if (!waterfall) {
					DOC.querySelector("div.clearfix").insertAdjacentHTML(
						"beforebegin",
						`<div id="sample-waterfall"></div>`
					);
					waterfall = DOC.querySelector("#sample-waterfall");
					waterfall.insertAdjacentHTML("beforebegin", `<h4>樣品圖像</h4>`);
					return waterfall.appendChild(box);
				}
				// jump to preview
				const ref = waterfall.querySelector("a");
				waterfall.insertBefore(box, ref);
				const imgBtn = DOC.create(
					"button",
					{ title: "樣品圖像", type: "button", class: "mfp-close", style: "right:44px" },
					"📷"
				);
				imgBtn.addEventListener("click", () => {
					pauseVideo();
					ref.click();
				});
				mask.appendChild(imgBtn);
			},
			async handlePlayer({ code, res }) {
				let player = res?.player ?? [];
				if (!player?.length) {
					player = await this.fetchPlayer(code);
					if (player?.length) upItem(code, { player });
				}
				const box = DOC.querySelector("#playerBox");
				const genre = box.querySelector(".genre");
				if (!player?.length) return (genre.textContent = "无");
				genre.remove();
				box.insertAdjacentHTML(
					"beforeend",
					player.reduce(
						(pre, { zh, link, name, from }) => `
                        ${pre}
                        <button
                            class="btn btn-mini-new ${zh ? "btn-warning" : "btn-primary"}"
                            data-link="${link}"
                            title="${name}"
                        >
                            <strong>▶ ${from}</strong>
                        </button>
                        `,
						""
					)
				);
				for (const btn of DOC.querySelectorAll("#playerBox button")) {
					btn.addEventListener("click", () => GM_openInTab(btn.dataset.link, { active: true }));
				}
			},
			async handleMagnet({ code, res }) {
				let magnet = res?.magnet ?? [];
				if (!magnet?.length) {
					magnet = await this.fetchMagnet(code);
					if (!magnet?.length) return;
					upItem(code, { magnet });
				}
				const tdStyle = "text-align:center;white-space:nowrap";
				magnet = magnet.reduce(
					(pre, { link, name, styleClass, href, from, zh, size, date }) => `
                    ${pre}
                    <tr>
                        <td>
                            <a href="${link}">${name}</a>
                            <a
                                class="btn btn-mini-new btn-${styleClass}"
                                href="${href}"
                                target="_blank"
                                title="磁链详情页"
                            >
                                <strong>★ ${from}</strong>
                            </a>
                            ${zh ? `<a class="btn btn-mini-new btn-warning disabled">字幕</a>` : ""}
                        </td>
                        <td style="${tdStyle}">${size}</td>
                        <td style="${tdStyle}">${date}</td>
                    </tr>
                    `,
					`<tr style="color:#CC0000"><td colspan="4">以下磁链来自外部</td></tr>`
				);
				DOC.querySelector("#magnet-table").insertAdjacentHTML("beforeend", magnet);
			},
			async handleOffLine(e, link) {
				e.preventDefault();
				e.stopPropagation();
				if (!link) return;
				const node = e.target;
				const text = node?.textContent ?? "";
				node.textContent = "请求中...";
				node.setAttribute("disabled", true);
				node.setAttribute("style", "pointer-events:none");

				const clickUrl = this.clickUrl;
				if (link !== "smart") {
					let res = await this.fetchOffLine(link);
					const { state = false, error_msg = "" } = res;
					notify({
						title: `请求${state ? "成功" : "失败"}`,
						text: `${error_msg ? error_msg : "仅添加离线任务"}`,
						image: `${state ? "info" : "fail"}`,
						clickUrl,
					});
				} else {
					let links = [];
					const trs = DOC.querySelectorAll("#magnet-table tr");
					for (let index = 1; index < trs.length; index++) {
						let [link, size, date] = trs[index]?.querySelectorAll("td");
						if (!link || !size || !date) continue;
						links.push({
							link: link.querySelector("a").href.split("&")[0],
							zh: !!link.querySelector("a.btn.btn-mini-new.btn-warning.disabled"),
							size: transToBytes(size.textContent.trim()),
							date: date.textContent.trim(),
						});
					}
					if (links.length) {
						if (links.length > 1) links = unique(links, "link");
						if (links.length > 1) {
							links.sort((pre, next) => {
								if (pre.zh === next.zh) {
									if (pre.size === next.size) return next.date - pre.date;
									return next.size - pre.size;
								} else {
									return pre.zh > next.zh ? -1 : 1;
								}
							});
						}
						tooltip("磁链列表");
						console.table(links);
						const params = this.getParams();
						for (let index = 0; index < links.length; index++) {
							const { link, zh } = links[index];
							let res = await this.fetchOffLine(link);
							if (!res || res?.errcode === 911) break;
							if (res?.state) {
								res = await this.checkResource(params);
								if (res?.length) {
									this.afterAction({ ...params, zh, res });
									notify({
										title: "一键离线任务成功",
										text: "进行后续操作",
										image: "success",
										clickUrl,
									});
									break;
								}
							}
							if (index !== links.length - 1) continue;
							notify({ title: "一键离线任务失败", image: "fail", clickUrl });
						}
					}
				}

				node.textContent = text;
				node.removeAttribute("disabled");
				node.removeAttribute("style");
			},
			async checkResource({ code }) {
				const old = getItem(code)?.resource ?? [];
				await delay(2500);
				const res = await this.handleResource({ code });
				if (Array.isArray(res) && res?.length) {
					const today = res.filter(({ date }) => date === getDate());
					if (today.length && res.length > old.length) return today;
				}
				return false;
			},
			async afterAction({ zh, res, title, code }) {
				res = res.filter(({ name }) => name.indexOf(title) === -1);
				if (!res.length) return;
				title = `${zh ? "【中文字幕】" : ""}${title}`;
				res = res.map(item => {
					const suffix = item.name.split(".").pop().toLowerCase();
					return { ...item, file_name: `${title}.${suffix}` };
				});
				await this.fetchRename(res);
				if (this.CID) {
					const moveRes = await this.fetchMove(res);
					if (moveRes?.state) this.fetchDelDir(res);
				}
				this.handleResource({ code });
			},
		};
	}
	class JavDB extends Common {
		constructor() {
			super();
			this.start();
			const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname));
			return { ...this, ...this[tag] };
		}
		routeReg = {
			waterfall:
				/^\/$|^\/(censored|uncensored|western|fc2|anime|search|video_codes|tags|rankings|actors|series|makers|directors|publishers)/i,
			details: /^\/v\//i,
		};
		start = () => {
			this.registerMenu(this.menus.filter(item => !["DM"].includes(item.key)));
			this.initDB();
			this.customStyle();
			this.customMode();
			this.searchOnKey("#video-search");
		};
		customMode = () => {
			GM_addStyle(`.message-body .moj-content { display: none !important; }`);
		};
		waterfall = {
			container: {},
			containerList: [
				{
					selectors: "#videos .columns",
					itemSelectors: "#videos .columns .column",
					item: "#videos .columns .column a",
					itemSizer: "160px",
				},
				{
					selectors: "#actors",
					itemSelectors: "#actors .box",
					item: "#actors .box",
					itemSizer: "130px",
				},
			],
			docStart() {
				GM_addStyle(`
                #tags { display: none; }
                #expBtn::after { content: "展开"; }
                #exp:checked + .tabs.is-boxed + #tags { display: block; }
                #exp:checked + .tabs.is-boxed > #expBtn::after { content: "收起"; }
                `);
				if (this.LM) {
					const gutterSizer = `20px`;
					GM_addStyle(`
                    nav.pagination, nav#footer { display: none; }
                    .gutter-sizer { width: ${gutterSizer} !important; }
                    #videos .columns .column img {
                        height: 200px !important;
                        object-fit: cover;
                    }
                    ${this.containerList.map(item => item.selectors).join(", ")} { opacity: 0; }
                    ${this.containerList.reduce(
						(pre, { itemSelectors, item, itemSizer }) => `
                        ${pre}
                        ${itemSelectors} {
                            padding: 0 !important;
                            margin: 0 0 ${gutterSizer} 0 !important;
                            width: ${itemSizer} !important;
                        }
                        ${item} { width: ${itemSizer} !important; }
                        `,
						""
					)}
                    `);
				}
			},
			contentLoaded() {
				this.modifyItem();

				if (DOC.querySelector("#tags")) {
					const tabs = DOC.querySelector(".tabs.is-boxed");
					tabs.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`);
					tabs.insertAdjacentHTML(
						"beforeend",
						`<label class="button is-link" for="exp" id="expBtn"></label>`
					);
				}

				if (!this.LM) return;
				this.container = this.containerList.find(({ selectors }) => DOC.querySelector(selectors));
				if (location.pathname === "/rankings/fanza_award" || !this.container) {
					return GM_addStyle(`nav.pagination, nav#footer { display: flex; }`);
				}
				GM_addStyle(`
                .item-sizer { width: ${this.container.itemSizer} !important; }
                ${this.container.selectors} { margin: 0 auto !important; }
                `);
				this.modifyLayout();
				this.handleWaterfall();
			},
			async modifyItem(node = DOC) {
				if (this.CK) {
					const extra = [
						{ itemSelectors: "#series .columns .column" },
						{ itemSelectors: "#makers .columns .column" },
						{ itemSelectors: "#directors .columns .column" },
					];
					for (const { itemSelectors } of unique([...this.containerList, ...extra], "itemSelectors")) {
						this.handleClick(`${itemSelectors} a`, node);
					}
				}
				const items = node.querySelectorAll("#videos .columns .column");
				for (const item of items) {
					let img = item.querySelector("img");
					if (img) img.src = img.dataset.src;
				}
				for (const item of items) {
					let code = item.querySelector(".uid");
					if (!code) continue;
					code = code.textContent.trim().toUpperCase();
					const resource = await this.fetchMatch(code);
					if (!resource?.length) continue;
					const title = item.querySelector(".video-title");
					const titleTxt = title.textContent;
					title.textContent = "";
					title.insertAdjacentHTML("beforeend", `<span class="matched">${titleTxt}</span>`);
					const photo = item.querySelector(".item-image");
					photo.classList.add("playBtn");
					photo.setAttribute("title", "点击播放");
					photo.addEventListener("click", e => {
						e.stopPropagation();
						e.preventDefault();
						GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true });
					});
				}
			},
			modifyLayout() {
				const waterfall = DOC.querySelector(this.container.selectors);
				waterfall.classList.remove("grid");
				waterfall.insertAdjacentHTML(
					"afterbegin",
					`<div class="item-sizer"></div><div class="gutter-sizer"></div>`
				);
			},
			handleWaterfall() {
				const infScroll = this.waterfallLayout(
					this.container.selectors,
					this.container.itemSelectors,
					{},
					{ path: ".pagination-next" }
				);
				infScroll?.on("load", e => this.modifyItem(e));
			},
		};
		details = {
			docStart() {
				GM_addStyle(`
				.video-meta-panel {
				    padding-left: 0;
				    padding-right: 0;
				}
				.video-meta-panel > .columns {
				    margin-left: 0;
				    margin-right: 0;
				}
				.video-meta-panel > .columns > .column {
				    padding: 0;
				    margin: .75rem;
				}
				.column-video-cover .cover-container::after { height: 100%; }
                .column-video-cover img {
                    width: 100%;
                    max-height: none;
                }
				.movie-panel-info div.panel-block { padding: 8px 0; }
                #smartOff { flex: 1; }
				#resBox .button,
				#playerBox .button {
				    height: auto;
				    padding: 3px 6px;
				    line-height: unset;
				}
                #playerBox { border-bottom: 0; }
                .preview-video-container::after { height: 100%; }
				`);
				GM_addStyle(`
                #magnets-content { overflow: hidden; }
                #collapseBtn::after { content: "▼ 展开表格" }
                #collapse:checked + .message-body > #magnets-content { max-height: none; }
                #collapse:checked + .message-body > #collapseBtn::after { content: "▲ 收起表格" }
                `);
			},
			contentLoaded() {
				if (this.CK) this.handleClick(".tile-small a.tile-item");
				// insert copy
				this.handleCopy("h2");

				this.handlePreviewFold(".column-video-cover");
				this.modifyTable();

				const info = DOC.querySelector("nav.movie-panel-info");
				// insert smartOff
				const smartRes = DOC.create(
					"a",
					{ class: "button is-info", id: "smartOff", href: "javascript:;" },
					"一键离线"
				);
				smartRes.addEventListener("click", e => this.handleOffLine(e, "smart"));
				const infos = info.querySelectorAll(".panel-block");
				const lastPanel = infos[infos.length - 1];
				lastPanel.querySelector(".columns").setAttribute("style", "flex:1");
				lastPanel.querySelector(".buttons").appendChild(smartRes);
				// insert player & resource
				info.insertAdjacentHTML(
					"beforeend",
					`<div class="panel-block" id="resBox">
				        <strong>网盘资源:</strong><span class="value">查询中...</span>
				    </div>
				    <div class="panel-block" id="playerBox">
				        <strong>在线播放:</strong><span class="value">查询中...</span>
				    </div>`
				);
				// handleFetch
				const params = this.getParams();
				if (!params) return;
				this.handleTransTitle(params);
				this.handleResource(params);
				this.handleImage(params);
				this.handleVideo(params);
				this.handlePlayer(params);
				this.handleMagnet(params);
			},
			getParams() {
				const charReg = /[\u4e00-\u9fa5:]/g;
				const studioReg = /片商:/g;
				const infos = DOC.querySelectorAll("nav.movie-panel-info .panel-block");
				let [code, date] = infos;
				if (!code || !date) return;
				code = code.textContent.replace(charReg, "").trim().toUpperCase();
				date = date.textContent.replace(charReg, "").trim().toUpperCase();
				let studio = "";
				for (const { textContent: text } of infos) {
					if (!studioReg.test(text)) continue;
					studio = text.replace(studioReg, "").trim();
					break;
				}
				return {
					code,
					date,
					studio,
					video: !!DOC.querySelector(".preview-video-container"),
					title: DOC.querySelector("h2").textContent.replace("复制", "").trim(),
					res: getItem(code),
				};
			},
			modifyTable() {
				const table = DOC.querySelector("#magnets-content table");
				if (!table) return;
				const trs = table.querySelectorAll("tr");
				for (const tr of trs) {
					const href = tr.querySelector(".magnet-name a").href;
					if (!href || tr.querySelector(".offline")) continue;
					const td = DOC.create("td", { class: "sub-column offline", width: "94" });
					const btn = DOC.create(
						"button",
						{ class: "button is-info is-small", type: "button", title: "仅添加离线任务" },
						"离线下载"
					);
					btn.addEventListener("click", e => this.handleOffLine(e, href));
					td.appendChild(btn);
					tr.appendChild(td);
				}

				const limit = 6;
				if (trs?.length <= limit) return;
				let maxHeight = 0;
				for (let index = 0; index < limit; index++) maxHeight += trs[index].offsetHeight;
				const body = DOC.querySelector("#magnet-links .message-body");
				body.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="collapse" class="hidden">`);
				body.querySelector(".moj-content").insertAdjacentHTML(
					"beforebegin",
					`<label for="collapse" id="collapseBtn" class="button is-info is-small mb-2"></label>`
				);
				GM_addStyle(`#magnets-content { max-height: ${maxHeight}px; }`);
			},
			async handleTransTitle({ code, title, res }) {
				let transTitle = res?.transTitle ?? "";
				if (!transTitle) {
					transTitle = await this.fetchTranslate(title);
					if (!transTitle) return;
					upItem(code, { transTitle });
				}
				DOC.querySelector("h2").setAttribute("title", transTitle);
			},
			async handleResource({ code }) {
				const resource = await this.fetchResource(code);
				upItem(code, { resource });
				const resBox = DOC.querySelector("#resBox");
				resBox.querySelector(".value")?.remove();
				resBox.querySelector(".columns")?.remove();
				if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="value">无</span>`);
				resBox.insertAdjacentHTML(
					"beforeend",
					`
                    <div class="columns">
                    <div class="column">
                    <div class="buttons are-small review-buttons">
                    ${resource.reduce(
						(acc, { name, pickCode, date }) => `
				        ${acc}
				        <a
				            class="button is-small is-danger"
				            href="${this.pcPreview}${pickCode}"
				            title="${date}/${name}"
				            target="_blank"
				        >
				        ${name.length > 20 ? `${name.substr(0, 20)}...` : name}
				        </a>
				        `,
						""
					)}
                    </div>
                    </div>
                    </div>
                    `
				);
				return resource;
			},
			async handleImage({ code, res, date }) {
				let image = res?.image ?? "";
				if (!image) {
					image = await this.fetchImage(code, date);
					if (!image) return;
					upItem(code, { image });
				}
				const cover = DOC.querySelector(".column-video-cover");
				const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") });
				cover.appendChild(placeholder);
				const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
				img.addEventListener("click", () => {
					if (getScrollTop() >= this.expHei) window.scrollTo(0, 0);
					DOC.querySelector("#exp").checked = false;
				});
				img.onload = () => cover.replaceChild(img, placeholder);
			},
			async handleVideo({ video, code, studio, res }) {
				if (video) {
					video = DOC.querySelector("#preview-video source").src;
				} else {
					video = res?.video ?? "";
					if (!video) video = await this.fetchVideo({ code, studio });
				}
				if (!video) return;
				upItem(code, { video });

				let preview = DOC.querySelector(".tile-images.preview-images");
				if (!preview) {
					DOC.querySelector(".video-meta-panel").insertAdjacentHTML(
						"afterend",
						`<div class="columns">
                        <div class="column">
                        <article class="message video-panel">
                        <div class="message-body">
                        <div class="tile-images preview-images">
                        </div>
                        </div>
                        </article>
                        </div>
                        </div>`
					);
					preview = DOC.querySelector(".tile-images.preview-images");
				}
				if (!preview.querySelector(".preview-video-container")) {
					preview.querySelector("#preview-video")?.remove();
					preview.insertAdjacentHTML(
						"afterbegin",
						`<a class="preview-video-container" data-fancybox="gallery" href="#preview-video">
                            <span>預告片</span>
                            <img
                                src="${DOC.querySelector(".column-video-cover img").src}"
                                class="video-cover"
                                style="width:150px;height:auto"
                            />
                        </a>
                        <video
                            id="preview-video"
                            playsinline=""
                            controls=""
                            muted=""
                            preload="auto"
                            style="display:none"
                        >
                            <source src="${video}"/>
                        </video>`
					);
				}

				const playBtn = DOC.create("a", { class: "play-button" });
				playBtn.addEventListener("click", e => {
					e.stopPropagation();
					e.preventDefault();
					DOC.querySelector(".preview-video-container").click();
				});
				playBtn.insertAdjacentHTML(
					"beforeend",
					`<span class="icon"><img src="/packs/media/images/btn-play-b414746c.svg"></span>
                    <span class="text">播放预览</span>`
				);

				let cover = DOC.querySelector(".column-video-cover .cover-container");
				if (!cover) {
					const gallery = DOC.querySelector(".column-video-cover a");
					gallery.removeAttribute("data-fancybox");
					gallery.classList.add("cover-container");
					cover = DOC.querySelector(".column-video-cover .cover-container");
					playBtn.setAttribute("style", "z-index:9");
				} else {
					playBtn.setAttribute("style", "margin-top:60px;z-index:9");
				}
				cover.insertAdjacentElement("beforeend", playBtn);
			},
			async handlePlayer({ code, res }) {
				let player = res?.player ?? [];
				if (!player?.length) {
					player = await this.fetchPlayer(code);
					if (player?.length) upItem(code, { player });
				}
				const box = DOC.querySelector("#playerBox");
				box.querySelector(".value")?.remove();
				box.querySelector(".columns")?.remove();
				if (!player?.length) return box.insertAdjacentHTML("beforeend", `<span class="value">无</span>`);
				box.insertAdjacentHTML(
					"beforeend",
					`
                    <div class="columns">
                    <div class="column">
                    <div class="buttons are-small review-buttons">
                    ${player.reduce(
						(acc, { zh, link, name, from }) => `
				        ${acc}
				        <a
				            class="button is-small ${zh ? "is-warning" : "is-info"}"
				            href="${link}"
				            title="${name}"
				            target="_blank"
				        >
				        ▶ ${from}
				        </a>
				        `,
						""
					)}
                    </div>
                    </div>
                    </div>
                    `
				);
			},
			async handleMagnet({ code, res }) {
				let magnet = res?.magnet ?? [];
				if (!magnet?.length) {
					magnet = await this.fetchMagnet(code);
					if (!magnet?.length) return;
					upItem(code, { magnet });
				}
				magnet = magnet.reduce(
					(pre, { link, name, styleClass, href, from, zh, size, date }) => `
                    ${pre}
                    <tr>
                        <td class="magnet-name">
                            <a href="${link}">
                                <span>${name}</span>
                                <br>
                                ${zh ? `<span class="tag is-warning is-small">字幕</span>` : ""}
                                <span class="tag is-${styleClass} is-small">${from}</span>
                                <span class="meta">&nbsp;( ${size} )</span>
                            </a>
                        </td>
                        <td class="sub-column" width="80">
                            <span class="time">${date}</span>
                        </td>
                        <td class="sub-column" width="70">
                            <button
                                class="button is-info is-small copy-to-clipboard"
                                data-clipboard-text="${link}"
                                type="button"
                            >
                                複製
                            </button>
                        </td>
                    </tr>`,
					""
				);
				const magnets = DOC.querySelector("#magnets-content");
				let tbody = magnets.querySelector("table tbody");
				if (!tbody) {
					magnets.textContent = "";
					magnets.insertAdjacentHTML("beforeend", `<table width="100%"><tbody></tbody></table>`);
					tbody = magnets.querySelector("table tbody");
				}
				tbody.insertAdjacentHTML("beforeend", magnet);
				this.modifyTable();
			},
			async handleOffLine(e, link) {
				e.preventDefault();
				e.stopPropagation();
				if (!link) return;
				const node = e.target;
				const text = node?.textContent ?? "";
				node.textContent = "请求中...";
				node.setAttribute("disabled", true);
				node.setAttribute("style", "pointer-events:none");

				const clickUrl = this.clickUrl;
				if (link !== "smart") {
					let res = await this.fetchOffLine(link);
					const { state = false, error_msg = "" } = res;
					notify({
						title: `请求${state ? "成功" : "失败"}`,
						text: `${error_msg ? error_msg : "仅添加离线任务"}`,
						image: `${state ? "info" : "fail"}`,
						clickUrl,
					});
				} else {
					let links = [];
					const trs = DOC.querySelectorAll("#magnets-content tr");
					for (const tr of trs) {
						let [info, date] = tr?.querySelectorAll("td");
						if (!info || !date) continue;
						links.push({
							link: info.querySelector("a").href.split("&")[0],
							zh: info.textContent.includes("字幕"),
							size: (() => {
								let size = info.querySelector(".meta").textContent.trim();
								size = size.split(",")[0].replace(/\(|\)/gi, "").trim();
								return transToBytes(size);
							})(),
							date: date.textContent.trim(),
						});
					}
					if (links.length) {
						if (links.length > 1) links = unique(links, "link");
						if (links.length > 1) {
							links.sort((pre, next) => {
								if (pre.zh === next.zh) {
									if (pre.size === next.size) return next.date - pre.date;
									return next.size - pre.size;
								} else {
									return pre.zh > next.zh ? -1 : 1;
								}
							});
						}
						tooltip("磁链列表");
						console.table(links);
						const params = this.getParams();
						for (let index = 0; index < links.length; index++) {
							const { link, zh } = links[index];
							let res = await this.fetchOffLine(link);
							if (!res || res?.errcode === 911) break;
							if (res?.state) {
								res = await this.checkResource(params);
								if (res?.length) {
									this.afterAction({ ...params, zh, res });
									notify({
										title: "一键离线任务成功",
										text: "进行后续操作",
										image: "success",
										clickUrl,
									});
									break;
								}
							}
							if (index !== links.length - 1) continue;
							notify({ title: "一键离线任务失败", image: "fail", clickUrl });
						}
					}
				}

				node.textContent = text;
				node.removeAttribute("disabled");
				node.removeAttribute("style");
			},
			async checkResource({ code }) {
				const old = getItem(code)?.resource ?? [];
				await delay(2500);
				const res = await this.handleResource({ code });
				if (Array.isArray(res) && res?.length) {
					const today = res.filter(({ date }) => date === getDate());
					if (today.length && res.length > old.length) return today;
				}
				return false;
			},
			async afterAction({ zh, res, title, code }) {
				res = res.filter(({ name }) => name.indexOf(title) === -1);
				if (!res.length) return;
				title = `${zh ? "【中文字幕】" : ""}${title}`;
				res = res.map(item => {
					const suffix = item.name.split(".").pop().toLowerCase();
					return { ...item, file_name: `${title}.${suffix}` };
				});
				await this.fetchRename(res);
				if (this.CID) {
					const moveRes = await this.fetchMove(res);
					if (moveRes?.state) this.fetchDelDir(res);
				}
				this.handleResource({ code });
			},
		};
	}

	let matched = MatchDomains.find(({ regex }) => regex.test(location.host))?.domain;
	if (matched) matched = eval(`new ${matched}();`);
	matched?.docStart();
	DOC.addEventListener("DOMContentLoaded", () => matched?.contentLoaded());
	window.addEventListener("load", () => matched?.load());

	if (/captchaapi\.115\.com/g.test(location.host)) {
		DOC.addEventListener("DOMContentLoaded", () => {
			window.focus();
			const btn = DOC.querySelector("#js_ver_code_box button[rel='verify']");
			btn.addEventListener("click", () => {
				const interval = setInterval(() => {
					if (DOC.querySelector("div[rel='error_box']").getAttribute("style").indexOf("none") !== -1) {
						window.open("", "_self");
						window.close();
					}
				}, 300);
				setTimeout(() => clearInterval(interval), 600);
			});
		});
	}
})();