JAV-JHS

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

// ==UserScript==
// @name         JAV-JHS
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695
// @version      1.1.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/*
// @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
// @connect      *
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        window.close
// ==/UserScript==

(t => {
    if (typeof GM_addStyle == "function") {
        GM_addStyle(t);
        return
    }
    const i = document.createElement("style");
    i.textContent = t, document.head.append(i)
})(" .container{min-width:85%}.navbar{z-index:12345679!important}.movie-list.h{grid-template-columns:repeat(5,minmax(0,1fr))!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} ");

(function (n, a) {
    'use strict';

    var t = Object.defineProperty, e = (e, n, a) => ((e, n, a) => n in e ? t(e, n, {
        enumerable: true,
        configurable: true,
        writable: true,
        value: a
    }) : e[n] = a)(e, "symbol" != typeof n ? n + "" : n, a);

    class Utils {
        constructor() {
            return e(this, "insertStyle", (t => {
                t && (-1 === t.indexOf("<style>") && (t = "<style>" + t + "</style>"), $("head").append(t));
            })), e(this, "msg", {
                success(t, e = {}) {
                    const n = Array.isArray(t) ? t : [t];
                    this.list(n, [], [], e);
                },
                error(t, e = {}) {
                    const n = Array.isArray(t) ? t : [t];
                    this.list([], n, [], e);
                },
                info(t, e = {}) {
                    const n = Array.isArray(t) ? t : [t];
                    this.list([], [], n, e);
                },
                list(t = [], e = [], n = [], a = {}) {
                    let i = "";
                    const r = (t, e) => {
                        t && 0 !== t.length && (i += `<div style="color:${e.color};margin-bottom:10px;">`,
                            t.forEach((t => i += `${e.prefix} ${t}<br/>`)), i += "</div>");
                    };
                    r(t, {
                        prefix: "✓",
                        color: "#76d25e"
                    }), r(e, {
                        prefix: "✗",
                        color: "#dc4b64"
                    }), r(n, {
                        prefix: "ℹ",
                        color: "#d7c88b"
                    });
                    let s = 0;
                    e.length > 0 && 0 === t.length && 0 === n.length ? s = 2 : t.length > 0 && 0 === e.length && (s = 1),
                        layer.msg(i, {
                            icon: s,
                            time: 2e3
                        });
                }
            }), e(this, "http", {
                alertFun: t => {
                    layer.msg(t, {
                        icon: 2
                    });
                },
                get(t, e = {}, n = {}, a) {
                    return this.jqueryRequest("GET", t, null, e, n, a);
                },
                post(t, e = {}, n = {}, a) {
                    return this.jqueryRequest("POST", t, e, null, n, a);
                },
                put(t, e = {}, n = {}, a) {
                    return this.jqueryRequest("PUT", t, e, null, n, a);
                },
                del(t, e = {}, n = {}, a) {
                    return this.jqueryRequest("DELETE", t, null, e, n, a);
                },
                jqueryRequest(t, e, n = {}, a = {}, i = {}, r = true) {
                    return "POST" === t && (i = {
                        "Content-Type": "application/json",
                        ...i
                    }), new Promise(((s, o) => {
                        $.ajax({
                            method: t,
                            url: e,
                            async: r,
                            data: "GET" === t || "DELETE" === t ? a : JSON.stringify(n),
                            headers: i,
                            success: t => this.handleResponse(t, s, o),
                            error: t => o(t)
                        });
                    }));
                },
                handleResponse(t, e, n) {
                    const a = t;
                    if (200 === a.code) e(a); else if (401 === a.code) window.location.reload(); else {
                        const t = a.msg || "请求失败";
                        this.alertFun ? this.alertFun(t) : console.error(t), n(new Error(t));
                    }
                }
            }), Utils.instance || (Utils.instance = this, this.intervalContainer = {}), Utils.instance;
        }

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

        loopDetector(t, e, n = 20, a = 1e4, i = true) {
            let r = false;
            const s = Math.random(), o = (new Date).getTime();
            this.intervalContainer[s] = setInterval((() => {
                (new Date).getTime() - o > a && (console.warn("loopDetector timeout!", t, e), r = i),
                (t() || r) && (clearInterval(this.intervalContainer[s]), e && e(), delete this.intervalContainer[s]);
            }), n);
        }

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

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

        getNowStr(t = "-", e = ":") {
            const n = new Date, a = n.getFullYear(), i = String(n.getMonth() + 1).padStart(2, "0"), r = String(n.getDate()).padStart(2, "0"), s = String(n.getHours()).padStart(2, "0"), o = String(n.getMinutes()).padStart(2, "0"), l = String(n.getSeconds()).padStart(2, "0");
            return `${[a, i, r].join(t)} ${[s, o, l].join(e)}`;
        }
    }

    const i = new Utils;

    class LocalStorageManager {
        constructor() {
            return LocalStorageManager.instance || (LocalStorageManager.instance = this), LocalStorageManager.instance;
        }

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

        async getData() {
            const t = {
                dataList: [],
                filterKeywordList: [],
                filterActorList: []
            }, e = localStorage.appData;
            e || (localStorage.appData = JSON.stringify(t));
            const n = e ? JSON.parse(e) : t, a = [], i = [], r = [];
            for (const s of n.dataList) s.filter ? a.push(s) : s.hasDown ? r.push(s) : s.favorite && i.push(s);
            return {
                filterList: a,
                favoriteList: i,
                hasDownList: r,
                filterKeywordList: n.filterKeywordList,
                filterActorList: n.filterActorList,
                data: n
            };
        }

        async saveKeyData(t, e) {
        }

        async changeData(t, e, n, a) {
            e.includes("http") || (e = window.location.origin + e);
            const r = localStorage.appData, s = JSON.parse(r);
            let o = s.dataList, l = this.findData(t, o);
            if (l || (l = {
                carNum: t,
                url: e,
                actress: n,
                createDate: i.getNowStr(),
                filter: false,
                favorite: false,
                hasDown: false
            }, o.push(l)), "filter" === a) {
                if (l.filter) throw new Error(t + " 已在屏蔽列表中");
                l.filter = true, l.favorite = false, l.hasDown = false;
            } else if ("favorite" === a) {
                if (l.favorite) throw new Error(t + " 已在收藏列表中");
                l.filter = false, l.favorite = true, l.hasDown = false;
            } else {
                if ("hasDown" !== a) throw new Error("actionType错误");
                l.filter = true, l.favorite = true, l.hasDown = true;
            }
            localStorage.setItem("appData", JSON.stringify(s));
        }

        async saveFilterActor(t) {
            const e = localStorage.appData, n = JSON.parse(e);
            if (n.filterActorList.includes(t)) throw new Error(t + " 已存在");
            n.filterActorList.push(t), localStorage.setItem("appData", JSON.stringify(n));
        }

        async saveFilterKeyword(t) {
            const e = localStorage.appData, n = JSON.parse(e);
            if (n.filterKeywordList.includes(t)) throw new Error(t + " 已存在");
            n.filterKeywordList.push(t), localStorage.setItem("appData", JSON.stringify(n));
        }

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

    const r = new LocalStorageManager;

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

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

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

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

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

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

    const s = {
        filterList: [],
        favoriteList: [],
        filterKeywordList: [],
        filterActorList: [],
        hasHandleList: [],
        answerCount: 1,
        reviewKeyword: ["像", "是你"]
    };

    class BasePlugin {
        constructor() {
            this.pluginManager = null, this.utils = i, 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 = r, Object.keys(s).forEach((t => {
                Object.defineProperty(this, t, {
                    get: () => s[t],
                    set(e) {
                        s[t] = e;
                    },
                    enumerable: true,
                    configurable: true
                });
            }));
        }

        injectBean() {
        }

        initCss() {
        }

        handle() {
        }

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

        closePage() {
            layer.msg("操作成功", {
                icon: 1
            }), layer.closeAll();
            [".layui-layer-shade", ".layui-layer-move", ".layui-layer"].forEach((function (t) {
                parent.document.querySelectorAll(t).forEach((function (t) {
                    t.parentNode.removeChild(t);
                }));
            })), window.close();
        }

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

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

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

        getSetting(t, e) {
            const n = localStorage.getItem("setting");
            if (null === n) return e;
            const a = JSON.parse(n);
            return a && "object" == typeof a && t in a ? a[t] : e;
        }
    }

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

        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"),
                this.refresh(), window.addEventListener("storage", (t => {
                "refresh" === t.key && this.refresh();
            }));
        }

        async refresh() {
            if (!this.isListPage) return;
            const t = await this.storageManager.getData();
            this.filterList = t.filterList, this.favoriteList = t.favoriteList, this.filterKeywordList = t.filterKeywordList,
                this.filterActorList = t.filterActorList, this.filterMovieList(), this.autoPagePlugin.handlePaging();
        }

        filterMovieList() {
            if (window.location.href.includes("search?q")) return;
            let t = $(".movie-list .item").toArray();
            const e = this.favoriteList.map((t => t.carNum));
            let n = this.getSetting("hideFilterItem", "yes");
            t.forEach((t => {
                let a = $(t), i = a.find("a"), r = i.attr("href"), s = i.attr("title"), o = a.find(".video-title").find("strong").text();
                const l = `${o}-hide`, c = `${o}-tag`, d = `${o}-click`;
                if ((this.filterList.some((t => t.carNum === o)) && "yes" === n || this.filterKeywordList.some((t => s.includes(t) || o.includes(t)))) && !this.hasHandleList.includes(l)) return a.hide(),
                    void this.hasHandleList.push(l);
                e.includes(o) && !this.hasHandleList.includes(c) && (a.find(".tags").append('<span class="tag is-success" style="margin-right: 5px">待下载</span>'),
                    this.hasHandleList.push(c)), this.hasHandleList.includes(d) || (a.on("click", (t => {
                    if (t.preventDefault(), o.includes("FC2-")) {
                        let t = r.split("/").filter(Boolean).pop();
                        this.fc2Plugin.openFc2Page(t, o, r);
                    } else this.openPage(r, o, false, t);
                })), this.utils.rightClick(a.find("img"), (t => {
                    this.utils.q(t, `是否屏蔽番号${o}?`, (() => {
                        this.changeData(o, r, "", "filter").then((t => layer.msg("操作成功", {
                            icon: 1
                        })));
                    }));
                })), this.hasHandleList.push(d));
            })), $("#wait-down-btn span").text(`打开待下载(${this.favoriteList.length})`);
        }
    }

    class SearchPlugin extends BasePlugin {
        initCss() {
            return "\n            .search-bar-container {\n                margin-bottom: 0 !important;\n            }\n            \n            .search-bar-container .column {\n                padding: 10px 12px !important;\n            }\n            \n            .search-bar-wrap {\n                background-color: inherit;\n                padding: 0;\n            }\n        ";
        }

        handle() {
            this.isListPage && ($(".search-input").html('<input id="search-keyword" class="input is-medium" data-type="all" type="text" value="" placeholder="輸入影片番號,演員名等關鍵字進行檢索">'),
                $("#search-bar-container").prependTo("#tags"), $(".search-submit").html('<button type="button" id="search-btn" class="button is-medium is-info">檢索</button>'),
                $("#search-btn").on("click", (t => {
                    let e = $("#search-keyword").val(), n = $("#search-type option:selected").val();
                    "" !== e && this.openPage("/search?q=" + e + "&f=" + n, "搜索", false, t);
                })), $("#search-keyword").on("paste", (t => {
                setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })).on("keypress", (t => {
                "Enter" === t.key && setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })));
        }
    }

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

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

        handlePaging() {
            if (!this.isListPage) return;
            if (this.paging) return;
            let t = true;
            if ($(".movie-list .item:visible").each(((e, n) => {
                0 === $(n).find("span:contains('待下载')").length && (t = false);
            })), !t) return;
            if ("yes" !== localStorage.autoPage) return;
            let e = $(".pagination-next");
            0 !== e.length && (this.paging = true, layer.msg("下一页....", {
                time: 500,
                end: () => {
                    e[0].click();
                }
            }));
        }
    }

    class DetailPagePlugin extends BasePlugin {
        injectBean(detailPageMenuPlugin) {
            this.detailPageMenuPlugin = detailPageMenuPlugin;
        }

        initCss() {
            return this.isDetailPage ? "\n            .main-nav,#search-bar-container {\n                display: none !important;\n            }\n            \n            html {\n                padding-top:0px!important;\n            }\n        " : "";
        }

        async handle() {
            if (!this.isDetailPage) return;
            const t = await this.storageManager.getData();
            this.filterList = t.filterList, this.favoriteList = t.favoriteList, this.filterKeywordList = t.filterKeywordList,
                this.filterActorList = t.filterActorList, this.checkFilterActor();
        }

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

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

    const l = "https://api.ffaoa.com/api", c = (t, e = 20) => new Promise(((n, a) => {
        $.ajax({
            method: "GET",
            url: `${l}/v1/movies/${t}/reviews`,
            data: {
                page: 1,
                sort_by: "hotly",
                limit: e
            },
            headers: {
                jdSignature: o()
            },
            success: t => {
                const e = t.data.reviews;
                n(e);
            },
            error: t => {
                console.error(t), msg.error("发生错误" + t), a(t);
            }
        });
    }));

    class ReviewPlugin extends BasePlugin {
        async handle() {
            if (!this.isDetailPage) return;
            const t = window.location.href.split("/"), e = t[t.length - 1].split("#")[0];
            let n = $("#magnets-content");
            n.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
            let a = this.getSetting("reviewCount", 20);
            const i = await c(e, a);
            $("#reviewsLoading").remove(), 0 === i.length && n.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'),
                n.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.forEach((t => {
                    let e = false;
                    for (let n = 0; n < this.reviewKeyword.length; n++) if (t.content.indexOf(this.reviewKeyword[n]) > -1) {
                        e = true;
                        break;
                    }
                    if (e) return;
                    let a = "";
                    for (let n = 0; n < t.score; n++) a += '<i class="icon-star"></i>';
                    let i = `\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;">\n                        ${t.username} &nbsp;&nbsp; <span class="score-stars">${a}</span> <span class="time">${t.created_at.replace("T", " ").replace(".000Z", "")}</span> &nbsp;&nbsp; 点赞:${t.likes_count}\n                        <p style="margin-top: 5px;">${t.content}</p>\n                    </div>\n                `;
                    n.append(i);
                }));
        }
    }

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

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

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

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

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

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

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

    let h = d;

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

    class DetailPageMenuPlugin extends BasePlugin {
        constructor() {
            super(), this.allowRepeatDown = false;
        }

        injectBean() {
        }

        initCss() {
            return "\n            .fr-btn {\n                float: right;\n                margin-left: 4px !important;\n            }\n        ";
        }

        handle() {
            this.bindHotkey(), this.isDetailPage && this.createMenuBtn();
        }

        hotkey() {
            if (this.isDetailPage) return [{
                hotkey: ["a"],
                callback: () => {
                    this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
                }
            }, {
                hotkey: ["s"],
                callback: () => this.favoriteOne(null)
            }, {
                hotkey: ["z"],
                callback: () => this.speedVideo()
            }];
        }

        createMenuBtn() {
            const t = this.getPageInfo(), e = t.carNum, n = [{
                id: "favoriteBtn",
                sort: 1,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#25b1dc"><span>收藏(s)</span></a>`;
                },
                action: t => this.favoriteOne()
            }, {
                id: "filterBtn",
                sort: 2,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#de3333"><span>屏蔽(a)</span></a>`;
                },
                action: t => this.filterOne(t)
            }, {
                id: "hasDownBtn",
                sort: 3,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>`;
                },
                action: e => this.changeData(t.carNum, t.url, t.actress, "hasDown").then((t => this.closePage()))
            }, {
                id: "enable-magnets-filter",
                sort: 5,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#c2bd4c"><span>关闭磁力过滤</span></a>`;
                },
                action: t => {
                    $("#magnets-content .item").toArray().forEach((t => $(t).show()));
                }
            }, {
                id: "jable-video-btn",
                sort: 6,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>`;
                },
                action: t => this.openPage(`https://jable.tv/videos/${e}/`, e, false, t)
            }, {
                id: "missav-video-btn",
                sort: 7,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>`;
                },
                action: t => window.open(`https://missav.ws/search/${e}`, "_blank")
            }, {
                id: "preview-video-btn",
                sort: 8,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>预览视频(z)</span></a>`;
                },
                action: t => this.openPage(`https://javtrailers.com/video/${e.toLowerCase().replace("-", "00")}`, e, false, t)
            }, {
                id: "search-subtitle-btn",
                sort: 9,
                html: function () {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background-color: #2196F3"><span>搜索字幕</span></a>`;
                },
                action: t => this.openPage(`https://subtitlecat.com/index.php?search=${e}`, e, false, t)
            }];
            n.sort(((t, e) => t.sort - e.sort));
            const a = `\n            <div style="transform: translateY(-50%);">\n                ${n.map((t => t.html())).join("\n")}\n            </div>\n        `;
            $(".tabs").after(a), n.forEach((({id: t, action: e}) => {
                $(`#${t}`).on("click", e);
            }));
        }

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

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

        checkHasDown() {
            let t = this.getPageInfo().carNum;
            $("#magnets-content a, #magnets-content button").on("click", (e => {
                this.allowRepeatDown || this.storageManager.checkHasDown(t).then((t => {
                    "yes" === t.data && (e.preventDefault(), e.stopPropagation(), layer.msg(t.msg, {
                        icon: 2
                    }));
                }));
            }));
        }

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

        bindHotkey() {
            const t = {
                a: () => {
                    this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
                },
                s: () => this.favoriteOne(null),
                z: () => this.speedVideo()
            }, e = (t, e) => {
                h.registerHotkey(t, (() => {
                    this.isDetailPage ? e() : (t => {
                        const e = $(".layui-layer-content iframe");
                        0 !== e.length && e[0].contentWindow.postMessage(t, "*");
                    })(t);
                }));
            };
            this.isDetailPage && window.addEventListener("message", (e => {
                t[e.data] && t[e.data]();
            })), Object.entries(t).forEach((([t, n]) => {
                e(t, n);
            }));
        }
    }

    class ListPageMenuPlugin extends BasePlugin {
        constructor() {
            super(), this.buttons = [];
        }

        handle() {
            this.isListPage && this.createMenuBtn();
        }

        initCss() {
            return "\n            .menu-box {\n                position: fixed;\n                right: 10px;\n                top: 50%;\n                transform: translateY(-50%);\n                display: flex;\n                flex-direction: column;\n                z-index: 1000;\n                gap: 6px;\n            }\n            \n            .menu-btn {\n                display: inline-block;\n                min-width: 80px;\n                padding: 7px 12px;\n                border-radius: 4px;\n                color: white;\n                text-decoration: none;\n                font-weight: bold;\n                font-size: 12px;\n                text-align: center;\n                cursor: pointer;\n                transition: all 0.3s ease;\n                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n                text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n                border: none;\n                line-height: 1.3;\n                margin: 0;\n            }\n            \n            .menu-btn:hover {\n                transform: translateY(-1px);\n                box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);\n                opacity: 0.9;\n            }\n            \n            .menu-btn:active {\n                transform: translateY(0);\n                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\n            }\n\n        ";
        }

        createMenuBtn() {
            [].sort(((t, e) => t.sort - e.sort));
            $(".tabs ul").append('\n            <li class="is-active" id="waitCheckBtn">\n                <a class="menu-btn" style="background-color:#82d26d !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                    <span>打开待鉴定</span>\n                </a>\n            </li>\n             <li class="is-active" id="waitDownBtn">\n                <a class="menu-btn" style="background-color:#7cb7e8 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                    <span>打开待下载</span>\n                </a>\n            </li>\n        '),
                $("#waitCheckBtn").on("click", (t => {
                    this.openWaitCheck(t);
                })), $("#waitDownBtn").on("click", (t => {
                this.openFavorite(t);
            }));
        }

        openWaitCheck() {
            let t = 0, e = this.getSetting("waitCheckCount", 5);
            $(".movie-list .item:visible").each(((n, a) => {
                if (t >= e) return false;
                if (0 === $(a).find("span:contains('待下载')").length) {
                    const e = $(a).find("a").attr("href");
                    e && (window.open(e), t++);
                }
            }));
        }

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

        archiveFile() {
            this.storageManager.archiveFile().then((t => {
                let e = t.successMsgList, n = t.errorMsgList;
                msg.list(e, n), e.length || n.length || layer.msg("没有可归档文件");
            }));
        }

        checkSubTitle() {
            this.storageManager.checkSubTitle().then((t => {
                let e = t.data;
                if (0 === e.length) return void layer.msg("视频字幕完整");
                let n = '<table class="data-table">';
                n += "<thead><tr>", n += "<th>番号</th>", n += "<th>文件路径</th>", n += "<th>操作</th>",
                    n += "</tr></thead>", n += "<tbody>", $.each(e, (function (t, e) {
                    n += "<tr>", n += "<td>" + e.carNum + "</td>", n += "<td>" + e.filePath + "</td>",
                        n += `<td>\n                            <a href="${"https://subtitlecat.com/index.php?search=" + e.carNum}" target="_blank">搜索字幕</a>\n                            <a href="${e.url}"  target="_blank">详情页</a>\n                         </td>`,
                        n += "</tr>";
                })), n += "</tbody>", n += "</table>", layer.open({
                    type: 1,
                    title: "检查字幕",
                    content: n,
                    area: ["1000px", "400px"]
                });
            }));
        }
    }

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

    class PreviewVideoPlugin extends BasePlugin {
        handle() {
            $(".preview-video-container").on("click", (t => {
                t.preventDefault(), $("#preview-video-btn").click();
            })), "yes" === this.getSetting("autoPlay", "no") && $("#preview-video-btn").click();
        }
    }

    class SelectTextFilterPlugin extends BasePlugin {
        injectBean(detailPageMenuPlugin) {
            this.detailPageMenuPlugin = detailPageMenuPlugin;
        }

        handle() {
            this.isDetailPage && (this.utils.rightClick($("h2"), (t => {
                const e = window.getSelection().toString();
                if (e) {
                    let n = {
                        clientX: t.clientX,
                        clientY: t.clientY + 120
                    };
                    this.utils.q(n, `是否屏蔽关键词${e}?`, (() => {
                        this.saveFilterKeyword(e).then((t => {
                            this.closePage();
                        }));
                    }));
                }
            })), $(".male").prev().toArray().forEach((t => {
                this.utils.rightClick($(t), (e => {
                    let n = $(t).text().trim();
                    this.utils.q(e, `是否屏蔽演员${n}?`, (() => {
                        this.saveFilterActor(n).then((t => {
                            this.detailPageMenuPlugin.filterOne(null, true);
                        }));
                    }));
                }));
            })), this.utils.rightClick($(".preview-images"), (t => {
                let e = this.getPageInfo();
                this.utils.q(t, `是否屏蔽${e.carNum}?`, (() => {
                    this.changeData(e.carNum, e.url, "", "filter").then((t => {
                        this.closePage();
                    }));
                }));
            })), this.utils.rightClick($(".column-video-cover"), (t => {
                let e = this.getPageInfo();
                this.utils.q(t, `是否屏蔽${e.carNum}?`, (() => {
                    this.changeData(e.carNum, e.url, "", "filter").then((t => {
                        this.closePage();
                    }));
                }));
            })));
        }

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

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

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

        handlePlayJavTrailers() {
            this.hasBand || this.utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
                setTimeout((() => {
                    this.hasBand = true;
                    let t = document.getElementById("vjs_video_3_html5_api");
                    t.play(), t.currentTime = 5, t.addEventListener("timeupdate", (function () {
                        t.currentTime >= 14 && t.currentTime < 16 && (t.currentTime += 2);
                    })), $("#vjs_video_3_html5_api").css({
                        position: "fixed",
                        width: "100vw",
                        height: "100vh",
                        objectFit: "cover",
                        zIndex: "999999999"
                    }), $(".vjs-control-bar").css({
                        position: "fixed",
                        bottom: "20px",
                        zIndex: "999999999"
                    });
                }), 0);
            }));
        }

        handle() {
            if (!window.location.hostname.includes("javtrailers")) return;
            if ($("h1:contains('Page not found')").length > 0) {
                let t = window.location.href.split("video/")[1].toLowerCase().replace("00", "-");
                return void (window.location.href = "https://javtrailers.com/search/" + t);
            }
            let t = $(".videos-list .video-link").toArray();
            if (t.length) {
                const e = window.location.href.split("search/")[1].toLowerCase(), n = t.find((t => $(t).find(".vid-title").text().toLowerCase().includes(e)));
                if (n) return void (window.location.href = $(n).attr("href"));
            }
            this.handlePlayJavTrailers(), $("#videoPlayerContainer").on("click", (() => {
                this.handlePlayJavTrailers();
            })), window.addEventListener("message", (t => {
                let e = document.getElementById("vjs_video_3_html5_api");
                e && (e.currentTime += 5);
            })), h.registerHotkey("z", (() => {
                const t = document.getElementById("vjs_video_3_html5_api");
                t && (t.currentTime += 5);
            })), h.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), h.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
        }
    }

    class SubTitleCatPlugin extends BasePlugin {
        handle() {
            if (!window.location.hostname.includes("subtitlecat")) return;
            $(".t-banner-inner").hide(), $("#navbar").hide();
            let t = window.location.href.split("=")[1].toLowerCase();
            $(".sub-table tr td a").toArray().forEach((e => {
                let n = $(e);
                n.text().toLowerCase().includes(t) || n.parent().parent().hide();
            }));
        }
    }

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

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

    class Fc2Plugin extends BasePlugin {
        injectBean(detailPageMenuPlugin) {
            this.detailPageMenuPlugin = detailPageMenuPlugin;
        }

        handle() {
        }

        initCss() {
            return "\n            /* 弹层样式 */\n            .movie-detail-layer .layui-layer-title {\n                font-size: 18px;\n                color: #333;\n                background: #f8f8f8;\n            }\n            \n            \n            /* 容器样式 */\n            .movie-detail-container {\n                display: flex;\n                height: 100%;\n                background: #fff;\n            }\n            \n            .movie-poster-container {\n                flex: 0 0 60%;\n                padding: 15px;\n            }\n            \n            .right-box {\n                flex: 1;\n                padding: 20px;\n                overflow-y: auto;\n            }\n            \n            /* 预告片iframe */\n            .movie-trailer {\n                width: 100%;\n                height: 100%;\n                min-height: 400px;\n                background: #000;\n                border-radius: 4px;\n            }\n            \n            /* 电影信息样式 */\n            .movie-title {\n                font-size: 24px;\n                margin-bottom: 15px;\n                color: #333;\n            }\n            \n            .movie-meta {\n                margin-bottom: 20px;\n                color: #666;\n            }\n            \n            .movie-meta span {\n                margin-right: 15px;\n            }\n            \n            /* 演员列表 */\n            .actor-list {\n                display: flex;\n                flex-wrap: wrap;\n                gap: 8px;\n                margin-top: 10px;\n            }\n            \n            .actor-tag {\n                padding: 4px 12px;\n                background: #f0f0f0;\n                border-radius: 15px;\n                font-size: 12px;\n                color: #555;\n            }\n            \n            /* 图片列表 */\n            .image-list {\n                display: flex;\n                flex-wrap: wrap;\n                gap: 10px;\n                margin-top: 10px;\n            }\n            \n            .movie-image-thumb {\n                width: 120px;\n                height: 80px;\n                object-fit: cover;\n                border-radius: 4px;\n                cursor: pointer;\n                transition: transform 0.3s;\n            }\n            \n            .movie-image-thumb:hover {\n                transform: scale(1.05);\n            }\n            \n            /* 加载中和错误状态 */\n            .search-loading, .movie-error {\n                padding: 40px;\n                text-align: center;\n                color: #999;\n            }\n            \n            .movie-error {\n                color: #f56c6c;\n            }\n            \n            .fancybox-container{\n                z-index:99999999\n             }\n             \n             \n             /* 错误提示样式 */\n            .movie-not-found, .movie-error {\n                text-align: center;\n                padding: 30px;\n                color: #666;\n            }\n            \n            .movie-not-found h3, .movie-error h3 {\n                color: #f56c6c;\n                margin: 15px 0;\n            }\n            \n            .icon-warning, .icon-error {\n                font-size: 50px;\n                color: #e6a23c;\n            }\n            \n            .icon-error {\n                color: #f56c6c;\n            }\n\n        ";
        }

        openFc2Page(t, e, n) {
            (t => new Promise(((e, n) => {
                let a = `https://hohoj.tv/search?text=${t}`;
                console.log("请求页面", a), g({
                    method: "GET",
                    url: a,
                    onload: function (n) {
                        let a = n.responseText, i = null;
                        if (a.includes("找不到任何影片")) return void e({
                            pageUrl: i
                        });
                        const r = (new DOMParser).parseFromString(a, "text/html");
                        $(r).find(".video-item a").toArray().forEach((e => {
                            if ($(e).find(".video-item-title").text().includes(t)) {
                                let t = $(e).attr("href").split("id=")[1];
                                i = "https://hohoj.tv/embed?id=" + t;
                            }
                        })), console.log("解析成功:", i), e({
                            pageUrl: i
                        });
                    },
                    onerror: function (t) {
                        console.error("Request failed:", t);
                    }
                });
            })))(e.replace("FC2-", "")).then((t => {
                let e = t.pageUrl;
                const n = document.querySelector(".movie-poster-container"), a = document.querySelector(".movie-trailer");
                document.querySelector(".movie-info-container"), e ? $(a).attr("src", e) : (n.innerHTML = '\n                    <div class="movie-not-found">\n                        <i class="icon-warning"></i>\n                        <h3>未找到相关内容</h3>\n                        <p>hohoj.tv 中没有找到与当前番号相关的影片信息</p>\n                    </div>\n                ',
                    a.style.display = "none");
            }));
            let a = "";
            (t => new Promise(((e, n) => {
                $.ajax({
                    method: "GET",
                    url: `${l}/v4/movies/${t}`,
                    headers: {
                        jdSignature: o()
                    },
                    success: t => {
                        t.data || (i.msg.error("发生错误" + t.message), n(t.message));
                        const a = t.data.movie, r = a.id, s = a.actors, o = a.origin_title, l = a.number, c = a.score, d = a.release_date, h = a.preview_images, g = [];
                        h.forEach((t => {
                            g.push(t.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
                        })), console.log(s), e({
                            movieId: r,
                            actors: s,
                            title: o,
                            carNum: l,
                            score: c,
                            releaseDate: d,
                            imgList: g
                        });
                    },
                    error: t => {
                        i.msg.error("发生错误" + t.responseJSON.message), n(t);
                    }
                });
            })))(t).then((t => {
                const e = t.actors || [], n = t.imgList || [];
                let i = "";
                if (e.length > 0) for (let s = 0; s < e.length; s++) {
                    let t = e[s];
                    i += `<span class="actor-tag"><a href="/actors/${t.id}" target="_blank">${t.name}</a></span>`,
                    0 === t.gender && (a += t.name);
                } else i = '<span class="no-data">暂无演员信息</span>';
                let r = "";
                r = Array.isArray(n) && n.length > 0 ? n.map(((t, e) => `\n                <a href="${t}" data-fancybox="movie-gallery" data-caption="剧照 ${e + 1}">\n                    <img src="${t}" class="movie-image-thumb"  alt=""/>\n                </a>\n            `)).join("") : '<div class="no-data">暂无剧照</div>',
                    $(".movie-info-container").html(`\n                <h3 class="movie-title">${t.title || "无标题"}</h3>\n                <div class="movie-meta">\n                    <span>番号: ${t.carNum || "未知"}</span>\n                    <span>年份: ${t.releaseDate || "未知"}</span>\n                    <span>评分: ${t.score || "无"}</span>\n                </div>\n                <div class="movie-actors">\n                    <div class="actor-list">主演: ${i}</div>\n                </div>\n                <div class="movie-gallery" style="margin-top:10px">\n                    <h4>剧照: </h4>\n                    <div class="image-list">${r}</div>\n                </div>\n            `);
            })).catch((t => {
                console.error(t), $(".movie-info-container").html(`\n                <div class="movie-error">加载失败: ${t.message}</div>\n            `);
            })), (t => new Promise(((e, n) => {
                $.ajax({
                    method: "GET",
                    url: `${l}/v1/movies/${t}/magnets`,
                    headers: {
                        jdSignature: o()
                    },
                    success: t => {
                        let n = t.data.magnets;
                        e({
                            magnetList: n
                        });
                    },
                    error: t => {
                        i.msg.error("发生错误" + t), n(t);
                    }
                });
            })))(t).then((t => {
                let e = t.magnetList, n = "";
                if (e.length > 0) for (let a = 0; a < e.length; a++) {
                    let t = e[a], i = "";
                    a % 2 == 0 && (i = "odd"), n += `\n                        <div class="item columns is-desktop ${i}">\n                            <div class="magnet-name column is-four-fifths">\n                                <a href="magnet:?xt=urn:btih:${t.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n                                    <span class="name">${t.name}.torrent</span>\n                                    <br>\n                                    <span class="meta">\n                                        ${t.hash}, ${t.files_count}個文件\n                                      </span>\n                                    <br>\n                                    <div class="tags">\n                                        ${t.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n                                    </div>\n                                </a>\n                            </div>\n                            <div class="buttons column">\n                                <button class="button is-info is-small copy-to-clipboard" data-clipboard-text="magnet:?xt=urn:btih:${t.hash}" type="button">&nbsp;複製&nbsp;</button>\n                            </div>\n                            <div class="date column"><span class="time">${t.created_at}</span></div>\n                        </div>\n                    `;
                } else n = '<span class="no-data">暂无磁力信息</span>';
                $("#magnets-content").html(n);
            })).catch((t => {
                console.error(t), $("#magnets-content").html(`\n                <div class="movie-error">加载失败: ${t.message}</div>\n            `);
            }));
            let r = this.getSetting("reviewCount", 20);
            c(t, r).then((t => {
                let e = $("#reviews-content");
                if (0 === t.length) return void e.html('<div class="movie-error">无评论</div> ');
                let n = '<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));"/>';
                t.forEach((t => {
                    let e = false;
                    for (let n = 0; n < this.reviewKeyword.length; n++) if (t.content.indexOf(this.reviewKeyword[n]) > -1) {
                        e = true;
                        break;
                    }
                    if (e) return;
                    let a = "";
                    for (let n = 0; n < t.score; n++) a += '<i class="icon-star"></i>';
                    n += `\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;">\n                        ${t.username} &nbsp;&nbsp; <span class="score-stars">${a}</span> <span class="time">${t.created_at.replace("T", " ").replace(".000Z", "")}</span> &nbsp;&nbsp; 点赞:${t.likes_count}\n                        <p style="margin-top: 5px;">${t.content}</p>\n                    </div>\n                `;
                })), e.html(n);
            })).catch((t => {
                console.error(t), $("#reviews-content").html(`\n                <div class="movie-error">加载失败: ${t.message}</div>\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 id="btn-box">\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">\n                            <div class="search-loading">加载中...</div>\n                        </div>\n                    </div>\n                    <div id="reviews-content">\n                        <div class="search-loading">加载中...</div>\n                    </div>\n                </div>\n            </div>\n        ',
                area: ["80%", "90%"],
                skin: "movie-detail-layer",
                scrollbar: false,
                success: (t, i) => {
                    $("#favoriteBtn").on("click", (t => {
                        this.changeData(e, n, a, "favorite").then((t => layer.closeAll()));
                    })), $("#filterBtn").on("click", (t => {
                        this.utils.q(t, `是否屏蔽${e}?`, (() => {
                            this.changeData(e, n, a, "filter").then((t => layer.closeAll()));
                        }));
                    })), $("#hasDownBtn").on("click", (t => {
                        this.changeData(e, n, a, "hasDown").then((t => layer.closeAll()));
                    }));
                }
            });
        }
    }

    class FoldCategoryPlugin extends BasePlugin {
        handle() {
            if (!this.isListPage) return;
            let t = $(".tabs ul"), e = $("h2.section-title"), n = "y" === localStorage.foldCategory;
            const [a, i] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
            let r;
            if (t.length > 0) {
                if (r = $("#tags"), !$("#tags dl div.tag.is-info").map((function () {
                    return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
                })).get().join(" ")) return;
                t.append(`\n                <li class="is-active" id="foldCategoryBtn">\n                    <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                        <span>${a}</span>\n                        <i style="margin-left: 10px" class="${i}"></i>\n                    </a>\n                </li>\n            `);
            }
            e.length > 0 && (e.append(`\n                <div id="foldCategoryBtn">\n                    <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                        <span>${a}</span>\n                        <i style="margin-left: 10px" class="${i}"></i>\n                    </a>\n                </div>\n            `),
                r = $("section > div > div.box")), r[n ? "hide" : "show"](), $("#foldCategoryBtn").on("click", (t => {
                t.preventDefault(), n = !n, localStorage.foldCategory = n ? "y" : "n";
                const [e, a] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
                $("#foldCategoryBtn").find("span").text(e).end().find("i").attr("class", a), r[n ? "hide" : "show"]();
            }));
        }
    }

    class SettingPlugin extends BasePlugin {
        initCss() {
            return "\n            <style>\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: 150px;\n                    font-weight: bold;\n                    margin-right: 10px;\n                }\n                .form-content{\n                    min-width: 100px;\n                }\n                .form-content * {\n                    width: 100%;\n                    padding: 5px;\n                    margin-right: 10px;\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                    float:right;\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>');
            localStorage.getItem("setting") || localStorage.setItem("setting", JSON.stringify({
                hideFilterItem: "yes",
                autoPlay: "no",
                reviewCount: 20,
                waitCheckCount: 5
            })), $("#setting-btn").on("click", (() => {
                layer.open({
                    type: 1,
                    title: "设置",
                    content: '\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            </div>\n            <div style="margin: 20px">\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="autoPlay">\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                        </select>\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                <button id="saveBtn">保存设置</button>\n            </div>\n        ',
                    area: ["30%", "80%"],
                    scrollbar: false,
                    success: (t, e) => {
                        this.loadForm(), this.bindClick();
                    }
                });
            }));
        }

        bindClick() {
            $("#importBtn").on("click", (t => this.importData(t))), $("#exportBtn").on("click", (t => this.exportData(t)));
        }

        loadForm() {
            const t = JSON.parse(localStorage.getItem("setting")) || {};
            void 0 !== t.hideFilterItem && $("#hideFilterItem").val(t.hideFilterItem), void 0 !== t.reviewCount && $("#reviewCount").val(t.reviewCount),
            void 0 !== t.waitCheckCount && $("#waitCheckCount").val(t.waitCheckCount), void 0 !== t.autoPlay && $("#autoPlay").val(t.autoPlay),
                $("#saveBtn").on("click", (() => {
                    const t = $("#hideFilterItem").val(), e = $("#autoPlay").val(), n = $("#reviewCount").val(), a = $("#waitCheckCount").val();
                    let i = JSON.parse(localStorage.getItem("setting")) || {};
                    i.hideFilterItem = t, i.reviewCount = n, i.waitCheckCount = a, i.autoPlay = e, console.log(i),
                        localStorage.setItem("setting", JSON.stringify(i)), layer.success("保存成功"), window.location.reload();
                }));
        }

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

        exportData(t) {
            try {
                const t = localStorage.appData;
                if (!t) return void layer.msg("没有找到可导出的appData数据", {
                    icon: 2
                });
                const e = `${this.utils.getNowStr("_", "_")}_appData.json`, n = new Blob([t], {
                    type: "application/json"
                }), a = URL.createObjectURL(n), i = document.createElement("a");
                i.href = a, i.download = e, document.body.appendChild(i), i.click(), setTimeout((() => {
                    document.body.removeChild(i), URL.revokeObjectURL(a);
                }), 100), layer.msg("数据导出成功", {
                    icon: 1
                }), console.log("数据导出成功:", e);
            } catch (e) {
                console.error("导出数据时出错:", e), layer.msg("导出数据时出错: " + e.message, {
                    icon: 2
                });
            }
        }
    }

    class HistoryPlugin extends BasePlugin {
        initCss() {
            return "\n            <style>\n                .data-table {\n                    width: 100%;\n                    border-collapse: separate;\n                    border-spacing: 0;\n                    font-family: 'Helvetica Neue', Arial, sans-serif;\n                    background: #fff;\n                    border-radius: 12px;\n                    overflow: hidden;\n                    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);\n                    margin: 0;\n                }\n                \n                .data-table thead tr {\n                    background: #f8fafc;\n                }\n                \n                .data-table th {\n                    padding: 16px 20px;\n                    text-align: left;\n                    color: #64748b;\n                    font-weight: 500;\n                    font-size: 14px;\n                    text-transform: uppercase;\n                    letter-spacing: 0.5px;\n                    border-bottom: 1px solid #e2e8f0;\n                }\n                \n                .data-table td {\n                    padding: 14px 20px;\n                    color: #334155;\n                    font-size: 15px;\n                    border-bottom: 1px solid #f1f5f9;\n                }\n                \n                .data-table tbody tr:last-child td {\n                    border-bottom: none;\n                }\n                \n                .data-table tbody tr {\n                    transition: all 0.2s ease;\n                }\n                \n                .data-table tbody tr:hover {\n                    background: #f8fafc;\n                }\n                \n                .data-table a {\n                    display: inline-flex;\n                    align-items: center;\n                    padding: 6px 14px;\n                    margin-right: 10px;\n                    border-radius: 6px;\n                    text-decoration: none;\n                    font-size: 13px;\n                    font-weight: 500;\n                    transition: all 0.2s ease;\n                }\n                \n                .data-table a:first-child {\n                    background: #f0fdf4;\n                    color: #16a34a;\n                    border: 1px solid #dcfce7;\n                }\n                \n                .data-table a:last-child {\n                    background: #f0f9ff;\n                    color: #0284c7;\n                    border: 1px solid #e0f2fe;\n                }\n                \n                .data-table a:hover {\n                    transform: translateY(-1px);\n                    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n                }\n                \n                .data-table a:first-child:hover {\n                    background: #dcfce7;\n                }\n                \n                .data-table a:last-child:hover {\n                    background: #e0f2fe;\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: #aade66 !important;padding-right:15px !important;">\n                    历史列表\n                </a>\n            </div>'),
                $("#setting-btn").on("click", (t => {
                    this.openHistory(t);
                }));
        }

        openHistory(t) {
            this.storageManager.getData().then((t => {
                console.log(t);
                const e = t.data.dataList || [];
                e.reverse();
                let n = [...e];
                const a = {
                    filtered: {
                        text: "已屏蔽",
                        color: "#ec4949",
                        condition: t => !t.hasDown && !t.favorite
                    },
                    favorite: {
                        text: "已收藏",
                        color: "#50adb9",
                        condition: t => t.favorite && !t.hasDown
                    },
                    hasDown: {
                        text: "已下载",
                        color: "#8ebd6e",
                        condition: t => t.hasDown
                    }
                }, i = t => {
                    let e = a.filtered;
                    return t.hasDown ? e = a.hasDown : t.favorite && (e = a.favorite), `\n                <tr>\n                    <td>${t.carNum}</td>\n                        <td>${t.actress ? t.actress : ""}</td>\n                        <td>${t.createDate ? t.createDate : ""}</td>\n                        <td style="color:${e.color}">${e.text}</td>\n                    <td>\n                        <a class="action-remove" data-car-num="${t.carNum}" data-url="${t.url}">移除</a>\n                        <a class="action-detail" data-car-num="${t.carNum}" data-url="${t.url}">详情页</a>\n                    </td>\n                </tr>\n            `;
                }, r = t => `\n                <table class="data-table">\n                    <thead>\n                        <tr>\n                            <th>番号</th>\n                            <th width="300px">演员</th>\n                            <th>创建日期</th>\n                            <th>状态</th>\n                            <th>操作</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        ${t.map(i).join("")}\n                    </tbody>\n                </table>\n            `, s = (t, i) => {
                    n = (t => "all" === t ? [...e] : e.filter(a[t].condition))(i), $(t).find(".data-table").replaceWith(r(n)),
                        $(t).find(".history-btn").removeClass("active").filter(`[data-action="${i}"]`).addClass("active");
                }, o = (t, e, n) => {
                    "详情" === t && this.openPage(e.url), "移除" === t && this.utils.q(n, `是否移除${e.carNum}?`, (() => {
                        this.storageManager.removeData(e.carNum).then((t => {
                            let n = $(".movie-list .item").toArray();
                            for (let a = 0; a < n.length; a++) {
                                let t = $(n[a]), i = t.find(".video-title").find("strong").text();
                                if (i === e.carNum) {
                                    t.show();
                                    const e = `${i}-hide`;
                                    this.hasHandleList = this.hasHandleList.filter((t => t !== e));
                                    break;
                                }
                            }
                            layer.close(l), this.openHistory();
                        }));
                    }));
                };
                let l = layer.open({
                    type: 1,
                    title: "历史列表",
                    content: `\n                <div style="margin: 10px">\n                    ${[{
                        action: "filtered",
                        text: "已屏蔽",
                        color: "#ec4949"
                    }, {
                        action: "favorite",
                        text: "已收藏",
                        color: "#50adb9"
                    }, {
                        action: "hasDown",
                        text: "已下载",
                        color: "#8ebd6e"
                    }, {
                        action: "all",
                        text: "所有",
                        color: "#d3c8a5"
                    }].map((t => `\n                <a class="menu-btn history-btn" data-action="${t.action}" style="background-color:${t.color} !important;">\n                   ${t.text}\n                </a>\n            `)).join("")}\n                </div>\n                ${r(n)}\n            `,
                    area: ["60%", "80%"],
                    success: (t, e) => {
                        $(t).on("click", ".history-btn", (function () {
                            s(t, $(this).data("action"));
                        })).on("click", ".action-remove", (function (t) {
                            t.stopPropagation(), o("移除", $(this).data(), t);
                        })).on("click", ".action-detail", (function (t) {
                            t.stopPropagation(), o("详情", $(this).data(), t);
                        }));
                    },
                    end: () => {
                        this.refresh();
                    }
                });
            }));
        }
    }

    window.$ = window.jQuery = a, i.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/css/layui.min.css"),
        layer.info = t => {
            layer.msg(t, {
                icon: 0
            });
        }, layer.success = t => {
        layer.msg(t, {
            icon: 1
        });
    }, layer.error = t => {
        layer.msg(t, {
            icon: 2
        });
    }, function () {
        const t = new PluginManager;
        t.register(ListPagePlugin), t.register(AutoPagePlugin), t.register(Fc2Plugin), t.register(FoldCategoryPlugin),
            t.register(ListPageMenuPlugin), t.register(HistoryPlugin), t.register(SettingPlugin),
            t.register(SearchPlugin), t.register(DetailPagePlugin), t.register(ReviewPlugin),
            t.register(DetailPageMenuPlugin), t.register(HighlightMagnetPlugin), t.register(PreviewVideoPlugin),
            t.register(SelectTextFilterPlugin), t.register(JavTrailersPlugin), t.register(SubTitleCatPlugin),
            t.register(JablePlugin), t.process();
    }();

})(md5, $);