JavScript

暗黑模式、滚动加载、在线播放、磁力搜索、115资源匹配 & 离线下载、预览视频、预览大图...

Tính đến 13-01-2022. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            JavScript
// @namespace       https://greasyfork.org/users/175514
// @description     暗黑模式、滚动加载、在线播放、磁力搜索、115资源匹配 & 离线下载、预览视频、预览大图...
// @version         1.6.0
// @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: createReg(["javdb.com", "javdb35.com"]),
		// },
	];
	// 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);
	};

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

		mPoint = 0;
		pcPreview = "https://v.anxia.com/?pickcode=";
		menus = [
			{ title: "点击事件", key: "CK", command: "c" },
			{ title: "暗黑模式", key: "DM", command: "d" },
			{ title: "滚动加载", key: "LM", command: "s" },
			{
				title: "离线后操作目录cid",
				key: "CID",
				command: "a",
				cb: uniKey => {
					const val = prompt("用于离线下载后移动,删除操作", GM_getValue(uniKey) ?? "");
					if (!val) return;
					GM_setValue(uniKey, val);
					location.reload();
				},
			},
		];
		registerMenu = (menus = this.menus) => {
			for (const menu of menus) {
				const { title, key, command } = menu;
				const uniKey = `${this.constructor.name}_${key}`;
				const val = GM_getValue(`${uniKey}`) ?? "";
				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;
            }
            .line-4 { -webkit-line-clamp: 4; }
            .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 {
                color: rgb(10,132,255) !important;
                font-weight: bold;
            }
            `);
		};
		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,
				elementScroll: ".scrollBox",
				history: false,
				historyTitle: false,
				hideNav: ".pagination",
				status: ".page-load-status",
				debug: false,
				...iParams,
			});
			return infScroll;
		};
		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);
		};
		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;
		};
		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 };
			});
		};
		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: "btn-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: "btn-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: "btn-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",
			});
		};
	}
	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] };
		}
		maxHei = 600;
		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();
			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("#search-input").focus();
					}
				});
			});
		};
		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 - 50px);
                        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);
						title.classList.add("line-4");
					}
					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}`;
									}
							  },
					}
				);
				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; }
                .screencap { max-height: 600px; overflow: hidden; }
                #avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px !important; }
                .photo-info { height: auto !important; }
                .bigImage { display: block; }
                `);
				// insert dom
				GM_addStyle(`
                #exp, #collapse { 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: "▲ 收起"; }

                #collapseBtn::after { content: "▼ 展开表格"; }
                #collapse:checked + .movie { max-height: none; }
                #collapse:checked + .movie #collapseBtn::after { content: "▲ 收起表格"; }

                #resBox a { color: #CC0000 !important; }
                #smartOff { width: 100%; }
                `);
				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
				const 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);
				};
				handleCopy("h3");
				handleCopy("span[style='color:#CC0000;']");
				// expBtn
				const screencap = DOC.querySelector(".screencap");
				const bigImage = screencap.querySelector(".bigImage img");
				bigImage.onload = () => this.load();
				if (bigImage.complete) this.load();
				screencap.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp">`);
				screencap.insertAdjacentHTML("beforeend", `<label for="exp" id="expBtn"></label>`);

				const info = DOC.querySelector(".col-md-3.info");
				// resource
				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.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 });
			},
			load() {
				const maxHei = this.maxHei;
				const img = DOC.querySelector(".screencap").children[0];
				const styleHei = img.height || img.clientHeight || img.offsetHeight;
				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; }`
					);
				}
			},
			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.height || tr.clientHeight || tr.offsetHeight) + 1;
					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">`
				);
				GM_addStyle(`#collapse + .movie { max-height: ${maxHeight - 1}px; }`);
			},
			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.maxHei) 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 });
				}

				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;
				};

				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);

				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);
				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);
				}
				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}
                        <a
                            class="btn btn-mini-new ${zh ? "btn-warning" : "btn-primary"}"
                            href="${link}"
                            target="_blank"
                            title="${name}"
                        >
                            <strong>▶ ${from}</strong>
                        </a>
                        `,
						""
					)
				);
			},
			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 ${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 = "http://115.com/?tab=offline&mode=wangpan";
				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 {
					const params = this.getParams();
					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(),
						});
					}
					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;
							}
						});
					}
					for (let index = 0; index < links.length; index++) {
						const item = links[index];
						let res = await this.fetchOffLine(item.link);
						if (!res || res?.errcode === 911) break;
						if (res?.state) {
							res = await this.checkResource(params);
							if (res) {
								this.afterAction({ zh: item.zh, ...params, 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|tags|search|video_codes|rankings|series|makers)/i,
			actors: /^\/actors/i,
			details: /^\/v\//i,
		};
		start = () => {
			this.registerMenu(this.menus.filter(item => item.key !== "DM"));
			this.initDB();
			this.customStyle();
			this.customMode();
			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("#video-search").focus();
					}
				});
			});
		};
		customMode = () => {
			GM_addStyle(`.message-body .moj-content { display: none !important; }`);
		};
		waterfall = {
			docStart() {},
			contentLoaded() {
				this.modifyItem();
			},
			load() {},
			async modifyItem(node = DOC) {
				if (this.CK) this.handleClick(".column a", node);
				const items = node.querySelectorAll(".column");
				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;
					item.querySelector(".video-title").classList.add("matched");
					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 });
					});
				}
			},
		};
		actors = {
			docStart() {},
			contentLoaded() {
				if (this.CK) this.handleClick(".actor-box a");
			},
			load() {},
		};
		details = {
			docStart() {},
			contentLoaded() {},
			load() {},
		};
	}

	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);
			});
		});
	}
})();