您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Jav-鉴黄师 收藏, 屏蔽, 标记已下载, 在线预览, fc2ppv, 云盘备份
当前为
// ==UserScript== // @name JAV-JHS // @namespace https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs // @version 1.4.3 // @author xie bro // @description Jav-鉴黄师 收藏, 屏蔽, 标记已下载, 在线预览, fc2ppv, 云盘备份 // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com // @match https://javdb.com/* // @match https://javtrailers.com/* // @match https://subtitlecat.com/* // @match https://jable.tv/videos/* // @match https://missav.ws/* // @match https://www.aliyundrive.com/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/layui.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.js // @connect hohoj.tv // @connect xunlei.com // @connect geilijiasu.com // @connect aliyundrive.com // @connect aliyundrive.net // @connect ja.wikipedia.org // @connect * // @grant GM_addStyle // @grant GM_xmlhttpRequest // ==/UserScript== (t => { if (typeof GM_addStyle == "function") { GM_addStyle(t); return } const o = document.createElement("style"); o.textContent = t, document.head.append(o) })(" .navbar{z-index:12345679!important}.sub-header,#footer,.search-recent-keywords,.app-desktop-banner,div[data-controller=movie-tab] .tabs,h3.main-title,div.video-meta-panel>div>div:nth-child(2)>nav>div.review-buttons>div:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(3),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(1),.top-meta,.float-buttons{display:none!important}div.tabs.no-bottom,.tabs ul{border-bottom:none!important}.movie-list .item{position:relative!important}.fr-btn{float:right;margin-left:4px!important}.menu-box{position:fixed;right:10px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;z-index:1000;gap:6px}.menu-btn{display:inline-block!important;min-width:80px;padding:7px 12px;border-radius:4px;color:#fff!important;text-decoration:none;font-weight:700;font-size:12px;text-align:center;cursor:pointer;transition:all .3s ease;box-shadow:0 2px 5px #0000001a;text-shadow:0 1px 1px rgba(0,0,0,.2);border:none;line-height:1.3;margin:0}.menu-btn:hover{transform:translateY(-1px);box-shadow:0 3px 6px #00000026;opacity:.9}.menu-btn:active{transform:translateY(0);box-shadow:0 1px 2px #0000001a}.btn-primary,.btn-success,.btn-danger,.btn-warning,.btn-info,.btn-dark,.btn-outline,.btn-disabled{display:inline-flex;align-items:center;justify-content:center;padding:6px 14px;margin-left:10px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:500;transition:all .2s ease;cursor:pointer;border:1px solid rgba(0,0,0,.08);white-space:nowrap}.btn:hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000000d}.btn-primary{background:#e0f2fe;color:#0369a1;border-color:#bae6fd}.btn-primary:hover{background:#bae6fd}.btn-success{background:#dcfce7;color:#166534;border-color:#bbf7d0}.btn-success:hover{background:#bbf7d0}.btn-danger{background:#fee2e2;color:#b91c1c;border-color:#fecaca}.btn-danger:hover{background:#fecaca}.btn-warning{background:#ffedd5;color:#9a3412;border-color:#fed7aa}.btn-warning:hover{background:#fed7aa}.btn-info{background:#ccfbf1;color:#0d9488;border-color:#99f6e4}.btn-info:hover{background:#99f6e4}.btn-dark{background:#e2e8f0;color:#334155;border-color:#cbd5e1}.btn-dark:hover{background:#cbd5e1}.btn-outline{background:transparent;color:#64748b;border-color:#cbd5e1}.btn-outline:hover{background:#f8fafc}.btn-disabled{background:#f1f5f9;color:#94a3b8;border-color:#e2e8f0;cursor:not-allowed}.btn-disabled:hover{transform:none;box-shadow:none;background:#f1f5f9}.data-table{width:100%;border-collapse:separate;border-spacing:0;font-family:Helvetica Neue,Arial,sans-serif;background:#fff;overflow:hidden;box-shadow:0 4px 20px #00000008;margin:0 auto}.data-table thead tr{background:#f8fafc}.data-table th{padding:16px 20px;text-align:center!important;color:#64748b;font-weight:500;font-size:14px;text-transform:uppercase;letter-spacing:.5px;border-bottom:1px solid #e2e8f0}.data-table td{padding:14px 20px;color:#334155;font-size:15px;border-bottom:1px solid #f1f5f9;text-align:center!important;vertical-align:middle}.data-table tbody tr:last-child td{border-bottom:none}.data-table tbody tr{transition:all .2s ease}.data-table tbody tr:hover{background:#f8fafc}.data-table .text-left{text-align:left}.data-table .text-right{text-align:right}.data-table.show-border,.data-table.show-border th,.data-table.show-border td{border:1px solid #e2e8f0} "); (function (n, layui, a, i) { 'use strict'; var t = Object.defineProperty, e = (e, n, a) => ((e, n, a) => n in e ? t(e, n, { enumerable: true, configurable: true, writable: true, value: a }) : e[n] = a)(e, "symbol" != typeof n ? n + "" : n, a); document.head.insertAdjacentHTML("beforeend", '\n <style>\n .loading-container {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.1);\n z-index: 99999999;\n }\n \n .loading-animation {\n position: relative;\n width: 60px;\n height: 12px;\n background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);\n border-radius: 6px;\n animation: loading-animate 1.8s ease-in-out infinite;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n \n .loading-animation:before,\n .loading-animation:after {\n position: absolute;\n display: block;\n content: "";\n animation: loading-animate 1.8s ease-in-out infinite;\n height: 12px;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n \n .loading-animation:before {\n top: -20px;\n left: 10px;\n width: 40px;\n background: linear-gradient(90deg, #ff758c 0%, #ff7eb3 100%);\n }\n \n .loading-animation:after {\n bottom: -20px;\n width: 35px;\n background: linear-gradient(90deg, #ff9a9e 0%, #fad0c4 100%);\n }\n \n @keyframes loading-animate {\n 0% {\n transform: translateX(40px);\n }\n 50% {\n transform: translateX(-30px);\n }\n 100% {\n transform: translateX(40px);\n }\n }\n </style>\n '), window.loading = function () { const t = document.createElement("div"); t.className = "loading-container"; const e = document.createElement("div"); return e.className = "loading-animation", t.appendChild(e), document.body.appendChild(t), { close: () => { t && t.parentNode && t.parentNode.removeChild(t); } }; }; var s = (() => "undefined" != typeof GM_xmlhttpRequest ? GM_xmlhttpRequest : void 0)(); class Utils { constructor() { return e(this, "SECOND", 1e3), e(this, "MINUTE", 60 * this.SECOND), e(this, "HOUR", 60 * this.MINUTE), e(this, "DAY", 24 * this.HOUR), e(this, "WEEK", 7 * this.DAY), e(this, "MONTH", 30.44 * this.DAY), e(this, "YEAR", 365.25 * this.DAY), e(this, "insertStyle", (t => { t && (-1 === t.indexOf("<style>") && (t = "<style>" + t + "</style>"), $("head").append(t)); })), e(this, "http", { get(t, e = {}, n = {}) { return this.jqueryRequest("GET", t, null, e, n); }, post(t, e = {}, n = {}) { return this.jqueryRequest("POST", t, e, null, n); }, put(t, e = {}, n = {}) { return this.jqueryRequest("PUT", t, e, null, n); }, del(t, e = {}, n = {}) { return this.jqueryRequest("DELETE", t, null, e, n); }, jqueryRequest: (t, e, n = {}, a = {}, i = {}) => ("POST" === t && (i = { "Content-Type": "application/json", ...i }), new Promise(((s, r) => { $.ajax({ method: t, url: e, data: "GET" === t || "DELETE" === t ? a : JSON.stringify(n), headers: i, success: (t, e, n) => { var a; if (null == (a = n.getResponseHeader("Content-Type")) ? void 0 : a.includes("application/json")) try { s("object" == typeof t ? t : JSON.parse(t)); } catch (i) { s(t); } else s(t); }, error: (t, e, n) => { let a = n; if (t.responseText) try { const e = JSON.parse(t.responseText); a = e.message || e.msg || t.responseText; } catch { a = t.responseText; } r(new Error(a)); } }); }))) }), e(this, "gmHttp", { get(t, e = {}, n = {}) { return this.gmRequest("GET", t, null, e, n); }, post(t, e = {}, n = {}) { return this.gmRequest("POST", t, e, null, n); }, put(t, e = {}, n = {}) { return this.gmRequest("PUT", t, e, null, n); }, del(t, e = {}, n = {}) { return this.gmRequest("DELETE", t, null, e, n); }, gmRequest(t, e, n = {}, a = {}, i = {}) { if (("GET" === t || "DELETE" === t) && a && Object.keys(a).length) { const t = new URLSearchParams(a).toString(); e += (e.includes("?") ? "&" : "?") + t; } return "POST" !== t && "PUT" !== t || (i = { "Content-Type": "application/json", ...i }), new Promise(((a, r) => { s({ method: t, url: e, headers: i, data: "POST" === t || "PUT" === t ? JSON.stringify(n) : void 0, onload: t => { var e; try { if (t.status >= 200 && t.status < 300) if (t.responseText && (null == (e = t.responseHeaders) ? void 0 : e.toLowerCase().includes("application/json"))) try { a(JSON.parse(t.responseText)); } catch (n) { a(t.responseText); } else a(t.responseText || t); else if (t.responseText) try { const e = JSON.parse(t.responseText); r(e); } catch { r(new Error(t.responseText || `HTTP Error ${t.status}`)); } else r(new Error(`HTTP Error ${t.status}`)); } catch (n) { r(n); } }, onerror: t => { r(new Error(t.error || "Network Error")); }, ontimeout: () => { r(new Error("Request Timeout")); } }); })); } }), Utils.instance || (Utils.instance = this, this.intervalContainer = {}), Utils.instance; } importResource(t) { let e; t.indexOf("css") >= 0 ? (e = document.createElement("link"), e.setAttribute("rel", "stylesheet"), e.href = t) : (e = document.createElement("script"), e.setAttribute("type", "text/javascript"), e.src = t), document.documentElement.appendChild(e); } loopDetector(t, e, n = 20, a = 1e4, i = true) { let s = false; const r = Math.random(), o = (new Date).getTime(); this.intervalContainer[r] = setInterval((() => { (new Date).getTime() - o > a && (console.warn("loopDetector timeout!", t, e), s = i), (t() || s) && (clearInterval(this.intervalContainer[r]), e && e(), delete this.intervalContainer[r]); }), n); } rightClick(t, e) { t.jquery && (t = t[0]), t ? t.addEventListener("contextmenu", (t => { e(t); })) : console.error("rightClick(), 找不到元素"); } q(t, e, n, a) { let i, s; t ? (i = t.clientX - 120, s = t.clientY - 120) : (i = window.innerWidth / 2 - 120, s = window.innerHeight / 2 - 120); let r = layer.confirm(e, { offset: [s, i], btn: ["确定", "取消"], zIndex: 99999999999 }, (function () { n(), layer.close(r); }), (function () { a && a(); })); } getNowStr(t = "-", e = ":", n = null) { let a; a = n ? new Date(n) : new Date; const i = a.getFullYear(), s = String(a.getMonth() + 1).padStart(2, "0"), r = String(a.getDate()).padStart(2, "0"), o = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0"); return `${[i, s, r].join(t)} ${[o, l, c].join(e)}`; } formatDate(t, e = "-", n = ":") { let a; if (t instanceof Date) a = t; else { if ("string" != typeof t) throw new Error("Invalid date input: must be Date object or date string"); if (a = new Date(t), isNaN(a.getTime())) throw new Error("Invalid date string"); } const i = a.getFullYear(), s = String(a.getMonth() + 1).padStart(2, "0"), r = String(a.getDate()).padStart(2, "0"), o = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0"); return `${[i, s, r].join(e)} ${[o, l, c].join(n)}`; } download(t, e) { const n = new Blob([t], { type: "application/json" }), a = URL.createObjectURL(n), i = document.createElement("a"); i.href = a, i.download = e, document.body.appendChild(i), i.click(), setTimeout((() => { document.body.removeChild(i), URL.revokeObjectURL(a); }), 100); } getLocalStorage(t) { try { const e = localStorage.getItem(t); if (null === e) return null; try { const n = JSON.parse(e); if (n && "object" == typeof n && "value" in n) return "expires" in n && Date.now() > n.expires ? (localStorage.removeItem(t), null) : n.value; } catch { } try { return JSON.parse(e); } catch { return e; } } catch (e) { return console.error("访问localStorage失败:", e), null; } } setLocalStorage(t, e, n) { try { let a; if (void 0 !== n) { let t = Date.now() + n; a = { value: e, expires: t, expiresStr: this.formatDate(new Date(t)) }; } else a = e; localStorage.setItem(t, JSON.stringify(a)); } catch (a) { console.error("写入localStorage失败:", a), "QuotaExceededError" === a.name && console.warn("localStorage空间不足,请清理数据"); } } } const r = new Utils; class LocalStorageManager { constructor() { return e(this, "initData", { dataList: [], filterKeywordList: [], filterActorList: [], reviewKeywordList: [] }), e(this, "FAVORITE", "favorite"), e(this, "FILTER", "filter"), e(this, "HASDOWN", "hasDown"), LocalStorageManager.instance || (LocalStorageManager.instance = this), LocalStorageManager.instance; } findData(t, e) { return e.find((e => e.carNum === t)); } _getJsonData() { const t = localStorage.getItem("appData"); return t || localStorage.setItem("appData", JSON.stringify(this.initData)), t ? JSON.parse(t) : this.initData; } _saveJsonData(t) { localStorage.setItem("appData", JSON.stringify(t)); } saveKeyData(t, e) { const n = this._getJsonData(); n[t] = e, this._saveJsonData(n); } getData() { const t = this._getJsonData(); return { dataList: t.dataList, filterKeywordList: t.filterKeywordList, filterActorList: t.filterActorList, reviewKeywordList: t.reviewKeywordList || [] }; } async changeData(t, e, n, a) { e.includes("http") || (e = window.location.origin + e); const i = this._getJsonData(); let s = i.dataList, o = this.findData(t, s); if (o || (o = { carNum: t, url: e, actress: n, status: "" }, s.push(o)), o.createDate = r.getNowStr(), a === this.FILTER) { if (o.status === this.FILTER) throw new Error(t + " 已在屏蔽列表中"); o.status = this.FILTER; } else if (a === this.FAVORITE) { if (o.favorite) throw new Error(t + " 已在收藏列表中"); o.status = this.FAVORITE; } else { if (a !== this.HASDOWN) throw new Error("actionType错误"); o.status = this.HASDOWN; } this._saveJsonData(i); } async saveFilterActor(t) { const e = this._getJsonData(); if (e.filterActorList.includes(t)) throw new Error(t + " 已存在"); e.filterActorList.push(t), this._saveJsonData(e); } async saveFilterKeyword(t) { const e = this._getJsonData(); if (e.filterKeywordList.includes(t)) throw new Error(t + " 已存在"); e.filterKeywordList.push(t), this._saveJsonData(e); } async removeData(t) { const e = this._getJsonData(); let n = e.dataList; if (!this.findData(t, n)) throw new Error("未找到该番号信息:" + t); n = n.filter((e => e.carNum !== t)), e.dataList = n, this._saveJsonData(e); } } const o = new LocalStorageManager; class PluginManager { constructor() { this.plugins = new Map, this.isInitialized = false; } register(t) { if ("function" != typeof t) throw new Error("插件必须是一个类"); const e = t.name; if (!e) throw new Error("类必须要有名称"); const n = e.toLowerCase(); if (this.plugins.has(n)) throw new Error(`插件"${e}"已注册`); const a = new t; a.pluginManager = this, this.plugins.set(n, a); } getBean(t) { return this.plugins.get(t.toLowerCase()); } _initialize() { if (this.isInitialized) return; const t = new Map; for (const [e, n] of this.plugins) "function" == typeof n.injectBean && t.set(e, { instance: n, deps: this._getDependencies(n.injectBean) }); for (const [e, {instance: n, deps: a}] of t) { const t = a.map((t => { const n = t.toLowerCase(); if (!this.plugins.has(n)) throw new Error(`插件"${e}"依赖的插件"${t}"未注册`); return this.plugins.get(n); })); n.injectBean(...t); } this.isInitialized = true; } _getDependencies(t) { const e = t.toString(); return e.slice(e.indexOf("(") + 1, e.indexOf(")")).split(",").map((t => t.trim())).filter((t => t)); } process() { this.isInitialized || this._initialize(); for (const [e, n] of this.plugins) try { "function" == typeof n.handle && (r.insertStyle(n.initCss()), n.handle()); } catch (t) { console.error("执行插件失败", t); } } } const l = { dataList: [], filterKeywordList: [], filterActorList: [], reviewKeywordList: [], hasHandleList: [], answerCount: 1 }; class BasePlugin { constructor() { this.pluginManager = null, this.utils = r, this.isDetailPage = window.location.href.includes("javdb") && window.location.href.includes("/v/"), this.isListPage = window.location.href.includes("javdb") && !window.location.href.includes("/v/"), this.storageManager = o, this.FAVORITE = "favorite", this.FILTER = "filter", this.HASDOWN = "hasDown", Object.keys(l).forEach((t => { Object.defineProperty(this, t, { get: () => l[t], set(e) { l[t] = e; }, enumerable: true, configurable: true }); })); } injectBean() { } initCss() { } handle() { } openPage(t, e, n, a) { n || (n = true), a && a.ctrlKey ? window.open(t) : layer.open({ type: 2, title: e, content: t, shadeClose: n, area: ["80%", "90%"], isOutAnim: false, anim: -1 }); } closePage() { layer.closeAll(); [".layui-layer-shade", ".layui-layer-move", ".layui-layer"].forEach((function (t) { parent.document.querySelectorAll(t).forEach((function (t) { t.parentNode.removeChild(t); })); })), window.close(); } getPageInfo() { return { carNum: $('a[title="複製番號"]').attr("data-clipboard-text"), url: window.location.href.split("#")[0], actress: $(".female").prev().map(((t, e) => $(e).text())).get().join(" "), actors: $(".male").prev().map(((t, e) => $(e).text())).get().join(" ") }; } refresh() { localStorage.refresh = Date.now().toString(), this.isListPage && this.pluginManager.getBean("ListPagePlugin").doFilter(); } async changeData(t, e, n, a) { if (!t) throw layer.error("番号为空!"), new Error("番号为空!"); if (!e) throw layer.error("url为空!"), new Error("url为空!"); await this.storageManager.changeData(t, e, n, a), this.refresh(); } getSetting(t, e) { const n = localStorage.getItem("setting"); if (null === n) return e; try { const a = JSON.parse(n); if (!a || "object" != typeof a) return e; if (!(t in a)) return e; const i = a[t]; return "" === i ? i : "true" === i || "false" === i ? "true" === i.toLowerCase() : "string" != typeof i || isNaN(Number(i)) ? i : Number(i); } catch (a) { return console.error("Error parsing settings:", a), e; } } } class ListPagePlugin extends BasePlugin { injectBean(autoPagePlugin, fc2Plugin) { this.autoPagePlugin = autoPagePlugin, this.fc2Plugin = fc2Plugin; } handle() { this.doFilter(), this.replaceBigImg(), window.addEventListener("storage", (t => { "refresh" === t.key && this.refresh(); })); } doFilter() { if (!this.isListPage) return; const t = this.storageManager.getData(); this.dataList = t.dataList, this.filterKeywordList = t.filterKeywordList, this.filterActorList = t.filterActorList, this.reviewKeywordList = t.reviewKeywordList, this.filterMovieList(), this.autoPagePlugin.handlePaging(); } filterMovieList() { const t = this.dataList.filter((t => t.status === this.FILTER)).map((t => t.carNum)), e = this.dataList.filter((t => t.status === this.FAVORITE)).map((t => t.carNum)), n = this.dataList.filter((t => t.status === this.HASDOWN)).map((t => t.carNum)); let a = $(".movie-list .item").toArray(), i = this.getSetting("hideFilterItem", "yes"); (window.location.href.includes("search?q") || window.location.href.includes("handlePlayback=1") || window.location.href.includes("handleTop=1")) && (i = "no"), a.forEach((a => { let s = $(a), r = s.find("a"), o = r.attr("href"), l = r.attr("title"), c = s.find(".video-title").find("strong").text(); const d = `${c}-hide`, h = `${c}-keywordHide`, p = `${c}-tag`, g = `${c}-click`; if ("no" === i && this.hasHandleList.includes(d) && (s.show(), this.hasHandleList = this.hasHandleList.filter((t => t !== d))), this.filterKeywordList.some((t => l.includes(t) || c.includes(t))) && !this.hasHandleList.includes(h)) return s.hide(), void this.hasHandleList.push(h); if (t.includes(c) && "yes" === i && !this.hasHandleList.includes(d)) return s.hide(), void this.hasHandleList.push(d); if (n.includes(c) && "yes" === i && !this.hasHandleList.includes(d)) return s.hide(), void this.hasHandleList.push(d); let u = "", m = ""; t.includes(c) ? (u = "已屏蔽", m = "#d95427") : e.includes(c) ? (u = "已收藏", m = "#2caac0") : n.includes(c) && (u = "已下载", m = "#58c433"), u && !this.hasHandleList.includes(p) && (s.find(".tags").append(`\n <span class="tag is-success" \n style="margin-right: 5px; border-radius:10px; position:absolute; right: 0; top:5px;z-index:10;background-color: ${m} !important;">\n ${u}\n </span>`), this.hasHandleList.push(p)), this.hasHandleList.includes(g) || (s.on("click", (t => { if (t.preventDefault(), c.includes("FC2-")) { let t = o.split("/").filter(Boolean).pop(); this.fc2Plugin.openFc2Page(t, c, o); } else this.openPage(o, c, false, t); })), this.utils.rightClick(s.find("img"), (t => { t.preventDefault(), this.utils.q(t, `是否屏蔽番号${c}?`, (() => { this.changeData(c, o, "", "filter").then((t => layer.success("操作成功"))); })); })), this.hasHandleList.push(g)); })), $("#waitDownBtn span").text(`打开待下载(${e.length})`); } replaceBigImg() { document.querySelectorAll(".cover img").forEach((t => { t.src = t.src.replace("thumbs", "covers"); })); } } class SearchPlugin extends BasePlugin { initCss() { return "\n .search-bar-container {\n margin-bottom: 0 !important;\n }\n \n .search-bar-container .column {\n padding: 10px 12px !important;\n }\n \n .search-bar-wrap {\n background-color: inherit;\n padding: 0;\n }\n "; } handle() { this.isListPage && ($(".search-input").html('<input id="search-keyword" class="input is-medium" data-type="all" type="text" value="" placeholder="輸入影片番號,演員名等關鍵字進行檢索">'), $("#search-bar-container").prependTo("#tags"), $(".search-submit").html('<button type="button" id="search-btn" class="button is-medium is-info">檢索</button>'), $("#search-btn").on("click", (t => { let e = $("#search-keyword").val(), n = $("#search-type option:selected").val(); "" !== e && window.open("/search?q=" + e + "&f=" + n); })), $("#search-keyword").on("paste", (t => { setTimeout((() => { $("#search-btn").click(); }), 0); })).on("keypress", (t => { "Enter" === t.key && setTimeout((() => { $("#search-btn").click(); }), 0); }))); } } class AutoPagePlugin extends BasePlugin { constructor() { super(), this.paging = false; } handle() { if (!this.isListPage) return; let t = "yes" === localStorage.getItem("autoPage") ? "关闭自动翻页" : "开启自动翻页"; $(".pagination").prepend(`<a class='pagination-previous' id='auto-page'>${t}</a>`), $("#auto-page").on("click", (t => { t.preventDefault(), "yes" === localStorage.getItem("autoPage") ? (localStorage.setItem("autoPage", "no"), $("#auto-page").html("开启自动翻页")) : (localStorage.setItem("autoPage", "yes"), $("#auto-page").html("关闭自动翻页"), this.handlePaging()); })); } handlePaging() { if (window.location.href.includes("search?q") || window.location.href.includes("handlePlayback=1") || window.location.href.includes("handleTop=1")) return; if (!this.isListPage) return; if (this.paging) return; let t = true; if ($(".movie-list .item:visible").each(((e, n) => { 0 === $(n).find("span:contains('已收藏')").length && 0 === $(n).find("span:contains('已屏蔽')").length && 0 === $(n).find("span:contains('已下载')").length && (t = false); })), !t) return; if ("yes" !== localStorage.getItem("autoPage")) return; let e = $(".pagination-next"); 0 !== e.length && (this.paging = true, layer.info("下一页....", { duration: 500, callback: () => { e[0].click(); } })); } } class DetailPagePlugin extends BasePlugin { injectBean(detailPageMenuPlugin) { this.detailPageMenuPlugin = detailPageMenuPlugin; } initCss() { return this.isDetailPage ? "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n " : ""; } handle() { if (!this.isDetailPage) return; const t = this.storageManager.getData(); this.dataList = t.dataList, this.filterKeywordList = t.filterKeywordList, this.filterActorList = t.filterActorList, this.reviewKeywordList = t.reviewKeywordList, this.checkFilterActor(); } checkFilterActor() { if (!this.isDetailPage) return; let t = this.getPageInfo().actors; this.filterActorList.forEach((e => { t.indexOf(e) > -1 && (this.answerCount++, this.utils.q(null, "存在xxx演员, 是否屏蔽?", (() => { this.detailPageMenuPlugin.filterOne(null, true); }))); })); } } function c() { const t = Math.floor(Date.now() / 1e3); if (t - (localStorage.review_ts || 0) <= 20) return localStorage.review_sign; const e = `${t}.lpw6vgqzsp.${a(`${t}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`; return localStorage.review_ts = t, localStorage.review_sign = e, e; } const d = "https://jdforrepam.com/api", h = (t, e = 1, n = 20) => new Promise(((a, i) => { $.ajax({ method: "GET", url: `${d}/v1/movies/${t}/reviews`, data: { page: e, sort_by: "hotly", limit: n }, headers: { jdSignature: c() }, success: t => { const e = t.data.reviews; a(e); }, error: t => { layer.error("获取评论失败: " + t.responseText), i(t); } }); })), p = t => new Promise(((e, n) => { $.ajax({ method: "GET", url: `${d}/v4/movies/${t}`, headers: { jdSignature: c() }, success: t => { t.data || (layer.error("获取视频详情失败: " + t.message), n(t.message)); const a = t.data.movie, i = a.id, s = a.actors, r = a.origin_title, o = a.number, l = a.score, c = a.release_date, d = a.watched_count, h = a.preview_images, p = []; h.forEach((t => { p.push(t.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")); })), e({ movieId: i, actors: s, title: r, carNum: o, score: l, releaseDate: c, imgList: p, watchedCount: d }); }, error: t => { layer.error("获取视频详情失败" + t.responseJSON.message), n(t); } }); })); class ReviewPlugin extends BasePlugin { constructor() { super(...arguments), e(this, "floorIndex", 1); } async handle() { if (!this.isDetailPage) return; const t = window.location.href.split("?")[0].split("/"), e = t[t.length - 1].split("#")[0]; this.showReview(e).then(); } async showReview(t, e) { let n = $("#magnets-content"); e && (n = e), n.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>'); let a = this.getSetting("reviewCount", 20), i = null; try { i = await h(t, 1, a); } catch (o) { console.error(o); } $("#reviewsLoading").remove(), n.append('<div id="reviewsContainer"></div>'), n.append('<div id="reviewsFooter"></div>'); const s = $("#reviewsContainer"), r = $("#reviewsFooter"); if (s.append('<hr style="border: 0; height: 2px; background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>'), i) if (0 === i.length && s.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'), this.displayReviews(i, s), i.length === a) { r.html('\n <button id="loadMoreReviews" style="width:100%; background-color: #e1f5fe; border:none; padding:10px; margin-top:10px; cursor:pointer; color:#0277bd; font-weight:bold; border-radius:4px;">\n 加载更多评论\n </button>\n <div id="reviewsEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已到底</div>\n '); let e = 1; $("#loadMoreReviews").click((async () => { $("#loadMoreReviews").text("加载中...").prop("disabled", true), e++; const n = await h(t, e, a); this.displayReviews(n, s), n.length < a ? ($("#loadMoreReviews").remove(), $("#reviewsEnd").show()) : $("#loadMoreReviews").text("加载更多评论").prop("disabled", false); })); } else i.length > 0 && r.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已到底</div>'); else s.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论失败</div>'); } displayReviews(t, e) { t.length && (t.forEach((t => { let n = false; for (let e = 0; e < this.reviewKeywordList.length; e++) if (t.content.indexOf(this.reviewKeywordList[e]) > -1) { n = true; break; } if (n) return; let a = ""; for (let e = 0; e < t.score; e++) a += '<i class="icon-star"></i>'; let i = t.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (t => `<a href="${t}" class="btn-primary" style="padding:0" target="_blank" rel="noopener noreferrer">${t}</a>`)), s = `\n <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;position:relative;">\n <span style="position:absolute;top:5px;right:10px;color:#999;font-size:12px;">#${this.floorIndex++}楼</span>\n ${t.username} <span class="score-stars">${a}</span> \n <span class="time">${t.created_at.replace("T", " ").replace(".000Z", "")}</span> \n 点赞:${t.likes_count}\n <p class="review-content" style="margin-top: 5px;"> ${i} </p>\n </div>\n `; e.append(s); })), this.utils.rightClick($(".review-content"), (t => { const e = window.getSelection().toString(); e && (t.preventDefault(), this.utils.q(t, `是否将 '${e}' 加入评论区关键词?`, (() => { let t = this.storageManager.getData().reviewKeywordList; t || (t = []), t.push(e), this.storageManager.saveKeyData("reviewKeywordList", t), layer.success("操作成功, 刷新页面后生效"); }))); }))); } } const g = class _HotkeyManager { constructor() { if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated."); } static registerHotkey(t, e, n = null) { if (Array.isArray(t)) { let a = []; return t.forEach((t => { if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误"); let i = this.recordHotkey(t, e, n); a.push(i); })), a; } if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误"); return this.recordHotkey(t, e, n); } static recordHotkey(t, e, n) { let a = Math.random().toString(36).substr(2); return this.registerHotKeyMap.set(a, { hotkeyString: t, callback: e, keyupCallback: n }), a; } static unregisterHotkey(t) { this.registerHotKeyMap.has(t) && this.registerHotKeyMap.delete(t); } static isHotkeyFormat(t) { return t.toLowerCase().split("+").map((t => t.trim())).every((t => ["ctrl", "shift", "alt"].includes(t) || 1 === t.length)); } static judgeHotkey(t, e) { const n = t.toLowerCase().split("+").map((t => t.trim())), a = n.includes("ctrl"), i = n.includes("shift"), s = n.includes("alt"), r = n.find((t => "ctrl" !== t && "shift" !== t && "alt" !== t)); return (this.isMac ? e.metaKey : e.ctrlKey) === a && e.shiftKey === i && e.altKey === s && e.key.toLowerCase() === r; } }; e(g, "isMac", 0 === navigator.platform.indexOf("Mac")), e(g, "registerHotKeyMap", new Map), e(g, "handleKeydown", (t => { for (const [e, n] of g.registerHotKeyMap) { let e = n.hotkeyString, a = n.callback; g.judgeHotkey(e, t) && a(t); } })), e(g, "handleKeyup", (t => { for (const [e, n] of g.registerHotKeyMap) { let e = n.hotkeyString, a = n.keyupCallback; a && (g.judgeHotkey(e, t) && a(t)); } })); let u = g; function m(t) { const e = { tableClass: "data-table", showBorder: false, buttons: [], ...t }; if (!(e.containerId && e.columns && Array.isArray(e.columns) && Array.isArray(e.data))) return void console.error("缺少必要参数或参数类型不正确"); const n = document.getElementById(e.containerId); if (!n) return void console.error(`未找到ID为${e.containerId}的容器`); n.innerHTML = ""; const a = document.createElement("table"); a.className = e.showBorder ? e.tableClass + " show-border" : e.tableClass; const i = document.createElement("thead"), s = document.createElement("tr"); if (e.columns.forEach((t => { const e = document.createElement("th"); e.textContent = t.title || t.key, t.width && (e.style.width = t.width), t.headerClass && (e.className = t.headerClass), s.appendChild(e); })), e.buttons && e.buttons.length > 0) { const t = document.createElement("th"); t.textContent = "操作", e.buttonColumnWidth && (t.style.width = e.buttonColumnWidth), s.appendChild(t); } i.appendChild(s), a.appendChild(i); const r = document.createElement("tbody"); if (0 === e.data.length) { const t = document.createElement("tr"), n = document.createElement("td"); n.colSpan = e.columns.length + (e.buttons.length > 0 ? 1 : 0), n.textContent = "暂无数据", n.style.textAlign = "center", t.appendChild(n), r.appendChild(t); } else e.data.forEach(((t, n) => { const a = document.createElement("tr"); if (e.columns.forEach((e => { const i = document.createElement("td"); e.render ? i.innerHTML = e.render(t, n) : i.textContent = t[e.key] || "", e.cellClass && (i.className = e.cellClass), a.appendChild(i); })), e.buttons && e.buttons.length > 0) { const i = document.createElement("td"); e.buttons.forEach((e => { const a = document.createElement("a"); a.textContent = e.text, a.className = e.class || "btn-primary", a.addEventListener("click", (() => { e.onClick && e.onClick(t, n); })), i.appendChild(a); })), a.appendChild(i); } r.appendChild(a); })); return a.appendChild(r), n.appendChild(a), { getTableElement: () => a, updateData: t => { e.data = t, m(e); } }; } document.addEventListener("keydown", (t => { u.handleKeydown(t); })), document.addEventListener("keyup", (t => { u.handleKeyup(t); })); class DetailPageMenuPlugin extends BasePlugin { constructor() { super(); } injectBean(highlightMagnetPlugin) { this.highlightMagnetPlugin = highlightMagnetPlugin; } handle() { this.bindHotkey(), this.isDetailPage && this.createMenuBtn(); } hotkey() { if (this.isDetailPage) return [{ hotkey: ["a"], callback: () => { this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++; } }, { hotkey: ["s"], callback: () => this.favoriteOne(null) }, { hotkey: ["z"], callback: () => this.speedVideo() }]; } createMenuBtn() { const t = this.getPageInfo(), e = t.carNum, n = [{ id: "favoriteBtn", sort: 1, html: function () { return `<a id="${this.id}" class="menu-btn" style="background-color:#25b1dc"><span>收藏(s)</span></a>`; }, action: t => this.favoriteOne() }, { id: "filterBtn", sort: 2, html: function () { return `<a id="${this.id}" class="menu-btn" style="background-color:#de3333"><span>屏蔽(a)</span></a>`; }, action: t => this.filterOne(t) }, { id: "hasDownBtn", sort: 3, html: function () { return `<a id="${this.id}" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>`; }, action: e => this.changeData(t.carNum, t.url, t.actress, "hasDown").then((t => this.closePage())) }, { id: "enable-magnets-filter", sort: 5, html: function () { return `<a id="${this.id}" class="menu-btn" style="background-color:#c2bd4c"><span id="magnets-span">关闭磁力过滤</span></a>`; }, action: t => { let e = $("#magnets-span"); if ("关闭磁力过滤" === e.text()) { $("#magnets-content .item").toArray().forEach((t => $(t).show())), e.text("开启磁力过滤"); } else this.highlightMagnetPlugin.handle(), e.text("关闭磁力过滤"); } }, { id: "jable-video-btn", sort: 6, html: function () { return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>`; }, action: t => this.openPage(`https://jable.tv/videos/${e}/?handle=1`, e, false, t) }, { id: "missav-video-btn", sort: 7, html: function () { return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>`; }, action: t => window.open(`https://missav.ws/search/${e}`, "_blank") }, { id: "preview-video-btn", sort: 8, html: function () { return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>预览视频(z)</span></a>`; }, action: t => this.openPage(`https://javtrailers.com/video/${e.toLowerCase().replace("-", "00")}?handle=1`, e, false, t) }, { id: "search-subtitle-btn", sort: 9, html: function () { return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to bottom, #8d5656, rgb(196,159,91))"><span>字幕 (SubTitleCat)</span></a>`; }, action: t => this.openPage(`https://subtitlecat.com/index.php?search=${e}`, e, false, t) }, { id: "xunLeiSubtitleBtn", sort: 10, html: function () { return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)"><span>字幕 (迅雷)</span></a>`; }, action: t => this.searchXunLeiSubtitle(e) }]; n.sort(((t, e) => t.sort - e.sort)); const a = `\n <div style="transform: translateY(-50%);">\n ${n.map((t => t.html())).join("\n")}\n </div>\n `; $(".tabs").after(a), n.forEach((({id: t, action: e}) => { $(`#${t}`).on("click", e); })); } favoriteOne() { let t = this.getPageInfo(); this.changeData(t.carNum, t.url, t.actress, "favorite").then((t => this.closePage())); } searchXunLeiSubtitle(t) { let e = loading(); this.utils.gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${t}`).then((e => { let n = e.data; layer.open({ type: 1, title: "迅雷字幕", content: '<div id="table-container"></div>', area: ["50%", "70%"], success: e => { m({ containerId: "table-container", columns: [{ key: "name", title: "文件名" }, { key: "ext", title: "类型" }, { key: "extra_name", title: "来源" }], data: n, buttons: [{ text: "下载", class: "btn-primary", onClick: e => { this.utils.gmHttp.get(e.url).then((n => { this.utils.download(n, t + "." + e.ext); })); } }] }); } }); })).finally((() => { e.close(); })); } filterOne(t, e) { t && t.preventDefault(); let n = this.getPageInfo(); e ? this.changeData(n.carNum, n.url, n.actress, "filter").then((t => this.closePage())) : this.utils.q(t, `是否屏蔽${n.carNum}?`, (() => { this.changeData(n.carNum, n.url, n.actress, "filter").then((t => this.closePage())); })); } speedVideo() { const t = $('iframe[id^="layui-layer-iframe"]'); 0 === t.length ? $("#preview-video-btn").click() : t[0].contentWindow.postMessage("speedVideo", "*"); } bindHotkey() { const t = { a: () => { this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++; }, s: () => this.favoriteOne(null), z: () => this.speedVideo() }, e = (t, e) => { u.registerHotkey(t, (() => { this.isDetailPage ? e() : (t => { const e = $(".layui-layer-content iframe"); 0 !== e.length && e[0].contentWindow.postMessage(t, "*"); })(t); })); }; this.isDetailPage && window.addEventListener("message", (e => { t[e.data] && t[e.data](); })), Object.entries(t).forEach((([t, n]) => { e(t, n); })); } } class ListPageMenuPlugin extends BasePlugin { constructor() { super(), this.buttons = []; } handle() { this.isListPage && this.createMenuBtn(); } createMenuBtn() { $(".tabs ul").append('\n <li class="is-active" id="waitCheckBtn">\n <a class="menu-btn" style="background-color:#82d26d !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>打开待鉴定</span>\n </a>\n </li>\n <li class="is-active" id="waitDownBtn">\n <a class="menu-btn" style="background-color:#7cb7e8 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>打开待下载</span>\n </a>\n </li>\n '), $("#waitCheckBtn").on("click", (t => { this.openWaitCheck(t); })), $("#waitDownBtn").on("click", (t => { this.openFavorite(t); })); } openWaitCheck() { let t = 0, e = this.getSetting("waitCheckCount", 5); $(".movie-list .item:visible").each(((n, a) => { if (t >= e) return false; if (0 === $(a).find("span:contains('已收藏')").length && 0 === $(a).find("span:contains('已屏蔽')").length && 0 === $(a).find("span:contains('已下载')").length) { let e = $(a).find("a").attr("href"); e && (e = e.includes("?") ? e + "&autoPlay=1" : e + "?autoPlay=1", window.open(e), t++); } })); } openFavorite() { let t = this.getSetting("waitCheckCount", 10); for (let e = 0; e < t; e++) { if (e >= this.favoriteList.length) return; window.open(this.favoriteList[e].url); } } } class HighlightMagnetPlugin extends BasePlugin { handle() { let t = $("#magnets-content .name").toArray(), e = false; t.forEach((t => { let n = $(t), a = n.text().toLowerCase(); a.indexOf("4k") > -1 && n.css("color", "#f40"), (a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1) && (e = true); })), e && t.forEach((t => { let e = $(t), n = e.text().toLowerCase(); n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.indexOf("4k") > -1 || e.parent().parent().parent().hide(); })); } } class PreviewVideoPlugin extends BasePlugin { handle() { $(".preview-video-container").on("click", (t => { t.preventDefault(), $("#preview-video-btn").click(); })), window.location.href.includes("autoPlay=1") && $("#preview-video-btn").click(); } } class SelectTextFilterPlugin extends BasePlugin { injectBean(detailPageMenuPlugin) { this.detailPageMenuPlugin = detailPageMenuPlugin; } handle() { this.isDetailPage && (this.utils.rightClick($("h2"), (t => { const e = window.getSelection().toString(); if (e) { t.preventDefault(); let n = { clientX: t.clientX, clientY: t.clientY + 50 }; this.utils.q(n, `是否屏蔽关键词${e}?`, (() => { this.saveFilterKeyword(e).then((t => { this.closePage(); })); })); } })), $(".male").prev().toArray().forEach((t => { this.utils.rightClick($(t), (e => { e.preventDefault(); let n = $(t).text().trim(); this.utils.q(e, `是否屏蔽演员${n}?`, (() => { this.saveFilterActor(n).then((t => { this.detailPageMenuPlugin.filterOne(null, true); })); })); })); }))); } async saveFilterKeyword(t) { await this.storageManager.saveFilterKeyword(t), this.refresh(); } async saveFilterActor(t) { await this.storageManager.saveFilterActor(t), this.refresh(); } } class JavTrailersPlugin extends BasePlugin { constructor() { super(), this.hasBand = false; } handle() { let t = window.location.href; if (!t.includes("handle=1")) return; if ($("h1:contains('Page not found')").length) { let e = t.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-"); return void (window.location.href = "https://javtrailers.com/search/" + e + "?handle=1"); } let e = $(".videos-list .video-link").toArray(); if (e.length) { const n = t.split("?")[0].split("search/")[1].toLowerCase(), a = e.find((t => $(t).find(".vid-title").text().toLowerCase().includes(n))); if (a) return void (window.location.href = $(a).attr("href") + "?handle=1"); } this.handlePlayJavTrailers(), $("#videoPlayerContainer").on("click", (() => { this.handlePlayJavTrailers(); })), window.addEventListener("message", (t => { let e = document.getElementById("vjs_video_3_html5_api"); e && (e.currentTime += 5); })), u.registerHotkey("z", (() => { const t = document.getElementById("vjs_video_3_html5_api"); t && (t.currentTime += 5); })), u.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), u.registerHotkey("s", (() => window.parent.postMessage("s", "*"))); } handlePlayJavTrailers() { this.hasBand || this.utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => { setTimeout((() => { this.hasBand = true; let t = document.getElementById("vjs_video_3_html5_api"); t.play(), t.currentTime = 5, t.addEventListener("timeupdate", (function () { t.currentTime >= 14 && t.currentTime < 16 && (t.currentTime += 2); })), $("#vjs_video_3_html5_api").css({ position: "fixed", width: "100vw", height: "100vh", objectFit: "cover", zIndex: "999999999" }), $(".vjs-control-bar").css({ position: "fixed", bottom: "20px", zIndex: "999999999" }); }), 0); })); } } class SubTitleCatPlugin extends BasePlugin { handle() { $(".t-banner-inner").hide(), $("#navbar").hide(); let t = window.location.href.split("=")[1].toLowerCase(); $(".sub-table tr td a").toArray().forEach((e => { let n = $(e); n.text().toLowerCase().includes(t) || n.parent().parent().hide(); })); } } class JablePlugin extends BasePlugin { handle() { $("#player")[0].play(), $('button[data-plyr="fullscreen"]').click(), u.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), u.registerHotkey("s", (() => window.parent.postMessage("s", "*"))); } } class Fc2Plugin extends BasePlugin { constructor() { super(...arguments), e(this, "actress", ""); } injectBean(detailPageMenuPlugin, reviewPlugin) { this.detailPageMenuPlugin = detailPageMenuPlugin, this.reviewPlugin = reviewPlugin; } handle() { $('.navbar-item:contains("FC2")').attr("href", "/advanced_search?type=3&score_min=4&d=1"), $('.tabs a:contains("FC2")').attr("href", "/advanced_search?type=3&score_min=4&d=1"); } initCss() { return "\n /* 弹层样式 */\n .movie-detail-layer .layui-layer-title {\n font-size: 18px;\n color: #333;\n background: #f8f8f8;\n }\n \n \n /* 容器样式 */\n .movie-detail-container {\n display: flex;\n height: 100%;\n background: #fff;\n }\n \n .movie-poster-container {\n flex: 0 0 60%;\n padding: 15px;\n }\n \n .right-box {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n }\n \n /* 预告片iframe */\n .movie-trailer {\n width: 100%;\n height: 100%;\n min-height: 400px;\n background: #000;\n border-radius: 4px;\n }\n \n /* 电影信息样式 */\n .movie-title {\n font-size: 24px;\n margin-bottom: 15px;\n color: #333;\n }\n \n .movie-meta {\n margin-bottom: 20px;\n color: #666;\n }\n \n .movie-meta span {\n margin-right: 15px;\n }\n \n /* 演员列表 */\n .actor-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 10px;\n }\n \n .actor-tag {\n padding: 4px 12px;\n background: #f0f0f0;\n border-radius: 15px;\n font-size: 12px;\n color: #555;\n }\n \n /* 图片列表 */\n .image-list {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 10px;\n }\n \n .movie-image-thumb {\n width: 120px;\n height: 80px;\n object-fit: cover;\n border-radius: 4px;\n cursor: pointer;\n transition: transform 0.3s;\n }\n \n .movie-image-thumb:hover {\n transform: scale(1.05);\n }\n \n /* 加载中和错误状态 */\n .search-loading, .movie-error {\n padding: 40px;\n text-align: center;\n color: #999;\n }\n \n .movie-error {\n color: #f56c6c;\n }\n \n .fancybox-container{\n z-index:99999999\n }\n \n \n /* 错误提示样式 */\n .movie-not-found, .movie-error {\n text-align: center;\n padding: 30px;\n color: #666;\n }\n \n .movie-not-found h3, .movie-error h3 {\n color: #f56c6c;\n margin: 15px 0;\n }\n \n .icon-warning, .icon-error {\n font-size: 50px;\n color: #e6a23c;\n }\n \n .icon-error {\n color: #f56c6c;\n }\n\n "; } openFc2Page(t, e, n) { layer.open({ type: 1, title: "影片详情", content: '\n <div class="movie-detail-container">\n <div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>\n <div class="right-box">\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n <div style="margin: 10px 0">\n <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>收藏</span></a>\n <a id="filterBtn" class="menu-btn" style="background-color:#de3333"><span>屏蔽</span></a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>\n </div>\n <div class="message video-panel" style="margin-top:20px">\n <div id="magnets-content" class="magnet-links" style="margin: 0 0.75rem">\n <div class="search-loading">加载中...</div>\n </div>\n </div>\n <div id="reviews-content">\n </div>\n </div>\n </div>\n ', area: ["80%", "90%"], skin: "movie-detail-layer", scrollbar: false, success: (a, i) => { this.loadData(t, e), $("#favoriteBtn").on("click", (t => { this.changeData(e, n, this.actress, "favorite").then((t => layer.closeAll())); })), $("#filterBtn").on("click", (t => { this.utils.q(t, `是否屏蔽${e}?`, (() => { this.changeData(e, n, this.actress, "filter").then((t => layer.closeAll())); })); })), $("#hasDownBtn").on("click", (t => { this.changeData(e, n, this.actress, "hasDown").then((t => layer.closeAll())); })); } }); } loadData(t, e) { this.handleVideo(e.replace("FC2-", "")), this.handleMovieDetail(t), this.handleMagnets(t), this.reviewPlugin.showReview(t, $("#reviews-content")).then(); } handleMovieDetail(t) { p(t).then((t => { const e = t.actors || [], n = t.imgList || []; let a = ""; if (e.length > 0) for (let s = 0; s < e.length; s++) { let t = e[s]; a += `<span class="actor-tag"><a href="/actors/${t.id}" target="_blank">${t.name}</a></span>`, 0 === t.gender && (this.actress += t.name); } else a = '<span class="no-data">暂无演员信息</span>'; let i = ""; i = Array.isArray(n) && n.length > 0 ? n.map(((t, e) => `\n <a href="${t}" data-fancybox="movie-gallery" data-caption="剧照 ${e + 1}">\n <img src="${t}" class="movie-image-thumb" alt=""/>\n </a>\n `)).join("") : '<div class="no-data">暂无剧照</div>', $(".movie-info-container").html(`\n <h3 class="movie-title">${t.title || "无标题"}</h3>\n <div class="movie-meta">\n <span>番号: ${t.carNum || "未知"}</span>\n <span>年份: ${t.releaseDate || "未知"}</span>\n <span>评分: ${t.score || "无"}</span>\n </div>\n <div class="movie-actors">\n <div class="actor-list">主演: ${a}</div>\n </div>\n <div class="movie-gallery" style="margin-top:10px">\n <h4>剧照: </h4>\n <div class="image-list">${i}</div>\n </div>\n `); })).catch((t => { console.error(t), $(".movie-info-container").html(`\n <div class="movie-error">加载失败: ${t.message}</div>\n `); })); } handleMagnets(t) { (t => new Promise(((e, n) => { $.ajax({ method: "GET", url: `${d}/v1/movies/${t}/magnets`, headers: { jdSignature: c() }, success: t => { let n = t.data.magnets; e({ magnetList: n }); }, error: t => { layer.error("获取磁力失败: " + t), n(t); } }); })))(t).then((t => { let e = t.magnetList, n = ""; if (e.length > 0) for (let a = 0; a < e.length; a++) { let t = e[a]; console.log(t); let i = ""; a % 2 == 0 && (i = "odd"), n += `\n <div class="item columns is-desktop ${i}">\n <div class="magnet-name column is-four-fifths">\n <a href="magnet:?xt=urn:btih:${t.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n <span class="name">${t.name}</span>\n <br>\n <span class="meta">\n ${(t.size / 1024).toFixed(2)}GB, ${t.files_count}個文件 \n </span>\n <br>\n <div class="tags">\n ${t.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n ${t.cnsub ? '<span class="tag is-warning is-small is-light">字幕</span>' : ""}\n </div>\n </a>\n </div>\n <div class="buttons column">\n <button class="button is-info is-small copy-to-clipboard" data-clipboard-text="magnet:?xt=urn:btih:${t.hash}" type="button"> 複製 </button>\n </div>\n <div class="date column"><span class="time">${t.created_at}</span></div>\n </div>\n `; } else n = '<span class="no-data">暂无磁力信息</span>'; $("#magnets-content").html(n); })).catch((t => { console.error(t), $("#magnets-content").html(`\n <div class="movie-error">加载失败: ${t.message}</div>\n `); })); } handleVideo(t) { (async t => { let e = `https://hohoj.tv/search?text=${t}`, n = await r.gmHttp.get(e), a = null; if (n.includes("找不到任何影片")) return a; const i = (new DOMParser).parseFromString(n, "text/html"); return $(i).find(".video-item a").toArray().forEach((e => { if ($(e).find(".video-item-title").text().includes(t)) { let t = $(e).attr("href").split("id=")[1]; a = "https://hohoj.tv/embed?id=" + t; } })), a; })(t).then((e => { const n = document.querySelector(".movie-poster-container"), a = document.querySelector(".movie-trailer"); e ? $(a).attr("src", e) : (n.innerHTML = `\n <div class="movie-not-found">\n <i class="icon-warning"></i>\n <h3>未找到相关内容</h3>\n <p>hohoj.tv 中没有找到与当前番号相关的影片信息</p>\n <p style="margin:20px">请尝试以下网站</p>\n <p><a class="menu-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))" href="https://missav.ws/dm3/fc2-ppv-${t}" target="_blank">missav</a></p>\n </div>\n `, a.style.display = "none"); })); } } class FoldCategoryPlugin extends BasePlugin { handle() { if (!this.isListPage) return; let t = $(".tabs ul"), e = $("h2.section-title"), n = "y" === localStorage.foldCategory; const [a, i] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; let s; if (t.length > 0) { s = $("#tags"); let e = $("#tags dl div.tag.is-info").map((function () { return $(this).text().replaceAll("\n", "").replaceAll(" ", ""); })).get().join(" "); if (!e) return; t.append(`\n <li class="is-active" id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>${a}</span>\n <i style="margin-left: 10px" class="${i}"></i>\n </a>\n </li>\n `), $("#navbar-menu-hero .navbar-start").append(`<div class="navbar-item" style="color: #c7a145"><span>已选分类: ${e}</span></div>`); } e.length > 0 && (e.append(`\n <div id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>${a}</span>\n <i style="margin-left: 10px" class="${i}"></i>\n </a>\n </div>\n `), s = $("section > div > div.box")), s && (s[n ? "hide" : "show"](), $("#foldCategoryBtn").on("click", (t => { t.preventDefault(), n = !n, localStorage.foldCategory = n ? "y" : "n"; const [e, a] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; $("#foldCategoryBtn").find("span").text(e).end().find("i").attr("class", a), s[n ? "hide" : "show"](); }))); } } class AliyunApi { constructor(t) { this.baseApiUrl = "https://api.aliyundrive.com", this.refresh_token = t, this.authorization = null, this.default_drive_id = null, this.backupFolderId = null; } async getDefaultDriveId() { return this.default_drive_id || (this.userInfo = await this.getUserInfo(), this.default_drive_id = this.userInfo.default_drive_id), this.default_drive_id; } async getHeaders() { return this.authorization || (this.authorization = await this.getAuthorization()), { authorization: this.authorization }; } async getAuthorization() { let t = this.baseApiUrl + "/v2/account/token", e = { refresh_token: this.refresh_token, grant_type: "refresh_token" }; try { return "Bearer " + (await r.http.post(t, e)).access_token; } catch (n) { throw n.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : n; } } async getUserInfo() { const t = await this.getHeaders(); let e = this.baseApiUrl + "/v2/user/get"; return await r.http.post(e, {}, t); } async deleteFile(t, e = null) { if (!t) throw new Error("未传入file_id"); e || (e = await this.getDefaultDriveId()); let n = { file_id: t, drive_id: e }, a = this.baseApiUrl + "/v2/recyclebin/trash"; const i = await this.getHeaders(); return await r.gmHttp.post(a, n, i), {}; } async createFolder(t, e = null, n = "root") { e || (e = await this.getDefaultDriveId()); let a = this.baseApiUrl + "/adrive/v2/file/createWithFolders", i = { name: t, type: "folder", parent_file_id: n, check_name_mode: "auto_rename", content_hash_name: "sha1", drive_id: e }; const s = await this.getHeaders(), o = await r.gmHttp.post(a, i, s); return JSON.parse(o); } async getFileList(t = "root", e = null) { e || (e = await this.getDefaultDriveId()); let n = this.baseApiUrl + "/adrive/v3/file/list"; const a = { drive_id: e, parent_file_id: t, limit: 200, all: false, url_expire_sec: 14400, image_thumbnail_process: "image/resize,w_256/format,avif", image_url_process: "image/resize,w_1920/format,avif", video_thumbnail_process: "video/snapshot,t_120000,f_jpg,m_lfit,w_256,ar_auto,m_fast", fields: "*", order_by: "updated_at", order_direction: "DESC" }, i = await this.getHeaders(); return (await r.gmHttp.post(n, a, i)).items; } async uploadFile(t, e, n, a = null) { let i = this.baseApiUrl + "/adrive/v2/file/createWithFolders"; a || (a = await this.getDefaultDriveId()); let s = { drive_id: a, part_info_list: [{ part_number: 1 }], parent_file_id: t, name: e, type: "file", check_name_mode: "auto_rename" }; const o = await this.getHeaders(), l = await r.gmHttp.post(i, s, o), c = l.upload_id, d = l.file_id, h = l.part_info_list[0].upload_url; console.log("创建完成: ", l), await this._doUpload(h, n); const p = await r.gmHttp.post("https://api.aliyundrive.com/v2/file/complete", s = { drive_id: "745851", file_id: d, upload_id: c }, o); console.log("标记完成:", p); } _doUpload(t, e) { return new Promise(((n, a) => { $.ajax({ type: "PUT", url: t, data: e, contentType: " ", processData: false, success: (t, e, i) => { 200 === i.status ? (console.log("上传成功:", t), n({})) : a(i); }, error: t => { console.error("上传失败", t.responseText), a(t); } }); })); } async getDownloadUrl(t, e = null) { e || (e = await this.getDefaultDriveId()); let n = this.baseApiUrl + "/v2/file/get_download_url"; const a = await this.getHeaders(); let i = { file_id: t, drive_id: e }; return (await r.gmHttp.post(n, i, a)).url; } async _createBackupFolder(t) { const e = await this.getFileList(); let n = null; for (let a = 0; a < e.length; a++) { let i = e[a]; if (i.name === t) { n = i; break; } } n || (console.log("不存在目录, 进行创建"), n = await this.createFolder(t)), this.backupFolderId = n.file_id; } async backup(t, e, n) { this.backupFolderId || await this._createBackupFolder(t), await this.uploadFile(this.backupFolderId, e, n); } async getBackupList(t) { let e = null; this.backupFolderId || await this._createBackupFolder(t), e = await this.getFileList(this.backupFolderId); const n = []; return e.forEach((t => { n.push({ name: t.name, fileId: t.file_id, createTime: t.created_at, size: t.size }); })), n; } } class WebDavApi { constructor(t, e, n) { this.davUrl = t.endsWith("/") ? t : t + "/", this.username = e, this.password = n, this.folderName = null; } _getAuthHeaders() { return { Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`, Depth: "1" }; } _sendRequest(t, e, n = {}, a) { return new Promise(((i, r) => { const o = this.davUrl + e, l = { ...this._getAuthHeaders(), ...n }; s({ method: t, url: o, headers: l, data: a, onload: t => { t.status >= 200 && t.status < 300 ? i(t) : r(new Error(`Request failed with status ${t.status}: ${t.statusText}`)); }, onerror: t => { console.error("请求WebDav发生错误:", t), r(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确")); } }); })); } async backup(t, e, n) { await this._sendRequest("MKCOL", t); const a = t + "/" + e; await this._sendRequest("PUT", a, { "Content-Type": "text/plain" }, n); } async getFileList(t) { var e, n; const a = (await this._sendRequest("PROPFIND", t, { "Content-Type": "application/xml" }, '\n <?xml version="1.0"?>\n <d:propfind xmlns:d="DAV:">\n <d:prop>\n <d:displayname />\n <d:getcontentlength />\n <d:creationdate />\n <d:iscollection />\n </d:prop>\n </d:propfind>\n ')).responseText, i = (new DOMParser).parseFromString(a, "text/xml").getElementsByTagNameNS("DAV:", "response"), s = []; for (let r = 0; r < i.length; r++) { if (0 === r) continue; if ("1" === i[r].getElementsByTagNameNS("DAV:", "iscollection")[0].textContent) continue; const t = i[r].getElementsByTagNameNS("DAV:", "displayname")[0].textContent, a = (null == (e = i[r].getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : e.textContent) || "0", o = (null == (n = i[r].getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : n.textContent) || ""; s.push({ fileId: t, name: t, size: a, createTime: o }); } return s.reverse(), s; } async deleteFile(t) { let e = this.folderName + "/" + encodeURI(t); await this._sendRequest("DELETE", e, { "Cache-Control": "no-cache" }); } async getBackupList(t) { return this.folderName = t, await this._sendRequest("MKCOL", t), this.getFileList(t); } async getFileContent(t) { let e = this.folderName + "/" + t; return (await this._sendRequest("GET", e, { Accept: "application/octet-stream" })).responseText; } } class SettingPlugin extends BasePlugin { constructor() { super(...arguments), e(this, "folderName", "JSH-数据备份"); } initCss() { return `\n <style>\n ${`\n section .container{\n max-width: 1000px !important;\n min-width: ${this.getSetting("containerWidth", "100")}%;\n }\n `}\n .nav-btn::after {\n content:none !important;\n }\n \n .setting-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n padding: 10px;\n border: 1px solid #ddd;\n border-radius: 5px;\n background-color: #f9f9f9;\n }\n .setting-label {\n min-width: 250px;\n font-weight: bold;\n margin-right: 10px;\n }\n .form-content{\n max-width: 150px;\n min-width: 150px;\n }\n .form-content * {\n width: 100%;\n padding: 5px;\n margin-right: 10px;\n min-width: 150px;\n text-align: center;\n }\n .keyword-label {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n border-radius: 4px;\n color: white;\n font-size: 14px;\n position: relative;\n margin-left: 8px;\n margin-bottom: 2px;\n }\n \n .keyword-remove {\n margin-left: 6px;\n cursor: pointer;\n font-size: 12px;\n line-height: 1;\n }\n \n .keyword-input {\n padding: 6px 12px;\n border: 1px solid #ccc;\n border-radius: 4px;\n font-size: 14px;\n float:right;\n }\n \n .add-tag-btn {\n padding: 6px 12px;\n background-color: #45d0b6;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n margin-left: 8px;\n float:right;\n }\n \n .add-tag-btn:hover {\n background-color: #3fceb7;\n }\n #saveBtn {\n padding: 8px 20px;\n background-color: #4CAF50;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n margin-top: 10px;\n }\n #saveBtn:hover {\n background-color: #45a049;\n }\n </style\n `; } handle() { $(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-right:15px !important;">\n 设置\n </a>\n </div>'); $("#setting-btn").on("click", (() => { layer.open({ type: 1, title: "设置", content: '\n <div style=" display: flex; flex-direction: column; height: 100%; ">\n <div style="margin: 20px">\n <a id="importBtn" class="menu-btn" style="background-color:#d25a88"><span>导入数据</span></a>\n <a id="exportBtn" class="menu-btn" style="background-color:#85d0a3"><span>导出数据</span></a>\n <a id="getRefreshTokenBtn" class="menu-btn fr-btn" style="background-color:#c4a35e"><span>获取refresh_token</span></a>\n\n </div>\n <div style=" flex: 1; overflow-y: auto; margin: 0 20px; padding-bottom: 20px; ">\n <div class="setting-item">\n <span class="setting-label">阿里云盘备份</span>\n <div>\n <a id="backupBtn" class="menu-btn" style="background-color:#5d87c2"><span>备份数据</span></a>\n <a id="backupListBtn" class="menu-btn" style="background-color:#48c554"><span>查看备份</span></a>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">refresh_token:</span>\n <div class="form-content">\n <input id="refresh_token" >\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">WebDav备份</span>\n <div>\n <a id="webdavBackupBtn" class="menu-btn" style="background-color:#5d87c2"><span>备份数据</span></a>\n <a id="webdavBackupListBtn" class="menu-btn" style="background-color:#48c554"><span>查看备份</span></a>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">服务地址:</span>\n <div class="form-content">\n <input id="webDavUrl" >\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">用户名:</span>\n <div class="form-content">\n <input id="webDavUsername" >\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">密码:</span>\n <div class="form-content">\n <input id="webDavPassword" >\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">隐藏已屏蔽内容:</span>\n <div class="form-content">\n <select id="hideFilterItem">\n <option value="yes">是</option>\n <option value="no">否</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">评论区条数:</span>\n <div class="form-content">\n <select id="reviewCount">\n <option value="10">10条</option>\n <option value="20">20条</option>\n <option value="30">30条</option>\n <option value="40">40条</option>\n <option value="50">50条</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" id="widthValue">页面宽度:</span>\n <div class="form-content">\n <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">每次打开待鉴定待下载数量:</span>\n <div class="form-content">\n <input type="number" id="waitCheckCount" min="1" max="20" style="width: 100%;">\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">评论区屏蔽词:</span>\n <div id="reviewKeywordContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">视频列表屏蔽词:</span>\n <div id="filterKeywordContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">屏蔽男演员:</span>\n <div id="filterActorContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n </div>\n <div style=" flex-shrink: 0; padding: 15px 20px; text-align: right; border-top: 1px solid #eee; background: white; "> \n <button id="saveBtn">保存设置</button>\n </div>\n </div>\n ', area: ["50%", "80%"], scrollbar: false, success: (t, e) => { $(t).find(".layui-layer-content").css("position", "relative"), this.loadForm(), this.bindClick(); } }); })); } loadForm() { $("#hideFilterItem").val(this.getSetting("hideFilterItem", "yes")), $("#reviewCount").val(this.getSetting("reviewCount", 20)), $("#waitCheckCount").val(this.getSetting("waitCheckCount", 5)), $("#containerWidth").val(this.getSetting("containerWidth", "100") - 70), $("#refresh_token").val(this.getSetting("refresh_token", "")), $("#webDavUrl").val(this.getSetting("webDavUrl", "")), $("#webDavUsername").val(this.getSetting("webDavUsername", "")), $("#webDavPassword").val(this.getSetting("webDavPassword", "")); let t = this.storageManager.getData().reviewKeywordList; t && t.forEach((t => { this.addLabelTag("#reviewKeywordContainer", t); })); let e = this.storageManager.getData().filterKeywordList; e && e.forEach((t => { this.addLabelTag("#filterKeywordContainer", t); })); let n = this.storageManager.getData().filterActorList; n && n.forEach((t => { this.addLabelTag("#filterActorContainer", t); })), ["#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer"].forEach((t => { $(`${t} .add-tag-btn`).on("click", (e => this.addKeyword(e, t))), $(`${t} .keyword-input`).on("keypress", (e => { "Enter" === e.key && this.addKeyword(e, t); })); })); } bindClick() { $("#importBtn").on("click", (t => this.importData(t))), $("#exportBtn").on("click", (t => this.exportData(t))), $("#backupBtn").on("click", (t => this.backupData(t))), $("#backupListBtn").on("click", (t => this.backupListBtn(t))), $("#webdavBackupBtn").on("click", (t => this.backupDataByWebDav(t))), $("#webdavBackupListBtn").on("click", (t => this.backupListBtnByWebDav(t))), $("#getRefreshTokenBtn").on("click", (t => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", { yes: function (t, e, n) { window.open("https://www.aliyundrive.com/drive/home"), layer.close(t); } }))), $("#containerWidth").on("input", (t => { const e = parseInt($(t.target).val()) + 70 + "%"; document.querySelector("section .container").style.minWidth = e; })), $("#saveBtn").on("click", (() => this.saveForm())); } saveForm() { const t = $("#hideFilterItem").val(), e = $("#reviewCount").val(), n = $("#waitCheckCount").val(), a = parseInt($("#containerWidth").val()), i = $("#refresh_token").val(); let s = JSON.parse(localStorage.getItem("setting")) || {}; s.hideFilterItem = t, s.reviewCount = e, s.waitCheckCount = n, s.containerWidth = a + 70, s.refresh_token = i, s.webDavUrl = $("#webDavUrl").val(), s.webDavUsername = $("#webDavUsername").val(), s.webDavPassword = $("#webDavPassword").val(), localStorage.setItem("setting", JSON.stringify(s)); let r = []; $("#reviewKeywordContainer .keyword-label").toArray().forEach((t => { let e = $(t).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim(); r.push(e); })), this.storageManager.saveKeyData("reviewKeywordList", r); let o = []; $("#filterKeywordContainer .keyword-label").toArray().forEach((t => { let e = $(t).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim(); o.push(e); })), this.storageManager.saveKeyData("filterKeywordList", o); let l = []; $("#filterActorContainer .keyword-label").toArray().forEach((t => { let e = $(t).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim(); l.push(e); })), this.storageManager.saveKeyData("filterActorList", l), layer.success("保存成功"), this.refresh(); } addLabelTag(t, e) { const n = $(`${t} .tag-box`), a = $(`\n <div class="keyword-label" style="background-color: #c5b9a0">\n ${e}\n <span class="keyword-remove">×</span>\n </div>\n `); a.find(".keyword-remove").click((t => { $(t.target).parent().remove(); })), n.append(a); } addKeyword(t, e) { let n = $(`${e} .keyword-input`); const a = n.val().trim(); a && (this.addLabelTag(e, a), n.val("")); } importData() { try { const t = document.createElement("input"); t.type = "file", t.accept = ".json", t.onchange = t => { const e = t.target.files[0]; if (!e) return; const n = new FileReader; n.onload = t => { try { const e = t.target.result, n = JSON.parse(e); if (!(null == n ? void 0 : n.dataList)) return void layer.error("数据格式无效: 缺少 dataList 字段"); localStorage.getItem("appData") ? layer.confirm("当前已有数据,确定要覆盖吗?", { icon: 3, title: "确认覆盖", btn: ["确定", "取消"] }, (function (t) { localStorage.setItem("appData", e), layer.success("数据导入成功"), layer.close(t), location.reload(); }), (function (t) { layer.error("已取消导入"), layer.close(t); })) : (localStorage.setItem("appData", e), layer.success("数据导入成功")); } catch (e) { layer.error("导入失败:文件内容不是有效的JSON格式 " + e); } }, n.onerror = () => { layer.error("读取文件时出错"); }, n.readAsText(e); }, document.body.appendChild(t), t.click(), setTimeout((() => document.body.removeChild(t)), 1e3); } catch (t) { layer.error("导入数据时出错: " + t.message); } } async backupData(t) { const e = this.getSetting("refresh_token", ""); if (!e) return void layer.error("请填写refresh_token并保存后, 再试此功能"); let n = this.utils.getNowStr("_", "_") + ".json", a = localStorage.getItem("appData"); a = v(a); let i = loading(); try { const t = new AliyunApi(e); await t.backup(this.folderName, n, a), layer.success("备份完成"); } catch (s) { console.error(s), layer.error(s.toString()); } finally { i.close(); } } async backupListBtn(t) { const e = this.getSetting("refresh_token", ""); if (!e) return void layer.error("请填写refresh_token并保存后, 再试此功能"); let n = loading(); try { const t = new AliyunApi(e), n = await t.getBackupList(this.folderName); this.openFileListDialog(n, t, "阿里云盘"); } catch (a) { console.error(a), layer.error(`发生错误: ${a ? a.message : a}`); } finally { n.close(); } } async backupDataByWebDav(t) { const e = this.getSetting("webDavUrl", ""); if (!e) return void layer.error("请填写webDav服务地址并保存后, 再试此功能"); const n = this.getSetting("webDavUsername", ""); if (!n) return void layer.error("请填写webDav用户名并保存后, 再试此功能"); const a = this.getSetting("webDavPassword", ""); if (!a) return void layer.error("请填写webDav密码并保存后, 再试此功能"); let i = this.utils.getNowStr("_", "_") + ".json", s = localStorage.getItem("appData"); s = v(s); let r = loading(); try { const t = new WebDavApi(e, n, a); await t.backup(this.folderName, i, s), layer.success("备份完成"); } catch (o) { console.error(o), layer.error(o.toString()); } finally { r.close(); } } async backupListBtnByWebDav(t) { const e = this.getSetting("webDavUrl", ""); if (!e) return void layer.error("请填写webDav服务地址并保存后, 再试此功能"); const n = this.getSetting("webDavUsername", ""); if (!n) return void layer.error("请填写webDav用户名并保存后, 再试此功能"); const a = this.getSetting("webDavPassword", ""); if (!a) return void layer.error("请填写webDav密码并保存后, 再试此功能"); let i = loading(); try { const t = new WebDavApi(e, n, a), i = await t.getBackupList(this.folderName); this.openFileListDialog(i, t, "WebDav"); } catch (s) { console.error(s), layer.error(`发生错误: ${s ? s.message : s}`); } finally { i.close(); } } openFileListDialog(t, e, n) { layer.open({ type: 1, title: n + "备份文件", content: '<div id="table-container"></div>', area: ["40%", "70%"], success: a => { const i = m({ containerId: "table-container", columns: [{ key: "name", title: "文件名" }, { key: "createTime", title: "备份日期", render: t => `${this.utils.getNowStr("-", ":", t.createTime)}` }, { key: "size", title: "文件大小", render: t => { const e = ["B", "KB", "MB", "GB", "TB", "PB"]; let n = 0, a = t.size; for (; a >= 1024 && n < e.length - 1;) a /= 1024, n++; return `${a % 1 == 0 ? a.toFixed(0) : a.toFixed(2)} ${e[n]}`; } }], data: t, buttons: [{ text: "删除", class: "btn-danger", onClick: async t => { layer.confirm(`是否将 ${t.name} 放入回收站?`, { icon: 3, title: "提示", btn: ["确定", "取消"] }, (async a => { layer.close(a); let s = loading(); try { await e.deleteFile(t.fileId); let a = await e.getBackupList(this.folderName); i.updateData(a), "阿里云盘" === n ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功"); } catch (r) { console.error(r), layer.error(`发生错误: ${r ? r.message : r}`); } finally { s.close(); } })); } }, { text: "下载", class: "btn-primary", onClick: t => { let a = loading(); try { "阿里云盘" === n ? e.getDownloadUrl(t.fileId).then((e => { this.utils.gmHttp.get(e, null, { Referer: "https://www.aliyundrive.com/" }).then((e => { e = y(e), this.utils.download(e, t.name); })); })).catch((t => { console.error(t), layer.error("下载失败: " + t); })) : e.getFileContent(t.fileId).then((e => { this.utils.download(e, t.name); })); } catch (i) { console.error(i), layer.error("下载失败: " + i); } finally { a.close(); } } }, { text: "导入", class: "btn-success", onClick: t => { layer.confirm(`是否将该云备份数据 ${t.name} 导入?`, { icon: 3, title: "提示", btn: ["确定", "取消"] }, (async a => { layer.close(a); let i = loading(); try { let a; if ("阿里云盘" === n) { const n = await e.getDownloadUrl(t.fileId); a = await this.utils.gmHttp.get(n, null, { Referer: "https://www.aliyundrive.com/" }); } else a = await e.getFileContent(t.fileId); a = y(a); const i = JSON.parse(a); if (!i || !i.dataList) return void layer.error("数据格式无效: 缺少 dataList 字段"); localStorage.setItem("appData", a), layer.success("导入成功!"), window.location.reload(); } catch (s) { console.error(s); const t = s.message.includes("dataList") ? "数据格式错误: 缺少必要字段" : "请求失败,请重试"; layer.error(t); } finally { i.close(); } })); } }] }); } }); } exportData(t) { try { const t = localStorage.getItem("appData"); if (!t) return void layer.error("没有找到可导出的数据"); const e = `${this.utils.getNowStr("_", "_")}.json`; this.utils.download(t, e), layer.success("数据导出成功"); } catch (e) { layer.error("导出数据时出错: " + e.message); } } } const f = "x7k9p3"; function v(t) { return (f + t + f).split("").map((t => { const e = t.codePointAt(0); return String.fromCodePoint(e + 5); })).join(""); } function y(t) { return t.split("").map((t => { const e = t.codePointAt(0); return String.fromCodePoint(e - 5); })).join("").slice(f.length, -f.length); } class HistoryPlugin extends BasePlugin { injectBean(fc2Plugin) { this.fc2Plugin = fc2Plugin, this.dataType = "all", this.tableObj = "all"; } handle() { $(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n 历史列表\n </a>\n </div>'), $("#setting-btn").on("click", (t => this.openHistory())); } openHistory() { layer.open({ type: 1, title: "历史列表", content: '\n <div style="margin: 10px">\n <a class="menu-btn history-btn" data-action="all" style="background-color:#d3c8a5">所有</a>\n <a class="menu-btn history-btn" data-action="filter" style="background-color:#ec4949">已屏蔽</a>\n <a class="menu-btn history-btn" data-action="favorite" style="background-color:#50adb9;">已收藏</a>\n <a class="menu-btn history-btn" data-action="hasDown" style="background-color:#8ebd6e;">已下载</a>\n </div>\n <div id="table-container"></div>\n ', area: ["60%", "80%"], success: t => { const e = this.getDataList(); this.loadTableData(e), $(".layui-layer-content").on("click", ".history-btn", (async t => { this.dataType = $(t.target).data("action"), this.reloadTable(); })); }, end: () => this.refresh() }); } handleClickDetail(t, e) { if (e.carNum.includes("FC2-")) { const t = e.url.split("/"), n = t[t.length - 1].split("#")[0]; this.fc2Plugin.openFc2Page(n, e.carNum, e.url); } else this.openPage(e.url, e.carNum, false, t); } reloadTable() { const t = this.getDataList(); this.tableObj.updateData(t); } handleDelete(t, e) { this.utils.q(t, `是否移除${e.carNum}?`, (() => { this.storageManager.removeData(e.carNum).then((t => { this.showCarNumBox(e.carNum), this.reloadTable(); })); })); } showCarNumBox(t) { const e = $(".movie-list .item").toArray().find((e => $(e).find(".video-title strong").text() === t)); e && ($(e).show(), this.hasHandleList = this.hasHandleList.filter((e => e !== `${t}-hide`))); } getDataList() { let t = this.storageManager.getData().dataList; return t.reverse(), this.allCount = t.length, this.filterCount = 0, this.favoriteCount = 0, this.hasDownCount = 0, t.forEach((t => { switch (t.status) { case "filter": this.filterCount++; break; case "favorite": this.favoriteCount++; break; case "hasDown": this.hasDownCount++; } })), $('a[data-action="all"]').text(`所有 (${this.allCount})`), $('a[data-action="filter"]').text(`已屏蔽 (${this.filterCount})`), $('a[data-action="favorite"]').text(`已收藏 (${this.favoriteCount})`), $('a[data-action="hasDown"]').text(`已下载 (${this.hasDownCount})`), "all" === this.dataType ? t : t.filter((t => t.status === this.dataType)); } loadTableData(t) { this.tableObj = m({ containerId: "table-container", columns: [{ key: "carNum", title: "番号" }, { key: "actress", title: "演员", width: "250px" }, { key: "createDate", title: "创建日期", width: "250px" }, { key: "status", title: "状态", render: t => { let e, n = ""; switch (t.status) { case "filter": e = "#ec4949", n = "已屏蔽"; break; case "favorite": e = "#50adb9", n = "已收藏"; break; case "hasDown": e = "#8ebd6e", n = "已下载"; } return `<span style="color:${e}">${n}</span>`; } }], data: t, buttons: [{ text: "移除", class: "btn-danger", onClick: t => { this.handleDelete(event, t); } }, { text: "详情页", class: "btn-info", onClick: t => { this.handleClickDetail(event, t); } }] }); } } class ActressInfoPlugin extends BasePlugin { constructor() { super(...arguments), e(this, "apiUrl", "https://ja.wikipedia.org/wiki/"); } handle() { this.handleDetailPage().then(), this.handleStarPage().then(); } initCss() { return "\n <style>\n .info-tag {\n background-color: #ecf5ff;\n display: inline-block;\n height: 32px;\n padding: 0 10px;\n line-height: 30px;\n font-size: 12px;\n color: #409eff;\n border: 1px solid #d9ecff;\n border-radius: 4px;\n box-sizing: border-box;\n white-space: nowrap;\n }\n </style>\n "; } async handleDetailPage() { let t = $(".female").prev().map(((t, e) => $(e).text().trim())).get(); if (!t.length) return; let e = null, n = ""; for (let i = 0; i < t.length; i++) { let s = t[i]; if (e = this.utils.getLocalStorage(s), !e) try { e = await this.searchInfo(s), e && this.utils.setLocalStorage(s, e, this.utils.MONTH); } catch (a) { console.error("该名称查询失败,尝试其它名称"); } let r = ""; r = e ? `\n <div class="panel-block" style="margin-top: 20px;">\n <strong>${s}:</strong>\n <a href="${e.url}" style="margin-left: 5px" target="_blank">\n <span class="info-tag">${e.birthday} ${e.age}</span>\n <span class="info-tag">${e.height} ${e.weight}</span>\n <span class="info-tag">${e.threeSizeText} ${e.braSize}</span>\n </a>\n </div>\n ` : `<div class="panel-block" style="margin-top: 20px;"><a href="${this.apiUrl + s}" target="_blank"><strong>${s}:</strong></a></div> `, n += r; } $('strong:contains("演員")').parent().after(n); } async handleStarPage() { let t = [], e = $(".actor-section-name"); e.length && e.text().trim().split(",").forEach((e => { t.push(e.trim()); })); let n = $(".section-meta:not(:contains('影片'))"); if (n.length && n.text().trim().split(",").forEach((e => { t.push(e.trim()); })), !t.length) return; let a = null; for (let r = 0; r < t.length; r++) { let e = t[r]; if (a = this.utils.getLocalStorage(e), a) { console.log("来自缓存"); break; } try { a = await this.searchInfo(e); } catch (s) { console.error("该名称查询失败,尝试其它名称"); } if (a) break; } a && t.forEach((t => { this.utils.setLocalStorage(t, a, this.utils.MONTH); })); let i = '<div style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>'; a && (i = `\n <a href="${a.url}" target="_blank">\n <div style="font-size: 17px; font-weight: normal; margin-top: 5px;">\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">出生日期: ${a.birthday}</span>\n <span style="width: 200px;">年龄: ${a.age}</span>\n <span style="width: 200px;">身高: ${a.height}</span>\n </div>\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">体重: ${a.weight}</span>\n <span style="width: 200px;">三围: ${a.threeSizeText}</span>\n <span style="width: 200px;">罩杯: ${a.braSize}</span>\n </div>\n </div>\n </a>\n `), e.parent().append(i); } async searchInfo(t) { "三上悠亞" === t && (t = "三上悠亜"); let e = this.apiUrl + t; const n = await this.utils.gmHttp.get(e), a = new DOMParser, i = $(a.parseFromString(n, "text/html")); let s = i.find('tr:has(a[title="誕生日"]) td').text().trim(), r = i.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt(i.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", o = i.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", l = i.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim(); return "― kg" === l && (l = ""), { birthday: s, age: r, height: o, weight: l, threeSizeText: i.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(), braSize: i.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(), url: e }; } } class AliyunPanPlugin extends BasePlugin { handle() { $("body").append('<a class="menu-btn btn-success" id="refresh-token-btn" style="position:fixed; right: 0; top:50%;z-index:99999">获取refresh_token</a>'), $("#refresh-token-btn").on("click", (t => { let e = localStorage.getItem("token"); if (!e) return void alert("请先登录!"); let n = JSON.parse(e).refresh_token; navigator.clipboard.writeText(n).then((() => { alert("已复制到剪切板 如失败, 请手动复制: " + n); })).catch((t => { console.error("Failed to copy refresh token: ", t); })); })); } } class HitShowPlugin extends BasePlugin { constructor() { super(); } handle() { n('a[href*="rankings/playback"]').on("click", (t => { t.preventDefault(), t.stopPropagation(), window.location.href = "/?handlePlayback=1&period=daily"; })), this.handlePlayback().then(); } async handlePlayback() { if (!window.location.href.includes("handlePlayback=1")) return; let t = new URLSearchParams(window.location.search).get("period"); this.toolBar(t); let e = n(".movie-list"); e.html(""); const a = await ((t = "daily", e = "high_score") => new Promise(((n, a) => { $.ajax({ method: "GET", url: `${d}/v1/rankings/playback?period=${t}&filter_by=${e}`, headers: { jdSignature: c() }, success: t => { n(t.data.movies); }, error: t => { layer.error("获取热播失败: " + t), a(t); } }); })))(t); let i = this.markDataListHtml(a); e.html(i), this.refresh(), this.loadScore(a); } toolBar(t) { n(".pagination").remove(), n(".main-tabs ul li").removeClass("is-active"), n(".main-tabs ul li:first").addClass("is-active"); let e = `\n <div class="button-group" style="margin-top:18px">\n <div class="buttons has-addons" id="conditionBox">\n <a style="padding:18px 18px !important;" class="button is-small ${"daily" === t ? "is-info" : ""}" href="/?handlePlayback=1&period=daily">日榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === t ? "is-info" : ""}" href="/?handlePlayback=1&period=weekly">周榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === t ? "is-info" : ""}" href="/?handlePlayback=1&period=monthly">月榜</a>\n </div>\n </div>\n `; n(".toolbar").html(e); } getStarRating(t) { let e = ""; const n = Math.floor(t); for (let a = 0; a < n; a++) e += '<i class="icon-star"></i>'; for (let a = 0; a < 5 - n; a++) e += '<i class="icon-star gray"></i>'; return e; } loadScore(t) { if (0 === t.length) return; let e = Promise.resolve(); for (let a = 0; a < t.length; a++) { const i = t[a].id; if (n(`#${i}`).is(":hidden")) continue; const s = this.utils.getLocalStorage("SCORE_" + i); s ? this.appendScoreHtml(i, s) : e = e.then((async () => { const t = await p(i); let e = t.score, n = t.watchedCount, a = `\n <span class="value">\n <span class="score-stars">${this.getStarRating(e)}</span> \n ${e}分,由${n}人評價\n </span>\n `; this.appendScoreHtml(i, a), this.utils.setLocalStorage("SCORE_" + i, a, this.utils.WEEK), await new Promise((t => setTimeout(t, 1e3))); })); } } appendScoreHtml(t, e) { let a = n(`#score_${t}`); "" === a.html().trim() && a.html(e); } markDataListHtml(t) { let e = ""; return t.forEach((t => { e += `\n <div class="item" id="${t.id}">\n <a href="/v/${t.id}" class="box" title="${t.origin_title}">\n <div class="cover ">\n <img loading="lazy" src="${t.cover_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt="">\n </div>\n <div class="video-title"><strong>${t.number}</strong> ${t.origin_title}</div>\n <div class="score" id="score_${t.id}">\n </div>\n <div class="meta">\n ${t.release_date}\n </div>\n <div class="tags has-addons">\n ${t.has_cnsub ? '<span class="tag is-warning">含中字磁鏈</span>' : t.magnets_count > 0 ? '<span class="tag is-success">含磁鏈</span>' : '<span class="tag is-info">无磁鏈</span>'}\n ${t.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""}\n </div>\n </a>\n </div>\n `; })), e; } } class MissavPlugin extends BasePlugin { handle() { const t = window.player.pause; window.player.pause = () => { document.hasFocus() && t(); }; } } class TOP250Plugin extends BasePlugin { constructor() { super(), e(this, "has_cnsub", ""), e(this, "movies", []); } injectBean(hitShowPlugin) { this.hitShowPlugin = hitShowPlugin; } handle() { n('.main-tabs ul li:contains("猜你喜歡")').html('<a href="/rankings/top"><span>Top250</span></a>'), n('a[href*="rankings/top"]').on("click", (t => { t.preventDefault(), t.stopPropagation(); const e = n(t.target), a = (e.is("a") ? e : e.closest("a")).attr("href"); let i = a.includes("?") ? a.split("?")[1] : a; const s = new URLSearchParams(i); this.checkLogin(t, s); })), this.handleTop().then(); } async handleTop() { if (!window.location.href.includes("handleTop=1")) return; const t = new URLSearchParams(window.location.search); let e = t.get("type") || "all", a = t.get("type_value") || ""; this.has_cnsub = t.get("has_cnsub") || ""; let i = t.get("page") || 1; this.toolBar(e, a, i); let s = n(".movie-list"); s.html(""); let o = loading(); try { const t = await (async (t = "all", e = "", n = 1, a = 40) => { let i = `${d}/v1/movies/top?start_rank=1&type=${t}&type_value=${e}&ignore_watched=false&page=${n}&limit=${a}`, s = { "user-agent": "Dart/3.5 (dart:io)", "accept-language": "zh-TW", "accept-encoding": "gzip", authorization: "Bearer " + localStorage.getItem("appAuthorization"), jdsignature: c() }; return await r.gmHttp.get(i, null, s); })(e, a, i, 50); let o = t.success, l = t.message, h = t.action; if (1 === o) { let e = t.data.movies; if (0 === e.length) return void layer.error("无数据"); this.movies = e; let a = this.hitShowPlugin.markDataListHtml(e); s.html(a), this.refresh(), "1" === this.has_cnsub ? (n(".item:contains('含中字磁鏈')").show(), n(".item:contains('含磁鏈')").hide()) : "0" === this.has_cnsub ? (n(".item:contains('含中字磁鏈')").hide(), n(".item:contains('含磁鏈')").show()) : (n(".item:contains('含中字磁鏈')").show(), n(".item:contains('含磁鏈')").show()), this.hitShowPlugin.loadScore(e); } else console.error(t), s.html(`<h3>${l}</h3>`), layer.error(l); "JWTVerificationError" === h && (localStorage.removeItem("appAuthorization"), this.checkLogin(null, new URLSearchParams(window.location.search))); } catch (l) { console.error("获取Top数据失败:", l), layer.error(`获取Top数据失败: ${l ? l.message : l}`); } finally { o.close(); } } toolBar(t, e, a) { n(".main-tabs ul li").removeClass("is-active"), n(".main-tabs ul li:eq(1)").addClass("is-active"), "5" === a.toString() && (n("#auto-page").remove(), n(".pagination-next").remove()), n(".pagination-ellipsis").closest("li").remove(), n(".pagination-list li a").each((function () { parseInt(n(this).text()) > 5 && n(this).closest("li").remove(); })); let i = ""; for (let n = (new Date).getFullYear(); n >= 2008; n--) i += `\n <a style="padding:18px 18px !important;" \n class="button is-small ${e === n.toString() ? "is-info" : ""}" \n href="/?handleTop=1&type=year&type_value=${n}&has_cnsub=${this.has_cnsub}">\n ${n}\n </a>\n `; let s = `\n <div class="button-group">\n <div class="buttons has-addons" id="conditionBox" style="margin-bottom: 0!important;">\n <a style="padding:18px 18px !important;" class="button is-small ${"all" === t ? "is-info" : ""}" href="/?handleTop=1&type=all&type_value=&has_cnsub=${this.has_cnsub}">全部</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"0" === e ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=0&has_cnsub=${this.has_cnsub}">有码</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"1" === e ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=1&has_cnsub=${this.has_cnsub}">无码</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"2" === e ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=2&has_cnsub=${this.has_cnsub}">欧美</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"3" === e ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=3&has_cnsub=${this.has_cnsub}">Fc2</a>\n \n <a style="padding:18px 18px !important;margin-left: 50px" class="button is-small ${"1" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="1">含中字磁鏈</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"0" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="0">无字幕</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-cnsub-value="">重置</a>\n </div>\n \n <div class="buttons has-addons" id="conditionBox">\n ${i}\n </div>\n </div>\n `; n(".toolbar").html(s), n("a[data-cnsub-value]").on("click", (t => { const e = n(t.currentTarget).data("cnsub-value"); this.has_cnsub = e.toString(), n("a[data-cnsub-value]").removeClass("is-info"), n(t.currentTarget).addClass("is-info"), n(".toolbar a.button").not("[data-cnsub-value]").each(((t, a) => { const i = n(a), s = new URL(i.attr("href"), window.location.origin); s.searchParams.set("has_cnsub", e), i.attr("href", s.toString()); })); const a = new URL(window.location.href); a.searchParams.set("has_cnsub", e), history.pushState({}, "", a.toString()), "1" === this.has_cnsub ? (n(".item:contains('含中字磁鏈')").show(), n(".item:contains('含磁鏈')").hide()) : "0" === this.has_cnsub ? (n(".item:contains('含中字磁鏈')").hide(), n(".item:contains('含磁鏈')").show()) : (n(".item:contains('含中字磁鏈')").show(), n(".item:contains('含磁鏈')").show()), this.hitShowPlugin.loadScore(this.movies); })); } checkLogin(t, e) { if (!localStorage.getItem("appAuthorization")) return layer.error("未登录手机端接口, 无法查看"), void this.openLoginDialog(); let n = "all", a = "", i = e.get("t") || ""; /^y\d+$/.test(i) ? (n = "year", a = i.substring(1)) : "" !== i && (n = "video_type", a = i); let s = `/?handleTop=1&type=${n}&type_value=${a}`; t && t.ctrlKey ? window.open(s, "_blank") : window.location.href = s; } openLoginDialog() { layer.open({ type: 1, title: "JavDB", closeBtn: 1, area: ["360px", "auto"], shadeClose: false, content: '\n <div style="padding: 30px; font-family: \'Helvetica Neue\', Arial, sans-serif;">\n <div style="margin-bottom: 25px;">\n <input type="text" id="username" name="username" \n style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n background: #f9f9f9; color: #333;"\n placeholder="用户名 | 邮箱"\n onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n </div>\n \n <div style="margin-bottom: 15px;">\n <input type="password" id="password" name="password" \n style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n background: #f9f9f9; color: #333;"\n placeholder="密码"\n onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n </div>\n \n <button id="loginBtn" \n style="width: 100%; padding: 12px; background: #4a8bfc; color: white; \n border: none; border-radius: 4px; font-size: 15px; cursor: pointer;\n transition: background 0.3s;"\n onmouseover="this.style.background=\'#3a7be0\'"\n onmouseout="this.style.background=\'#4a8bfc\'">\n 登录\n </button>\n </div>\n ', success: (t, e) => { n("#loginBtn").click((function () { const t = n("#username").val(), a = n("#password").val(); if (!t || !a) return void layer.error("请输入用户名和密码"); let i = loading(); (async (t, e) => { let n = `${d}//v1/sessions?username=${t}&password=${e}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, a = { "user-agent": "Dart/3.5 (dart:io)", "accept-language": "zh-TW", "accept-encoding": "gzip", "content-type": "multipart/form-data; boundary=--dio-boundary-2210433284", jdsignature: c() }; return await r.gmHttp.post(n, null, a); })(t, a).then((t => { let n = t.success; if (0 === n) layer.error(t.message); else { if (1 !== n) throw new Error(t.message); { let n = t.data.token; localStorage.setItem("appAuthorization", n), localStorage.setItem("appUser", JSON.stringify(t.data)), layer.success("登录成功"), layer.close(e), window.location.href = "/?handleTop=1&period=daily"; } } })).catch((t => { console.error("登录失败:", t), layer.error(t.message); })).finally((() => { i.close(); })); })); } }); } } window.$ = window.jQuery = n, window.Toastify = i, r.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/css/layui.min.css"), r.importResource("https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css"); const b = (t, e, n, a, s) => { let r = {}; "object" == typeof n ? r = n : (r = "object" == typeof a ? a : s || {}, r.gravity = n || "top", r.position = "string" == typeof a ? a : "center"), r.gravity && "center" !== r.gravity || (r.offset = { y: "calc(50vh - 150px)" }); const o = "#60A5FA", l = "#93C5FD", c = "#10B981", d = "#6EE7B7", h = "#EF4444", p = "#FCA5A5", g = { borderRadius: "12px", color: "white", padding: "12px 16px", boxShadow: "0 4px 6px rgba(0,0,0,0.1)", minWidth: "150px", textAlign: "center" }, u = { text: t, duration: 2e3, close: false, gravity: "top", position: "center", style: { info: { ...g, background: `linear-gradient(to right, ${o}, ${l})` }, success: { ...g, background: `linear-gradient(to right, ${c}, ${d})` }, error: { ...g, background: `linear-gradient(to right, ${h}, ${p})` } }[e], stopOnFocus: true, oldestFirst: false, ...r }, m = i(u); if (m.showToast(), r.onClose) { const t = m.toastElement; console.log("监听", t), t.addEventListener("animationend", (() => { r.onClose(); })); } }; layer.success = (t, e = "center", n, a) => b(t, "success", e, n, a), layer.error = (t, e = "center", n, a) => b(t, "error", e, n, a), layer.info = (t, e = "center", n, a) => b(t, "info", e, n, a), function () { const t = new PluginManager; let e = window.location.hostname; e.includes("javdb") && (t.register(ListPagePlugin), t.register(AutoPagePlugin), t.register(Fc2Plugin), t.register(FoldCategoryPlugin), t.register(ListPageMenuPlugin), t.register(HistoryPlugin), t.register(SettingPlugin), t.register(SearchPlugin), t.register(HitShowPlugin), t.register(TOP250Plugin), t.register(DetailPagePlugin), t.register(ReviewPlugin), t.register(DetailPageMenuPlugin), t.register(HighlightMagnetPlugin), t.register(PreviewVideoPlugin), t.register(SelectTextFilterPlugin), t.register(ActressInfoPlugin)), e.includes("javtrailers") && t.register(JavTrailersPlugin), e.includes("subtitlecat") && t.register(SubTitleCatPlugin), e.includes("jable") && t.register(JablePlugin), e.includes("missav") && t.register(MissavPlugin), e.includes("aliyundrive") && t.register(AliyunPanPlugin), t.process(); }(), Object.keys(localStorage).forEach((t => t.startsWith("info_") && localStorage.removeItem(t))); })($, layui, md5, Toastify);