JAV-JHS

Jav-鉴黄师 收藏, 屏蔽, 标记已下载, 在线预览, fc2ppv, 云盘备份

// ==UserScript==
// @name         JAV-JHS
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version      1.4.1
// @author       fuajofkewmrw
// @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 e = Object.defineProperty, t = (t, n, a) => ((t, n, a) => n in t ? e(t, n, {
        enumerable: true,
        configurable: true,
        writable: true,
        value: a
    }) : t[n] = a)(t, "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 e = document.createElement("div");
            e.className = "loading-container";
            const t = document.createElement("div");
            return t.className = "loading-animation", e.appendChild(t), document.body.appendChild(e),
                {
                    close: () => {
                        e && e.parentNode && e.parentNode.removeChild(e);
                    }
                };
        };

    var s = (() => "undefined" != typeof GM_xmlhttpRequest ? GM_xmlhttpRequest : void 0)();

    class Utils {
        constructor() {
            return t(this, "insertStyle", (e => {
                e && (-1 === e.indexOf("<style>") && (e = "<style>" + e + "</style>"), $("head").append(e));
            })), t(this, "http", {
                get(e, t = {}, n = {}) {
                    return this.jqueryRequest("GET", e, null, t, n);
                },
                post(e, t = {}, n = {}) {
                    return this.jqueryRequest("POST", e, t, null, n);
                },
                put(e, t = {}, n = {}) {
                    return this.jqueryRequest("PUT", e, t, null, n);
                },
                del(e, t = {}, n = {}) {
                    return this.jqueryRequest("DELETE", e, null, t, n);
                },
                jqueryRequest: (e, t, n = {}, a = {}, i = {}) => ("POST" === e && (i = {
                    "Content-Type": "application/json",
                    ...i
                }), new Promise(((s, r) => {
                    $.ajax({
                        method: e,
                        url: t,
                        data: "GET" === e || "DELETE" === e ? a : JSON.stringify(n),
                        headers: i,
                        success: (e, t, n) => {
                            var a;
                            if (null == (a = n.getResponseHeader("Content-Type")) ? void 0 : a.includes("application/json")) try {
                                s("object" == typeof e ? e : JSON.parse(e));
                            } catch (i) {
                                s(e);
                            } else s(e);
                        },
                        error: (e, t, n) => {
                            let a = n;
                            if (e.responseText) try {
                                const t = JSON.parse(e.responseText);
                                a = t.message || t.msg || e.responseText;
                            } catch {
                                a = e.responseText;
                            }
                            r(new Error(a));
                        }
                    });
                })))
            }), t(this, "gmHttp", {
                get(e, t = {}, n = {}) {
                    return this.gmRequest("GET", e, null, t, n);
                },
                post(e, t = {}, n = {}) {
                    return this.gmRequest("POST", e, t, null, n);
                },
                put(e, t = {}, n = {}) {
                    return this.gmRequest("PUT", e, t, null, n);
                },
                del(e, t = {}, n = {}) {
                    return this.gmRequest("DELETE", e, null, t, n);
                },
                gmRequest(e, t, n = {}, a = {}, i = {}) {
                    if (("GET" === e || "DELETE" === e) && a && Object.keys(a).length) {
                        const e = new URLSearchParams(a).toString();
                        t += (t.includes("?") ? "&" : "?") + e;
                    }
                    return "POST" !== e && "PUT" !== e || (i = {
                        "Content-Type": "application/json",
                        ...i
                    }), new Promise(((a, r) => {
                        s({
                            method: e,
                            url: t,
                            headers: i,
                            data: "POST" === e || "PUT" === e ? JSON.stringify(n) : void 0,
                            onload: e => {
                                var t;
                                try {
                                    if (e.status >= 200 && e.status < 300) if (e.responseText && (null == (t = e.responseHeaders) ? void 0 : t.toLowerCase().includes("application/json"))) try {
                                        a(JSON.parse(e.responseText));
                                    } catch (n) {
                                        a(e.responseText);
                                    } else a(e.responseText || e); else if (e.responseText) try {
                                        const t = JSON.parse(e.responseText);
                                        r(t);
                                    } catch {
                                        r(new Error(e.responseText || `HTTP Error ${e.status}`));
                                    } else r(new Error(`HTTP Error ${e.status}`));
                                } catch (n) {
                                    r(n);
                                }
                            },
                            onerror: e => {
                                r(new Error(e.error || "Network Error"));
                            },
                            ontimeout: () => {
                                r(new Error("Request Timeout"));
                            }
                        });
                    }));
                }
            }), Utils.instance || (Utils.instance = this, this.intervalContainer = {}), Utils.instance;
        }

        importResource(e) {
            let t;
            e.indexOf("css") >= 0 ? (t = document.createElement("link"), t.setAttribute("rel", "stylesheet"),
                t.href = e) : (t = document.createElement("script"), t.setAttribute("type", "text/javascript"),
                t.src = e), document.documentElement.appendChild(t);
        }

        loopDetector(e, t, 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!", e, t), s = i),
                (e() || s) && (clearInterval(this.intervalContainer[r]), t && t(), delete this.intervalContainer[r]);
            }), n);
        }

        rightClick(e, t) {
            e.jquery && (e = e[0]), e ? e.addEventListener("contextmenu", (e => {
                t(e);
            })) : console.error("rightClick(), 找不到元素");
        }

        q(e, t, n, a) {
            let i, s;
            e ? (i = e.clientX - 120, s = e.clientY - 120) : (i = window.innerWidth / 2 - 120,
                s = window.innerHeight / 2 - 120);
            let r = layer.confirm(t, {
                offset: [s, i],
                btn: ["确定", "取消"],
                zIndex: 99999999999
            }, (function () {
                n(), layer.close(r);
            }), (function () {
                a && a();
            }));
        }

        getNowStr(e = "-", t = ":", 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(e)} ${[o, l, c].join(t)}`;
        }

        download(e, t) {
            const n = new Blob([e], {
                type: "application/json"
            }), a = URL.createObjectURL(n), i = document.createElement("a");
            i.href = a, i.download = t, document.body.appendChild(i), i.click(), setTimeout((() => {
                document.body.removeChild(i), URL.revokeObjectURL(a);
            }), 100);
        }
    }

    const r = new Utils;

    class LocalStorageManager {
        constructor() {
            return t(this, "initData", {
                dataList: [],
                filterKeywordList: [],
                filterActorList: [],
                reviewKeywordList: []
            }), t(this, "FAVORITE", "favorite"), t(this, "FILTER", "filter"), t(this, "HASDOWN", "hasDown"),
            LocalStorageManager.instance || (LocalStorageManager.instance = this), LocalStorageManager.instance;
        }

        findData(e, t) {
            return t.find((t => t.carNum === e));
        }

        _getJsonData() {
            const e = localStorage.getItem("appData");
            return e || localStorage.setItem("appData", JSON.stringify(this.initData)), e ? JSON.parse(e) : this.initData;
        }

        _saveJsonData(e) {
            localStorage.setItem("appData", JSON.stringify(e));
        }

        saveKeyData(e, t) {
            const n = this._getJsonData();
            n[e] = t, this._saveJsonData(n);
        }

        getData() {
            const e = this._getJsonData();
            return {
                dataList: e.dataList,
                filterKeywordList: e.filterKeywordList,
                filterActorList: e.filterActorList,
                reviewKeywordList: e.reviewKeywordList || []
            };
        }

        async changeData(e, t, n, a) {
            t.includes("http") || (t = window.location.origin + t);
            const i = this._getJsonData();
            let s = i.dataList, o = this.findData(e, s);
            if (o || (o = {
                carNum: e,
                url: t,
                actress: n,
                status: ""
            }, s.push(o)), o.createDate = r.getNowStr(), a === this.FILTER) {
                if (o.status === this.FILTER) throw new Error(e + " 已在屏蔽列表中");
                o.status = this.FILTER;
            } else if (a === this.FAVORITE) {
                if (o.favorite) throw new Error(e + " 已在收藏列表中");
                o.status = this.FAVORITE;
            } else {
                if (a !== this.HASDOWN) throw new Error("actionType错误");
                o.status = this.HASDOWN;
            }
            this._saveJsonData(i);
        }

        async saveFilterActor(e) {
            const t = this._getJsonData();
            if (t.filterActorList.includes(e)) throw new Error(e + " 已存在");
            t.filterActorList.push(e), this._saveJsonData(t);
        }

        async saveFilterKeyword(e) {
            const t = this._getJsonData();
            if (t.filterKeywordList.includes(e)) throw new Error(e + " 已存在");
            t.filterKeywordList.push(e), this._saveJsonData(t);
        }

        async removeData(e) {
            const t = this._getJsonData();
            let n = t.dataList;
            if (!this.findData(e, n)) throw new Error("未找到该番号信息:" + e);
            n = n.filter((t => t.carNum !== e)), t.dataList = n, this._saveJsonData(t);
        }
    }

    const o = new LocalStorageManager;

    class PluginManager {
        constructor() {
            this.plugins = new Map, this.isInitialized = false;
        }

        register(e) {
            if ("function" != typeof e) throw new Error("插件必须是一个类");
            const t = e.name;
            if (!t) throw new Error("类必须要有名称");
            const n = t.toLowerCase();
            if (this.plugins.has(n)) throw new Error(`插件"${t}"已注册`);
            const a = new e;
            a.pluginManager = this, this.plugins.set(n, a);
        }

        getBean(e) {
            return this.plugins.get(e.toLowerCase());
        }

        _initialize() {
            if (this.isInitialized) return;
            const e = new Map;
            for (const [t, n] of this.plugins) "function" == typeof n.injectBean && e.set(t, {
                instance: n,
                deps: this._getDependencies(n.injectBean)
            });
            for (const [t, {instance: n, deps: a}] of e) {
                const e = a.map((e => {
                    const n = e.toLowerCase();
                    if (!this.plugins.has(n)) throw new Error(`插件"${t}"依赖的插件"${e}"未注册`);
                    return this.plugins.get(n);
                }));
                n.injectBean(...e);
            }
            this.isInitialized = true;
        }

        _getDependencies(e) {
            const t = e.toString();
            return t.slice(t.indexOf("(") + 1, t.indexOf(")")).split(",").map((e => e.trim())).filter((e => e));
        }

        process() {
            this.isInitialized || this._initialize();
            for (const [t, n] of this.plugins) try {
                "function" == typeof n.handle && (r.insertStyle(n.initCss()), n.handle());
            } catch (e) {
                console.error("执行插件失败", e);
            }
        }
    }

    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((e => {
                    Object.defineProperty(this, e, {
                        get: () => l[e],
                        set(t) {
                            l[e] = t;
                        },
                        enumerable: true,
                        configurable: true
                    });
                }));
        }

        injectBean() {
        }

        initCss() {
        }

        handle() {
        }

        openPage(e, t, n, a) {
            n || (n = true), a && a.ctrlKey ? window.open(e) : layer.open({
                type: 2,
                title: t,
                content: e,
                shadeClose: n,
                area: ["80%", "90%"],
                isOutAnim: false,
                anim: -1
            });
        }

        closePage() {
            layer.closeAll();
            [".layui-layer-shade", ".layui-layer-move", ".layui-layer"].forEach((function (e) {
                parent.document.querySelectorAll(e).forEach((function (e) {
                    e.parentNode.removeChild(e);
                }));
            })), window.close();
        }

        getPageInfo() {
            return {
                carNum: $('a[title="複製番號"]').attr("data-clipboard-text"),
                url: window.location.href.split("#")[0],
                actress: $(".female").prev().map(((e, t) => $(t).text())).get().join(" "),
                actors: $(".male").prev().map(((e, t) => $(t).text())).get().join(" ")
            };
        }

        refresh() {
            localStorage.refresh = Date.now().toString(), this.isListPage && this.pluginManager.getBean("ListPagePlugin").doFilter();
        }

        async changeData(e, t, n, a) {
            if (!e) throw layer.error("番号为空!"), new Error("番号为空!");
            if (!t) throw layer.error("url为空!"), new Error("url为空!");
            await this.storageManager.changeData(e, t, n, a), this.refresh();
        }

        getSetting(e, t) {
            const n = localStorage.getItem("setting");
            if (null === n) return t;
            try {
                const a = JSON.parse(n);
                if (!a || "object" != typeof a) return t;
                if (!(e in a)) return t;
                const i = a[e];
                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), t;
            }
        }
    }

    class ListPagePlugin extends BasePlugin {
        injectBean(autoPagePlugin, fc2Plugin) {
            this.autoPagePlugin = autoPagePlugin, this.fc2Plugin = fc2Plugin;
        }

        handle() {
            this.doFilter(), this.replaceBigImg(), window.addEventListener("storage", (e => {
                "refresh" === e.key && this.refresh();
            }));
        }

        doFilter() {
            if (!this.isListPage) return;
            const e = this.storageManager.getData();
            this.dataList = e.dataList, this.filterKeywordList = e.filterKeywordList, this.filterActorList = e.filterActorList,
                this.reviewKeywordList = e.reviewKeywordList, this.filterMovieList(), this.autoPagePlugin.handlePaging();
        }

        filterMovieList() {
            const e = this.dataList.filter((e => e.status === this.FILTER)).map((e => e.carNum)), t = this.dataList.filter((e => e.status === this.FAVORITE)).map((e => e.carNum)), n = this.dataList.filter((e => e.status === this.HASDOWN)).map((e => e.carNum));
            let a = $(".movie-list .item").toArray(), i = this.getSetting("hideFilterItem", "yes");
            (window.location.href.includes("search?q") || window.location.href.includes("handlePlayback=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((e => e !== d))),
                    this.filterKeywordList.some((e => l.includes(e) || c.includes(e))) && !this.hasHandleList.includes(h)) return s.hide(),
                        void this.hasHandleList.push(h);
                    if (e.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 = "";
                    e.includes(c) ? (u = "已屏蔽", m = "#d95427") : t.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", (e => {
                        if (e.preventDefault(), c.includes("FC2-")) {
                            let e = o.split("/").filter(Boolean).pop();
                            this.fc2Plugin.openFc2Page(e, c, o);
                        } else this.openPage(o, c, false, e);
                    })), this.utils.rightClick(s.find("img"), (e => {
                        e.preventDefault(), this.utils.q(e, `是否屏蔽番号${c}?`, (() => {
                            this.changeData(c, o, "", "filter").then((e => layer.success("操作成功")));
                        }));
                    })), this.hasHandleList.push(g));
                })), $("#waitDownBtn span").text(`打开待下载(${t.length})`);
        }

        replaceBigImg() {
            document.querySelectorAll(".cover img").forEach((e => {
                e.src = e.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", (e => {
                    let t = $("#search-keyword").val(), n = $("#search-type option:selected").val();
                    "" !== t && window.open("/search?q=" + t + "&f=" + n);
                })), $("#search-keyword").on("paste", (e => {
                setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })).on("keypress", (e => {
                "Enter" === e.key && setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })));
        }
    }

    class AutoPagePlugin extends BasePlugin {
        constructor() {
            super(), this.paging = false;
        }

        handle() {
            if (!this.isListPage) return;
            let e = "yes" === localStorage.getItem("autoPage") ? "关闭自动翻页" : "开启自动翻页";
            $(".pagination").prepend(`<a class='pagination-previous' id='auto-page'>${e}</a>`),
                $("#auto-page").on("click", (e => {
                    e.preventDefault(), "yes" === localStorage.getItem("autoPage") ? (localStorage.setItem("autoPage", "no"),
                        $("#auto-page").html("开启自动翻页")) : (localStorage.setItem("autoPage", "yes"), $("#auto-page").html("关闭自动翻页"),
                        this.handlePaging());
                }));
        }

        handlePaging() {
            if (!this.isListPage) return;
            if (this.paging) return;
            let e = true;
            if ($(".movie-list .item:visible").each(((t, n) => {
                0 === $(n).find("span:contains('已收藏')").length && 0 === $(n).find("span:contains('已屏蔽')").length && 0 === $(n).find("span:contains('已下载')").length && (e = false);
            })), !e) return;
            if ("yes" !== localStorage.getItem("autoPage")) return;
            let t = $(".pagination-next");
            0 !== t.length && (this.paging = true, layer.info("下一页....", {
                duration: 500,
                callback: () => {
                    t[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 e = this.storageManager.getData();
            this.dataList = e.dataList, this.filterKeywordList = e.filterKeywordList, this.filterActorList = e.filterActorList,
                this.reviewKeywordList = e.reviewKeywordList, this.checkFilterActor();
        }

        checkFilterActor() {
            if (!this.isDetailPage) return;
            let e = this.getPageInfo().actors;
            this.filterActorList.forEach((t => {
                e.indexOf(t) > -1 && (this.answerCount++, this.utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
                    this.detailPageMenuPlugin.filterOne(null, true);
                })));
            }));
        }
    }

    function c() {
        const e = Math.floor(Date.now() / 1e3);
        if (e - (localStorage.review_ts || 0) <= 20) return localStorage.review_sign;
        const t = `${e}.lpw6vgqzsp.${a(`${e}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
        return localStorage.review_ts = e, localStorage.review_sign = t, t;
    }

    const d = "https://jdforrepam.com/api", h = (e, t = 1, n = 20) => new Promise(((a, i) => {
        $.ajax({
            method: "GET",
            url: `${d}/v1/movies/${e}/reviews`,
            data: {
                page: t,
                sort_by: "hotly",
                limit: n
            },
            headers: {
                jdSignature: c()
            },
            success: e => {
                const t = e.data.reviews;
                a(t);
            },
            error: e => {
                layer.error("获取评论失败: " + e.responseText), i(e);
            }
        });
    }));

    class ReviewPlugin extends BasePlugin {
        constructor() {
            super(...arguments), t(this, "floorIndex", 1);
        }

        async handle() {
            if (!this.isDetailPage) return;
            const e = window.location.href.split("?")[0].split("/"), t = e[e.length - 1].split("#")[0];
            this.showReview(t).then();
        }

        async showReview(e, t) {
            let n = $("#magnets-content");
            t && (n = t), 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(e, 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 t = 1;
                $("#loadMoreReviews").click((async () => {
                    $("#loadMoreReviews").text("加载中...").prop("disabled", true), t++;
                    const n = await h(e, t, 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(e, t) {
            e.length && (e.forEach((e => {
                let n = false;
                for (let t = 0; t < this.reviewKeywordList.length; t++) if (e.content.indexOf(this.reviewKeywordList[t]) > -1) {
                    n = true;
                    break;
                }
                if (n) return;
                let a = "";
                for (let t = 0; t < e.score; t++) a += '<i class="icon-star"></i>';
                let i = e.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (e => `<a href="${e}" class="btn-primary" style="padding:0" target="_blank" rel="noopener noreferrer">${e}</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                    ${e.username} &nbsp;&nbsp; <span class="score-stars">${a}</span> \n                    <span class="time">${e.created_at.replace("T", " ").replace(".000Z", "")}</span> \n                    &nbsp;&nbsp; 点赞:${e.likes_count}\n                    <p class="review-content" style="margin-top: 5px;"> ${i} </p>\n                </div>\n            `;
                t.append(s);
            })), this.utils.rightClick($(".review-content"), (e => {
                const t = window.getSelection().toString();
                t && (e.preventDefault(), this.utils.q(e, `是否将 '${t}' 加入评论区关键词?`, (() => {
                    let e = this.storageManager.getData().reviewKeywordList;
                    e || (e = []), e.push(t), this.storageManager.saveKeyData("reviewKeywordList", e),
                        layer.success("操作成功, 刷新页面后生效");
                })));
            })));
        }
    }

    const p = class _HotkeyManager {
        constructor() {
            if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
        }

        static registerHotkey(e, t, n = null) {
            if (Array.isArray(e)) {
                let a = [];
                return e.forEach((e => {
                    if (!this.isHotkeyFormat(e)) throw new Error("快捷键格式错误");
                    let i = this.recordHotkey(e, t, n);
                    a.push(i);
                })), a;
            }
            if (!this.isHotkeyFormat(e)) throw new Error("快捷键格式错误");
            return this.recordHotkey(e, t, n);
        }

        static recordHotkey(e, t, n) {
            let a = Math.random().toString(36).substr(2);
            return this.registerHotKeyMap.set(a, {
                hotkeyString: e,
                callback: t,
                keyupCallback: n
            }), a;
        }

        static unregisterHotkey(e) {
            this.registerHotKeyMap.has(e) && this.registerHotKeyMap.delete(e);
        }

        static isHotkeyFormat(e) {
            return e.toLowerCase().split("+").map((e => e.trim())).every((e => ["ctrl", "shift", "alt"].includes(e) || 1 === e.length));
        }

        static judgeHotkey(e, t) {
            const n = e.toLowerCase().split("+").map((e => e.trim())), a = n.includes("ctrl"), i = n.includes("shift"), s = n.includes("alt"), r = n.find((e => "ctrl" !== e && "shift" !== e && "alt" !== e));
            return (this.isMac ? t.metaKey : t.ctrlKey) === a && t.shiftKey === i && t.altKey === s && t.key.toLowerCase() === r;
        }
    };

    t(p, "isMac", 0 === navigator.platform.indexOf("Mac")), t(p, "registerHotKeyMap", new Map),
        t(p, "handleKeydown", (e => {
            for (const [t, n] of p.registerHotKeyMap) {
                let t = n.hotkeyString, a = n.callback;
                p.judgeHotkey(t, e) && a(e);
            }
        })), t(p, "handleKeyup", (e => {
        for (const [t, n] of p.registerHotKeyMap) {
            let t = n.hotkeyString, a = n.keyupCallback;
            a && (p.judgeHotkey(t, e) && a(e));
        }
    }));

    let g = p;

    function u(e) {
        const t = {
            tableClass: "data-table",
            showBorder: false,
            buttons: [],
            ...e
        };
        if (!(t.containerId && t.columns && Array.isArray(t.columns) && Array.isArray(t.data))) return void console.error("缺少必要参数或参数类型不正确");
        const n = document.getElementById(t.containerId);
        if (!n) return void console.error(`未找到ID为${t.containerId}的容器`);
        n.innerHTML = "";
        const a = document.createElement("table");
        a.className = t.showBorder ? t.tableClass + " show-border" : t.tableClass;
        const i = document.createElement("thead"), s = document.createElement("tr");
        if (t.columns.forEach((e => {
            const t = document.createElement("th");
            t.textContent = e.title || e.key, e.width && (t.style.width = e.width), e.headerClass && (t.className = e.headerClass),
                s.appendChild(t);
        })), t.buttons && t.buttons.length > 0) {
            const e = document.createElement("th");
            e.textContent = "操作", t.buttonColumnWidth && (e.style.width = t.buttonColumnWidth),
                s.appendChild(e);
        }
        i.appendChild(s), a.appendChild(i);
        const r = document.createElement("tbody");
        if (0 === t.data.length) {
            const e = document.createElement("tr"), n = document.createElement("td");
            n.colSpan = t.columns.length + (t.buttons.length > 0 ? 1 : 0), n.textContent = "暂无数据",
                n.style.textAlign = "center", e.appendChild(n), r.appendChild(e);
        } else t.data.forEach(((e, n) => {
            const a = document.createElement("tr");
            if (t.columns.forEach((t => {
                const i = document.createElement("td");
                t.render ? i.innerHTML = t.render(e, n) : i.textContent = e[t.key] || "", t.cellClass && (i.className = t.cellClass),
                    a.appendChild(i);
            })), t.buttons && t.buttons.length > 0) {
                const i = document.createElement("td");
                t.buttons.forEach((t => {
                    const a = document.createElement("a");
                    a.textContent = t.text, a.className = t.class || "btn-primary", a.addEventListener("click", (() => {
                        t.onClick && t.onClick(e, n);
                    })), i.appendChild(a);
                })), a.appendChild(i);
            }
            r.appendChild(a);
        }));
        return a.appendChild(r), n.appendChild(a), {
            getTableElement: () => a,
            updateData: e => {
                t.data = e, u(t);
            }
        };
    }

    document.addEventListener("keydown", (e => {
        g.handleKeydown(e);
    })), document.addEventListener("keyup", (e => {
        g.handleKeyup(e);
    }));

    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 e = this.getPageInfo(), t = e.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: e => 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: e => this.filterOne(e)
            }, {
                id: "hasDownBtn",
                sort: 3,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>`;
                },
                action: t => this.changeData(e.carNum, e.url, e.actress, "hasDown").then((e => 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: e => {
                    let t = $("#magnets-span");
                    if ("关闭磁力过滤" === t.text()) {
                        $("#magnets-content .item").toArray().forEach((e => $(e).show())), t.text("开启磁力过滤");
                    } else this.highlightMagnetPlugin.handle(), t.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: e => this.openPage(`https://jable.tv/videos/${t}/?handle=1`, t, false, e)
            }, {
                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: e => window.open(`https://missav.ws/search/${t}`, "_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: e => this.openPage(`https://javtrailers.com/video/${t.toLowerCase().replace("-", "00")}?handle=1`, t, false, e)
            }, {
                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: e => this.openPage(`https://subtitlecat.com/index.php?search=${t}`, t, false, e)
            }, {
                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: e => this.searchXunLeiSubtitle(t)
            }];
            n.sort(((e, t) => e.sort - t.sort));
            const a = `\n            <div style="transform: translateY(-50%);">\n                ${n.map((e => e.html())).join("\n")}\n            </div>\n        `;
            $(".tabs").after(a), n.forEach((({id: e, action: t}) => {
                $(`#${e}`).on("click", t);
            }));
        }

        favoriteOne() {
            let e = this.getPageInfo();
            this.changeData(e.carNum, e.url, e.actress, "favorite").then((e => this.closePage()));
        }

        searchXunLeiSubtitle(e) {
            let t = loading();
            this.utils.gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${e}`).then((t => {
                let n = t.data;
                layer.open({
                    type: 1,
                    title: "迅雷字幕",
                    content: '<div id="table-container"></div>',
                    area: ["50%", "70%"],
                    success: t => {
                        u({
                            containerId: "table-container",
                            columns: [{
                                key: "name",
                                title: "文件名"
                            }, {
                                key: "ext",
                                title: "类型"
                            }, {
                                key: "extra_name",
                                title: "来源"
                            }],
                            data: n,
                            buttons: [{
                                text: "下载",
                                class: "btn-primary",
                                onClick: t => {
                                    this.utils.gmHttp.get(t.url).then((n => {
                                        this.utils.download(n, e + "." + t.ext);
                                    }));
                                }
                            }]
                        });
                    }
                });
            })).finally((() => {
                t.close();
            }));
        }

        filterOne(e, t) {
            e && e.preventDefault();
            let n = this.getPageInfo();
            t ? this.changeData(n.carNum, n.url, n.actress, "filter").then((e => this.closePage())) : this.utils.q(e, `是否屏蔽${n.carNum}?`, (() => {
                this.changeData(n.carNum, n.url, n.actress, "filter").then((e => this.closePage()));
            }));
        }

        speedVideo() {
            const e = $('iframe[id^="layui-layer-iframe"]');
            0 === e.length ? $("#preview-video-btn").click() : e[0].contentWindow.postMessage("speedVideo", "*");
        }

        bindHotkey() {
            const e = {
                a: () => {
                    this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
                },
                s: () => this.favoriteOne(null),
                z: () => this.speedVideo()
            }, t = (e, t) => {
                g.registerHotkey(e, (() => {
                    this.isDetailPage ? t() : (e => {
                        const t = $(".layui-layer-content iframe");
                        0 !== t.length && t[0].contentWindow.postMessage(e, "*");
                    })(e);
                }));
            };
            this.isDetailPage && window.addEventListener("message", (t => {
                e[t.data] && e[t.data]();
            })), Object.entries(e).forEach((([e, n]) => {
                t(e, 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", (e => {
                    this.openWaitCheck(e);
                })), $("#waitDownBtn").on("click", (e => {
                this.openFavorite(e);
            }));
        }

        openWaitCheck() {
            let e = 0, t = this.getSetting("waitCheckCount", 5);
            $(".movie-list .item:visible").each(((n, a) => {
                if (e >= t) return false;
                if (0 === $(a).find("span:contains('已收藏')").length) {
                    let t = $(a).find("a").attr("href");
                    t && (t = t.includes("?") ? t + "&autoPlay=1" : t + "?autoPlay=1", window.open(t),
                        e++);
                }
            }));
        }

        openFavorite() {
            let e = this.getSetting("waitCheckCount", 10);
            for (let t = 0; t < e; t++) {
                if (t >= this.favoriteList.length) return;
                window.open(this.favoriteList[t].url);
            }
        }
    }

    class HighlightMagnetPlugin extends BasePlugin {
        handle() {
            let e = $("#magnets-content .name").toArray(), t = false;
            e.forEach((e => {
                let n = $(e), a = n.text().toLowerCase();
                a.indexOf("4k") > -1 && n.css("color", "#f40"), (a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1) && (t = true);
            })), t && e.forEach((e => {
                let t = $(e), n = t.text().toLowerCase();
                n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.indexOf("4k") > -1 || t.parent().parent().parent().hide();
            }));
        }
    }

    class PreviewVideoPlugin extends BasePlugin {
        handle() {
            $(".preview-video-container").on("click", (e => {
                e.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"), (e => {
                const t = window.getSelection().toString();
                if (t) {
                    e.preventDefault();
                    let n = {
                        clientX: e.clientX,
                        clientY: e.clientY + 50
                    };
                    this.utils.q(n, `是否屏蔽关键词${t}?`, (() => {
                        this.saveFilterKeyword(t).then((e => {
                            this.closePage();
                        }));
                    }));
                }
            })), $(".male").prev().toArray().forEach((e => {
                this.utils.rightClick($(e), (t => {
                    t.preventDefault();
                    let n = $(e).text().trim();
                    this.utils.q(t, `是否屏蔽演员${n}?`, (() => {
                        this.saveFilterActor(n).then((e => {
                            this.detailPageMenuPlugin.filterOne(null, true);
                        }));
                    }));
                }));
            })));
        }

        async saveFilterKeyword(e) {
            await this.storageManager.saveFilterKeyword(e), this.refresh();
        }

        async saveFilterActor(e) {
            await this.storageManager.saveFilterActor(e), this.refresh();
        }
    }

    class JavTrailersPlugin extends BasePlugin {
        constructor() {
            super(), this.hasBand = false;
        }

        handle() {
            let e = window.location.href;
            if (!e.includes("handle=1")) return;
            if ($("h1:contains('Page not found')").length) {
                let t = e.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
                return void (window.location.href = "https://javtrailers.com/search/" + t + "?handle=1");
            }
            let t = $(".videos-list .video-link").toArray();
            if (t.length) {
                const n = e.split("?")[0].split("search/")[1].toLowerCase(), a = t.find((e => $(e).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", (e => {
                let t = document.getElementById("vjs_video_3_html5_api");
                t && (t.currentTime += 5);
            })), g.registerHotkey("z", (() => {
                const e = document.getElementById("vjs_video_3_html5_api");
                e && (e.currentTime += 5);
            })), g.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), g.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
        }

        handlePlayJavTrailers() {
            this.hasBand || this.utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
                setTimeout((() => {
                    this.hasBand = true;
                    let e = document.getElementById("vjs_video_3_html5_api");
                    e.play(), e.currentTime = 5, e.addEventListener("timeupdate", (function () {
                        e.currentTime >= 14 && e.currentTime < 16 && (e.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 e = window.location.href.split("=")[1].toLowerCase();
            $(".sub-table tr td a").toArray().forEach((t => {
                let n = $(t);
                n.text().toLowerCase().includes(e) || n.parent().parent().hide();
            }));
        }
    }

    class JablePlugin extends BasePlugin {
        handle() {
            $("#player")[0].play(), $('button[data-plyr="fullscreen"]').click(), g.registerHotkey("a", (() => window.parent.postMessage("a", "*"))),
                g.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
        }
    }

    class Fc2Plugin extends BasePlugin {
        constructor() {
            super(...arguments), t(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(e, t, 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(e, t), $("#favoriteBtn").on("click", (e => {
                        this.changeData(t, n, this.actress, "favorite").then((e => layer.closeAll()));
                    })), $("#filterBtn").on("click", (e => {
                        this.utils.q(e, `是否屏蔽${t}?`, (() => {
                            this.changeData(t, n, this.actress, "filter").then((e => layer.closeAll()));
                        }));
                    })), $("#hasDownBtn").on("click", (e => {
                        this.changeData(t, n, this.actress, "hasDown").then((e => layer.closeAll()));
                    }));
                }
            });
        }

        loadData(e, t) {
            this.handleVideo(t.replace("FC2-", "")), this.handleMovieDetail(e), this.handleMagnets(e),
                this.reviewPlugin.showReview(e, $("#reviews-content")).then();
        }

        handleMovieDetail(e) {
            (e => new Promise(((t, n) => {
                $.ajax({
                    method: "GET",
                    url: `${d}/v4/movies/${e}`,
                    headers: {
                        jdSignature: c()
                    },
                    success: e => {
                        e.data || (layer.error("获取视频详情失败: " + e.message), n(e.message));
                        const a = e.data.movie, i = a.id, s = a.actors, r = a.origin_title, o = a.number, l = a.score, c = a.release_date, d = a.preview_images, h = [];
                        d.forEach((e => {
                            h.push(e.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
                        })), t({
                            movieId: i,
                            actors: s,
                            title: r,
                            carNum: o,
                            score: l,
                            releaseDate: c,
                            imgList: h
                        });
                    },
                    error: e => {
                        layer.error("获取视频详情失败" + e.responseJSON.message), n(e);
                    }
                });
            })))(e).then((e => {
                const t = e.actors || [], n = e.imgList || [];
                let a = "";
                if (t.length > 0) for (let s = 0; s < t.length; s++) {
                    let e = t[s];
                    a += `<span class="actor-tag"><a href="/actors/${e.id}" target="_blank">${e.name}</a></span>`,
                    0 === e.gender && (this.actress += e.name);
                } else a = '<span class="no-data">暂无演员信息</span>';
                let i = "";
                i = Array.isArray(n) && n.length > 0 ? n.map(((e, t) => `\n                <a href="${e}" data-fancybox="movie-gallery" data-caption="剧照 ${t + 1}">\n                    <img src="${e}" class="movie-image-thumb"  alt=""/>\n                </a>\n            `)).join("") : '<div class="no-data">暂无剧照</div>',
                    $(".movie-info-container").html(`\n                <h3 class="movie-title">${e.title || "无标题"}</h3>\n                <div class="movie-meta">\n                    <span>番号: ${e.carNum || "未知"}</span>\n                    <span>年份: ${e.releaseDate || "未知"}</span>\n                    <span>评分: ${e.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((e => {
                console.error(e), $(".movie-info-container").html(`\n                <div class="movie-error">加载失败: ${e.message}</div>\n            `);
            }));
        }

        handleMagnets(e) {
            (e => new Promise(((t, n) => {
                $.ajax({
                    method: "GET",
                    url: `${d}/v1/movies/${e}/magnets`,
                    headers: {
                        jdSignature: c()
                    },
                    success: e => {
                        let n = e.data.magnets;
                        t({
                            magnetList: n
                        });
                    },
                    error: e => {
                        layer.error("获取磁力失败: " + e), n(e);
                    }
                });
            })))(e).then((e => {
                let t = e.magnetList, n = "";
                if (t.length > 0) for (let a = 0; a < t.length; a++) {
                    let e = t[a];
                    console.log(e);
                    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:${e.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n                                    <span class="name">${e.name}</span>\n                                    <br>\n                                    <span class="meta">\n                                        ${(e.size / 1024).toFixed(2)}GB, ${e.files_count}個文件 \n                                     </span>\n                                    <br>\n                                    <div class="tags">\n                                        ${e.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n                                        ${e.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:${e.hash}" type="button">&nbsp;複製&nbsp;</button>\n                            </div>\n                            <div class="date column"><span class="time">${e.created_at}</span></div>\n                        </div>\n                    `;
                } else n = '<span class="no-data">暂无磁力信息</span>';
                $("#magnets-content").html(n);
            })).catch((e => {
                console.error(e), $("#magnets-content").html(`\n                <div class="movie-error">加载失败: ${e.message}</div>\n            `);
            }));
        }

        handleVideo(e) {
            (async e => {
                let t = `https://hohoj.tv/search?text=${e}`, n = await r.gmHttp.get(t), a = null;
                if (n.includes("找不到任何影片")) return a;
                const i = (new DOMParser).parseFromString(n, "text/html");
                return $(i).find(".video-item a").toArray().forEach((t => {
                    if ($(t).find(".video-item-title").text().includes(e)) {
                        let e = $(t).attr("href").split("id=")[1];
                        a = "https://hohoj.tv/embed?id=" + e;
                    }
                })), a;
            })(e).then((t => {
                const n = document.querySelector(".movie-poster-container"), a = document.querySelector(".movie-trailer");
                t ? $(a).attr("src", t) : (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-${e}" target="_blank">missav</a></p>\n                    </div>\n                `,
                    a.style.display = "none");
            }));
        }
    }

    class FoldCategoryPlugin extends BasePlugin {
        handle() {
            if (!this.isListPage) return;
            let e = $(".tabs ul"), t = $("h2.section-title"), n = "y" === localStorage.foldCategory;
            const [a, i] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
            let s;
            if (e.length > 0) {
                s = $("#tags");
                let t = $("#tags dl div.tag.is-info").map((function () {
                    return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
                })).get().join(" ");
                if (!t) return;
                e.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>已选分类: ${t}</span></div>`);
            }
            t.length > 0 && (t.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", (e => {
                e.preventDefault(), n = !n, localStorage.foldCategory = n ? "y" : "n";
                const [t, a] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
                $("#foldCategoryBtn").find("span").text(t).end().find("i").attr("class", a), s[n ? "hide" : "show"]();
            })));
        }
    }

    class AliyunApi {
        constructor(e) {
            this.baseApiUrl = "https://api.aliyundrive.com", this.refresh_token = e, 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 e = this.baseApiUrl + "/v2/account/token", t = {
                refresh_token: this.refresh_token,
                grant_type: "refresh_token"
            };
            try {
                return "Bearer " + (await r.http.post(e, t)).access_token;
            } catch (n) {
                throw n.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : n;
            }
        }

        async getUserInfo() {
            const e = await this.getHeaders();
            let t = this.baseApiUrl + "/v2/user/get";
            return await r.http.post(t, {}, e);
        }

        async deleteFile(e, t = null) {
            if (!e) throw new Error("未传入file_id");
            t || (t = await this.getDefaultDriveId());
            let n = {
                file_id: e,
                drive_id: t
            }, a = this.baseApiUrl + "/v2/recyclebin/trash";
            const i = await this.getHeaders();
            return await r.gmHttp.post(a, n, i), {};
        }

        async createFolder(e, t = null, n = "root") {
            t || (t = await this.getDefaultDriveId());
            let a = this.baseApiUrl + "/adrive/v2/file/createWithFolders", i = {
                name: e,
                type: "folder",
                parent_file_id: n,
                check_name_mode: "auto_rename",
                content_hash_name: "sha1",
                drive_id: t
            };
            const s = await this.getHeaders(), o = await r.gmHttp.post(a, i, s);
            return JSON.parse(o);
        }

        async getFileList(e = "root", t = null) {
            t || (t = await this.getDefaultDriveId());
            let n = this.baseApiUrl + "/adrive/v3/file/list";
            const a = {
                drive_id: t,
                parent_file_id: e,
                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(e, t, 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: e,
                name: t,
                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(e, t) {
            return new Promise(((n, a) => {
                $.ajax({
                    type: "PUT",
                    url: e,
                    data: t,
                    contentType: " ",
                    processData: false,
                    success: (e, t, i) => {
                        200 === i.status ? (console.log("上传成功:", e), n({})) : a(i);
                    },
                    error: e => {
                        console.error("上传失败", e.responseText), a(e);
                    }
                });
            }));
        }

        async getDownloadUrl(e, t = null) {
            t || (t = await this.getDefaultDriveId());
            let n = this.baseApiUrl + "/v2/file/get_download_url";
            const a = await this.getHeaders();
            let i = {
                file_id: e,
                drive_id: t
            };
            return (await r.gmHttp.post(n, i, a)).url;
        }

        async _createBackupFolder(e) {
            const t = await this.getFileList();
            let n = null;
            for (let a = 0; a < t.length; a++) {
                let i = t[a];
                if (i.name === e) {
                    n = i;
                    break;
                }
            }
            n || (console.log("不存在目录, 进行创建"), n = await this.createFolder(e)), this.backupFolderId = n.file_id;
        }

        async backup(e, t, n) {
            this.backupFolderId || await this._createBackupFolder(e), await this.uploadFile(this.backupFolderId, t, n);
        }

        async getBackupList(e) {
            let t = null;
            this.backupFolderId || await this._createBackupFolder(e), t = await this.getFileList(this.backupFolderId);
            const n = [];
            return t.forEach((e => {
                n.push({
                    name: e.name,
                    fileId: e.file_id,
                    createTime: e.created_at,
                    size: e.size
                });
            })), n;
        }
    }

    class WebDavApi {
        constructor(e, t, n) {
            this.davUrl = e.endsWith("/") ? e : e + "/", this.username = t, this.password = n,
                this.folderName = null;
        }

        _getAuthHeaders() {
            return {
                Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`,
                Depth: "1"
            };
        }

        _sendRequest(e, t, n = {}, a) {
            return new Promise(((i, r) => {
                const o = this.davUrl + t, l = {
                    ...this._getAuthHeaders(),
                    ...n
                };
                s({
                    method: e,
                    url: o,
                    headers: l,
                    data: a,
                    onload: e => {
                        e.status >= 200 && e.status < 300 ? i(e) : r(new Error(`Request failed with status ${e.status}: ${e.statusText}`));
                    },
                    onerror: e => {
                        console.error("请求WebDav发生错误:", e), r(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确"));
                    }
                });
            }));
        }

        async backup(e, t, n) {
            await this._sendRequest("MKCOL", e);
            const a = e + "/" + t;
            await this._sendRequest("PUT", a, {
                "Content-Type": "text/plain"
            }, n);
        }

        async getFileList(e) {
            var t, n;
            const a = (await this._sendRequest("PROPFIND", e, {
                "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 e = i[r].getElementsByTagNameNS("DAV:", "displayname")[0].textContent, a = (null == (t = i[r].getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : t.textContent) || "0", o = (null == (n = i[r].getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : n.textContent) || "";
                s.push({
                    fileId: e,
                    name: e,
                    size: a,
                    createTime: o
                });
            }
            return s.reverse(), s;
        }

        async deleteFile(e) {
            let t = this.folderName + "/" + encodeURI(e);
            await this._sendRequest("DELETE", t, {
                "Cache-Control": "no-cache"
            });
        }

        async getBackupList(e) {
            return this.folderName = e, await this._sendRequest("MKCOL", e), this.getFileList(e);
        }

        async getFileContent(e) {
            let t = this.folderName + "/" + e;
            return (await this._sendRequest("GET", t, {
                Accept: "application/octet-stream"
            })).responseText;
        }
    }

    class SettingPlugin extends BasePlugin {
        constructor() {
            super(...arguments), t(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: (e, t) => {
                        $(e).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 e = this.storageManager.getData().reviewKeywordList;
            e && e.forEach((e => {
                this.addLabelTag("#reviewKeywordContainer", e);
            }));
            let t = this.storageManager.getData().filterKeywordList;
            t && t.forEach((e => {
                this.addLabelTag("#filterKeywordContainer", e);
            }));
            let n = this.storageManager.getData().filterActorList;
            n && n.forEach((e => {
                this.addLabelTag("#filterActorContainer", e);
            })), ["#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer"].forEach((e => {
                $(`${e} .add-tag-btn`).on("click", (t => this.addKeyword(t, e))), $(`${e} .keyword-input`).on("keypress", (t => {
                    "Enter" === t.key && this.addKeyword(t, e);
                }));
            }));
        }

        bindClick() {
            $("#importBtn").on("click", (e => this.importData(e))), $("#exportBtn").on("click", (e => this.exportData(e))),
                $("#backupBtn").on("click", (e => this.backupData(e))), $("#backupListBtn").on("click", (e => this.backupListBtn(e))),
                $("#webdavBackupBtn").on("click", (e => this.backupDataByWebDav(e))), $("#webdavBackupListBtn").on("click", (e => this.backupListBtnByWebDav(e))),
                $("#getRefreshTokenBtn").on("click", (e => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", {
                    yes: function (e, t, n) {
                        window.open("https://www.aliyundrive.com/drive/home"), layer.close(e);
                    }
                }))), $("#containerWidth").on("input", (e => {
                const t = parseInt($(e.target).val()) + 70 + "%";
                document.querySelector("section .container").style.minWidth = t;
            })), $("#saveBtn").on("click", (() => this.saveForm()));
        }

        saveForm() {
            const e = $("#hideFilterItem").val(), t = $("#reviewCount").val(), n = $("#waitCheckCount").val(), a = parseInt($("#containerWidth").val()), i = $("#refresh_token").val();
            let s = JSON.parse(localStorage.getItem("setting")) || {};
            s.hideFilterItem = e, s.reviewCount = t, 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((e => {
                let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                r.push(t);
            })), this.storageManager.saveKeyData("reviewKeywordList", r);
            let o = [];
            $("#filterKeywordContainer .keyword-label").toArray().forEach((e => {
                let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                o.push(t);
            })), this.storageManager.saveKeyData("filterKeywordList", o);
            let l = [];
            $("#filterActorContainer .keyword-label").toArray().forEach((e => {
                let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                l.push(t);
            })), this.storageManager.saveKeyData("filterActorList", l), layer.success("保存成功"),
                this.refresh();
        }

        addLabelTag(e, t) {
            const n = $(`${e} .tag-box`), a = $(`\n            <div class="keyword-label" style="background-color: #c5b9a0">\n                ${t}\n                <span class="keyword-remove">×</span>\n            </div>\n        `);
            a.find(".keyword-remove").click((e => {
                $(e.target).parent().remove();
            })), n.append(a);
        }

        addKeyword(e, t) {
            let n = $(`${t} .keyword-input`);
            const a = n.val().trim();
            a && (this.addLabelTag(t, a), n.val(""));
        }

        importData() {
            try {
                const e = document.createElement("input");
                e.type = "file", e.accept = ".json", e.onchange = e => {
                    const t = e.target.files[0];
                    if (!t) return;
                    const n = new FileReader;
                    n.onload = e => {
                        try {
                            const t = e.target.result, n = JSON.parse(t);
                            if (!(null == n ? void 0 : n.dataList)) return void layer.error("数据格式无效: 缺少 dataList 字段");
                            localStorage.getItem("appData") ? layer.confirm("当前已有数据,确定要覆盖吗?", {
                                icon: 3,
                                title: "确认覆盖",
                                btn: ["确定", "取消"]
                            }, (function (e) {
                                localStorage.setItem("appData", t), layer.success("数据导入成功"), layer.close(e), location.reload();
                            }), (function (e) {
                                layer.error("已取消导入"), layer.close(e);
                            })) : (localStorage.setItem("appData", t), layer.success("数据导入成功"));
                        } catch (t) {
                            layer.error("导入失败:文件内容不是有效的JSON格式 " + t);
                        }
                    }, n.onerror = () => {
                        layer.error("读取文件时出错");
                    }, n.readAsText(t);
                }, document.body.appendChild(e), e.click(), setTimeout((() => document.body.removeChild(e)), 1e3);
            } catch (e) {
                layer.error("导入数据时出错: " + e.message);
            }
        }

        async backupData(e) {
            const t = this.getSetting("refresh_token", "");
            if (!t) return void layer.error("请填写refresh_token并保存后, 再试此功能");
            let n = this.utils.getNowStr("_", "_") + ".json", a = localStorage.getItem("appData");
            a = f(a);
            let i = loading();
            try {
                const e = new AliyunApi(t);
                await e.backup(this.folderName, n, a), layer.success("备份完成");
            } catch (s) {
                console.error(s), layer.error(s.toString());
            } finally {
                i.close();
            }
        }

        async backupListBtn(e) {
            const t = this.getSetting("refresh_token", "");
            if (!t) return void layer.error("请填写refresh_token并保存后, 再试此功能");
            let n = loading();
            try {
                const e = new AliyunApi(t), n = await e.getBackupList(this.folderName);
                this.openFileListDialog(n, e, "阿里云盘");
            } catch (a) {
                console.error(a), layer.error(`发生错误: ${a ? a.message : a}`);
            } finally {
                n.close();
            }
        }

        async backupDataByWebDav(e) {
            const t = this.getSetting("webDavUrl", "");
            if (!t) 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 = f(s);
            let r = loading();
            try {
                const e = new WebDavApi(t, n, a);
                await e.backup(this.folderName, i, s), layer.success("备份完成");
            } catch (o) {
                console.error(o), layer.error(o.toString());
            } finally {
                r.close();
            }
        }

        async backupListBtnByWebDav(e) {
            const t = this.getSetting("webDavUrl", "");
            if (!t) 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 e = new WebDavApi(t, n, a), i = await e.getBackupList(this.folderName);
                this.openFileListDialog(i, e, "WebDav");
            } catch (s) {
                console.error(s), layer.error(`发生错误: ${s ? s.message : s}`);
            } finally {
                i.close();
            }
        }

        openFileListDialog(e, t, n) {
            layer.open({
                type: 1,
                title: n + "备份文件",
                content: '<div id="table-container"></div>',
                area: ["40%", "70%"],
                success: a => {
                    const i = u({
                        containerId: "table-container",
                        columns: [{
                            key: "name",
                            title: "文件名"
                        }, {
                            key: "createTime",
                            title: "备份日期",
                            render: e => `${this.utils.getNowStr("-", ":", e.createTime)}`
                        }, {
                            key: "size",
                            title: "文件大小",
                            render: e => {
                                const t = ["B", "KB", "MB", "GB", "TB", "PB"];
                                let n = 0, a = e.size;
                                for (; a >= 1024 && n < t.length - 1;) a /= 1024, n++;
                                return `${a % 1 == 0 ? a.toFixed(0) : a.toFixed(2)} ${t[n]}`;
                            }
                        }],
                        data: e,
                        buttons: [{
                            text: "删除",
                            class: "btn-danger",
                            onClick: async e => {
                                layer.confirm(`是否将 ${e.name} 放入回收站?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: ["确定", "取消"]
                                }, (async a => {
                                    layer.close(a);
                                    let s = loading();
                                    try {
                                        await t.deleteFile(e.fileId);
                                        let a = await t.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: e => {
                                let a = loading();
                                try {
                                    "阿里云盘" === n ? t.getDownloadUrl(e.fileId).then((t => {
                                        this.utils.gmHttp.get(t, null, {
                                            Referer: "https://www.aliyundrive.com/"
                                        }).then((t => {
                                            t = v(t), this.utils.download(t, e.name);
                                        }));
                                    })).catch((e => {
                                        console.error(e), layer.error("下载失败: " + e);
                                    })) : t.getFileContent(e.fileId).then((t => {
                                        this.utils.download(t, e.name);
                                    }));
                                } catch (i) {
                                    console.error(i), layer.error("下载失败: " + i);
                                } finally {
                                    a.close();
                                }
                            }
                        }, {
                            text: "导入",
                            class: "btn-success",
                            onClick: e => {
                                layer.confirm(`是否将该云备份数据 ${e.name} 导入?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: ["确定", "取消"]
                                }, (async a => {
                                    layer.close(a);
                                    let i = loading();
                                    try {
                                        let a;
                                        if ("阿里云盘" === n) {
                                            const n = await t.getDownloadUrl(e.fileId);
                                            a = await this.utils.gmHttp.get(n, null, {
                                                Referer: "https://www.aliyundrive.com/"
                                            });
                                        } else a = await t.getFileContent(e.fileId);
                                        a = v(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 e = s.message.includes("dataList") ? "数据格式错误: 缺少必要字段" : "请求失败,请重试";
                                        layer.error(e);
                                    } finally {
                                        i.close();
                                    }
                                }));
                            }
                        }]
                    });
                }
            });
        }

        exportData(e) {
            try {
                const e = localStorage.getItem("appData");
                if (!e) return void layer.error("没有找到可导出的数据");
                const t = `${this.utils.getNowStr("_", "_")}.json`;
                this.utils.download(e, t), layer.success("数据导出成功");
            } catch (t) {
                layer.error("导出数据时出错: " + t.message);
            }
        }
    }

    const m = "x7k9p3";

    function f(e) {
        return (m + e + m).split("").map((e => {
            const t = e.codePointAt(0);
            return String.fromCodePoint(t + 5);
        })).join("");
    }

    function v(e) {
        return e.split("").map((e => {
            const t = e.codePointAt(0);
            return String.fromCodePoint(t - 5);
        })).join("").slice(m.length, -m.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", (e => 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: e => {
                    const t = this.getDataList();
                    this.loadTableData(t), $(".layui-layer-content").on("click", ".history-btn", (async e => {
                        this.dataType = $(e.target).data("action"), this.reloadTable();
                    }));
                },
                end: () => this.refresh()
            });
        }

        handleClickDetail(e, t) {
            if (t.carNum.includes("FC2-")) {
                const e = t.url.split("/"), n = e[e.length - 1].split("#")[0];
                this.fc2Plugin.openFc2Page(n, t.carNum, t.url);
            } else this.openPage(t.url, t.carNum, false, e);
        }

        reloadTable() {
            const e = this.getDataList();
            this.tableObj.updateData(e);
        }

        handleDelete(e, t) {
            this.utils.q(e, `是否移除${t.carNum}?`, (() => {
                this.storageManager.removeData(t.carNum).then((e => {
                    this.showCarNumBox(t.carNum), this.reloadTable();
                }));
            }));
        }

        showCarNumBox(e) {
            const t = $(".movie-list .item").toArray().find((t => $(t).find(".video-title strong").text() === e));
            t && ($(t).show(), this.hasHandleList = this.hasHandleList.filter((t => t !== `${e}-hide`)));
        }

        getDataList() {
            let e = this.storageManager.getData().dataList;
            return e.reverse(), this.allCount = e.length, this.filterCount = 0, this.favoriteCount = 0,
                this.hasDownCount = 0, e.forEach((e => {
                switch (e.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 ? e : e.filter((e => e.status === this.dataType));
        }

        loadTableData(e) {
            this.tableObj = u({
                containerId: "table-container",
                columns: [{
                    key: "carNum",
                    title: "番号"
                }, {
                    key: "actress",
                    title: "演员",
                    width: "250px"
                }, {
                    key: "createDate",
                    title: "创建日期",
                    width: "250px"
                }, {
                    key: "status",
                    title: "状态",
                    render: e => {
                        let t, n = "";
                        switch (e.status) {
                            case "filter":
                                t = "#ec4949", n = "已屏蔽";
                                break;

                            case "favorite":
                                t = "#50adb9", n = "已收藏";
                                break;

                            case "hasDown":
                                t = "#8ebd6e", n = "已下载";
                        }
                        return `<span style="color:${t}">${n}</span>`;
                    }
                }],
                data: e,
                buttons: [{
                    text: "移除",
                    class: "btn-danger",
                    onClick: e => {
                        this.handleDelete(event, e);
                    }
                }, {
                    text: "详情页",
                    class: "btn-info",
                    onClick: e => {
                        this.handleClickDetail(event, e);
                    }
                }]
            });
        }
    }

    class ActressInfoPlugin extends BasePlugin {
        constructor() {
            super(...arguments), t(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        ";
        }

        setCache(e, t) {
            const n = new Date;
            n.setTime(n.getTime() + 36e5);
            const a = "expires=" + n.toUTCString();
            document.cookie = e + "=" + JSON.stringify(t) + ";" + a + ";path=/";
        }

        getCache(e) {
            const t = e + "=", n = document.cookie.split(";");
            for (let a = 0; a < n.length; a++) {
                let e = n[a];
                for (; " " === e.charAt(0);) e = e.substring(1);
                if (0 === e.indexOf(t)) return JSON.parse(e.substring(t.length));
            }
            return null;
        }

        async handleDetailPage() {
            let e = $(".female").prev().map(((e, t) => $(t).text().trim())).get();
            if (!e.length) return;
            let t = null, n = "";
            for (let i = 0; i < e.length; i++) {
                let s = e[i];
                if (t = this.getCache(s), !t) try {
                    t = await this.searchInfo(s), t && this.setCache(s, t);
                } catch (a) {
                    console.error("该名称查询失败,尝试其它名称");
                }
                let r = "";
                r = t ? `\n                    <div class="panel-block" style="margin-top: 20px;">\n                        <strong>${s}:</strong>\n                        <a href="${t.url}" style="margin-left: 5px" target="_blank">\n                            <span class="info-tag">${t.birthday} ${t.age}</span>\n                            <span class="info-tag">${t.height} ${t.weight}</span>\n                            <span class="info-tag">${t.threeSizeText} ${t.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 e = [], t = $(".actor-section-name");
            t.length && t.text().trim().split(",").forEach((t => {
                e.push(t.trim());
            }));
            let n = $(".section-meta:not(:contains('影片'))");
            if (n.length && n.text().trim().split(",").forEach((t => {
                e.push(t.trim());
            })), !e.length) return;
            let a = null;
            for (let r = 0; r < e.length; r++) {
                let t = e[r];
                if (a = this.getCache(t), a) {
                    console.log("来自缓存");
                    break;
                }
                try {
                    a = await this.searchInfo(t);
                } catch (s) {
                    console.error("该名称查询失败,尝试其它名称");
                }
                if (a) break;
            }
            a && e.forEach((e => {
                this.setCache(e, a);
            }));
            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            `),
                t.parent().append(i);
        }

        async searchInfo(e) {
            "三上悠亞" === e && (e = "三上悠亜");
            let t = this.apiUrl + e;
            const n = await this.utils.gmHttp.get(t), 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: t
            };
        }
    }

    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", (e => {
                    let t = localStorage.getItem("token");
                    if (!t) return void alert("请先登录!");
                    let n = JSON.parse(t).refresh_token;
                    navigator.clipboard.writeText(n).then((() => {
                        alert("已复制到剪切板 如失败, 请手动复制: " + n);
                    })).catch((e => {
                        console.error("Failed to copy refresh token: ", e);
                    }));
                }));
        }
    }

    class HitShowPlugin extends BasePlugin {
        constructor() {
            super();
        }

        handle() {
            n('a[href*="rankings/playback"]').on("click", (e => {
                e.preventDefault(), e.stopPropagation(), window.location.href = "/?handlePlayback=1&period=daily";
            })), this.handlePlayback().then();
        }

        async handlePlayback() {
            if (!window.location.href.includes("handlePlayback=1")) return;
            let e = new URLSearchParams(window.location.search).get("period");
            this.toolBar(e);
            let t = n(".movie-list");
            t.html("");
            const a = await ((e = "daily", t = "high_score") => new Promise(((n, a) => {
                $.ajax({
                    method: "GET",
                    url: `${d}/v1/rankings/playback?period=${e}&filter_by=${t}`,
                    headers: {
                        jdSignature: c()
                    },
                    success: e => {
                        n(e.data.movies);
                    },
                    error: e => {
                        layer.error("获取热播失败: " + e), a(e);
                    }
                });
            })))(e);
            let i = "";
            a.forEach((e => {
                i += `\n            <div class="item">\n                <a href="/v/${e.id}" class="box" title="${e.origin_title}">\n                    <div class="cover ">\n                        <img loading="lazy" src="${e.cover_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt="">\n                    </div>\n                    <div class="video-title"><strong>${e.number}</strong> ${e.origin_title}</div>\n                    <div class="score" id="score_${e.number}">\n                    </div>\n                    <div class="meta">\n                        ${e.release_date}\n                    </div>\n                    <div class="tags has-addons">\n                       ${e.magnets_count ? '<span class="tag is-warning">含中字磁鏈</span>' : e.can_play ? '<span class="tag is-success">含磁鏈</span>' : ""}\n                       ${e.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""}\n                    </div>\n                </a>\n            </div>\n            \n            `;
            })), t.html(i), this.refresh(), this.loadScoreFormCache();
        }

        toolBar(e) {
            n(".pagination").remove(), n(".main-tabs ul li").removeClass("is-active"), n(".main-tabs ul li:first").addClass("is-active");
            let t = `\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" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=daily">日榜</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=weekly">周榜</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=monthly">月榜</a>\n                    <a class="btn-dark" id="parseScoreBtn" style="padding:0 10px;margin-bottom: .5rem; border-radius: inherit">加载评分</a>\n                </div>\n            </div>\n        `;
            n(".toolbar").html(t), n("#parseScoreBtn").hover((function () {
                n(this).attr("title"), layer.tips("接口不提供评分数据, 需要额外解析", this, {
                    tips: [1, "#333"],
                    time: 0
                });
            }), (function () {
                layer.closeAll("tips");
            })).on("click", (e => {
                this.parseScoreData();
            }));
        }

        parseScoreData() {
            const e = n(".movie-list .item").toArray();
            let t = Promise.resolve();
            e.forEach((e => {
                t = t.then((async () => {
                    const t = n(e), a = t.find(".video-title strong").text();
                    if (a.includes("FC2-")) return void layer.error(`${a} Fc2视频,跳过`, "top", "right");
                    const i = localStorage.getItem("info_" + a);
                    if (i) return void this.appendScoreHtml(a, i);
                    const s = await this.utils.gmHttp.get(t.find("a").attr("href")), r = n((new DOMParser).parseFromString(s, "text/html")).find("strong:contains('評分:')").parent().find(".value").html();
                    r ? (this.appendScoreHtml(a, r), localStorage.setItem("info_" + a, r), layer.success(`${a} 评分解析完成`, "top", "right"),
                        await new Promise((e => setTimeout(e, 500)))) : layer.error(`${a} 评分解析失败`, "top", "right");
                }));
            })), t.then((() => {
                layer.info("已全部解析完成", "top", "right");
            })).catch((e => {
                layer.error(`解析过程中出错: ${e}`, "top", "right");
            }));
        }

        appendScoreHtml(e, t) {
            n(`#score_${e}`).html(t);
        }

        loadScoreFormCache() {
            n(".movie-list .item").toArray().forEach((e => {
                const t = n(e).find(".video-title strong").text(), a = localStorage.getItem("info_" + t);
                a && this.appendScoreHtml(t, a);
            }));
        }
    }

    class MissavPlugin extends BasePlugin {
        handle() {
            const e = window.player.pause;
            window.player.pause = () => {
                document.hasFocus() && e();
            };
        }
    }

    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 y = (e, t, 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 = "#A78BFA", l = "#C4B5FD", c = "#68D391", d = "#9AE6B4", h = "#FC8181", p = "#FEB2B2", g = {
            borderRadius: "12px",
            color: "white",
            padding: "12px 16px",
            boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
            minWidth: "150px",
            textAlign: "center"
        }, u = {
            text: e,
            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})`
                }
            }[t],
            stopOnFocus: true,
            oldestFirst: false,
            ...r
        }, m = i(u);
        if (m.showToast(), r.onClose) {
            const e = m.toastElement;
            console.log("监听", e), e.addEventListener("animationend", (() => {
                r.onClose();
            }));
        }
    };

    layer.success = (e, t = "center", n, a) => y(e, "success", t, n, a), layer.error = (e, t = "center", n, a) => y(e, "error", t, n, a),
        layer.info = (e, t = "center", n, a) => y(e, "info", t, n, a), function () {
        const e = new PluginManager;
        let t = window.location.hostname;
        t.includes("javdb") && (e.register(ListPagePlugin), e.register(AutoPagePlugin),
            e.register(Fc2Plugin), e.register(FoldCategoryPlugin), e.register(ListPageMenuPlugin),
            e.register(HistoryPlugin), e.register(SettingPlugin), e.register(SearchPlugin),
            e.register(HitShowPlugin), e.register(DetailPagePlugin), e.register(ReviewPlugin),
            e.register(DetailPageMenuPlugin), e.register(HighlightMagnetPlugin), e.register(PreviewVideoPlugin),
            e.register(SelectTextFilterPlugin), e.register(ActressInfoPlugin)), t.includes("javtrailers") && e.register(JavTrailersPlugin),
        t.includes("subtitlecat") && e.register(SubTitleCatPlugin), t.includes("jable") && e.register(JablePlugin),
        t.includes("missav") && e.register(MissavPlugin), t.includes("aliyundrive") && e.register(AliyunPanPlugin),
            e.process();
    }();

})($, layui, md5, Toastify);