JAV-JSH

Jav-鉴黄师 收藏、屏蔽、标记已下载; 免VIP查看热榜、Top250排行榜、Fc2ppv等数据; 可查看所有评论信息; 支持云盘备份; 以图识图

// ==UserScript==
// @name         JAV-JSH
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version      1.6.7
// @author       xie bro
// @description  Jav-鉴黄师 收藏、屏蔽、标记已下载; 免VIP查看热榜、Top250排行榜、Fc2ppv等数据; 可查看所有评论信息; 支持云盘备份; 以图识图
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=javdb.com
// @include      https://javdb*.com/*
// @include      https://www.javbus.com/*
// @include      https://javtrailers.com/*
// @include      https://subtitlecat.com/*
// @include      https://www.aliyundrive.com/*
// @exclude      https://www.javbus.com/forum/*
// @exclude      https://www.javbus.com/*actresses
// @require      data:application/javascript,;(function%20hookBody()%20%7B%20if%20(document.readyState%20!%3D%3D%20%22loading%22)%20%7B%20return%3B%20%7D%20const%20initialHideStyle%20%3D%20document.createElement(%22style%22)%3B%20initialHideStyle.textContent%20%3D%20%60%20body%20%7B%20opacity%3A%200%20!important%3B%20visibility%3A%20hidden%20!important%3B%20%7D%20body.script-ready%20%7B%20opacity%3A%201%20!important%3B%20visibility%3A%20visible%20!important%3B%20%7D%20%60%3B%20document.head.appendChild(initialHideStyle)%3B%20setTimeout(()%20%3D%3E%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20%7D%2C%203e3)%3B%20%7D)()%3B
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.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
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js
// @connect      hohoj.tv
// @connect      xunlei.com
// @connect      geilijiasu.com
// @connect      aliyundrive.com
// @connect      aliyundrive.net
// @connect      ja.wikipedia.org
// @connect      beta.magnet.pics
// @connect      jdforrepam.com
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @run-at       document-start
// ==/UserScript==

var __defProp = Object.defineProperty, __typeError = msg => {
    throw TypeError(msg);
}, __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
    enumerable: !0,
    configurable: !0,
    writable: !0,
    value: value
}) : obj[key] = value, __publicField = (obj, key, value) => __defNormalProp(obj, "symbol" != typeof key ? key + "" : key, value), __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), 
method);

!function() {
    "use strict";
    var _StorageManager_instances, autoCleanup_fn, saveFilterItem_fn, _SettingPlugin_instances, handleSyncData_fn;
    const Bus = {
        boxSelector: ".masonry",
        itemSelector: ".masonry .item",
        coverImgSelector: ".item .photo-frame img",
        requestDomItemSelector: "#waterfall .item"
    }, Db = {
        boxSelector: ".movie-list",
        itemSelector: ".movie-list .item",
        coverImgSelector: ".cover img",
        requestDomItemSelector: ".movie-list .item"
    }, isJavDb = window.location.href.includes("javdb"), isJavBus = window.location.href.includes("javbus"), Status_FAVORITE = "favorite", Status_FILTER = "filter", Status_HAS_DOWN = "hasDown";
    function insertStyle(css) {
        if (css) if (css.includes("<style>")) document.head.insertAdjacentHTML("beforeend", css); else {
            const style = document.createElement("style");
            style.textContent = css;
            document.head.appendChild(style);
        }
    }
    isJavBus && insertStyle("\n<style>\n    .masonry {\n        height: 100% !important;\n        width: 100% !important;\n        padding: 0 15px !important;\n    }\n    .masonry {\n        display: grid;\n        column-gap: 10px; /* 列间距*/\n        row-gap: 10px; /* 行间距 */\n        grid-template-columns: repeat(4, minmax(0, 1fr));\n    }\n    .masonry .item {\n        /*position: initial !important;*/\n        top: initial !important;\n        left: initial !important;\n        float: none !important;\n        background-color:#c4b1b1;\n        position: relative !important;\n    }\n    \n    .masonry .item:hover {\n        box-shadow: 0 .5em 1em -.125em rgba(10, 10, 10, .1), 0 0 0 1px #485fc7;\n    }\n    .masonry .movie-box{\n        width: 100% !important;\n        height: 100% !important;\n        margin: 0 !important;\n    }\n    .masonry .movie-box .photo-frame {\n        height: 70% !important;\n        margin: 0 !important;\n    }\n    .masonry .movie-box img {\n        max-height: 300px;\n        height: 100% !important;\n        object-fit: cover; /* 保持比例,裁剪多余部分 */\n        object-position: top; /* 从中间裁剪(可调整:top, bottom, left, right) */\n    }\n    .masonry .movie-box img:hover {\n      transform: scale(1.04);\n      transition: transform 0.3s;\n    }\n    .masonry .photo-info{\n        height: 30% !important;\n    }\n    .masonry .photo-info span {\n      display: inline-block; /* 或者 block */\n      max-width: 100%;      /* 根据父容器限制宽度 */\n      white-space: nowrap;  /* 禁止换行 */\n      overflow: hidden;     /* 隐藏溢出内容 */\n      text-overflow: ellipsis; /* 显示省略号 */\n    }\n    \n    /* 无码页面的样式 */\n    .photo-frame .mheyzo,\n    .photo-frame .mcaribbeancom2{\n        margin-left: 0 !important;\n    }\n    .avatar-box{\n        width: 100% !important;\n        display: flex !important;\n        margin:0 !important;\n    }\n    .avatar-box .photo-info{\n        display: flex;\n        justify-content: center;\n        align-items: center;\n        gap: 30px;\n        flex-direction: row;\n        background-color:#fff !important;\n    }\n    /*.photo-info .item-tag{\n        position: relative;\n    }*/\n    footer,#related-waterfall{\n        display: none!important;\n    }\n</style>\n");
    isJavDb && insertStyle('\n<style>\n    .navbar {\n        z-index: 12345679 !important;\n    }\n    \n    .sub-header,\n    #search-bar-container, /*搜索框*/\n    #footer,\n    .search-recent-keywords, /*搜索框底部热搜词条*/\n    .app-desktop-banner,\n    div[data-controller="movie-tab"] .tabs,\n    h3.main-title,\n    div.video-meta-panel > div > div:nth-child(2) > nav > div.review-buttons > div:nth-child(2), /* 下载 订正 按钮*/\n    div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(3), /* 相关清单*/\n    div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(2), /* 短评按钮*/\n    div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(1), /*磁力面板 按钮*/\n    .top-meta,\n    .float-buttons {\n        display: none !important;\n    }\n    \n    div.tabs.no-bottom,\n    .tabs ul {\n        border-bottom: none !important;\n    }\n    \n    \n    /* 视频列表项 相对相对 方便标签绝对定位*/\n    .movie-list .item {\n        position: relative !important;\n    }\n\n</style>\n');
    insertStyle("\n<style>\n    .a-primary, /* 主按钮 - 浅蓝色 */\n    .a-success, /* 成功按钮 - 浅绿色 */\n    .a-danger, /* 危险按钮 - 浅粉色 */\n    .a-warning, /* 警告按钮 - 浅橙色 */\n    .a-info, /* 信息按钮 - 浅青色 */\n    .a-dark, /* 深色按钮 - 改为中等灰色(保持浅色系中的对比) */\n    .a-outline, /* 轮廓按钮 - 浅灰色边框 */\n    .a-disabled /* 禁用按钮 - 极浅灰色 */\n    {\n        display: inline-flex;\n        align-items: center;\n        justify-content: center;\n        padding: 6px 14px;\n        margin-left: 10px;\n        border-radius: 6px;\n        text-decoration: none;\n        font-size: 13px;\n        font-weight: 500;\n        transition: all 0.2s ease;\n        cursor: pointer;\n        border: 1px solid rgba(0, 0, 0, 0.08);\n        white-space: nowrap;\n    }\n    \n    .a-primary {\n        background: #e0f2fe;\n        color: #0369a1;\n        border-color: #bae6fd;\n    }\n    \n    .a-primary:hover {\n        background: #bae6fd;\n    }\n    \n    .a-success {\n        background: #dcfce7;\n        color: #166534;\n        border-color: #bbf7d0;\n    }\n    \n    .a-success:hover {\n        background: #bbf7d0;\n    }\n    \n    .a-danger {\n        background: #fee2e2;\n        color: #b91c1c;\n        border-color: #fecaca;\n    }\n    \n    .a-danger:hover {\n        background: #fecaca;\n    }\n    \n    .a-warning {\n        background: #ffedd5;\n        color: #9a3412;\n        border-color: #fed7aa;\n    }\n    \n    .a-warning:hover {\n        background: #fed7aa;\n    }\n    \n    .a-info {\n        background: #ccfbf1;\n        color: #0d9488;\n        border-color: #99f6e4;\n    }\n    \n    .a-info:hover {\n        background: #99f6e4;\n    }\n    \n    .a-dark {\n        background: #e2e8f0;\n        color: #334155;\n        border-color: #cbd5e1;\n    }\n    \n    .a-dark:hover {\n        background: #cbd5e1;\n    }\n    \n    .a-outline {\n        background: transparent;\n        color: #64748b;\n        border-color: #cbd5e1;\n    }\n    \n    .a-outline:hover {\n        background: #f8fafc;\n    }\n    \n    .a-disabled {\n        background: #f1f5f9;\n        color: #94a3b8;\n        border-color: #e2e8f0;\n        cursor: not-allowed;\n    }\n    \n    .a-disabled:hover {\n        transform: none;\n        box-shadow: none;\n        background: #f1f5f9;\n    }\n</style>\n");
    insertStyle("\n<style>\n    /* 全局通用样式 */\n    .fr-btn {\n        float: right;\n        margin-left: 4px !important;\n    }\n    \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 !important;\n        min-width: 80px;\n        padding: 7px 12px;\n        border-radius: 4px;\n        color: white !important;\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    .do-hide {\n        display: none !important;\n    }\n</style>\n");
    _StorageManager_instances = new WeakSet;
    autoCleanup_fn = async function() {
        if (!window.location.hostname.includes("javdb")) return;
        (await this.forage.keys()).forEach((k => k.startsWith("SCORE_") && this.forage.removeItem(k)));
        const now = Date.now();
        try {
            const lastCleanupTime = await this.forage.getItem("lastCleanupTime");
            if (lastCleanupTime && now - lastCleanupTime < 864e5) return;
            const keys = await this.forage.keys();
            for (const key of keys) {
                if (this.interceptedKeys.includes(key)) continue;
                const storedData = await this.forage.getItem(key);
                if ("object" == typeof storedData && "expires" in storedData && "expiresStr" in storedData && Date.now() > storedData.expires) {
                    console.log("清理过期数据:", key);
                    await this.forage.removeItem(key);
                }
            }
            await this.forage.setItem("lastCleanupTime", now);
        } catch (error) {
            console.error("[自动清理失败]", error);
            await this.forage.setItem("lastCleanupTime", now);
        }
    };
    saveFilterItem_fn = async function(items, storageKey, itemName) {
        let itemList;
        if (Array.isArray(items)) itemList = [ ...items ]; else {
            itemList = await this.forage.getItem(storageKey) || [];
            if (itemList.includes(items)) {
                const errorMsg = `${items} ${itemName}已存在`;
                show.error(errorMsg);
                throw new Error(errorMsg);
            }
            itemList.push(items);
        }
        await this.forage.setItem(storageKey, itemList);
        return itemList;
    };
    let StorageManager = class _StorageManager {
        constructor() {
            __privateAdd(this, _StorageManager_instances);
            __publicField(this, "car_list_key", "car_list");
            __publicField(this, "filter_actor_key", "filter_actor");
            __publicField(this, "title_filter_keyword_key", "title_filter_keyword");
            __publicField(this, "review_filter_keyword_key", "review_filter_keyword");
            __publicField(this, "setting_key", "setting");
            __publicField(this, "auto_page_key", "autoPage");
            __publicField(this, "fold_category_key", "foldCategory");
            __publicField(this, "review_ts_key", "review_ts");
            __publicField(this, "review_sign_key", "review_sign");
            __publicField(this, "actress_prefix_key", "z_actress_");
            __publicField(this, "score_prefix_key", "z_score_");
            __publicField(this, "forage", localforage.createInstance({
                driver: localforage.INDEXEDDB,
                name: "JAV-JSH",
                version: 1,
                storeName: "appData"
            }));
            __publicField(this, "interceptedKeys", [ this.car_list_key, this.filter_actor_key, this.title_filter_keyword_key, this.review_filter_keyword_key, this.setting_key ]);
            if (_StorageManager.instance) throw new Error("LocalStorageManager已被实例化过了!");
            _StorageManager.instance = this;
            __privateMethod(this, _StorageManager_instances, autoCleanup_fn).call(this).then();
        }
        async saveFilterActor(keywords) {
            return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_actor_key, "演员");
        }
        async saveReviewFilterKeyword(keywords) {
            return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.review_filter_keyword_key, "评论关键词");
        }
        async saveTitleFilterKeyword(keywords) {
            return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.title_filter_keyword_key, "标题关键词");
        }
        async getFilterActorList() {
            return await this.forage.getItem(this.filter_actor_key) || [];
        }
        async getTitleFilterKeyword() {
            return await this.forage.getItem(this.title_filter_keyword_key) || [];
        }
        async getSetting(attribute = null, defaultVal) {
            const settingObj = await this.forage.getItem(this.setting_key) || {};
            if (null === attribute) return settingObj;
            const value = settingObj[attribute];
            return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || isNaN(Number(value)) ? value : Number(value) : defaultVal;
        }
        async saveSetting(settingObj) {
            await this.forage.setItem(this.setting_key, settingObj);
        }
        async getReviewFilterKeywordList() {
            return await this.forage.getItem(this.review_filter_keyword_key) || [];
        }
        async saveCar(carNum, url, actress, actionType) {
            if (!carNum) {
                show.error("番号为空!");
                throw new Error("番号为空!");
            }
            if (!url) {
                show.error("url为空!");
                throw new Error("url为空!");
            }
            url.includes("http") || (url = window.location.origin + url);
            const carList = await this.forage.getItem(this.car_list_key) || [];
            let carData = carList.find((item => item.carNum === carNum));
            if (carData) carData.createDate = utils.getNowStr(); else {
                carData = {
                    carNum: carNum,
                    url: url,
                    actress: actress,
                    status: "",
                    createDate: utils.getNowStr()
                };
                carList.push(carData);
            }
            switch (actionType) {
              case Status_FILTER:
                if (carData.status === Status_FILTER) {
                    const msg2 = `${carNum} 已在屏蔽列表中`;
                    show.error(msg2);
                    throw new Error(msg2);
                }
                carData.status = Status_FILTER;
                break;

              case Status_FAVORITE:
                if (carData.status === Status_FAVORITE) {
                    const msg2 = `${carNum} 已在收藏列表中`;
                    show.error(msg2);
                    throw new Error(msg2);
                }
                carData.status = Status_FAVORITE;
                break;

              case Status_HAS_DOWN:
                carData.status = Status_HAS_DOWN;
                break;

              default:
                const msg = "actionType错误";
                show.error(msg);
                throw new Error(msg);
            }
            await this.forage.setItem(this.car_list_key, carList);
        }
        async getCarList() {
            return (await this.forage.getItem(this.car_list_key) || []).sort(((a, b) => {
                if (!a || !b) return 0;
                const dateA = a.createDate ? new Date(a.createDate).getTime() : 0;
                return (b.createDate ? new Date(b.createDate).getTime() : 0) - dateA;
            }));
        }
        async getCar(carNum) {
            return (await this.getCarList()).find((item => item.carNum === carNum));
        }
        async removeCar(carNum) {
            const carList = await this.getCarList(), initialLength = carList.length, updatedList = carList.filter((car => car.carNum !== carNum));
            if (updatedList.length === initialLength) {
                show.error(`${carNum} 不存在`);
                return !1;
            }
            await this.forage.setItem(this.car_list_key, updatedList);
            return !0;
        }
        async overrideCarList(newList) {
            if (!Array.isArray(newList)) throw new TypeError("必须传入数组类型数据");
            const invalidItems = newList.filter((item => !item || "object" != typeof item || !item.carNum));
            if (invalidItems.length > 0) throw new Error(`缺少必要字段 carNum 的数据项: ${invalidItems.length} 条`);
            const carNums = new Set, duplicates = newList.filter((item => {
                if (carNums.has(item.carNum)) return !0;
                carNums.add(item.carNum);
                return !1;
            }));
            if (duplicates.length > 0) throw new Error(`发现重复: ${duplicates.slice(0, 3).map((d => d.carNum)).join(", ")}${duplicates.length > 3 ? "..." : ""}`);
            await this.forage.setItem(this.car_list_key, newList);
        }
        async getItem(key) {
            if (this.interceptedKeys.includes(key)) {
                let errorMsg = `危险操作, 改key已有方法实现获取, 请用内部方法调用!  key: ${key}`;
                show.error(errorMsg);
                throw new Error(errorMsg);
            }
            const storedData = await this.forage.getItem(key);
            if (null == storedData) return null;
            if ("object" == typeof storedData && "expires" in storedData && "expiresStr" in storedData) {
                if (Date.now() > storedData.expires) {
                    await this.forage.removeItem(key);
                    return null;
                }
                return storedData.value;
            }
            return storedData;
        }
        async setItem(key, value, maxAge = null) {
            if (this.interceptedKeys.includes(key)) {
                let errorMsg = `危险操作, 改key已有方法实现获取, 请用内部方法调用!  key: ${key}`;
                show.error(errorMsg);
                throw new Error(errorMsg);
            }
            let data = value;
            if (null !== maxAge) {
                const expires = Date.now() + maxAge;
                data = {
                    value: value,
                    expires: expires,
                    expiresStr: utils.formatDate(new Date(expires))
                };
            }
            return await this.forage.setItem(key, data);
        }
        async removeItem(key) {
            if (this.interceptedKeys.includes(key)) {
                let errorMsg = `危险操作, 改key不可删除!  key: ${key}`;
                show.error(errorMsg);
                throw new Error(errorMsg);
            }
            return await this.forage.removeItem(key);
        }
        async importData(dataJson) {
            let arrayData = dataJson.filterKeywordList;
            Array.isArray(arrayData) && await this.forage.setItem(this.title_filter_keyword_key, arrayData);
            arrayData = dataJson.filterActorList;
            Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
            arrayData = dataJson.reviewKeywordList;
            Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
            dataJson.dataList && await this.overrideCarList(dataJson.dataList);
            arrayData = dataJson[this.title_filter_keyword_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.title_filter_keyword_key, arrayData);
            arrayData = dataJson[this.filter_actor_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
            arrayData = dataJson[this.review_filter_keyword_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
            dataJson[this.car_list_key] && await this.overrideCarList(dataJson[this.car_list_key]);
            dataJson.setting && await this.saveSetting(dataJson.setting);
        }
        async exportData() {
            return {
                car_list: await this.getCarList(),
                filter_actor: await this.getFilterActorList(),
                title_filter_keyword: await this.getTitleFilterKeyword(),
                review_filter_keyword: await this.getReviewFilterKeywordList(),
                setting: await this.getSetting()
            };
        }
    };
    class Utils {
        constructor() {
            __publicField(this, "intervalContainer", {});
            __publicField(this, "insertStyle", (css => {
                if (css) {
                    -1 === css.indexOf("<style>") && (css = "<style>" + css + "</style>");
                    $("head").append(css);
                }
            }));
            Utils.instance || (Utils.instance = this);
            return Utils.instance;
        }
        importResource(url) {
            let tag;
            if (url.indexOf("css") >= 0) {
                tag = document.createElement("link");
                tag.setAttribute("rel", "stylesheet");
                tag.href = url;
            } else {
                tag = document.createElement("script");
                tag.setAttribute("type", "text/javascript");
                tag.src = url;
            }
            document.documentElement.appendChild(tag);
        }
        openPage(url, title, shadeClose, event2) {
            shadeClose || (shadeClose = !0);
            if (event2 && (event2.ctrlKey || event2.metaKey)) window.open(url); else {
                url.includes("?") ? url += "&hideNav=1" : url += "?hideNav=1";
                layer.open({
                    type: 2,
                    title: title,
                    content: url,
                    scrollbar: !1,
                    shadeClose: shadeClose,
                    area: [ "80%", "90%" ],
                    isOutAnim: !1,
                    anim: -1
                });
            }
        }
        closePage() {
            parent.document.documentElement.style.overflow = "auto";
            [ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
                parent.document.querySelectorAll(selector).forEach((function(el) {
                    el.parentNode.removeChild(el);
                }));
            }));
            window.close();
        }
        loopDetector(condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = !0) {
            let run = !1;
            const uuid = Math.random(), start = (new Date).getTime();
            this.intervalContainer[uuid] = setInterval((() => {
                if ((new Date).getTime() - start > timeout) {
                    console.warn("loopDetector timeout!", condition, after);
                    run = runWhenTimeout;
                }
                if (condition() || run) {
                    clearInterval(this.intervalContainer[uuid]);
                    after && after();
                    delete this.intervalContainer[uuid];
                }
            }), detectInterval);
        }
        rightClick(element, callback) {
            if (element) {
                element.jquery ? element = element.toArray() : element instanceof HTMLElement ? element = [ element ] : Array.isArray(element) || (element = [ element ]);
                element && 0 !== element.length ? element.forEach((el => {
                    el && el.addEventListener("contextmenu", (event2 => {
                        callback(event2);
                    }));
                })) : console.error("rightClick(), 找不到元素");
            }
        }
        q(event2, msg, fun, cancelFun) {
            let x, y;
            if (event2) {
                x = event2.clientX - 130;
                y = event2.clientY - 120;
            } else {
                x = window.innerWidth / 2 - 120;
                y = window.innerHeight / 2 - 120;
            }
            let confirmIndex = layer.confirm(msg, {
                offset: [ y, x ],
                title: "提示",
                btn: [ "确定", "取消" ],
                zIndex: 999999991
            }, (function() {
                fun();
                layer.close(confirmIndex);
            }), (function() {
                cancelFun && cancelFun();
            }));
        }
        getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) {
            let now;
            now = dateString ? new Date(dateString) : new Date;
            const year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, "0"), day = String(now.getDate()).padStart(2, "0"), hours = String(now.getHours()).padStart(2, "0"), minutes = String(now.getMinutes()).padStart(2, "0"), seconds = String(now.getSeconds()).padStart(2, "0");
            return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
        }
        formatDate(date, dateSplitStr = "-", timeSplitStr = ":") {
            let targetDate;
            if (date instanceof Date) targetDate = date; else {
                if ("string" != typeof date) throw new Error("Invalid date input: must be Date object or date string");
                targetDate = new Date(date);
                if (isNaN(targetDate.getTime())) throw new Error("Invalid date string");
            }
            const year = targetDate.getFullYear(), month = String(targetDate.getMonth() + 1).padStart(2, "0"), day = String(targetDate.getDate()).padStart(2, "0"), hours = String(targetDate.getHours()).padStart(2, "0"), minutes = String(targetDate.getMinutes()).padStart(2, "0"), seconds = String(targetDate.getSeconds()).padStart(2, "0");
            return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
        }
        download(data, fileName) {
            const blob = new Blob([ data ], {
                type: "application/json"
            }), url = URL.createObjectURL(blob), a = document.createElement("a");
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            setTimeout((() => {
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }), 100);
        }
        smoothScrollToTop(duration = 500) {
            return new Promise((resolve => {
                const start = performance.now(), startPosition = window.pageYOffset;
                window.requestAnimationFrame((function scrollStep(timestamp) {
                    const elapsed = timestamp - start, progress = Math.min(elapsed / duration, 1), easeInOutCubic = progress < .5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
                    window.scrollTo(0, startPosition * (1 - easeInOutCubic));
                    progress < 1 ? window.requestAnimationFrame(scrollStep) : resolve();
                }));
            }));
        }
        simpleId() {
            return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
        }
        log(...data) {
            console.groupCollapsed("📌", ...data);
            const stackLines = (new Error).stack.split("\n").slice(2).map((line => line.trim())).filter((line => line.trim()));
            console.log(stackLines.join("\n"));
            console.groupEnd();
        }
        isUrl(urlString) {
            try {
                new URL(urlString);
                return !0;
            } catch (_) {
                return !1;
            }
        }
    }
    window.utils = new Utils;
    window.http = new class {
        get(url, params = {}, headers = {}) {
            return this.jqueryRequest("GET", url, null, params, headers);
        }
        post(url, data = {}, headers = {}) {
            return this.jqueryRequest("POST", url, data, null, headers);
        }
        put(url, data = {}, headers = {}) {
            return this.jqueryRequest("PUT", url, data, null, headers);
        }
        del(url, params = {}, headers = {}) {
            return this.jqueryRequest("DELETE", url, null, params, headers);
        }
        jqueryRequest(method, url, data = {}, params = {}, headers = {}) {
            "POST" === method && (headers = {
                "Content-Type": "application/json",
                ...headers
            });
            return new Promise(((resolve, reject) => {
                $.ajax({
                    method: method,
                    url: url,
                    data: "GET" === method || "DELETE" === method ? params : JSON.stringify(data),
                    headers: headers,
                    success: (response, textStatus, xhr) => {
                        var _a;
                        if (null == (_a = xhr.getResponseHeader("Content-Type")) ? void 0 : _a.includes("application/json")) try {
                            resolve("object" == typeof response ? response : JSON.parse(response));
                        } catch (e) {
                            resolve(response);
                        } else resolve(response);
                    },
                    error: (xhr, textStatus, errorThrown) => {
                        let errorMsg = errorThrown;
                        if (xhr.responseText) try {
                            const errorResponse = JSON.parse(xhr.responseText);
                            errorMsg = errorResponse.message || errorResponse.msg || xhr.responseText;
                        } catch {
                            errorMsg = xhr.responseText;
                        }
                        reject(new Error(errorMsg));
                    }
                });
            }));
        }
    };
    window.gmHttp = new class {
        get(url, params = {}, headers = {}) {
            return this.gmRequest("GET", url, null, params, headers);
        }
        post(url, data = {}, headers = {}) {
            return this.gmRequest("POST", url, data, null, headers);
        }
        put(url, data = {}, headers = {}) {
            return this.gmRequest("PUT", url, data, null, headers);
        }
        del(url, params = {}, headers = {}) {
            return this.gmRequest("DELETE", url, null, params, headers);
        }
        gmRequest(method, url, data = {}, params = {}, headers = {}) {
            if (("GET" === method || "DELETE" === method) && params && Object.keys(params).length) {
                const queryString = new URLSearchParams(params).toString();
                url += (url.includes("?") ? "&" : "?") + queryString;
            }
            "POST" !== method && "PUT" !== method || (headers = {
                "Content-Type": "application/json",
                ...headers
            });
            return new Promise(((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: method,
                    url: url,
                    headers: headers,
                    data: "POST" === method || "PUT" === method ? JSON.stringify(data) : void 0,
                    onload: response => {
                        var _a;
                        try {
                            if (response.status >= 200 && response.status < 300) if (response.responseText && (null == (_a = response.responseHeaders) ? void 0 : _a.toLowerCase().includes("application/json"))) try {
                                resolve(JSON.parse(response.responseText));
                            } catch (e) {
                                resolve(response.responseText);
                            } else resolve(response.responseText || response); else if (response.responseText) try {
                                const errorData = JSON.parse(response.responseText);
                                reject(errorData);
                            } catch {
                                reject(new Error(response.responseText || `HTTP Error ${response.status}`));
                            } else reject(new Error(`HTTP Error ${response.status}`));
                        } catch (e) {
                            reject(e);
                        }
                    },
                    onerror: error => {
                        reject(new Error(error.error || "Network Error"));
                    },
                    ontimeout: () => {
                        reject(new Error("Request Timeout"));
                    }
                });
            }));
        }
    };
    window.storageManager = new StorageManager;
    const channel = new BroadcastChannel("channel-refresh");
    window.refresh = function() {
        channel.postMessage({
            type: "refresh"
        });
    };
    !function() {
        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 container = document.createElement("div");
            container.className = "loading-container";
            const animation = document.createElement("div");
            animation.className = "loading-animation";
            container.appendChild(animation);
            document.body.appendChild(container);
            return {
                close: () => {
                    container && container.parentNode && container.parentNode.removeChild(container);
                }
            };
        };
    }();
    !function() {
        document.head.insertAdjacentHTML("beforeend", "\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                overflow: hidden;\n                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);\n                margin: 0 auto; /* 表格整体水平居中 */\n            }\n    \n            .data-table thead tr {\n                background: #f8fafc;\n            }\n            \n            /* 表头居中 */\n            .data-table th {\n                padding: 16px 20px;\n                text-align: center !important; /* 表头文字居中 */\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            /* 单元格内容居中 */\n            .data-table td {\n                padding: 14px 20px;\n                color: #334155;\n                font-size: 15px;\n                border-bottom: 1px solid #f1f5f9;\n                text-align: center !important; /* 单元格文字居中 */\n                vertical-align: middle; /* 垂直居中 */\n            }\n            \n            .data-table tbody tr:last-child td {\n                border-bottom: none;\n            }\n            \n            /* 行hover 变色*/\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            /* 可选:特定列左对齐/右对齐的示例 */\n            .data-table .text-left {\n                text-align: left;\n            }\n            \n            .data-table .text-right {\n                text-align: right;\n            }\n            \n            /* 添加.show-border时显示边框 */\n            .data-table.show-border {\n                border: 1px solid #e2e8f0;\n            }\n            \n            .data-table.show-border th,\n            .data-table.show-border td {\n            border: 1px solid #e2e8f0;\n        }\n        </style>\n    ");
        window.TableGenerator = class {
            constructor(options) {
                this.defaults = {
                    tableClass: "data-table",
                    showBorder: !1,
                    buttons: []
                };
                this.config = {
                    ...this.defaults,
                    ...options
                };
                this.validateConfig() && this.init();
            }
            validateConfig() {
                if (!(this.config.containerId && this.config.columns && Array.isArray(this.config.columns) && Array.isArray(this.config.data))) {
                    console.error("缺少必要参数或参数类型不正确");
                    return !1;
                }
                this.container = document.getElementById(this.config.containerId);
                if (!this.container) {
                    console.error(`未找到ID为${this.config.containerId}的容器`);
                    return !1;
                }
                return !0;
            }
            init() {
                this.container.innerHTML = "";
                this.table = document.createElement("table");
                this.table.className = this.config.showBorder ? `${this.config.tableClass} show-border` : this.config.tableClass;
                this.createHeader();
                this.createBody();
                this.container.appendChild(this.table);
            }
            createHeader() {
                const thead = document.createElement("thead"), headerRow = document.createElement("tr");
                this.config.columns.forEach((column => {
                    const th = document.createElement("th");
                    th.textContent = column.title || column.key;
                    column.width && (th.style.width = column.width);
                    column.headerClass && (th.className = column.headerClass);
                    headerRow.appendChild(th);
                }));
                if (this.config.buttons && this.config.buttons.length > 0) {
                    const th = document.createElement("th");
                    th.textContent = "操作";
                    this.config.buttonColumnWidth && (th.style.width = this.config.buttonColumnWidth);
                    headerRow.appendChild(th);
                }
                thead.appendChild(headerRow);
                this.table.appendChild(thead);
            }
            createBody() {
                const tbody = document.createElement("tbody");
                0 === this.config.data.length ? this.renderEmptyData(tbody) : this.renderDataRows(tbody);
                this.table.appendChild(tbody);
            }
            renderEmptyData(tbody) {
                const tr = document.createElement("tr"), td = document.createElement("td");
                td.colSpan = this.config.columns.length + (this.config.buttons.length > 0 ? 1 : 0);
                td.textContent = "暂无数据";
                td.style.textAlign = "center";
                tr.appendChild(td);
                tbody.appendChild(tr);
            }
            renderDataRows(tbody) {
                this.config.data.forEach(((item, rowIndex) => {
                    const tr = document.createElement("tr");
                    this.renderDataCells(tr, item, rowIndex);
                    this.config.buttons && this.config.buttons.length > 0 && this.renderButtonCells(tr, item, rowIndex);
                    tbody.appendChild(tr);
                }));
            }
            renderDataCells(tr, item, rowIndex) {
                this.config.columns.forEach((column => {
                    const td = document.createElement("td");
                    column.render ? td.innerHTML = column.render(item, rowIndex) : td.textContent = item[column.key] || "";
                    column.cellClass && (td.className = column.cellClass);
                    tr.appendChild(td);
                }));
            }
            renderButtonCells(tr, item, rowIndex) {
                const td = document.createElement("td");
                this.config.buttons.forEach((button => {
                    const btn = document.createElement("a");
                    btn.textContent = button.text;
                    btn.className = button.class || "a-primary";
                    btn.addEventListener("click", (event2 => {
                        if (button.onClick) {
                            const paramLength = button.onClick.length;
                            3 === paramLength ? button.onClick(event2, item, rowIndex) : 2 === paramLength ? button.onClick(event2, item) : button.onClick(item);
                        }
                    }));
                    td.appendChild(btn);
                }));
                tr.appendChild(td);
            }
            update(newData) {
                this.config.data = newData;
                this.init();
            }
            getTableElement() {
                return this.table;
            }
        };
    }();
    !function() {
        const showMessage = (msg, type, gravityOrOptions, positionOrOptions, options) => {
            let finalOptions;
            if ("object" == typeof gravityOrOptions) finalOptions = gravityOrOptions; else {
                finalOptions = "object" == typeof positionOrOptions ? positionOrOptions : options || {};
                finalOptions.gravity = gravityOrOptions || "top";
                finalOptions.position = "string" == typeof positionOrOptions ? positionOrOptions : "center";
            }
            finalOptions.gravity && "center" !== finalOptions.gravity || (finalOptions.offset = {
                y: "calc(50vh - 150px)"
            });
            const colors_infoStart = "#60A5FA", colors_infoEnd = "#93C5FD", colors_successStart = "#10B981", colors_successEnd = "#6EE7B7", colors_errorStart = "#EF4444", colors_errorEnd = "#FCA5A5", commonStyles = {
                borderRadius: "12px",
                color: "white",
                padding: "12px 16px",
                boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
                minWidth: "150px",
                textAlign: "center",
                zIndex: 999999999
            }, defaultConfig = {
                text: msg,
                duration: 2e3,
                close: !1,
                gravity: "top",
                position: "center",
                style: {
                    info: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_infoStart}, ${colors_infoEnd})`
                    },
                    success: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_successStart}, ${colors_successEnd})`
                    },
                    error: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_errorStart}, ${colors_errorEnd})`
                    }
                }[type],
                stopOnFocus: !0,
                oldestFirst: !1,
                ...finalOptions
            };
            Toastify(defaultConfig).showToast();
        };
        window.show = {
            ok: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "success", gravityOrOptions, positionOrOptions, options);
            },
            error: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "error", gravityOrOptions, positionOrOptions, options);
            },
            info: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "info", gravityOrOptions, positionOrOptions, options);
            }
        };
    }();
    class PluginManager {
        constructor() {
            this.plugins = new Map;
        }
        register(pluginClass) {
            if ("function" != typeof pluginClass) throw new Error("插件必须是一个类");
            const name = pluginClass.name;
            if (!name) throw new Error("类必须要有名称");
            const lowerName = name.toLowerCase();
            if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`);
            const instance = new pluginClass;
            instance.pluginManager = this;
            this.plugins.set(lowerName, instance);
        }
        getBean(name) {
            return this.plugins.get(name.toLowerCase());
        }
        _getDependencies(func) {
            const fnStr = func.toString();
            return fnStr.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")")).split(",").map((arg => arg.trim())).filter((arg => arg));
        }
        async process() {
            const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name, instance]) => {
                try {
                    if ("function" == typeof instance.handle) {
                        const css = await instance.initCss();
                        utils.insertStyle(css);
                        await instance.handle();
                        return {
                            name: name,
                            status: "fulfilled"
                        };
                    }
                    console.log("加载插件", name);
                } catch (e) {
                    console.error(`插件 ${name} 执行失败`, e);
                    return {
                        name: name,
                        status: "rejected",
                        error: e
                    };
                }
            })))).filter((r => "rejected" === r.status));
            failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map((p => p.name)));
            document.body.classList.add("script-ready");
        }
    }
    class BasePlugin {
        constructor() {
            __publicField(this, "pluginManager", null);
        }
        getBean(name) {
            let bean = this.pluginManager.getBean(name);
            if (!bean) {
                let msg = "容器中不存在: " + name;
                show.error(msg);
                throw new Error(msg);
            }
            return bean;
        }
        async initCss() {
            return "";
        }
        async handle() {}
        getPageInfo() {
            let carNum, url, actress, actors, movieId, currentHref = window.location.href;
            if (isJavDb) {
                carNum = $('a[title="複製番號"]').attr("data-clipboard-text");
                url = currentHref.split("?")[0].split("#")[0];
                actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" ");
                actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" ");
                const parts = window.location.href.split("?")[0].split("/");
                movieId = parts[parts.length - 1].split("#")[0];
            }
            if (isJavBus) {
                url = currentHref.split("?")[0];
                carNum = url.split("/").filter(Boolean).pop();
                actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ");
                actors = "";
            }
            return {
                carNum: carNum,
                url: url,
                actress: actress,
                actors: actors,
                movieId: movieId
            };
        }
        getSelector() {
            return isJavDb ? Db : isJavBus ? Bus : null;
        }
    }
    class DetailPagePlugin extends BasePlugin {
        constructor() {
            super();
        }
        async initCss() {
            return window.isDetailPage && window.location.href.includes("hideNav=1") ? "\n                .main-nav,#search-bar-container {\n                    display: none !important;\n                }\n                \n                html {\n                    padding-top:0px!important;\n                }\n            " : "";
        }
        handle() {
            window.isDetailPage && this.checkFilterActor().then();
        }
        async checkFilterActor() {
            if (!window.isDetailPage) return;
            const filterActorList = await storageManager.getFilterActorList();
            let actors = this.getPageInfo().actors;
            filterActorList.forEach((item => {
                if (actors.indexOf(item) > -1) {
                    const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
                    detailPageButtonPlugin.answerCount++;
                    utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
                        detailPageButtonPlugin.filterOne(null, !0);
                    }));
                }
            }));
        }
    }
    class PreviewVideoPlugin extends BasePlugin {
        async initCss() {
            return "\n            .video-control-btn {\n                position: absolute;\n                bottom: 10px;\n                right: 10px;\n                z-index: 99999999999;\n                min-width:100px;\n                padding: 8px 16px;\n                background: rgba(0,0,0,0.7);\n                color: white;\n                border: none;\n                border-radius: 4px;\n                cursor: pointer;\n            }\n            .video-control-btn.active {\n                background-color: #1890ff; /* 选中按钮的背景色 */\n                color: white;             /* 选中按钮的文字颜色 */\n                font-weight: bold;        /* 加粗显示 */\n                border: 2px solid #096dd9; /* 边框样式 */\n            }\n        ";
        }
        handle() {
            let $preview = $(".preview-video-container");
            $preview.on("click", (event2 => {
                utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
                    this.handleVideo().then();
                }));
            }));
            utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
                $(".fancybox-content #preview-video").length > 0 && this.handleVideo().then();
            }));
            window.location.href.includes("autoPlay=1") && $preview[0].click();
        }
        async handleVideo() {
            const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $videoContainer = $videoEl.parent();
            if (!$videoEl.length || !$previewSource.length) return;
            const videoEl = $videoEl[0];
            videoEl.muted = !1;
            $videoContainer.css("position", "relative");
            const videoSrc = $previewSource.attr("src"), qualityLevels = [ "hhb", "hmb", "mhb", "mmb" ], currentQuality = qualityLevels.find((q => videoSrc.includes(q))) || "mhb", qualityOptions = [ {
                id: "video-mmb",
                text: "低画质",
                quality: "mmb"
            }, {
                id: "video-mhb",
                text: "中画质",
                quality: "mhb"
            }, {
                id: "video-hmb",
                text: "高画质",
                quality: "hmb"
            }, {
                id: "video-hhb",
                text: "超高清",
                quality: "hhb"
            } ];
            const cacheKey = `videoQualities_${this.getPageInfo().carNum}`;
            let availableQualities = JSON.parse(sessionStorage.getItem(cacheKey));
            if (!availableQualities) {
                availableQualities = (await Promise.all(qualityOptions.map((async option => {
                    const testSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), option.quality);
                    try {
                        return (await fetch(testSrc, {
                            method: "HEAD"
                        })).ok ? option : null;
                    } catch {
                        return null;
                    }
                })))).filter(Boolean);
                sessionStorage.setItem(cacheKey, JSON.stringify(availableQualities));
            }
            if (availableQualities.length <= 1) return;
            const buttonsHtml = availableQualities.map(((option, index) => `\n                <button class="video-control-btn${option.quality === currentQuality ? " active" : ""}" \n                        id="${option.id}" \n                        data-quality="${option.quality}"\n                        style="bottom: ${50 * index}px; right: -105px;">\n                    ${option.text}\n                </button>\n            `)).join("");
            $videoContainer.append(buttonsHtml);
            const $buttons = $videoContainer.find(".video-control-btn");
            $videoContainer.on("click", ".video-control-btn", (async e => {
                const $button = $(e.currentTarget), quality = $button.data("quality");
                if (!$button.hasClass("active")) try {
                    const newSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), quality);
                    $previewSource.attr("src", newSrc);
                    videoEl.load();
                    videoEl.muted = !1;
                    await videoEl.play();
                    $buttons.removeClass("active");
                    $button.addClass("active");
                } catch (error) {
                    console.error("切换画质失败:", error);
                }
            }));
            $buttons.last().trigger("click");
        }
    }
    const _HotkeyManager = class _HotkeyManager {
        constructor() {
            if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
        }
        static registerHotkey(hotkeyString, callback, keyupCallback = null) {
            if (Array.isArray(hotkeyString)) {
                let id_list = [];
                hotkeyString.forEach((hotkey => {
                    if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误");
                    let id = this.recordHotkey(hotkey, callback, keyupCallback);
                    id_list.push(id);
                }));
                return id_list;
            }
            if (!this.isHotkeyFormat(hotkeyString)) throw new Error("快捷键格式错误");
            return this.recordHotkey(hotkeyString, callback, keyupCallback);
        }
        static recordHotkey(hotkeyString, callback, keyupCallback) {
            let id = Math.random().toString(36).substr(2);
            this.registerHotKeyMap.set(id, {
                hotkeyString: hotkeyString,
                callback: callback,
                keyupCallback: keyupCallback
            });
            return id;
        }
        static unregisterHotkey(id) {
            this.registerHotKeyMap.has(id) && this.registerHotKeyMap.delete(id);
        }
        static isHotkeyFormat(hotkeyString) {
            return hotkeyString.toLowerCase().split("+").map((k => k.trim())).every((k => [ "ctrl", "shift", "alt" ].includes(k) || 1 === k.length));
        }
        static judgeHotkey(hotkeyString, event2) {
            const keyList = hotkeyString.toLowerCase().split("+").map((k => k.trim())), ctrl = keyList.includes("ctrl"), shift = keyList.includes("shift"), alt = keyList.includes("alt"), key = keyList.find((k => "ctrl" !== k && "shift" !== k && "alt" !== k));
            return (this.isMac ? event2.metaKey : event2.ctrlKey) === ctrl && event2.shiftKey === shift && event2.altKey === alt && event2.key.toLowerCase() === key;
        }
    };
    __publicField(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac"));
    __publicField(_HotkeyManager, "registerHotKeyMap", new Map);
    __publicField(_HotkeyManager, "handleKeydown", (event2 => {
        for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
            let hotkeyString = data.hotkeyString, callback = data.callback;
            _HotkeyManager.judgeHotkey(hotkeyString, event2) && callback(event2);
        }
    }));
    __publicField(_HotkeyManager, "handleKeyup", (event2 => {
        for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
            let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback;
            keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event2) && keyupCallback(event2));
        }
    }));
    let HotkeyManager = _HotkeyManager;
    document.addEventListener("keydown", (event2 => {
        HotkeyManager.handleKeydown(event2);
    }));
    document.addEventListener("keyup", (event2 => {
        HotkeyManager.handleKeyup(event2);
    }));
    class JavTrailersPlugin extends BasePlugin {
        constructor() {
            super();
            this.hasBand = !1;
        }
        handle() {
            let href = window.location.href;
            if (!href.includes("handle=1")) return;
            if ($("h1:contains('Page not found')").length) {
                let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
                window.location.href = "https://javtrailers.com/search/" + keyword + "?handle=1";
                return;
            }
            let findList = $(".videos-list .video-link").toArray();
            if (findList.length) {
                const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword)));
                if (matchedLink) {
                    window.location.href = $(matchedLink).attr("href") + "?handle=1";
                    return;
                }
            }
            this.handlePlayJavTrailers();
            $("#videoPlayerContainer").on("click", (() => {
                this.handlePlayJavTrailers();
            }));
            window.addEventListener("message", (event2 => {
                let videoEl = document.getElementById("vjs_video_3_html5_api");
                videoEl && (videoEl.currentTime += 5);
            }));
            HotkeyManager.registerHotkey("z", (() => {
                const videoEl = document.getElementById("vjs_video_3_html5_api");
                videoEl && (videoEl.currentTime += 5);
            }));
            HotkeyManager.registerHotkey("a", (() => window.parent.postMessage("a", "*")));
            HotkeyManager.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
        }
        handlePlayJavTrailers() {
            this.hasBand || utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
                setTimeout((() => {
                    this.hasBand = !0;
                    let videoEl = document.getElementById("vjs_video_3_html5_api");
                    videoEl.play();
                    videoEl.currentTime = 5;
                    videoEl.addEventListener("timeupdate", (function() {
                        videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.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 keyword = window.location.href.split("=")[1].toLowerCase();
            $(".sub-table tr td a").toArray().forEach((el => {
                let item = $(el);
                item.text().toLowerCase().includes(keyword) || item.parent().parent().hide();
            }));
        }
    }
    const apiUrl = "https://jdforrepam.com/api";
    async function buildSignature() {
        const curr = Math.floor(Date.now() / 1e3);
        if (curr - (await storageManager.getItem(storageManager.review_ts_key) || 0) <= 20) return await storageManager.getItem(storageManager.review_sign_key);
        const sign = `${curr}.lpw6vgqzsp.${md5(`${curr}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
        await storageManager.setItem(storageManager.review_ts_key, curr);
        await storageManager.setItem(storageManager.review_sign_key, sign);
        return sign;
    }
    const getReviews = async (movieId, pageNum = 1, pageSize = 20) => {
        let url = `${apiUrl}/v1/movies/${movieId}/reviews`, headers = {
            jdSignature: await buildSignature()
        };
        return (await http.get(url, {
            page: pageNum,
            sort_by: "hotly",
            limit: pageSize
        }, headers)).data.reviews;
    }, getMovieDetail = async movieId => {
        let url = `${apiUrl}/v4/movies/${movieId}`, headers = {
            jdSignature: await buildSignature()
        };
        const res = await http.get(url, null, headers);
        if (!res.data) {
            show.error("获取视频详情失败: " + res.message);
            throw new Error(res.message);
        }
        const movie = res.data.movie, preview_images = movie.preview_images, imgList = [];
        preview_images.forEach((item => {
            imgList.push(item.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
        }));
        return {
            movieId: movie.id,
            actors: movie.actors,
            title: movie.origin_title,
            carNum: movie.number,
            score: movie.score,
            releaseDate: movie.release_date,
            watchedCount: movie.watched_count,
            imgList: imgList
        };
    }, related = async (movieId, page = 1, limit = 20) => {
        let url = `${apiUrl}/v1/lists/related?movie_id=${movieId}&page=${page}&limit=${limit}`, headers = {
            jdSignature: await buildSignature()
        };
        const res = await gmHttp.get(url, null, headers), dataList = [];
        res.data.lists.forEach((item => {
            dataList.push({
                relatedId: item.id,
                name: item.name,
                movieCount: item.movies_count,
                collectionCount: item.collections_count,
                viewCount: item.views_count,
                createTime: utils.formatDate(item.created_at)
            });
        }));
        return dataList;
    };
    class Fc2Plugin extends BasePlugin {
        handle() {
            let fc2Url = "/advanced_search?type=3&score_min=3&d=1";
            $('.navbar-item:contains("FC2")').attr("href", fc2Url);
            $('.tabs a:contains("FC2")').attr("href", fc2Url);
            if (window.location.href.includes("collection_codes?movieId")) {
                const urlParams = new URLSearchParams(window.location.search);
                let movieId = urlParams.get("movieId"), carNum = urlParams.get("carNum"), url = urlParams.get("url");
                movieId && carNum && url && this.openFc2Page(movieId, carNum, url);
            }
        }
        async 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(movieId, carNum, href) {
            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                    <span id="data-actress" style="display: none"></span>\n                </div>\n            </div>\n        ',
                area: [ "80%", "90%" ],
                skin: "movie-detail-layer",
                scrollbar: !1,
                success: (layero, index) => {
                    this.loadData(movieId, carNum);
                    $("#favoriteBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum, href, actress, Status_FAVORITE);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#filterBtn").on("click", (event2 => {
                        utils.q(event2, `是否屏蔽${carNum}?`, (async () => {
                            const actress = $("#data-actress").text();
                            await storageManager.saveCar(carNum, href, actress, Status_FILTER);
                            window.refresh();
                            layer.closeAll();
                            window.location.href.includes("collection_codes?movieId") && utils.closePage();
                        }));
                    }));
                    $("#hasDownBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum, href, actress, Status_HAS_DOWN);
                        window.refresh();
                        layer.closeAll();
                    }));
                },
                end() {
                    window.location.href.includes("collection_codes?movieId") && utils.closePage();
                }
            });
        }
        loadData(movieId, carNum) {
            this.handleVideo(carNum.replace("FC2-", ""));
            this.handleMovieDetail(movieId);
            this.handleMagnets(movieId);
            this.getBean("reviewPlugin").showReview(movieId, $("#reviews-content")).then();
        }
        handleMovieDetail(movieId) {
            getMovieDetail(movieId).then((res => {
                const actors = res.actors || [], imgList = res.imgList || [];
                let actorsHtml = "";
                if (actors.length > 0) {
                    let actress = "";
                    for (let i = 0; i < actors.length; i++) {
                        let actor = actors[i];
                        actorsHtml += `<span class="actor-tag"><a href="/actors/${actor.id}" target="_blank">${actor.name}</a></span>`;
                        0 === actor.gender && (actress += actor.name);
                    }
                    $("#data-actress").text(actress);
                } else actorsHtml = '<span class="no-data">暂无演员信息</span>';
                let imagesHtml = "";
                imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => `\n                <a href="${img}" data-fancybox="movie-gallery" data-caption="剧照 ${index + 1}">\n                    <img src="${img}" class="movie-image-thumb"  alt=""/>\n                </a>\n            `)).join("") : '<div class="no-data">暂无剧照</div>';
                $(".movie-info-container").html(`\n                <h3 class="movie-title">${res.title || "无标题"}</h3>\n                <div class="movie-meta">\n                    <span>番号: ${res.carNum || "未知"}</span>\n                    <span>年份: ${res.releaseDate || "未知"}</span>\n                    <span>评分: ${res.score || "无"}</span>\n                </div>\n                <div class="movie-actors">\n                    <div class="actor-list">主演: ${actorsHtml}</div>\n                </div>\n                <div class="movie-gallery" style="margin-top:10px">\n                    <h4>剧照: </h4>\n                    <div class="image-list">${imagesHtml}</div>\n                </div>\n            `);
            })).catch((err => {
                console.error(err);
                $(".movie-info-container").html(`\n                <div class="movie-error">加载失败: ${err.message}</div>\n            `);
            }));
        }
        handleMagnets(movieId) {
            (async movieId => {
                let url = `${apiUrl}/v1/movies/${movieId}/magnets`, headers = {
                    jdSignature: await buildSignature()
                };
                return (await http.get(url, null, headers)).data.magnets;
            })(movieId).then((magnetList => {
                let magnetsHtml = "";
                if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) {
                    let magnet = magnetList[i], oddClass = "";
                    i % 2 == 0 && (oddClass = "odd");
                    magnetsHtml += `\n                        <div class="item columns is-desktop ${oddClass}">\n                            <div class="magnet-name column is-four-fifths">\n                                <a href="magnet:?xt=urn:btih:${magnet.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n                                    <span class="name">${magnet.name}</span>\n                                    <br>\n                                    <span class="meta">\n                                        ${(magnet.size / 1024).toFixed(2)}GB, ${magnet.files_count}個文件 \n                                     </span>\n                                    <br>\n                                    <div class="tags">\n                                        ${magnet.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n                                        ${magnet.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:${magnet.hash}" type="button">&nbsp;複製&nbsp;</button>\n                            </div>\n                            <div class="date column"><span class="time">${magnet.created_at}</span></div>\n                        </div>\n                    `;
                } else magnetsHtml = '<span class="no-data">暂无磁力信息</span>';
                $("#magnets-content").html(magnetsHtml);
            })).catch((err => {
                console.error(err);
                $("#magnets-content").html(`\n                <div class="movie-error">加载失败: ${err.message}</div>\n            `);
            }));
        }
        handleVideo(searchKeyword) {
            (async carNum => {
                let url = `https://hohoj.tv/search?text=${carNum}`, html = await gmHttp.get(url), pageUrl = null;
                if (html.includes("找不到任何影片")) return pageUrl;
                const doc = (new DOMParser).parseFromString(html, "text/html");
                $(doc).find(".video-item a").toArray().forEach((item => {
                    if ($(item).find(".video-item-title").text().includes(carNum)) {
                        let pageId = $(item).attr("href").split("id=")[1];
                        pageUrl = "https://hohoj.tv/embed?id=" + pageId;
                    }
                }));
                return pageUrl;
            })(searchKeyword).then((pageUrl => {
                const moviePosterContainer = document.querySelector(".movie-poster-container"), iframe = document.querySelector(".movie-trailer");
                if (pageUrl) $(iframe).attr("src", pageUrl); else {
                    moviePosterContainer.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-${searchKeyword}" target="_blank">missav</a></p>\n                    </div>\n                `;
                    iframe.style.display = "none";
                }
            }));
        }
    }
    class FoldCategoryPlugin extends BasePlugin {
        async handle() {
            if (!window.isListPage) return;
            let $subTags, $topTabs = $(".tabs ul");
            if ($topTabs.length > 0) {
                $subTags = $("#tags");
                let checkTagStr = $("#tags dl div.tag.is-info").map((function() {
                    return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
                })).get().join(" ");
                if (!checkTagStr) return;
                $topTabs.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></span>\n                        <i style="margin-left: 10px"></i>\n                    </a>\n                </li>\n            ');
                $(".tabs").append(`<div style="padding-top:10px"><span>已选分类: ${checkTagStr}</span></div>`);
            }
            let $section = $("h2.section-title");
            if ($section.length > 0) {
                $section.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></span>\n                        <i style="margin-left: 10px"></i>\n                    </a>\n                </div>\n            ');
                $subTags = $("section > div > div.box");
            }
            if (!$subTags) return;
            let $foldCategoryBtn = $("#foldCategoryBtn"), isFolded = "yes" === await storageManager.getItem(storageManager.fold_category_key), [newText, newIcon] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
            $foldCategoryBtn.find("span").text(newText).end().find("i").attr("class", newIcon);
            window.location.href.includes("noFold=1") || $subTags[isFolded ? "hide" : "show"]();
            $foldCategoryBtn.on("click", (async event2 => {
                event2.preventDefault();
                isFolded = !isFolded;
                await storageManager.setItem(storageManager.fold_category_key, isFolded ? "yes" : "no");
                const [newText2, newIcon2] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
                $foldCategoryBtn.find("span").text(newText2).end().find("i").attr("class", newIcon2);
                $subTags[isFolded ? "hide" : "show"]();
            }));
        }
    }
    class ActressInfoPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "apiUrl", "https://ja.wikipedia.org/wiki/");
        }
        handle() {
            this.handleDetailPage().then();
            this.handleStarPage().then();
        }
        async initCss() {
            return "\n            <style>\n                .info-tag {\n                    background-color: #ecf5ff;\n                    display: inline-block;\n                    height: 32px;\n                    padding: 0 10px;\n                    line-height: 30px;\n                    font-size: 12px;\n                    color: #409eff;\n                    border: 1px solid #d9ecff;\n                    border-radius: 4px;\n                    box-sizing: border-box;\n                    white-space: nowrap;\n                }\n            </style>\n        ";
        }
        async handleDetailPage() {
            let nameList = $(".female").prev().map(((i, el) => $(el).text().trim())).get();
            if (!nameList.length) return;
            let result = null, infoHtml = "";
            for (let i = 0; i < nameList.length; i++) {
                let name = nameList[i];
                result = await storageManager.getItem(storageManager.actress_prefix_key + name);
                if (!result) try {
                    result = await this.searchInfo(name);
                    result && await storageManager.setItem(storageManager.actress_prefix_key + name, result, 2592e6);
                } catch (e) {
                    console.error("该名称查询失败,尝试其它名称");
                }
                let contentHtml = "";
                contentHtml = result ? `\n                    <div class="panel-block">\n                        <strong>${name}:</strong>\n                        <a href="${result.url}" style="margin-left: 5px" target="_blank">\n                            <span class="info-tag">${result.birthday} ${result.age}</span>\n                            <span class="info-tag">${result.height} ${result.weight}</span>\n                            <span class="info-tag">${result.threeSizeText} ${result.braSize}</span>\n                        </a>\n                    </div>\n                ` : `<div class="panel-block"><a href="${this.apiUrl + name}" target="_blank"><strong>${name}:</strong></a></div> `;
                infoHtml += contentHtml;
            }
            $('strong:contains("演員")').parent().after(infoHtml);
        }
        async handleStarPage() {
            let nameList = [], $actor = $(".actor-section-name");
            $actor.length && $actor.text().trim().split(",").forEach((name => {
                nameList.push(name.trim());
            }));
            let $sectionMeta = $(".section-meta:not(:contains('影片'))");
            $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach((name => {
                nameList.push(name.trim());
            }));
            if (!nameList.length) return;
            let result = null;
            for (let i = 0; i < nameList.length; i++) {
                let name = nameList[i];
                result = await storageManager.getItem(storageManager.actress_prefix_key + name);
                if (result) break;
                try {
                    result = await this.searchInfo(name);
                } catch (e) {
                    console.error("该名称查询失败,尝试其它名称");
                }
                if (result) break;
            }
            result && nameList.forEach((name => {
                storageManager.setItem(storageManager.actress_prefix_key + name, result, 2592e6);
            }));
            let contentHtml = '<div style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>';
            result && (contentHtml = `\n                <a href="${result.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;">出生日期: ${result.birthday}</span>\n                            <span style="width: 200px;">年龄: ${result.age}</span>\n                            <span style="width: 200px;">身高: ${result.height}</span>\n                        </div>\n                        <div style="display: flex; margin-bottom: 10px;">\n                            <span style="width: 300px;">体重: ${result.weight}</span>\n                            <span style="width: 200px;">三围: ${result.threeSizeText}</span>\n                            <span style="width: 200px;">罩杯: ${result.braSize}</span>\n                        </div>\n                    </div>\n                </a>\n            `);
            $actor.parent().append(contentHtml);
        }
        async searchInfo(name) {
            "三上悠亞" === name && (name = "三上悠亜");
            let url = this.apiUrl + name;
            const html = await gmHttp.get(url), parser = new DOMParser, $dom = $(parser.parseFromString(html, "text/html"));
            let birthday = $dom.find('tr:has(a[title="誕生日"]) td').text().trim(), age = $dom.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt($dom.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", height = $dom.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", weight = $dom.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim();
            "― kg" === weight && (weight = "");
            return {
                birthday: birthday,
                age: age,
                height: height,
                weight: weight,
                threeSizeText: $dom.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(),
                braSize: $dom.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(),
                url: url
            };
        }
    }
    class AliyunPanPlugin extends BasePlugin {
        handle() {
            $("body").append('<a class="a-success" id="refresh-token-btn" style="position:fixed; right: 0; top:50%;z-index:99999">获取refresh_token</a>');
            $("#refresh-token-btn").on("click", (event2 => {
                let tokenStr = localStorage.getItem("token");
                if (!tokenStr) {
                    alert("请先登录!");
                    return;
                }
                let refresh_token = JSON.parse(tokenStr).refresh_token;
                navigator.clipboard.writeText(refresh_token).then((() => {
                    alert("已复制到剪切板 如失败, 请手动复制: " + refresh_token);
                })).catch((err => {
                    console.error("Failed to copy refresh token: ", err);
                }));
            }));
        }
    }
    class HitShowPlugin extends BasePlugin {
        constructor() {
            super();
        }
        handle() {
            $('a[href*="rankings/playback"]').on("click", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                window.location.href = "/?handlePlayback=1&period=daily";
            }));
            this.handlePlayback().then();
        }
        async handlePlayback() {
            if (!window.location.href.includes("handlePlayback=1")) return;
            let period = new URLSearchParams(window.location.search).get("period");
            this.toolBar(period);
            let $movieBox = $(".movie-list");
            $movieBox.html("");
            let loadObj = loading();
            try {
                const movies = await (async (period = "daily", filter_by = "high_score") => {
                    let url = `${apiUrl}/v1/rankings/playback?period=${period}&filter_by=${filter_by}`, headers = {
                        jdSignature: await buildSignature()
                    };
                    return (await http.get(url, null, headers)).data.movies;
                })(period);
                let moviesHtml = this.markDataListHtml(movies);
                $movieBox.html(moviesHtml);
                window.refresh();
                this.loadScore(movies);
            } finally {
                loadObj.close();
            }
        }
        toolBar(period) {
            $(".pagination").remove();
            $(".main-tabs ul li").removeClass("is-active");
            $(".main-tabs ul li:first").addClass("is-active");
            let conditionHtml = `\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" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=daily">日榜</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=weekly">周榜</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=monthly">月榜</a>\n                </div>\n            </div>\n        `;
            $(".toolbar").html(conditionHtml);
        }
        getStarRating(score) {
            let stars = "";
            const fullStars = Math.floor(score);
            for (let i = 0; i < fullStars; i++) stars += '<i class="icon-star"></i>';
            for (let i = 0; i < 5 - fullStars; i++) stars += '<i class="icon-star gray"></i>';
            return stars;
        }
        loadScore(movies) {
            if (0 === movies.length) return;
            (async () => {
                const errors = [];
                for (const movie of movies) try {
                    const movieId = movie.id;
                    if ($(`#${movieId}`).is(":hidden")) continue;
                    const cached = await storageManager.getItem(storageManager.score_prefix_key + movieId);
                    if (cached) {
                        this.appendScoreHtml(movieId, cached);
                        continue;
                    }
                    for (;!document.hasFocus(); ) await new Promise((r => setTimeout(r, 500)));
                    const res = await getMovieDetail(movieId);
                    let score = res.score, watchedCount = res.watchedCount, html = `\n                        <span class="value">\n                            <span class="score-stars">${this.getStarRating(score)}</span> \n                            &nbsp; ${score}分,由${watchedCount}人評價\n                        </span>\n                    `;
                    this.appendScoreHtml(movieId, html);
                    await storageManager.setItem(storageManager.score_prefix_key + movieId, html, 6048e5);
                    await new Promise((r => setTimeout(r, 1e3)));
                } catch (err) {
                    errors.push({
                        carNum: movie.number,
                        error: err.message,
                        stack: err.stack
                    });
                    console.error(`🚨 解析评分数据失败 | 编号: ${movie.number}\n`, `错误详情: ${err.message}\n`, err.stack ? `调用栈:\n${err.stack}` : "");
                }
                if (errors.length > 0) {
                    show.error("解析评分数据失败, 个数:", errors.length);
                    console.table(errors);
                }
            })();
        }
        appendScoreHtml(movieId, scoreHtml) {
            let $scoreBox = $(`#score_${movieId}`);
            "" === $scoreBox.html().trim() && $scoreBox.slideUp(0, (function() {
                $(this).html(scoreHtml).slideDown(500);
            }));
        }
        markDataListHtml(movies) {
            let moviesHtml = "";
            movies.forEach((movie => {
                moviesHtml += `\n                <div class="item" id="${movie.id}">\n                    <a href="/v/${movie.id}" class="box" title="${movie.origin_title}">\n                        <div class="cover ">\n                            <img loading="lazy" src="${movie.cover_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt="">\n                        </div>\n                        <div class="video-title"><strong>${movie.number}</strong> ${movie.origin_title}</div>\n                        <div class="score" id="score_${movie.id}">\n                        </div>\n                        <div class="meta">\n                            ${movie.release_date}\n                        </div>\n                        <div class="tags has-addons">\n                           ${movie.has_cnsub ? '<span class="tag is-warning">含中字磁鏈</span>' : movie.magnets_count > 0 ? '<span class="tag is-success">含磁鏈</span>' : '<span class="tag is-info">无磁鏈</span>'}\n                           ${movie.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""}\n                        </div>\n                    </a>\n                </div>\n            `;
            }));
            return moviesHtml;
        }
    }
    class TOP250Plugin extends BasePlugin {
        constructor() {
            super();
            __publicField(this, "has_cnsub", "");
            __publicField(this, "movies", []);
        }
        handle() {
            $('.main-tabs ul li:contains("猜你喜歡")').html('<a href="/rankings/top"><span>Top250</span></a>');
            $('a[href*="rankings/top"]').on("click", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $target = $(event2.target), href = ($target.is("a") ? $target : $target.closest("a")).attr("href");
                let queryString = href.includes("?") ? href.split("?")[1] : href;
                const urlParams = new URLSearchParams(queryString);
                this.checkLogin(event2, urlParams);
            }));
            this.handleTop().then();
        }
        async handleTop() {
            if (!window.location.href.includes("handleTop=1")) return;
            const urlParams = new URLSearchParams(window.location.search);
            let type = urlParams.get("type") || "all", type_value = urlParams.get("type_value") || "";
            this.has_cnsub = urlParams.get("has_cnsub") || "";
            let page = urlParams.get("page") || 1;
            this.toolBar(type, type_value, page);
            let $movieBox = $(".movie-list");
            $movieBox.html("");
            let loadObj = loading();
            try {
                const res = await (async (type = "all", type_value = "", page = 1, limit = 40) => {
                    let url = `${apiUrl}/v1/movies/top?start_rank=1&type=${type}&type_value=${type_value}&ignore_watched=false&page=${page}&limit=${limit}`, headers = {
                        "user-agent": "Dart/3.5 (dart:io)",
                        "accept-language": "zh-TW",
                        host: "jdforrepam.com",
                        authorization: "Bearer " + await storageManager.getItem("appAuthorization"),
                        jdsignature: await buildSignature()
                    };
                    return await gmHttp.get(url, null, headers);
                })(type, type_value, page, 50);
                let success = res.success, message = res.message, action = res.action;
                if (1 === success) {
                    let movies = res.data.movies;
                    if (0 === movies.length) {
                        show.error("无数据");
                        return;
                    }
                    this.movies = movies;
                    const hitShowPlugin = this.getBean("hitShowPlugin");
                    let moviesHtml = hitShowPlugin.markDataListHtml(movies);
                    $movieBox.html(moviesHtml);
                    window.refresh();
                    if ("1" === this.has_cnsub) {
                        $(".item:contains('含中字磁鏈')").show();
                        $(".item:contains('含磁鏈')").hide();
                    } else if ("0" === this.has_cnsub) {
                        $(".item:contains('含中字磁鏈')").hide();
                        $(".item:contains('含磁鏈')").show();
                    } else {
                        $(".item:contains('含中字磁鏈')").show();
                        $(".item:contains('含磁鏈')").show();
                    }
                    hitShowPlugin.loadScore(movies);
                } else {
                    console.error(res);
                    $movieBox.html(`<h3>${message}</h3>`);
                    show.error(message);
                }
                if ("JWTVerificationError" === action) {
                    await storageManager.removeItem("appAuthorization");
                    await this.checkLogin(null, new URLSearchParams(window.location.search));
                }
            } catch (e) {
                console.error("获取Top数据失败:", e);
                show.error(`获取Top数据失败: ${e ? e.message : e}`);
            } finally {
                loadObj.close();
            }
        }
        toolBar(type, type_value, currentPage) {
            $(".main-tabs ul li").removeClass("is-active");
            $(".main-tabs ul li:eq(1)").addClass("is-active");
            if ("5" === currentPage.toString()) {
                $("#auto-page").remove();
                $(".pagination-next").remove();
            }
            $(".pagination-ellipsis").closest("li").remove();
            $(".pagination-list li a").each((function() {
                parseInt($(this).text()) > 5 && $(this).closest("li").remove();
            }));
            let yearHtml = "";
            for (let year = (new Date).getFullYear(); year >= 2008; year--) yearHtml += `\n                <a style="padding:18px 18px !important;" \n                   class="button is-small ${type_value === year.toString() ? "is-info" : ""}" \n                   href="/?handleTop=1&type=year&type_value=${year}&has_cnsub=${this.has_cnsub}">\n                  ${year}\n                </a>\n            `;
            let conditionHtml = `\n            <div class="button-group">\n                <div class="buttons has-addons" id="conditionBox" style="margin-bottom: 0!important;">\n                    <a style="padding:18px 18px !important;" class="button is-small ${"all" === type ? "is-info" : ""}" href="/?handleTop=1&type=all&type_value=&has_cnsub=${this.has_cnsub}">全部</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"0" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=0&has_cnsub=${this.has_cnsub}">有码</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"1" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=1&has_cnsub=${this.has_cnsub}">无码</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"2" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=2&has_cnsub=${this.has_cnsub}">欧美</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"3" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=3&has_cnsub=${this.has_cnsub}">Fc2</a>\n                    \n                    <a style="padding:18px 18px !important;margin-left: 50px" class="button is-small ${"1" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="1">含中字磁鏈</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"0" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="0">无字幕</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-cnsub-value="">重置</a>\n                </div>\n                \n                <div class="buttons has-addons" id="conditionBox">\n                    ${yearHtml}\n                </div>\n            </div>\n        `;
            $(".toolbar").html(conditionHtml);
            $("a[data-cnsub-value]").on("click", (event2 => {
                const cnsubValue = $(event2.currentTarget).data("cnsub-value");
                this.has_cnsub = cnsubValue.toString();
                $("a[data-cnsub-value]").removeClass("is-info");
                $(event2.currentTarget).addClass("is-info");
                $(".toolbar a.button").not("[data-cnsub-value]").each(((index, element) => {
                    const $link = $(element), url = new URL($link.attr("href"), window.location.origin);
                    url.searchParams.set("has_cnsub", cnsubValue);
                    $link.attr("href", url.toString());
                }));
                const newUrl = new URL(window.location.href);
                newUrl.searchParams.set("has_cnsub", cnsubValue);
                history.pushState({}, "", newUrl.toString());
                if ("1" === this.has_cnsub) {
                    $(".item:contains('含中字磁鏈')").show();
                    $(".item:contains('含磁鏈')").hide();
                } else if ("0" === this.has_cnsub) {
                    $(".item:contains('含中字磁鏈')").hide();
                    $(".item:contains('含磁鏈')").show();
                } else {
                    $(".item:contains('含中字磁鏈')").show();
                    $(".item:contains('含磁鏈')").show();
                }
                this.getBean("hitShowPlugin").loadScore(this.movies);
            }));
        }
        async checkLogin(event2, urlParams) {
            if (!(await storageManager.getItem("appAuthorization"))) {
                show.error("未登录手机端接口, 无法查看");
                this.openLoginDialog();
                return;
            }
            let type = "all", type_value = "", t = urlParams.get("t") || "";
            if (/^y\d+$/.test(t)) {
                type = "year";
                type_value = t.substring(1);
            } else if ("" !== t) {
                type = "video_type";
                type_value = t;
            }
            let url = `/?handleTop=1&type=${type}&type_value=${type_value}`;
            event2 && (event2.ctrlKey || event2.metaKey) ? window.open(url, "_blank") : window.location.href = url;
        }
        openLoginDialog() {
            layer.open({
                type: 1,
                title: "JavDB",
                closeBtn: 1,
                area: [ "360px", "auto" ],
                shadeClose: !1,
                content: '\n                <div style="padding: 30px; font-family: \'Helvetica Neue\', Arial, sans-serif;">\n                    <div style="margin-bottom: 25px;">\n                        <input type="text" id="username" name="username" \n                            style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n                                   box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n                                   background: #f9f9f9; color: #333;"\n                            placeholder="用户名 | 邮箱"\n                            onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n                            onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n                    </div>\n                    \n                    <div style="margin-bottom: 15px;">\n                        <input type="password" id="password" name="password" \n                            style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n                                   box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n                                   background: #f9f9f9; color: #333;"\n                            placeholder="密码"\n                            onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n                            onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n                    </div>\n                    \n                    <button id="loginBtn" \n                            style="width: 100%; padding: 12px; background: #4a8bfc; color: white; \n                                   border: none; border-radius: 4px; font-size: 15px; cursor: pointer;\n                                   transition: background 0.3s;"\n                            onmouseover="this.style.background=\'#3a7be0\'"\n                            onmouseout="this.style.background=\'#4a8bfc\'">\n                        登录\n                    </button>\n                </div>\n            ',
                success: (layero, index) => {
                    $("#loginBtn").click((function() {
                        const username = $("#username").val(), password = $("#password").val();
                        if (!username || !password) {
                            show.error("请输入用户名和密码");
                            return;
                        }
                        let loadObj = loading();
                        (async (username, password) => {
                            let url = `${apiUrl}//v1/sessions?username=${username}&password=${password}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, headers = {
                                "user-agent": "Dart/3.5 (dart:io)",
                                "accept-language": "zh-TW",
                                "content-type": "multipart/form-data; boundary=--dio-boundary-2210433284",
                                jdsignature: await buildSignature()
                            };
                            return await gmHttp.post(url, null, headers);
                        })(username, password).then((async res => {
                            let success = res.success;
                            if (0 === success) show.error(res.message); else {
                                if (1 !== success) {
                                    console.error("登录失败", res);
                                    throw new Error(res.message);
                                }
                                {
                                    let token = res.data.token;
                                    await storageManager.setItem("appAuthorization", token);
                                    await storageManager.setItem("appUser", res.data);
                                    show.ok("登录成功");
                                    layer.close(index);
                                    window.location.href = "/?handleTop=1&period=daily";
                                }
                            }
                        })).catch((err => {
                            console.error("登录异常:", err);
                            show.error(err.message);
                        })).finally((() => {
                            loadObj.close();
                        }));
                    }));
                }
            });
        }
    }
    class NavBarPlugin extends BasePlugin {
        handle() {
            this.margeNav();
            this.hookSearch();
            if (window.location.href.includes("/search?q")) {
                let q = new URLSearchParams(window.location.search).get("q");
                $("#search-keyword").val(q);
            }
        }
        hookSearch() {
            $("#navbar-menu-hero").after('\n            <div class="navbar-menu">\n                <div class="navbar-start" style="display: flex; align-items: center; gap: 5px;">\n                    <select id="search-type" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; background-color: #333; color: #eee; font-size: 14px; outline: none;">\n                        <option value="all">影片</option>\n                        <option value="actor">演員</option>\n                        <option value="series">系列</option>\n                        <option value="maker">片商</option>\n                        <option value="director">導演</option>\n                        <option value="code">番號</option>\n                        <option value="list">清單</option>\n                    </select>\n                    <input id="search-keyword" type="text" placeholder="輸入影片番號,演員名等關鍵字進行檢索" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; flex-grow: 1; font-size: 14px; background-color: #333; color: #eee; outline: none;">\n                    <a href="/advanced_search?noFold=1" title="進階檢索" style="padding: 6px 12px; background-color: #444; border-radius: 4px; text-decoration: none; color: #ddd; font-size: 14px; border: 1px solid #555;"><span>...</span></a>\n                    <a id="search-img-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">识图</a>\n                    <a id="search-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">檢索</a>\n                </div>\n            </div>\n        ');
            $("#search-keyword").on("paste", (event2 => {
                setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })).on("keypress", (event2 => {
                "Enter" === event2.key && setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            }));
            $("#search-btn").on("click", (event2 => {
                let keyword = $("#search-keyword").val(), searchCurrentType = $("#search-type option:selected").val();
                "" !== keyword && (window.location.href.includes("/search?q") ? window.location.href = "/search?q=" + keyword + "&f=" + searchCurrentType : window.open("/search?q=" + keyword + "&f=" + searchCurrentType));
            }));
            $("#search-img-btn").on("click", (() => {
                this.getBean("SearchByImagePlugin").open();
            }));
        }
        margeNav() {
            $('a[href*="/feedbacks/new"]').remove();
            $('a[href*="theporndude.com"]').remove();
            $('a.navbar-link[href="/makers"]').parent().after('\n            <div class="navbar-item has-dropdown is-hoverable">\n                <a class="navbar-link">其它</a>\n                <div class="navbar-dropdown is-boxed">\n                  <a class="navbar-item" href="/feedbacks/new" target="_blank" >反饋</a>\n                  <a class="navbar-item" rel="nofollow noopener" target="_blank" href="https://theporndude.com/zh">ThePornDude</a>\n                </div>\n              </div>\n        ');
        }
    }
    class OtherSitePlugin extends BasePlugin {
        handle() {
            let carNum = this.getPageInfo().carNum, html = `\n            <div style="margin-top:20px;margin-left: -16px;">\n                <div style="display: flex;gap: 5px">\n                    <a id="javTrailersBtn" class="menu-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>JavTrailers</span></a>\n                    <a href="https://jable.tv/videos/${carNum}/" target="_blank" class="menu-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>\n                    <a href="https://missav.ws/search/${carNum}" target="_blank" class="menu-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>\n                    <a href="https://www.av.gl/vod/search.html?wd=${carNum}" target="_blank" class="menu-btn" style="color:#f40 !important;background:linear-gradient(to bottom, rgb(238,164,238), #fff)"><span>Avgle</span></a>\n                </div>\n            </div>\n        `;
            $(".column-video-cover").append(html);
            $("#javTrailersBtn").on("click", (event2 => utils.openPage(`https://javtrailers.com/video/${carNum.toLowerCase().replace("-", "00")}?handle=1`, carNum, !1, event2)));
        }
    }
    class BusDetailPagePlugin extends BasePlugin {
        async initCss() {
            if (!window.isDetailPage) return "";
            $("h4:contains('論壇熱帖')").hide();
            $("h4:contains('同類影片')").hide();
            $("h4:contains('推薦')").hide();
            return window.location.href.includes("hideNav=1") ? "\n                .navbar-default {\n                    display: none !important;\n                }\n                body {\n                    padding-top:0px!important;\n                }\n            " : "";
        }
        async handle() {
            if (window.location.href.includes("/star/")) {
                const $avatarBox = $(".avatar-box");
                if ($avatarBox.length > 0) {
                    let parent2 = $avatarBox.parent();
                    parent2.css("position", "initial");
                    parent2.insertBefore(parent2.parent());
                }
            }
        }
    }
    class DetailPageButtonPlugin extends BasePlugin {
        constructor() {
            super();
            this.answerCount = 1;
        }
        handle() {
            this.bindHotkey();
            window.isDetailPage && this.createMenuBtn();
        }
        createMenuBtn() {
            const pageInfo = this.getPageInfo(), carNum = pageInfo.carNum, buttonsHtml = '\n            <div style="margin: 10px auto;">\n                <a id="filterBtn" class="menu-btn" style="background-color:#de3333">\n                    <span>屏蔽(a)</span>\n                </a>\n                <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc">\n                    <span>收藏(s)</span>\n                </a>\n                <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b">\n                    <span>加入已下载</span>\n                </a>\n                <a id="enable-magnets-filter" class="menu-btn" style="background-color:#c2bd4c">\n                    <span id="magnets-span">关闭磁力过滤</span>\n                </a>\n                \n\n                <a id="search-subtitle-btn" class="menu-btn fr-btn" style="background:linear-gradient(to bottom, #8d5656, rgb(196,159,91))">\n                    <span>字幕 (SubTitleCat)</span>\n                </a>\n                <a id="xunLeiSubtitleBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)">\n                    <span>字幕 (迅雷)</span>\n                </a>\n            </div>\n        ';
            isJavDb && $(".tabs").after(buttonsHtml);
            isJavBus && $("#mag-submit-show").before(buttonsHtml);
            $("#favoriteBtn").on("click", (() => this.favoriteOne()));
            $("#filterBtn").on("click", (event2 => this.filterOne(event2)));
            $("#hasDownBtn").on("click", (async () => {
                await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_HAS_DOWN);
                window.refresh();
                show.ok("操作成功", {
                    duration: 100,
                    callback: () => {
                        utils.closePage();
                    }
                });
            }));
            $("#enable-magnets-filter").on("click", (event2 => {
                let $span = $("#magnets-span");
                const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin");
                if ("关闭磁力过滤" === $span.text()) {
                    highlightMagnetPlugin.showAll();
                    $span.text("开启磁力过滤");
                } else {
                    highlightMagnetPlugin.handle();
                    $span.text("关闭磁力过滤");
                }
            }));
            $("#search-subtitle-btn").on("click", (event2 => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum}`, carNum, !1, event2)));
            $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum)));
            this.showStatus(carNum).then();
        }
        async showStatus(carNum) {
            let car = await storageManager.getCar(carNum);
            if (car) switch (car.status) {
              case Status_FILTER:
                $("#filterBtn").text("已屏蔽(a)");
                break;

              case Status_FAVORITE:
                $("#favoriteBtn").text("已收藏(s)");
                break;

              case Status_HAS_DOWN:
                $("#hasDownBtn").text("已加入已下载");
            }
        }
        async favoriteOne() {
            let pageInfo = this.getPageInfo();
            await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FAVORITE);
            window.refresh();
            show.ok("操作成功", {
                duration: 100,
                callback: () => {
                    utils.closePage();
                }
            });
        }
        searchXunLeiSubtitle(carNum) {
            let loadObj = loading();
            gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${carNum}`).then((res => {
                let dataList = res.data;
                dataList && 0 !== dataList.length ? layer.open({
                    type: 1,
                    title: "迅雷字幕",
                    content: '<div id="table-container"></div>',
                    area: [ "50%", "70%" ],
                    success: layero => {
                        new TableGenerator({
                            containerId: "table-container",
                            columns: [ {
                                key: "name",
                                title: "文件名"
                            }, {
                                key: "ext",
                                title: "类型"
                            }, {
                                key: "extra_name",
                                title: "来源"
                            } ],
                            data: dataList,
                            buttons: [ {
                                text: "下载",
                                class: "a-primary",
                                onClick: item => {
                                    gmHttp.get(item.url).then((content => {
                                        utils.download(content, carNum + "." + item.ext);
                                    }));
                                }
                            } ]
                        });
                    }
                }) : show.error("迅雷中找不到相关字幕!");
            })).finally((() => {
                loadObj.close();
            }));
        }
        async filterOne(event2, noAlert) {
            event2 && event2.preventDefault();
            let pageInfo = this.getPageInfo();
            if (noAlert) {
                await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
                window.refresh();
                show.ok("操作成功", {
                    duration: 100,
                    callback: () => {
                        utils.closePage();
                    }
                });
            } else utils.q(event2, `是否屏蔽${pageInfo.carNum}?`, (async () => {
                await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
                window.refresh();
                show.ok("操作成功", {
                    duration: 100,
                    callback: () => {
                        utils.closePage();
                    }
                });
            }));
        }
        speedVideo() {
            if ($("#preview-video").is(":visible")) {
                const videoEl = document.getElementById("preview-video");
                if (videoEl) {
                    videoEl.muted = !1;
                    videoEl.currentTime += 5;
                }
                return;
            }
            const iframe = $('iframe[id^="layui-layer-iframe"]');
            if (iframe.length > 0) {
                iframe[0].contentWindow.postMessage("speedVideo", "*");
                return;
            }
            let $videoPlayBtn = $(".preview-video-container");
            if ($videoPlayBtn.length > 0) {
                $videoPlayBtn[0].click();
                const videoEl = document.getElementById("preview-video");
                if (videoEl) {
                    videoEl.currentTime += 5;
                    videoEl.muted = !1;
                }
            } else $("#javTrailersBtn").click();
        }
        bindHotkey() {
            const handlers = {
                a: () => {
                    this.answerCount >= 2 ? this.filterOne(null, !0) : this.filterOne(null);
                    this.answerCount++;
                },
                s: () => this.favoriteOne(null),
                z: () => this.speedVideo()
            }, registerHotkey = (key, handler) => {
                HotkeyManager.registerHotkey(key, (() => {
                    window.isDetailPage ? handler() : (message => {
                        const childIframe = $(".layui-layer-content iframe");
                        if (0 === childIframe.length) return !1;
                        childIframe[0].contentWindow.postMessage(message, "*");
                    })(key);
                }));
            };
            window.isDetailPage && window.addEventListener("message", (event2 => {
                handlers[event2.data] && handlers[event2.data]();
            }));
            Object.entries(handlers).forEach((([key, handler]) => {
                registerHotkey(key, handler);
            }));
        }
    }
    class HistoryPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "dataType", "all");
            __publicField(this, "tableObj", null);
        }
        handle() {
            isJavDb && $(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n                    <a id="historyBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n                        历史列表\n                    </a>\n                </div>');
            isJavBus && $("#navbar").append('\n                <ul class="nav navbar-nav navbar-right" style="margin-right: 10px">\n                    <li><a id="historyBtn" style="color: #86e114 !important;padding-right:15px !important;" role="button">历史列表</a></li>\n                </ul>\n           ');
            $("#historyBtn").on("click", (event2 => 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: async layero => {
                    const dataList = await this.getDataList();
                    this.loadTableData(dataList);
                    $(".layui-layer-content").on("click", ".history-btn", (async event2 => {
                        this.dataType = $(event2.target).data("action");
                        this.reloadTable();
                    }));
                },
                end: async () => window.refresh()
            });
        }
        async handleClickDetail(event2, data) {
            if (isJavDb) if (data.carNum.includes("FC2-")) {
                const parts = data.url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
                this.getBean("fc2Plugin").openFc2Page(movieId, data.carNum, data.url);
            } else utils.openPage(data.url, data.carNum, !1, event2);
            if (isJavBus) {
                let url = data.url;
                if (url.includes("javdb")) if (data.carNum.includes("FC2-")) {
                    const parts = url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
                    let openUrl = `${await storageManager.getSetting("javDbUrl", "https://javdb.com")}/users/collection_codes?movieId=${movieId}&carNum=${data.carNum}&url=${url}`;
                    window.open(openUrl, "_blank");
                } else window.open(url, "_blank"); else utils.openPage(data.url, data.carNum, !1, event2);
            }
        }
        async reloadTable() {
            const dataList = await this.getDataList();
            this.tableObj.update(dataList);
        }
        handleDelete(event2, data) {
            utils.q(event2, `是否移除${data.carNum}?`, (async () => {
                await storageManager.removeCar(data.carNum);
                this.getBean("listPagePlugin").showCarNumBox(data.carNum);
                this.reloadTable().then();
            }));
        }
        async getDataList() {
            let dataList = await storageManager.getCarList();
            this.allCount = dataList.length;
            this.filterCount = 0;
            this.favoriteCount = 0;
            this.hasDownCount = 0;
            dataList.forEach((item => {
                switch (item.status) {
                  case Status_FILTER:
                    this.filterCount++;
                    break;

                  case Status_FAVORITE:
                    this.favoriteCount++;
                    break;

                  case Status_HAS_DOWN:
                    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})`);
            return "all" === this.dataType ? dataList : dataList.filter((item => item.status === this.dataType));
        }
        loadTableData(dataList) {
            this.tableObj = new TableGenerator({
                containerId: "table-container",
                columns: [ {
                    key: "carNum",
                    title: "番号"
                }, {
                    key: "actress",
                    title: "演员",
                    width: "250px"
                }, {
                    key: "createDate",
                    title: "操作日期",
                    width: "185px"
                }, {
                    key: "url",
                    title: "来源",
                    render: item => {
                        let url = item.url;
                        return url.includes("javdb") ? '<span style="color:#d34f9e">Javdb</span>' : url.includes("javbus") ? '<span style="color:#eaa813">JavBus</span>' : `<span style="color:#050505">${url}</span>`;
                    }
                }, {
                    key: "status",
                    title: "状态",
                    width: "250px",
                    render: item => {
                        let color, text = "";
                        switch (item.status) {
                          case "filter":
                            color = "#ec4949";
                            text = "已屏蔽";
                            break;

                          case "favorite":
                            color = "#50adb9";
                            text = "已收藏";
                            break;

                          case "hasDown":
                            color = "#8ebd6e";
                            text = "已下载";
                        }
                        return `<span style="color:${color}">${text}</span>`;
                    }
                } ],
                data: dataList,
                buttons: [ {
                    text: "移除",
                    class: "a-danger",
                    onClick: item => {
                        this.handleDelete(event, item);
                    }
                }, {
                    text: "详情页",
                    class: "a-info",
                    onClick: item => {
                        this.handleClickDetail(event, item);
                    }
                } ]
            });
        }
    }
    class ReviewPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "floorIndex", 1);
        }
        async handle() {
            if (window.isDetailPage) {
                if (isJavDb) {
                    const parts = window.location.href.split("?")[0].split("/"), movieId = parts[parts.length - 1].split("#")[0];
                    await this.showReview(movieId);
                    await this.getBean("RelatedPlugin").showRelated();
                }
                if (isJavBus) {
                    let carNum = this.getPageInfo().carNum;
                    const movies = await (async keyword => {
                        let url = `${apiUrl}/v2/search`, headers = {
                            "user-agent": "Dart/3.5 (dart:io)",
                            "accept-language": "zh-TW",
                            host: "jdforrepam.com",
                            jdsignature: await buildSignature()
                        }, params = {
                            q: keyword,
                            page: 1,
                            type: "movie",
                            limit: 1,
                            movie_type: "all",
                            from_recent: "false",
                            movie_filter_by: "all",
                            movie_sort_by: "relevance"
                        };
                        return (await gmHttp.get(url, params, headers)).data.movies;
                    })(carNum);
                    let movieId = null;
                    for (let i = 0; i < movies.length; i++) {
                        let item = movies[i];
                        if (item.number.toLowerCase() === carNum.toLowerCase()) {
                            movieId = item.id;
                            break;
                        }
                    }
                    if (!movieId) {
                        show.error("解析视频ID失败, 该视频可能在JavDb中不存在, 无法获取评论数据");
                        return;
                    }
                    this.showReview(movieId, $("#sample-waterfall")).then();
                }
            }
        }
        async showReview(movieId, $eleBox) {
            let $magnets = $("#magnets-content");
            $eleBox && ($magnets = $eleBox);
            $magnets.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
            let reviewCount = await storageManager.getSetting("reviewCount", 20), dataList = null;
            try {
                dataList = await getReviews(movieId, 1, reviewCount);
            } catch (e) {
                console.error(e);
            }
            $("#reviewsLoading").remove();
            $magnets.append('\n            <div style=" display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px; ">\n                <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n                <span style="padding: 0 10px;">评论区</span>\n                <a id="reviewsFold" style=" margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center; ">\n                    <span class="toggle-text">折叠</span>\n                    <span class="toggle-icon" style="margin-left: 4px;">▲</span>\n                </a>\n                <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n            </div>\n        ');
            $("#reviewsFold").on("click", (() => {
                const textSpan = $("#reviewsFold .toggle-text"), iconSpan = $("#reviewsFold .toggle-icon");
                if ("展开" === textSpan.text()) {
                    textSpan.text("折叠");
                    iconSpan.text("▲");
                    $reviewsContainer.show();
                    $reviewsFooter.show();
                } else {
                    textSpan.text("展开");
                    iconSpan.text("▼");
                    $reviewsContainer.hide();
                    $reviewsFooter.hide();
                }
            }));
            $magnets.append('<div id="reviewsContainer"></div>');
            $magnets.append('<div id="reviewsFooter"></div>');
            const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter");
            if (!dataList) {
                $reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论失败</div>');
                return;
            }
            0 === dataList.length && $reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>');
            const reviewKeywordList = await storageManager.getReviewFilterKeywordList();
            this.displayReviews(dataList, $reviewsContainer, reviewKeywordList);
            if (dataList.length === reviewCount) {
                $reviewsFooter.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 currentPage = 1;
                $("#loadMoreReviews").click((async () => {
                    $("#loadMoreReviews").text("加载中...").prop("disabled", !0);
                    currentPage++;
                    const moreData = await getReviews(movieId, currentPage, reviewCount);
                    this.displayReviews(moreData, $reviewsContainer, reviewKeywordList);
                    if (moreData.length < reviewCount) {
                        $("#loadMoreReviews").remove();
                        $("#reviewsEnd").show();
                    } else $("#loadMoreReviews").text("加载更多评论").prop("disabled", !1);
                }));
            } else dataList.length > 0 && $reviewsFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>');
        }
        displayReviews(dataList, $container, reviewKeywordList) {
            if (dataList.length) {
                dataList.forEach((item => {
                    let isReviewKeyword = !1;
                    for (let i = 0; i < reviewKeywordList.length; i++) if (item.content.indexOf(reviewKeywordList[i]) > -1) {
                        isReviewKeyword = !0;
                        break;
                    }
                    if (isReviewKeyword) return;
                    let starsHtml = "";
                    for (let i = 0; i < item.score; i++) starsHtml += '<i class="icon-star"></i>';
                    let content = item.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (match => `<a href="${match}" class="a-primary" \n                        style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${match}</a>\n\x3c!--                        <a class="a-success review-magnet" style="padding:0;margin-left:0">预览</a>--\x3e`)), commentHtml = `\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                    ${item.username} &nbsp;&nbsp; <span class="score-stars">${starsHtml}</span> \n                    <span class="time">${item.created_at.replace("T", " ").replace(".000Z", "")}</span> \n                    &nbsp;&nbsp; 点赞:${item.likes_count}\n                    <p class="review-content" style="margin-top: 5px;"> ${content} </p>\n                </div>\n            `;
                    $container.append(commentHtml);
                }));
                utils.rightClick($(".review-content"), (event2 => {
                    const selectedText = window.getSelection().toString();
                    if (selectedText) {
                        event2.preventDefault();
                        utils.q(event2, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => {
                            await storageManager.saveReviewFilterKeyword(selectedText);
                            show.ok("操作成功, 刷新页面后生效");
                        }));
                    }
                }));
            }
        }
    }
    class FilterTitleKeywordPlugin extends BasePlugin {
        handle() {
            if (!window.isDetailPage) return;
            let $titles, $male;
            if (isJavDb) {
                $titles = $("h2");
                $male = $(".male").prev();
            }
            isJavBus && ($titles = $("h3"));
            utils.rightClick($titles, (event2 => {
                const selectedText = window.getSelection().toString();
                if (selectedText) {
                    event2.preventDefault();
                    let tempEvent = {
                        clientX: event2.clientX,
                        clientY: event2.clientY + 80
                    };
                    utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => {
                        await storageManager.saveTitleFilterKeyword(selectedText);
                        window.refresh();
                        utils.closePage();
                    }));
                }
            }));
            $male && $male.length > 0 && utils.rightClick($male, (event2 => {
                event2.preventDefault();
                let text = $(event2.target).text().trim();
                utils.q(event2, `是否屏蔽演员${text}?`, (async () => {
                    await storageManager.saveFilterActor(text);
                    window.refresh();
                    const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
                    await detailPageButtonPlugin.filterOne(null, !0);
                }));
            }));
        }
    }
    class ListPageButtonPlugin extends BasePlugin {
        handle() {
            window.isListPage && this.createMenuBtn();
        }
        createMenuBtn() {
            if (isJavDb) {
                if (window.location.href.includes("/actors/")) {
                    $(".toolbar .buttons").append('\n                    <a class="menu-btn" id="waitCheckBtn" \n                       style="background-color:#56c938 !important;; margin-left: 40px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n                      <span>打开待鉴定</span>\n                    </a>\n                    <a class="menu-btn" id="waitDownBtn" \n                       style="background-color:#2caac0 !important;; margin-left: 10px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n                      <span>打开已收藏</span>\n                    </a>\n                ');
                    $(".toolbar .buttons").append(`\n                <a class="menu-btn" id="sort-toggle-btn" \n                   style="background-color:#8783ab !important; margin-left: 50px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">当前排序方式: ${"rateCount" === localStorage.getItem("sortMethod") ? "评价人数" : "date" === localStorage.getItem("sortMethod") ? "时间" : "默认"}</a>\n                `);
                } else {
                    $(".tabs ul").append('\n                    <li class="is-active" id="waitCheckBtn">\n                        <a class="menu-btn" style="background-color:#56c938 !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:#2caac0 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                            <span>打开已收藏</span>\n                        </a>\n                    </li>\n                ');
                    $(".tabs ul").after(`\n                  <div style="padding:10px">\n                    <a class="menu-btn" id="sort-toggle-btn" \n                       style="background-color:#8783ab !important; margin-left: 20px; border-bottom:none !important; border-radius:3px;">\n                      当前排序方式: ${"rateCount" === localStorage.getItem("sortMethod") ? "评价人数" : "date" === localStorage.getItem("sortMethod") ? "时间" : "默认"}\n                    </a>\n                  </div>\n                `);
                }
                this.sortItems();
            }
            if (isJavBus) {
                const buttonsHtml = '\n                <div style="margin-top: 10px">\n                    <a id="waitCheckBtn" class="menu-btn" style="background-color:#56c938 !important;margin-left: 14px;border-bottom:none !important;border-radius:3px;">\n                        <span>打开待鉴定</span>\n                    </a>\n                    <a id="waitDownBtn" class="menu-btn" style="background-color:#2caac0 !important;margin-left: 5px;border-bottom:none !important;border-radius:3px;">\n                        <span>打开已收藏</span>\n                    </a>\n                </div>\n            ';
                $(".masonry").parent().prepend(buttonsHtml);
            }
            $("#waitCheckBtn").on("click", (event2 => {
                this.openWaitCheck(event2).then();
            }));
            $("#waitDownBtn").on("click", (event2 => {
                this.openFavorite(event2).then();
            }));
            $("#sort-toggle-btn").on("click", (event2 => {
                const currentMethod = localStorage.getItem("sortMethod");
                let newMethod;
                newMethod = currentMethod && "default" !== currentMethod ? "rateCount" === currentMethod ? "date" : "default" : "rateCount";
                const methodText = {
                    default: "默认",
                    rateCount: "评价人数",
                    date: "时间"
                }[newMethod];
                $(event2.target).text(`当前排序方式: ${methodText}`);
                localStorage.setItem("sortMethod", newMethod);
                this.sortItems();
            }));
        }
        sortItems() {
            const method = localStorage.getItem("sortMethod");
            if (!method) return;
            $(".movie-list .item").each((function(index) {
                $(this).attr("data-original-index") || $(this).attr("data-original-index", index);
            }));
            const $container = $(".movie-list"), $items = $(".item", $container);
            if ("default" === method) $items.sort((function(a, b) {
                return $(a).data("original-index") - $(b).data("original-index");
            })).appendTo($container); else {
                const items = $items.get();
                items.sort((function(a, b) {
                    if ("rateCount" === method) {
                        const getScore = el => {
                            const match = $(el).find(".score .value").text().match(/由(\d+)人/);
                            return match ? parseFloat(match[1]) : 0;
                        };
                        return getScore(b) - getScore(a);
                    }
                    {
                        const getDate = el => {
                            const dateStr = $(el).find(".meta").text().trim();
                            return new Date(dateStr);
                        };
                        return getDate(b) - getDate(a);
                    }
                }));
                $container.empty().append(items);
            }
        }
        async openWaitCheck() {
            let selector;
            isJavDb && (selector = Db);
            isJavBus && (selector = Bus);
            const maxCount = await storageManager.getSetting("waitCheckCount", 5), excludedTexts = [ "已收藏", "已屏蔽", "已下载" ];
            let count = 0;
            $(`${selector.itemSelector}:visible`).each(((i, el) => {
                if (count >= maxCount) return !1;
                const $el = $(el);
                if (!excludedTexts.some((text => $el.find(`span:contains('${text}')`).length > 0))) {
                    const $link = $el.find("a");
                    if ($link.length) {
                        let href = $link.attr("href");
                        if (href) {
                            href += href.includes("?") ? "&autoPlay=1" : "?autoPlay=1";
                            window.open(href);
                            count++;
                        }
                    }
                }
            }));
            0 === count && show.info("没有需鉴定的视频");
        }
        async openFavorite() {
            let openCount = await storageManager.getSetting("waitCheckCount", 5);
            const favoriteList = (await storageManager.getCarList()).filter((item => item.status === Status_FAVORITE));
            for (let i = 0; i < openCount; i++) {
                if (i >= favoriteList.length) return;
                let data = favoriteList[i], carNum = data.carNum, url = data.url;
                if (carNum.includes("FC2-")) {
                    const parts = data.url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
                    let javDbUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com");
                    window.open(`${javDbUrl}/users/collection_codes?movieId=${movieId}&carNum=${carNum}&url=${url}`);
                } else window.open(url);
            }
        }
    }
    class ListPagePlugin extends BasePlugin {
        async handle() {
            this.cleanRepeatId();
            this.replaceHdImg();
            await this.doFilter();
            this.bindClick().then();
            new BroadcastChannel("channel-refresh").addEventListener("message", (async event2 => {
                "refresh" === event2.data.type && await this.doFilter();
            }));
            this.checkDom();
        }
        checkDom() {
            if (!window.isListPage) return;
            const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector), observer = new MutationObserver((mutations => {
                utils.log("检查");
                observer.disconnect();
                try {
                    this.replaceHdImg();
                    this.doFilter().then();
                    this.getBean("ListPageButtonPlugin").sortItems();
                } finally {
                    observer.observe(targetNode, config);
                }
            })), config = {
                childList: !0,
                subtree: !1
            };
            observer.observe(targetNode, config);
        }
        cleanRepeatId() {
            if (!isJavBus) return;
            $("#waterfall_h").removeAttr("id").attr("id", "no-page");
            const $waterfalls = $('[id="waterfall"]');
            0 !== $waterfalls.length && $waterfalls.each((function() {
                const $current = $(this);
                if (!$current.hasClass("masonry")) {
                    $current.children().insertAfter($current);
                    $current.remove();
                }
            }));
        }
        async doFilter() {
            if (!window.isListPage) return;
            let movieList = $(this.getSelector().itemSelector).toArray();
            await this.filterMovieList(movieList);
            await this.getBean("autoPagePlugin").handlePaging();
        }
        async filterMovieList(movieList) {
            const carList = await storageManager.getCarList(), filterKeywordList = await storageManager.getTitleFilterKeyword(), filterCarNums = carList.filter((item => item.status === Status_FILTER)).map((item => item.carNum)), favoriteCarNums = carList.filter((item => item.status === Status_FAVORITE)).map((item => item.carNum)), hasDownCarNums = carList.filter((item => item.status === Status_HAS_DOWN)).map((item => item.carNum));
            let hideFilterItem = await storageManager.getSetting("hideFilterItem", "yes"), href = window.location.href;
            (href.includes("search?q") || href.includes("/search/") || href.includes("/users/")) && (hideFilterItem = "no");
            movieList.forEach((ele => {
                let $box = $(ele);
                const {carNum: carNum, aHref: aHref, title: title} = this.findCarNumAndHref($box), hideKey = `${carNum}-hide`, keywordHideKey = `${carNum}-keywordHide`, tagKey = `${carNum}-tag`;
                if ("no" === hideFilterItem && $box.attr("data-hide") === hideKey) {
                    $box.show();
                    $box.removeAttr("data-hide");
                }
                if (filterKeywordList.some((filterKeyword => title.includes(filterKeyword) || carNum.includes(filterKeyword))) && $box.attr("data-keyword-hide") !== keywordHideKey) {
                    $box.hide();
                    $box.attr("data-keyword-hide", keywordHideKey);
                    return;
                }
                if (filterCarNums.includes(carNum) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                    $box.hide();
                    $box.attr("data-hide", hideKey);
                    return;
                }
                if (hasDownCarNums.includes(carNum) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                    $box.hide();
                    $box.attr("data-hide", hideKey);
                    return;
                }
                let tagText = "", color = "";
                if (filterCarNums.includes(carNum)) {
                    tagText = "已屏蔽";
                    color = "#d95427";
                } else if (favoriteCarNums.includes(carNum)) {
                    tagText = "已收藏";
                    color = "#2caac0";
                } else if (hasDownCarNums.includes(carNum)) {
                    tagText = "已下载";
                    color = "#58c433";
                }
                if (tagText && $box.attr("data-tag") !== tagKey) {
                    isJavDb && $box.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: ${color} !important;">\n                        ${tagText}\n                    </span>`);
                    if (isJavBus) {
                        let tagHtml = `<a class="a-primary" style="margin-right: 5px; padding: 0 5px;color: #fff; border-radius:10px; position:absolute; right: 0; top:5px;z-index:10;background-color: ${color} !important;"><span>${tagText}</span></a>`;
                        $box.find(".item-tag").append(tagHtml);
                    }
                    $box.attr("data-tag", tagKey);
                }
            }));
            $("#waitDownBtn span").text(`打开已收藏 (${favoriteCarNums.length})`);
        }
        async bindClick() {
            let selector = this.getSelector(), dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", "yes");
            $(selector.boxSelector).on("click", ".item img", (event2 => {
                event2.preventDefault();
                if ($(event2.target).closest("div.meta-buttons").length) return;
                const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
                if (carNum.includes("FC2-")) {
                    let movieId = aHref.split("/").filter(Boolean).pop();
                    this.getBean("fc2Plugin").openFc2Page(movieId, carNum, aHref);
                } else "yes" === dialogOpenDetail ? utils.openPage(aHref, carNum, !1, event2) : window.open(aHref);
            }));
            $(selector.boxSelector).on("contextmenu", ".item img", (event2 => {
                event2.preventDefault();
                const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
                utils.q(event2, `是否屏蔽番号 ${carNum}?`, (async () => {
                    await storageManager.saveCar(carNum, aHref, "", Status_FILTER);
                    window.refresh();
                    show.ok("操作成功");
                }));
            }));
        }
        findCarNumAndHref($box) {
            let carNum, title, aHref = $box.find("a").attr("href");
            if (isJavDb) {
                carNum = $box.find(".video-title").find("strong").text();
                title = $box.find(".video-title").text();
            }
            if (isJavBus) {
                carNum = aHref.split("/").filter(Boolean).pop();
                title = $box.find("img").attr("title");
            }
            return {
                carNum: carNum,
                aHref: aHref,
                title: title
            };
        }
        showCarNumBox(matchCarNum) {
            const matchingBox = $(".movie-list .item").toArray().find((item => $(item).find(".video-title strong").text() === matchCarNum));
            if (matchingBox) {
                const $matchingBox = $(matchingBox);
                if ($matchingBox.attr("data-hide") === `${matchCarNum}-hide`) {
                    $matchingBox.show();
                    $matchingBox.removeAttr("data-hide");
                }
            }
        }
        replaceHdImg(coverImgNodeList) {
            coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector));
            isJavDb && coverImgNodeList.forEach((img => {
                img.src = img.src.replace("thumbs", "covers");
            }));
            if (isJavBus) {
                const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = img => {
                    if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) {
                        img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1");
                        img.dataset.hdReplaced = "true";
                        img.loading = "lazy";
                    }
                };
                coverImgNodeList.forEach((img => {
                    replaceWithHd(img);
                }));
            }
        }
    }
    class AutoPagePlugin extends BasePlugin {
        constructor() {
            super();
            this.paging = !1;
            this.selector = null;
            isJavDb && (this.selector = Db);
            isJavBus && (this.selector = Bus);
        }
        async handle() {
            window.isListPage && !this.shouldDisablePaging() && this.bindPageClick().then();
        }
        async bindPageClick() {
            $(".pagination-link, .pagination-next, .pagination-previous, .pagination li a").on("click", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                let href = $(event2.target).attr("href");
                this.parsePage(href).then();
            }));
            0 === $("#auto-page").length && await this.insertPageBtn();
            $("#auto-page").on("click", (async event2 => {
                event2.preventDefault();
                if ("yes" === await storageManager.getItem(storageManager.auto_page_key)) {
                    await storageManager.setItem(storageManager.auto_page_key, "no");
                    $("#auto-page").html("开启自动翻页");
                } else {
                    await storageManager.setItem(storageManager.auto_page_key, "yes");
                    $("#auto-page").html("关闭自动翻页");
                    await this.handlePaging();
                }
            }));
        }
        async insertPageBtn() {
            let initText = "yes" === await storageManager.getItem(storageManager.auto_page_key) ? "关闭自动翻页" : "开启自动翻页";
            isJavDb && $(".pagination").prepend(`<a style="background-color:#fff; order: 2;padding: calc(.5em - 1px) .75em;" id='auto-page'>${initText}</a>`);
            isJavBus && $(".pagination").append(`<li><a style="margin-left: 20px;cursor: pointer;" id='auto-page'>${initText}</a></li>`);
        }
        async parsePage(href) {
            let loadObj = loading();
            try {
                const html = await http.get(href), dom = (new DOMParser).parseFromString(html, "text/html");
                let itemList = dom.querySelectorAll(this.selector.requestDomItemSelector), pagination = dom.querySelectorAll(".pagination");
                const listPagePlugin = this.getBean("listPagePlugin");
                await listPagePlugin.filterMovieList(itemList);
                let coverImgNodeList = dom.querySelectorAll(this.selector.coverImgSelector);
                listPagePlugin.replaceHdImg(coverImgNodeList);
                let $movieList = $(this.selector.boxSelector);
                $movieList.fadeOut(300, (() => {
                    $movieList.html(itemList).fadeIn(300, (async () => {}));
                }));
                await this.insertPageBtn();
                $(".pagination").replaceWith(pagination);
                window.history.pushState({}, "", href);
                if (isJavBus) {
                    const pageNumber = this.getPageNumberFromUrl(href);
                    document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁");
                }
                await utils.smoothScrollToTop();
                await this.bindPageClick();
                await this.handlePaging();
            } finally {
                loadObj.close();
            }
        }
        getPageNumberFromUrl(url) {
            const match = url.match(/\/page\/(\d+)/);
            return match ? parseInt(match[1], 10) : null;
        }
        shouldDisablePaging() {
            return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos" ].some((path => window.location.href.includes(path)));
        }
        async handlePaging() {
            if (this.shouldDisablePaging()) return;
            if ($("#no-page").length) {
                $("#auto-page").remove();
                return;
            }
            if (0 === $(this.selector.boxSelector).length) return;
            if (!window.isListPage) return;
            if (this.paging) return;
            let needPaging = !0;
            $(`${this.selector.itemSelector}:visible`).each(((i, el) => {
                0 === $(el).find("span:contains('已收藏')").length && 0 === $(el).find("span:contains('已屏蔽')").length && 0 === $(el).find("span:contains('已下载')").length && (needPaging = !1);
            }));
            if (!needPaging) return;
            if ("yes" !== await storageManager.getItem(storageManager.auto_page_key)) return;
            let nextBtn = null;
            isJavDb && (nextBtn = $(".pagination-next"));
            isJavBus && (nextBtn = $("#next"));
            if (nextBtn && 0 !== nextBtn.length) {
                this.paging = !0;
                show.info("下一页....", {
                    duration: 500,
                    callback: () => {
                        nextBtn[0].click();
                        this.paging = !1;
                    }
                });
            }
        }
    }
    class HighlightMagnetPlugin extends BasePlugin {
        handle() {
            this.handleDb();
            this.handleBus();
        }
        handleDb() {
            if (!isJavDb || !isDetailPage) return;
            let magnetNameList = $("#magnets-content .name").toArray(), has4k_C_UC = !1;
            magnetNameList.forEach((el => {
                let item = $(el), text = item.text().toLowerCase();
                text.indexOf("4k") > -1 && item.css("color", "#f40");
                (text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1) && (has4k_C_UC = !0);
            }));
            has4k_C_UC ? magnetNameList.forEach((el => {
                let item = $(el), text = item.text().toLowerCase();
                text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1 || item.parent().parent().parent().hide();
            })) : $("#enable-magnets-filter").addClass("do-hide");
        }
        handleBus() {
            isJavBus && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
                let magnetNameList = $("#magnet-table tr td:first-child a:first-child").toArray(), has4k_C_UC = !1;
                magnetNameList.forEach((el => {
                    let item = $(el), text = item.text().toLowerCase();
                    text.indexOf("4k") > -1 && item.css("color", "#f40");
                    (text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1) && (has4k_C_UC = !0);
                }));
                has4k_C_UC ? magnetNameList.forEach((el => {
                    let item = $(el), text = item.text().toLowerCase();
                    text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1 || item.parent().parent().hide();
                })) : $("#enable-magnets-filter").addClass("do-hide");
            }));
        }
        showAll() {
            if (isJavDb) {
                $("#magnets-content .item").toArray().forEach((el => $(el).show()));
            }
            isJavBus && $("#magnet-table tr").toArray().forEach((el => $(el).show()));
        }
    }
    class AliyunApi {
        constructor(refresh_token) {
            this.baseApiUrl = "https://api.aliyundrive.com";
            this.refresh_token = refresh_token;
            this.authorization = null;
            this.default_drive_id = null;
            this.backupFolderId = null;
        }
        async getDefaultDriveId() {
            if (this.default_drive_id) return this.default_drive_id;
            this.userInfo = await this.getUserInfo();
            this.default_drive_id = this.userInfo.default_drive_id;
            return this.default_drive_id;
        }
        async getHeaders() {
            if (this.authorization) return {
                authorization: this.authorization
            };
            this.authorization = await this.getAuthorization();
            return {
                authorization: this.authorization
            };
        }
        async getAuthorization() {
            let url = this.baseApiUrl + "/v2/account/token", data = {
                refresh_token: this.refresh_token,
                grant_type: "refresh_token"
            };
            try {
                return "Bearer " + (await http.post(url, data)).access_token;
            } catch (e) {
                throw e.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : e;
            }
        }
        async getUserInfo() {
            const headers = await this.getHeaders();
            let url = this.baseApiUrl + "/v2/user/get";
            return await http.post(url, {}, headers);
        }
        async deleteFile(file_id, drive_id = null) {
            if (!file_id) throw new Error("未传入file_id");
            drive_id || (drive_id = await this.getDefaultDriveId());
            let data = {
                file_id: file_id,
                drive_id: drive_id
            }, url = this.baseApiUrl + "/v2/recyclebin/trash";
            const headers = await this.getHeaders();
            await gmHttp.post(url, data, headers);
            return {};
        }
        async createFolder(name, drive_id = null, parent_folder_id = "root") {
            drive_id || (drive_id = await this.getDefaultDriveId());
            let url = this.baseApiUrl + "/adrive/v2/file/createWithFolders", data = {
                name: name,
                type: "folder",
                parent_file_id: parent_folder_id,
                check_name_mode: "auto_rename",
                content_hash_name: "sha1",
                drive_id: drive_id
            };
            const headers = await this.getHeaders(), result = await gmHttp.post(url, data, headers);
            return JSON.parse(result);
        }
        async getFileList(parent_folder_id = "root", drive_id = null) {
            drive_id || (drive_id = await this.getDefaultDriveId());
            let url = this.baseApiUrl + "/adrive/v3/file/list";
            const data = {
                drive_id: drive_id,
                parent_file_id: parent_folder_id,
                limit: 200,
                all: !1,
                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"
            }, headers = await this.getHeaders();
            return (await gmHttp.post(url, data, headers)).items;
        }
        async uploadFile(folder_id, fileName, uploadContent, drive_id = null) {
            let createFileUrl = this.baseApiUrl + "/adrive/v2/file/createWithFolders";
            drive_id || (drive_id = await this.getDefaultDriveId());
            let data = {
                drive_id: drive_id,
                part_info_list: [ {
                    part_number: 1
                } ],
                parent_file_id: folder_id,
                name: fileName,
                type: "file",
                check_name_mode: "auto_rename"
            };
            const headers = await this.getHeaders(), createFileResult = await gmHttp.post(createFileUrl, data, headers), upload_id = createFileResult.upload_id, upload_file_id = createFileResult.file_id, upload_url = createFileResult.part_info_list[0].upload_url;
            console.log("创建完成: ", createFileResult);
            await this._doUpload(upload_url, uploadContent);
            const completeResult = await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", data = {
                drive_id: "745851",
                file_id: upload_file_id,
                upload_id: upload_id
            }, headers);
            console.log("标记完成:", completeResult);
        }
        _doUpload(upload_url, uploadContent) {
            return new Promise(((resolve, reject) => {
                $.ajax({
                    type: "PUT",
                    url: upload_url,
                    data: uploadContent,
                    contentType: " ",
                    processData: !1,
                    success: (res, status, xhr) => {
                        if (200 === xhr.status) {
                            console.log("上传成功:", res);
                            resolve({});
                        } else reject(xhr);
                    },
                    error: xhr => {
                        console.error("上传失败", xhr.responseText);
                        reject(xhr);
                    }
                });
            }));
        }
        async getDownloadUrl(file_id, drive_id = null) {
            drive_id || (drive_id = await this.getDefaultDriveId());
            let url = this.baseApiUrl + "/v2/file/get_download_url";
            const headers = await this.getHeaders();
            let data = {
                file_id: file_id,
                drive_id: drive_id
            };
            return (await gmHttp.post(url, data, headers)).url;
        }
        async _createBackupFolder(folderName) {
            const fileList = await this.getFileList();
            let folderObj = null;
            for (let i = 0; i < fileList.length; i++) {
                let file = fileList[i];
                if (file.name === folderName) {
                    folderObj = file;
                    break;
                }
            }
            if (!folderObj) {
                console.log("不存在目录, 进行创建");
                folderObj = await this.createFolder(folderName);
            }
            this.backupFolderId = folderObj.file_id;
        }
        async backup(folderName, fileName, uploadContent) {
            if (this.backupFolderId) await this.uploadFile(this.backupFolderId, fileName, uploadContent); else {
                await this._createBackupFolder(folderName);
                await this.uploadFile(this.backupFolderId, fileName, uploadContent);
            }
        }
        async getBackupList(folderName) {
            let dataList = null;
            if (this.backupFolderId) dataList = await this.getFileList(this.backupFolderId); else {
                await this._createBackupFolder(folderName);
                dataList = await this.getFileList(this.backupFolderId);
            }
            const fileList = [];
            dataList.forEach((data => {
                fileList.push({
                    name: data.name,
                    fileId: data.file_id,
                    createTime: data.created_at,
                    size: data.size
                });
            }));
            return fileList;
        }
    }
    class WebDavApi {
        constructor(davUrl, username, password) {
            this.davUrl = davUrl.endsWith("/") ? davUrl : davUrl + "/";
            this.username = username;
            this.password = password;
            this.folderName = null;
        }
        _getAuthHeaders() {
            return {
                Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`,
                Depth: "1"
            };
        }
        _sendRequest(method, path, headers = {}, data) {
            return new Promise(((resolve, reject) => {
                const url = this.davUrl + path, allHeaders = {
                    ...this._getAuthHeaders(),
                    ...headers
                };
                GM_xmlhttpRequest({
                    method: method,
                    url: url,
                    headers: allHeaders,
                    data: data,
                    onload: response => {
                        response.status >= 200 && response.status < 300 ? resolve(response) : reject(new Error(`Request failed with status ${response.status}: ${response.statusText}`));
                    },
                    onerror: response => {
                        console.error("请求WebDav发生错误:", response);
                        reject(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确"));
                    }
                });
            }));
        }
        async backup(folderName, fileName, uploadContent) {
            await this._sendRequest("MKCOL", folderName);
            const path = folderName + "/" + fileName;
            await this._sendRequest("PUT", path, {
                "Content-Type": "text/plain"
            }, uploadContent);
        }
        async getFileList(folderName) {
            var _a, _b;
            const xmlResponse = (await this._sendRequest("PROPFIND", folderName, {
                "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, items = (new DOMParser).parseFromString(xmlResponse, "text/xml").getElementsByTagNameNS("DAV:", "response"), fileList = [];
            for (let i = 0; i < items.length; i++) {
                if (0 === i) continue;
                if ("1" === items[i].getElementsByTagNameNS("DAV:", "iscollection")[0].textContent) continue;
                const name = items[i].getElementsByTagNameNS("DAV:", "displayname")[0].textContent, size = (null == (_a = items[i].getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : _a.textContent) || "0", createTime = (null == (_b = items[i].getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : _b.textContent) || "";
                fileList.push({
                    fileId: name,
                    name: name,
                    size: size,
                    createTime: createTime
                });
            }
            fileList.reverse();
            return fileList;
        }
        async deleteFile(fileId) {
            let path = this.folderName + "/" + encodeURI(fileId);
            await this._sendRequest("DELETE", path, {
                "Cache-Control": "no-cache"
            });
        }
        async getBackupList(folderName) {
            this.folderName = folderName;
            await this._sendRequest("MKCOL", folderName);
            return this.getFileList(folderName);
        }
        async getFileContent(filePath) {
            let path = this.folderName + "/" + filePath;
            return (await this._sendRequest("GET", path, {
                Accept: "application/octet-stream"
            })).responseText;
        }
    }
    class SettingPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __privateAdd(this, _SettingPlugin_instances);
            __publicField(this, "folderName", "JSH-数据备份");
        }
        async initCss() {
            let containerWidth = await storageManager.getSetting("containerWidth", "100"), containerColumns = await storageManager.getSetting("containerColumns", 5), containerWidthCss = `\n            section .container{\n                max-width: 1000px !important;\n                min-width: ${containerWidth}%;\n            }\n            .movie-list{\n                grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n            }\n        `;
            isJavBus && (containerWidthCss = `\n                .container-fluid .row{\n                    max-width: 1000px !important;\n                    min-width: ${containerWidth}%;\n                    margin: auto auto;\n                }\n                \n                .masonry {\n                    grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n                }\n            `);
            return `\n            <style>\n                ${containerWidthCss}\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() {
            isJavDb && $(".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>');
            isJavBus ? $("#navbar").append('\n                <ul class="nav navbar-nav navbar-right">\n                    <li><a id="setting-btn" style="color: #ff8400 !important;padding-right:15px !important;" role="button">设置</a></li>\n                </ul>\n           ') : $("#pt").append('<div class="z" style="float: right">\n                    <a id="setting-btn" class="a-primary" style="padding: 0px 16px;">\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="syncDataBtn" class="menu-btn" style="background-color:#387ca9"><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">页面列数:</span>\n                        <div class="form-content" style="position: relative;padding-top: 20px">\n                            <input type="range" id="containerColumns" min="3" max="10" step="1" style="padding:5px 0">\n                            <span id="showContainerColumns" style="position:absolute; top:-10px;"></span>\n                        </div>\n                    </div>\n                    \n                    <div class="setting-item">\n                        <span class="setting-label">页面宽度:</span>\n                        <div class="form-content" style="position: relative;padding-top: 20px">\n                            <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0">\n                            <span id="showContainerWidth" style="position:absolute; top:-10px;"></span>\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                    <div class="setting-item">\n                        <span class="setting-label">详情页打开方式:</span>\n                        <div class="form-content">\n                            <select id="dialogOpenDetail">\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">JavDb地址(用于跟Bus同步数据):</span>\n                        <div class="form-content">\n                            <input id="javDbUrl" >\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: !1,
                    success: (layero, index) => {
                        $(layero).find(".layui-layer-content").css("position", "relative");
                        this.loadForm();
                        this.bindClick();
                    }
                });
            }));
        }
        async loadForm() {
            let settingObj = await storageManager.getSetting();
            $("#hideFilterItem").val(settingObj.hideFilterItem || "yes");
            $("#dialogOpenDetail").val(settingObj.dialogOpenDetail || "yes");
            $("#reviewCount").val(settingObj.reviewCount || 20);
            $("#waitCheckCount").val(settingObj.waitCheckCount || 5);
            $("#containerWidth").val((settingObj.containerWidth || 100) - 70);
            $("#showContainerWidth").text(settingObj.containerWidth + "%");
            $("#containerColumns").val(settingObj.containerColumns || 4);
            $("#showContainerColumns").text(settingObj.containerColumns || 4);
            $("#refresh_token").val(settingObj.refresh_token || "");
            $("#javDbUrl").val(settingObj.javDbUrl || "https://javdb.com");
            $("#webDavUrl").val(settingObj.webDavUrl || "");
            $("#webDavUsername").val(settingObj.webDavUsername || "");
            $("#webDavPassword").val(settingObj.webDavPassword || "");
            let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorList = await storageManager.getFilterActorList();
            reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => {
                this.addLabelTag("#reviewKeywordContainer", reviewKeyword);
            }));
            filterKeywordList && filterKeywordList.forEach((reviewKeyword => {
                this.addLabelTag("#filterKeywordContainer", reviewKeyword);
            }));
            filterActorList && filterActorList.forEach((filterActor => {
                this.addLabelTag("#filterActorContainer", filterActor);
            }));
            [ "#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer" ].forEach((containerId => {
                $(`${containerId} .add-tag-btn`).on("click", (event2 => this.addKeyword(event2, containerId)));
                $(`${containerId} .keyword-input`).on("keypress", (event2 => {
                    "Enter" === event2.key && this.addKeyword(event2, containerId);
                }));
            }));
        }
        bindClick() {
            $("#importBtn").on("click", (event2 => this.importData(event2)));
            $("#exportBtn").on("click", (event2 => this.exportData(event2)));
            $("#syncDataBtn").on("click", (event2 => this.syncData(event2)));
            $("#backupBtn").on("click", (event2 => this.backupData(event2)));
            $("#backupListBtn").on("click", (event2 => this.backupListBtn(event2)));
            $("#webdavBackupBtn").on("click", (event2 => this.backupDataByWebDav(event2)));
            $("#webdavBackupListBtn").on("click", (event2 => this.backupListBtnByWebDav(event2)));
            $("#getRefreshTokenBtn").on("click", (event2 => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", {
                yes: function(index, layero, that) {
                    window.open("https://www.aliyundrive.com/drive/home");
                    layer.close(index);
                }
            })));
            $("#containerWidth").on("input", (event2 => {
                const value = parseInt($(event2.target).val()) + 70 + "%";
                $("#showContainerWidth").text(value);
                if (isJavDb) {
                    document.querySelector("section .container").style.minWidth = value;
                }
                if (isJavBus) {
                    document.querySelector(".container-fluid .row").style.minWidth = value;
                }
            }));
            $("#containerColumns").on("input", (event2 => {
                let columns = $("#containerColumns").val();
                $("#showContainerColumns").text(columns);
                if (isJavDb) {
                    document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
                }
                if (isJavBus) {
                    document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
                }
            }));
            $("#saveBtn").on("click", (() => this.saveForm()));
        }
        async saveForm() {
            const hideFilterValue = $("#hideFilterItem").val(), reviewCount = $("#reviewCount").val(), waitCheckCount = $("#waitCheckCount").val(), containerWidth = parseInt($("#containerWidth").val()), containerColumns = parseInt($("#containerColumns").val()), refresh_token = $("#refresh_token").val();
            let settingObj = await storageManager.getSetting();
            settingObj.hideFilterItem = hideFilterValue;
            settingObj.reviewCount = reviewCount;
            settingObj.waitCheckCount = waitCheckCount;
            settingObj.containerWidth = containerWidth + 70;
            settingObj.containerColumns = containerColumns;
            settingObj.refresh_token = refresh_token;
            settingObj.webDavUrl = $("#webDavUrl").val();
            settingObj.webDavUsername = $("#webDavUsername").val();
            settingObj.webDavPassword = $("#webDavPassword").val();
            settingObj.dialogOpenDetail = $("#dialogOpenDetail").val();
            settingObj.javDbUrl = new URL($("#javDbUrl").val()).origin;
            await storageManager.saveSetting(settingObj);
            let reviewKeywordList = [];
            $("#reviewKeywordContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                reviewKeywordList.push(keyword);
            }));
            await storageManager.saveReviewFilterKeyword(reviewKeywordList);
            let filterKeywordList = [];
            $("#filterKeywordContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                filterKeywordList.push(keyword);
            }));
            await storageManager.saveTitleFilterKeyword(filterKeywordList);
            let filterActorList = [];
            $("#filterActorContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                filterActorList.push(keyword);
            }));
            await storageManager.saveFilterActor(filterActorList);
            show.ok("保存成功");
            window.refresh();
        }
        addLabelTag(containerId, keyword) {
            const $tagBox = $(`${containerId} .tag-box`), $label = $(`\n            <div class="keyword-label" style="background-color: #c5b9a0">\n                ${keyword}\n                <span class="keyword-remove">×</span>\n            </div>\n        `);
            $label.find(".keyword-remove").click((event2 => {
                $(event2.target).parent().remove();
            }));
            $tagBox.append($label);
        }
        addKeyword(event2, containerId) {
            let $keywordInput = $(`${containerId} .keyword-input`);
            const keyword = $keywordInput.val().trim();
            if (keyword) {
                this.addLabelTag(containerId, keyword);
                $keywordInput.val("");
            }
        }
        importData() {
            try {
                const input = document.createElement("input");
                input.type = "file";
                input.accept = ".json";
                input.onchange = e => {
                    const file = e.target.files[0];
                    if (!file) return;
                    const reader = new FileReader;
                    reader.onload = event2 => {
                        try {
                            const content = event2.target.result.toString(), updateJsonData = JSON.parse(content);
                            layer.confirm("确定是否要覆盖导入?", {
                                icon: 3,
                                title: "确认覆盖",
                                btn: [ "确定", "取消" ]
                            }, (async function(index) {
                                await storageManager.importData(updateJsonData);
                                show.ok("数据导入成功");
                                layer.close(index);
                                location.reload();
                            }));
                        } catch (err) {
                            console.error(err);
                            show.error("导入失败:文件内容不是有效的JSON格式 " + err);
                        }
                    };
                    reader.onerror = () => {
                        show.error("读取文件时出错");
                    };
                    reader.readAsText(file);
                };
                document.body.appendChild(input);
                input.click();
                setTimeout((() => document.body.removeChild(input)), 1e3);
            } catch (err) {
                console.error(err);
                show.error("导入数据时出错: " + err.message);
            }
        }
        async backupData(event2) {
            const refresh_token = await storageManager.getSetting("refresh_token");
            if (!refresh_token) {
                show.error("请填写refresh_token并保存后, 再试此功能");
                return;
            }
            let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
            uploadContent = simpleEncrypt(uploadContent);
            let loadObj = loading();
            try {
                const aliyunApi = new AliyunApi(refresh_token);
                await aliyunApi.backup(this.folderName, fileName, uploadContent);
                show.ok("备份完成");
            } catch (e) {
                console.error(e);
                show.error(e.toString());
            } finally {
                loadObj.close();
            }
        }
        async backupListBtn(event2) {
            const refresh_token = await storageManager.getSetting("refresh_token");
            if (!refresh_token) {
                show.error("请填写refresh_token并保存后, 再试此功能");
                return;
            }
            let loadObj = loading();
            try {
                const aliyunApi = new AliyunApi(refresh_token), fileList = await aliyunApi.getBackupList(this.folderName);
                this.openFileListDialog(fileList, aliyunApi, "阿里云盘");
            } catch (e) {
                console.error(e);
                show.error(`发生错误: ${e ? e.message : e}`);
            } finally {
                loadObj.close();
            }
        }
        async backupDataByWebDav(event2) {
            const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
            if (!webDavUrl) {
                show.error("请填写webDav服务地址并保存后, 再试此功能");
                return;
            }
            const webDavUsername = settingObj.webDavUsername;
            if (!webDavUsername) {
                show.error("请填写webDav用户名并保存后, 再试此功能");
                return;
            }
            const webDavPassword = settingObj.webDavPassword;
            if (!webDavPassword) {
                show.error("请填写webDav密码并保存后, 再试此功能");
                return;
            }
            let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
            uploadContent = simpleEncrypt(uploadContent);
            let loadObj = loading();
            try {
                const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword);
                await webDavApi.backup(this.folderName, fileName, uploadContent);
                show.ok("备份完成");
            } catch (e) {
                console.error(e);
                show.error(e.toString());
            } finally {
                loadObj.close();
            }
        }
        async backupListBtnByWebDav(event2) {
            const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
            if (!webDavUrl) {
                show.error("请填写webDav服务地址并保存后, 再试此功能");
                return;
            }
            const webDavUsername = settingObj.webDavUsername;
            if (!webDavUsername) {
                show.error("请填写webDav用户名并保存后, 再试此功能");
                return;
            }
            const webDavPassword = settingObj.webDavPassword;
            if (!webDavPassword) {
                show.error("请填写webDav密码并保存后, 再试此功能");
                return;
            }
            let loadObj = loading();
            try {
                const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword), fileList = await webDavApi.getBackupList(this.folderName);
                this.openFileListDialog(fileList, webDavApi, "WebDav");
            } catch (e) {
                console.error(e);
                show.error(`发生错误: ${e ? e.message : e}`);
            } finally {
                loadObj.close();
            }
        }
        openFileListDialog(fileList, api, apiType) {
            layer.open({
                type: 1,
                title: apiType + "备份文件",
                content: '<div id="table-container"></div>',
                area: [ "40%", "70%" ],
                success: layero => {
                    const tableObj = new TableGenerator({
                        containerId: "table-container",
                        columns: [ {
                            key: "name",
                            title: "文件名"
                        }, {
                            key: "createTime",
                            title: "备份日期",
                            render: item => `${utils.getNowStr("-", ":", item.createTime)}`
                        }, {
                            key: "size",
                            title: "文件大小",
                            render: item => {
                                const units = [ "B", "KB", "MB", "GB", "TB", "PB" ];
                                let unitIndex = 0, adjustedSize = item.size;
                                for (;adjustedSize >= 1024 && unitIndex < units.length - 1; ) {
                                    adjustedSize /= 1024;
                                    unitIndex++;
                                }
                                return `${adjustedSize % 1 == 0 ? adjustedSize.toFixed(0) : adjustedSize.toFixed(2)} ${units[unitIndex]}`;
                            }
                        } ],
                        data: fileList,
                        buttons: [ {
                            text: "删除",
                            class: "a-danger",
                            onClick: async (event2, item) => {
                                layer.confirm(`是否删除 ${item.name} ?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: [ "确定", "取消" ]
                                }, (async index => {
                                    layer.close(index);
                                    let loadObj = loading();
                                    try {
                                        await api.deleteFile(item.fileId);
                                        let newFileList = await api.getBackupList(this.folderName);
                                        tableObj.update(newFileList);
                                        "阿里云盘" === apiType ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功");
                                    } catch (e) {
                                        console.error(e);
                                        show.error(`发生错误: ${e ? e.message : e}`);
                                    } finally {
                                        loadObj.close();
                                    }
                                }));
                            }
                        }, {
                            text: "下载",
                            class: "a-primary",
                            onClick: item => {
                                let loadObj = loading();
                                try {
                                    "阿里云盘" === apiType ? api.getDownloadUrl(item.fileId).then((url => {
                                        gmHttp.get(url, null, {
                                            Referer: "https://www.aliyundrive.com/"
                                        }).then((content => {
                                            content = simpleDecrypt(content);
                                            utils.download(content, item.name);
                                        }));
                                    })).catch((e => {
                                        console.error(e);
                                        show.error("下载失败: " + e);
                                    })) : api.getFileContent(item.fileId).then((content => {
                                        content = simpleDecrypt(content);
                                        utils.download(content, item.name);
                                    }));
                                } catch (e) {
                                    console.error(e);
                                    show.error("下载失败: " + e);
                                } finally {
                                    loadObj.close();
                                }
                            }
                        }, {
                            text: "导入",
                            class: "a-success",
                            onClick: item => {
                                layer.confirm(`是否将该云备份数据 ${item.name} 导入?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: [ "确定", "取消" ]
                                }, (async index => {
                                    layer.close(index);
                                    let loadObj = loading();
                                    try {
                                        let content;
                                        if ("阿里云盘" === apiType) {
                                            const downUrl = await api.getDownloadUrl(item.fileId);
                                            content = await gmHttp.get(downUrl, null, {
                                                Referer: "https://www.aliyundrive.com/"
                                            });
                                        } else content = await api.getFileContent(item.fileId);
                                        content = simpleDecrypt(content);
                                        const updateJsonData = JSON.parse(content);
                                        await storageManager.importData(updateJsonData);
                                        show.ok("导入成功!");
                                        window.location.reload();
                                    } catch (err) {
                                        console.error(err);
                                        show.error(err);
                                    } finally {
                                        loadObj.close();
                                    }
                                }));
                            }
                        } ]
                    });
                }
            });
        }
        async exportData(event2) {
            try {
                const backupData = JSON.stringify(await storageManager.exportData()), fileName = `${utils.getNowStr("_", "_")}.json`;
                utils.download(backupData, fileName);
                show.ok("数据导出成功");
            } catch (err) {
                console.error(err);
                show.error("导出数据时出错: " + err.message);
            }
        }
        async syncData(event2) {
            let msg = null, targetUrl = null;
            if (isJavDb) {
                msg = "是否将JavBus的数据及配置同步到本站中? ";
                targetUrl = "https://www.javbus.com/temp?syncData=1";
            }
            if (isJavBus) {
                msg = "是否将JavDB的数据及配置同步到本站中? ";
                targetUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com") + "/feedbacks/new?syncData=1";
            }
            utils.q(event2, msg, (() => {
                const targetWindow = window.open(targetUrl);
                let targetOrigin = new URL(targetUrl).origin;
                console.log("开始连接接受方:", targetOrigin);
                let pingInterval, retryCount = 0;
                if (!this.hasListenMsg) {
                    window.addEventListener("message", (event3 => {
                        if (event3.origin === targetOrigin) if ("ok" === event3.data) {
                            clearInterval(pingInterval);
                            console.log("连接确认,开始同步数据");
                            targetWindow.postMessage("syncData", targetOrigin);
                        } else {
                            const resData = event3.data;
                            console.log("收到数据", resData);
                            __privateMethod(this, _SettingPlugin_instances, handleSyncData_fn).call(this, resData);
                        }
                    }));
                    this.hasListenMsg = !0;
                }
                const pingTarget = () => {
                    if (retryCount >= 8) {
                        clearInterval(pingInterval);
                        console.log("超过最大重试次数,停止尝试");
                        show.error("同步失败, 目标网站已中断, 请检查是否登录后再试!", {
                            close: !0,
                            duration: -1
                        });
                    } else {
                        console.log(`第 ${retryCount + 1} 次ping...`);
                        targetWindow.postMessage("ping", targetOrigin);
                        retryCount++;
                    }
                };
                pingInterval = setInterval(pingTarget, 1e3);
                pingTarget();
            }));
        }
    }
    _SettingPlugin_instances = new WeakSet;
    handleSyncData_fn = async function(resData) {
        try {
            const targetCarList = resData.carList || [], targetFilterActor = resData.filterActor || [], targetTitleFilterKeyword = resData.titleFilterKeyword || [], targetReviewFilterKeyword = resData.reviewFilterKeyword || [], targetSetting = resData.setting || {}, selfCarList = await storageManager.getCarList() || [], selfFilterActor = await storageManager.getFilterActorList() || [], selfTitleFilterKeyword = await storageManager.getTitleFilterKeyword() || [], selfReviewFilterKeyword = await storageManager.getReviewFilterKeywordList() || [], selfSetting = await storageManager.getSetting() || {}, newCarList = [ ...selfCarList ];
            targetCarList.forEach((targetCar => {
                selfCarList.some((selfCar => selfCar.carNum === targetCar.carNum)) || newCarList.push(targetCar);
            }));
            const newFilterActorKeyword = [ ...new Set([ ...selfFilterActor, ...targetFilterActor ]) ], newTitleFilterKeyword = [ ...new Set([ ...selfTitleFilterKeyword, ...targetTitleFilterKeyword ]) ], newReviewFilterKeyword = [ ...new Set([ ...selfReviewFilterKeyword, ...targetReviewFilterKeyword ]) ], newSetting = {
                ...selfSetting
            };
            Object.keys(targetSetting).forEach((key => {
                key in newSetting && newSetting[key] || (newSetting[key] = targetSetting[key]);
            }));
            await storageManager.overrideCarList(newCarList);
            await storageManager.saveFilterActor(newFilterActorKeyword);
            await storageManager.saveTitleFilterKeyword(newTitleFilterKeyword);
            await storageManager.saveReviewFilterKeyword(newReviewFilterKeyword);
            await storageManager.saveSetting(newSetting);
            show.ok("同步完成, 关闭提示后, 将重载数据", {
                close: !0,
                duration: -1,
                callback: () => {
                    window.location.reload();
                }
            });
        } catch (error) {
            console.error(error);
            show.error("同步数据时出错:", error);
        }
    };
    const SALT = "x7k9p3";
    function simpleEncrypt(str) {
        return (SALT + str + SALT).split("").map((char => {
            const code = char.codePointAt(0);
            return String.fromCodePoint(code + 5);
        })).join("");
    }
    function simpleDecrypt(encryptedStr) {
        return encryptedStr.split("").map((char => {
            const code = char.codePointAt(0);
            return String.fromCodePoint(code - 5);
        })).join("").slice(SALT.length, -SALT.length);
    }
    class SyncDataPlugin extends BasePlugin {
        async handle() {
            if (!window.location.href.includes("syncData=1")) return;
            isJavBus && $("h4").html("临时页面, 用于同步数据");
            let senderOrigin = null;
            isJavDb && (senderOrigin = "https://www.javbus.com");
            isJavBus && (senderOrigin = await storageManager.getSetting("javDbUrl", "https://javdb.com"));
            console.log("等待发送方:", senderOrigin);
            window.addEventListener("message", (async event2 => {
                if (event2.origin === senderOrigin) if ("ping" === event2.data) {
                    console.log("收到 ping,发送确认");
                    event2.source.postMessage("ok", event2.origin);
                } else if ("syncData" === event2.data) {
                    console.log("开始发送数据...");
                    const carList = await storageManager.getCarList(), filterActor = await storageManager.getFilterActorList(), titleFilterKeyword = await storageManager.getTitleFilterKeyword(), reviewFilterKeyword = await storageManager.getReviewFilterKeywordList(), setting = await storageManager.getSetting();
                    event2.source.postMessage({
                        carList: carList,
                        filterActor: filterActor,
                        titleFilterKeyword: titleFilterKeyword,
                        reviewFilterKeyword: reviewFilterKeyword,
                        setting: setting
                    }, event2.origin);
                    show.ok("数据已传输, 即将关闭页面...", {
                        callback: () => {
                            window.close();
                        }
                    });
                }
            }));
        }
    }
    class BusPreviewVideoPlugin extends BasePlugin {
        async initCss() {
            return "\n            .video-control-btn {\n                min-width:100px;\n                padding: 8px 16px;\n                background: rgba(0,0,0,0.7);\n                color: white;\n                border: none;\n                border-radius: 4px;\n                cursor: pointer;\n            }\n            .video-control-btn.active {\n                background-color: #1890ff; /* 选中按钮的背景色 */\n                color: white;             /* 选中按钮的文字颜色 */\n                font-weight: bold;        /* 加粗显示 */\n                border: 2px solid #096dd9; /* 边框样式 */\n            }\n        ";
        }
        handle() {
            if (!isDetailPage) return;
            const firstImageSrc = $("#sample-waterfall a:first").attr("href"), videoPreview = $(`\n            <a class="preview-video-container sample-box" style="cursor: pointer">\n                <div class="photo-frame" style="position:relative;">\n                    <img src="${firstImageSrc}" class="video-cover" alt="">\n                    <div class="play-icon" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); \n                            color:white; font-size:40px; text-shadow:0 0 10px rgba(0,0,0,0.5);">\n                        ▶\n                    </div>\n                </div>\n            </a>`);
            $("#sample-waterfall").prepend(videoPreview);
            let $preview = $(".preview-video-container");
            $preview.on("click", (async event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                if (!$("#preview-video").length) {
                    let videoUrl = await this.parseVideo(firstImageSrc);
                    console.log("解析播放地址:", videoUrl);
                    $("#magneturlpost").next().after(`<div><video id="preview-video" controls style="width: 100%;margin-top: 5px;"><source src="${videoUrl}" /></video></div>`);
                    this.handleVideo().then((() => {
                        const element = document.getElementById("preview-video");
                        if (element) {
                            const rect = element.getBoundingClientRect();
                            window.scrollTo({
                                top: window.scrollY + rect.top - 100,
                                behavior: "smooth"
                            });
                        }
                    }));
                }
            }));
            window.location.href.includes("autoPlay=1") && $preview[0].click();
        }
        async handleVideo() {
            const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $videoContainer = $videoEl.parent();
            if (!$videoEl.length || !$previewSource.length) return;
            const videoEl = $videoEl[0];
            videoEl.muted = !1;
            videoEl.play();
            $videoContainer.css("position", "relative");
            const videoSrc = $previewSource.attr("src"), qualityLevels = [ "hhb", "hmb", "mhb", "mmb" ], currentQuality = qualityLevels.find((q => videoSrc.includes(q))) || "mhb", qualityOptions = [ {
                id: "video-mmb",
                text: "低画质",
                quality: "mmb"
            }, {
                id: "video-mhb",
                text: "中画质",
                quality: "mhb"
            }, {
                id: "video-hmb",
                text: "高画质",
                quality: "hmb"
            }, {
                id: "video-hhb",
                text: "超高清",
                quality: "hhb"
            } ];
            const cacheKey = `videoQualities_${this.getPageInfo().carNum}`;
            let availableQualities = JSON.parse(sessionStorage.getItem(cacheKey));
            if (!availableQualities) {
                availableQualities = (await Promise.all(qualityOptions.map((async option => {
                    const testSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), option.quality);
                    try {
                        return (await fetch(testSrc, {
                            method: "HEAD"
                        })).ok ? option : null;
                    } catch {
                        return null;
                    }
                })))).filter(Boolean);
                availableQualities.length && sessionStorage.setItem(cacheKey, JSON.stringify(availableQualities));
            }
            if (availableQualities.length <= 1) return;
            const buttonsHtml = availableQualities.map(((option, index) => `\n                <button class="video-control-btn${option.quality === currentQuality ? " active" : ""}" \n                        id="${option.id}" \n                        data-quality="${option.quality}"\n                        style="bottom: ${50 * index}px; right: -105px;">\n                    ${option.text}\n                </button>\n            `)).join("");
            $videoContainer.append(buttonsHtml);
            const $buttons = $videoContainer.find(".video-control-btn");
            $videoContainer.on("click", ".video-control-btn", (async e => {
                const $button = $(e.currentTarget), quality = $button.data("quality");
                if (!$button.hasClass("active")) try {
                    const newSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), quality);
                    $previewSource.attr("src", newSrc);
                    videoEl.load();
                    videoEl.muted = !1;
                    await videoEl.play();
                    $buttons.removeClass("active");
                    $button.addClass("active");
                } catch (error) {
                    console.error("切换画质失败:", error);
                }
            }));
            $buttons.last().trigger("click");
        }
        async parseVideo(imgSrc) {
            const cacheKey = `ok_url_${this.getPageInfo().carNum}`;
            let ok_url = sessionStorage.getItem(cacheKey);
            if (ok_url) return ok_url;
            const videoIdMatch = imgSrc.match(/\/digital\/video\/([^\/]+)\//);
            if (!videoIdMatch || videoIdMatch.length < 2) {
                show.error("解析id错误" + imgSrc + ", 该视频没有对应的dmm视频");
                console.error("解析dmm视频id错误", imgSrc);
                setTimeout((() => {
                    $("#preview-video").remove();
                }), 1e3);
                return null;
            }
            const videoId = videoIdMatch[1], firstChar = videoId.charAt(0).toLowerCase();
            let idPrefix = videoId.substring(0, 3);
            const checkVideo = async testSrc => {
                try {
                    console.log("测试视频地址", testSrc);
                    return (await fetch(testSrc, {
                        method: "HEAD"
                    })).ok ? testSrc : null;
                } catch {
                    return null;
                }
            };
            let videoIdNo00 = videoId.replace("00", ""), testSrcList = [ `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoId}/${videoId}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoIdNo00}/${videoIdNo00}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoId}/${videoId}mhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoIdNo00}/${videoIdNo00}mhb.mp4` ], okUrl = null;
            for (let i = 0; i < testSrcList.length; i++) {
                let testUrl = await checkVideo(testSrcList[i]);
                if (testUrl) {
                    console.log("测试成功,", testUrl);
                    okUrl = testUrl;
                    break;
                }
            }
            if (!okUrl) {
                show.error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
                throw new Error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
            }
            sessionStorage.setItem(cacheKey, okUrl);
            return okUrl;
        }
    }
    class SearchByImagePlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "siteList", [ {
                name: "Google旧版",
                url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d",
                ico: "https://www.google.com/favicon.ico"
            }, {
                name: "Google",
                url: "https://lens.google.com/uploadbyurl?url={占位符}",
                ico: "https://www.google.com/favicon.ico"
            }, {
                name: "Yandex",
                url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}",
                ico: "https://yandex.ru/favicon.ico"
            } ]);
        }
        async initCss() {
            return "\n            <style>\n                #upload-area {\n                    border: 2px dashed #85af68;\n                    border-radius: 8px;\n                    padding: 40px;\n                    text-align: center;\n                    margin-bottom: 20px;\n                    transition: all 0.3s;\n                    background-color: #f9f9f9;\n                }\n                #upload-area:hover {\n                    border-color: #76b947;\n                    background-color: #f0f0f0;\n                }\n                /* 拖拽进入 */\n                #upload-area.highlight {\n                    border-color: #2196F3;\n                    background-color: #e3f2fd;\n                }\n                \n                \n                #select-image-btn {\n                    background-color: #4CAF50;\n                    color: white;\n                    border: none;\n                    padding: 10px 20px;\n                    border-radius: 4px;\n                    cursor: pointer;\n                    font-size: 16px;\n                    transition: background-color 0.3s;\n                }\n                #select-image-btn:hover {\n                    background-color: #45a049;\n                }\n                \n                \n                #handle-btn, #cancel-btn {\n                    padding: 8px 16px;\n                    border-radius: 4px;\n                    cursor: pointer;\n                    font-size: 14px;\n                    border: none;\n                    transition: opacity 0.3s;\n                }\n                #handle-btn {\n                    background-color: #2196F3;\n                    color: white;\n                }\n                #handle-btn:hover {\n                    opacity: 0.9;\n                }\n                #cancel-btn {\n                    background-color: #f44336;\n                    color: white;\n                }\n                #cancel-btn:hover {\n                    opacity: 0.9;\n                }\n                \n                .site-btns-container {\n                    display: flex;\n                    flex-wrap: wrap;\n                    gap: 10px;\n                    margin-top: 15px;\n                }\n                .site-btn {\n                    display: flex;\n                    align-items: center;\n                    padding: 8px 12px;\n                    background-color: #f5f5f5;\n                    border-radius: 4px;\n                    text-decoration: none;\n                    color: #333;\n                    transition: all 0.2s;\n                    font-size: 14px;\n                    border: 1px solid #ddd;\n                }\n                .site-btn:hover {\n                    background-color: #e0e0e0;\n                    transform: translateY(-2px);\n                    box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n                }\n                .site-btn img {\n                    width: 16px;\n                    height: 16px;\n                    margin-right: 6px;\n                }\n                .site-btn span {\n                    white-space: nowrap;\n                }\n            </style>\n        ";
        }
        open() {
            layer.open({
                type: 1,
                title: "以图识图",
                content: '\n            <div style="padding: 20px">\n                <div id="upload-area">\n                    <div style="color: #555;margin-bottom: 15px;">\n                        <p>拖拽图片到此处 或 点击按钮选择图片</p>\n                        <p>也可以直接 Ctrl+V 粘贴图片或 图片URL</p>\n                    </div>\n                    <button id="select-image-btn">选择图片</button>\n                    <input type="file" style="display: none" id="image-file" accept="image/*">\n                </div>\n                \n                <div id="url-input-container" style="margin-top: 15px;display: none;">\n                    <input type="text" id="image-url" placeholder="粘贴图片URL地址..." style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">\n                </div>\n                \n                <div id="preview-area" style="margin-bottom: 20px; text-align: center; display: none;">\n                    <img id="preview-image" alt="" src="" style="max-width: 100%; max-height: 300px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">\n                    <div style="margin-top: 15px; display: flex; justify-content: center; gap: 10px;" id="action-btns">\n                        <button id="handle-btn">搜索图片</button>\n                        <button id="cancel-btn">取消</button>\n                    </div>\n                    \n                    <div id="search-results" style="display: none;">\n                        <p style="margin: 20px auto">请选择识图网站:<a id="openAll" style="cursor: pointer">全部打开</a></p>\n                        <div class="site-btns-container" id="site-btns-container"></div>\n                    </div>\n                </div>\n                \n            </div>\n        ',
                area: [ "40%", "80%" ],
                success: async layero => {
                    this.initEventListeners();
                }
            });
        }
        initEventListeners() {
            const $uploadArea = $("#upload-area"), $fileInput = $("#image-file"), $selectBtn = $("#select-image-btn"), $previewArea = $("#preview-area"), $previewImage = $("#preview-image"), $actionBtns = $("#action-btns"), $searchImage = $("#handle-btn"), $cancelBtn = $("#cancel-btn"), $urlInputContainer = $("#url-input-container"), $imageUrlInput = $("#image-url"), $searchResults = $("#search-results"), $siteBtnsContainer = $("#site-btns-container");
            $uploadArea.on("dragover", (e => {
                e.preventDefault();
                $uploadArea.addClass("highlight");
            })).on("dragleave", (() => {
                $uploadArea.removeClass("highlight");
            })).on("drop", (e => {
                e.preventDefault();
                $uploadArea.removeClass("highlight");
                if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) {
                    this.handleImageFile(e.originalEvent.dataTransfer.files[0]);
                    this.resetSearchUI();
                }
            }));
            $selectBtn.on("click", (() => {
                $fileInput.trigger("click");
            }));
            $fileInput.on("change", (e => {
                if (e.target.files && e.target.files[0]) {
                    this.handleImageFile(e.target.files[0]);
                    this.resetSearchUI();
                }
            }));
            $(document).on("paste", (async e => {
                const items = e.originalEvent.clipboardData.items;
                for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) {
                    const blob = items[i].getAsFile();
                    this.handleImageFile(blob);
                    this.resetSearchUI();
                    return;
                }
                const text = e.originalEvent.clipboardData.getData("text");
                if (text && utils.isUrl(text)) {
                    $urlInputContainer.show();
                    $imageUrlInput.val(text);
                    $previewImage.attr("src", text);
                    $previewArea.show();
                    this.resetSearchUI();
                }
            }));
            $searchImage.on("click", (() => {
                const imageSrc = $previewImage.attr("src");
                imageSrc ? this.searchByImage(imageSrc).then((imgUrl => {
                    $actionBtns.hide();
                    $searchResults.show();
                    $siteBtnsContainer.empty();
                    this.siteList.forEach((site => {
                        const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl));
                        $siteBtnsContainer.append(`\n                        <a href="${siteUrl}" class="site-btn" target="_blank" title="${site.name}">\n                            <img src="${site.ico}" alt="${site.name}">\n                            <span>${site.name}</span>\n                        </a>\n                    `);
                    }));
                    $siteBtnsContainer.show();
                })) : show.info("请粘贴或上传图片");
            }));
            $cancelBtn.on("click", (() => {
                $previewArea.hide();
                $urlInputContainer.hide();
                $fileInput.val("");
                $imageUrlInput.val("");
            }));
            $imageUrlInput.on("change", (() => {
                if (utils.isUrl($imageUrlInput.val())) {
                    $previewImage.attr("src", $imageUrlInput.val());
                    $previewArea.show();
                }
            }));
            $("#openAll").on("click", (() => {
                $(".site-btn").toArray().forEach((item => {
                    window.open($(item).attr("href"));
                }));
            }));
        }
        resetSearchUI() {
            $("#action-btns").show();
            $("#search-results").hide();
            $("#site-btns-container").hide().empty();
        }
        handleImageFile(file) {
            const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container");
            if (!file.type.match("image.*")) {
                show.info("请选择图片文件");
                return;
            }
            const reader = new FileReader;
            reader.onload = e => {
                previewImage.src = e.target.result;
                previewArea.style.display = "block";
                urlInputContainer.style.display = "none";
            };
            reader.readAsDataURL(file);
        }
        async searchByImage(imageSrc) {
            let loadObj = loading();
            try {
                let imageUrl = imageSrc;
                if (imageSrc.startsWith("data:")) {
                    show.info("开始上传图片...");
                    const imgurUrl = await async function(base64Data) {
                        var _a;
                        const matches = base64Data.match(/^data:(.+);base64,(.+)$/);
                        if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据");
                        const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length);
                        for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i);
                        const byteArray = new Uint8Array(byteNumbers), blob = new Blob([ byteArray ], {
                            type: mimeType
                        }), formData = new FormData;
                        formData.append("image", blob);
                        const response = await fetch("https://api.imgur.com/3/image", {
                            method: "POST",
                            headers: {
                                Authorization: "Client-ID d70305e7c3ac5c6"
                            },
                            body: formData
                        }), data = await response.json();
                        if (data.success && data.data && data.data.link) return data.data.link;
                        throw new Error((null == (_a = data.data) ? void 0 : _a.error) || "上传到Imgur失败");
                    }(imageSrc);
                    if (!imgurUrl) {
                        show.error("上传到失败");
                        return;
                    }
                    imageUrl = imgurUrl;
                }
                return imageUrl;
            } catch (error) {
                show.error(`搜索失败: ${error.message}`);
                console.error("搜索失败:", error);
            } finally {
                loadObj.close();
            }
        }
    }
    class BusNavBarPlugin extends BasePlugin {
        handle() {
            $("#navbar > div > div > span").append('\n            <button class="btn btn-default" style="color: #0d9488" id="search-img-btn">识图</button>\n       ');
            $("#search-img-btn").on("click", (() => {
                this.getBean("SearchByImagePlugin").open();
            }));
        }
    }
    class RelatedPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "floorIndex", 1);
        }
        async showRelated() {
            let movieId = this.getPageInfo().movieId, $magnets = $("#magnets-content");
            $magnets.append('\n            <div style=" display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px; ">\n                <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n                <span style="padding: 0 10px;">相关清单</span>\n                <a id="relatedFold" style=" margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center; ">\n                    <span class="toggle-text">展开</span>\n                    <span class="toggle-icon" style="margin-left: 4px;">▼</span>\n                </a>\n                <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n            </div>\n        ');
            let dataList = null;
            $("#relatedFold").on("click", (async () => {
                const textSpan = $("#relatedFold .toggle-text"), iconSpan = $("#relatedFold .toggle-icon");
                if ("展开" === textSpan.text()) {
                    textSpan.text("折叠");
                    iconSpan.text("▲");
                    $relatedContainer.show();
                    $relatedFooter.show();
                    if (dataList) return;
                    try {
                        $relatedContainer.append('<div id="relate-load" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">正在查询中...</div>');
                        let pageNum = 1, reviewCount = 20;
                        dataList = await related(movieId, pageNum, reviewCount);
                        $("#relate-load").remove();
                        if (!dataList) {
                            $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单失败</div>');
                            return;
                        }
                        0 === dataList.length && $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>');
                        this.displayRelated(dataList, $relatedContainer);
                        if (dataList.length === reviewCount) {
                            $relatedFooter.html('\n                            <button id="loadMoreRelated" 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 currentPage = 1;
                            $("#loadMoreRelated").click((async () => {
                                $("#loadMoreRelated").text("加载中...").prop("disabled", !0);
                                currentPage++;
                                const moreData = await related(movieId, currentPage, reviewCount);
                                this.displayRelated(moreData, $relatedContainer);
                                if (moreData.length < reviewCount) {
                                    $("#loadMoreRelated").remove();
                                    $("#reviewsEnd").show();
                                } else $("#loadMoreRelated").text("加载更多清单").prop("disabled", !1);
                            }));
                        } else dataList.length > 0 && $relatedFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>');
                    } catch (e) {
                        console.error(e);
                        $("#relate-load").remove();
                        $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取失败</div>');
                    }
                } else {
                    textSpan.text("展开");
                    iconSpan.text("▼");
                    $relatedContainer.hide();
                    $relatedFooter.hide();
                }
            }));
            $magnets.append('<div id="relatedContainer"></div>');
            $magnets.append('<div id="relatedFooter"></div>');
            const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter");
        }
        displayRelated(dataList, $container) {
            dataList.length && dataList.forEach((item => {
                let commentHtml = `\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                   <span style="position:absolute;bottom:5px;right:10px;color:#999;font-size:12px;">创建时间: ${item.createTime}</span>\n                   <p><a href="/lists/${item.relatedId}" target="_blank" style="color:#2e8abb">${item.name}</a></p>\n                   <p style="margin-top: 5px;">收藏次数: ${item.collectionCount} 被查看次数: ${item.viewCount}</p>\n                </div>\n            `;
                $container.append(commentHtml);
            }));
        }
    }
    class WantAndWatchedVideosPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "type", null);
        }
        async handle() {
            if (window.location.href.includes("/want_watch_videos")) {
                $("h3").append('<a class="a-primary" id="wantWatchBtn" style="padding:10px;">导入至 JSH</a>');
                $("#wantWatchBtn").on("click", (event2 => {
                    this.type = Status_FAVORITE;
                    this.importWantWatchVideos(event2, "是否将 想看的影片 导入到 JSH-收藏?");
                }));
            }
            if (window.location.href.includes("/watched_videos")) {
                $("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JSH</a>');
                $("#wantWatchBtn").on("click", (event2 => {
                    this.type = Status_HAS_DOWN;
                    this.importWantWatchVideos(event2, "是否将 看过的影片 导入到 JSH-已下载?");
                }));
            }
        }
        importWantWatchVideos(event2, title) {
            utils.q(null, `${title} <br/> <span style='color: #f40'>执行此功能前请记得备份数据</span>`, (async () => {
                let loadObj = loading();
                try {
                    await this.parseMovieList();
                } catch (e) {
                    console.error(e);
                } finally {
                    loadObj.close();
                }
            }));
        }
        async parseMovieList($dom) {
            let movieList, nextPageLink;
            if ($dom) {
                movieList = $dom.find(this.getSelector().itemSelector);
                nextPageLink = $dom.find(".pagination-next").attr("href");
            } else {
                movieList = $(this.getSelector().itemSelector);
                nextPageLink = $(".pagination-next").attr("href");
            }
            for (const element of movieList) {
                const item = $(element), href = item.find("a").attr("href"), carNum = item.find(".video-title strong").text().trim();
                if (href && carNum) try {
                    if (await storageManager.getCar(carNum)) {
                        show.info(`${carNum} 已存在, 跳过`);
                        continue;
                    }
                    await storageManager.saveCar(carNum, href, "", this.type);
                } catch (error) {
                    console.error(`保存失败 [${carNum}]:`, error);
                }
            }
            if (nextPageLink) {
                show.info("发现下一页,正在解析:", nextPageLink);
                await new Promise((resolve => setTimeout(resolve, 1e3)));
                $.ajax({
                    url: nextPageLink,
                    method: "GET",
                    success: html => {
                        const parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html"));
                        this.parseMovieList(next$dom);
                    },
                    error: function(err) {
                        console.error(err);
                        show.error("加载下一页失败:" + err.message);
                    }
                });
            } else {
                show.ok("导入结束!");
                window.refresh();
            }
        }
    }
    class CopyTitleOrDownImgPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "titleSvg", '<svg t="1747553289744" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7507" width="200" height="200"><path d="M959.8 150.8c0-2.3-1.9-4.2-4.2-4.2H253.3c-2.3 0-4.2 1.9-4.2 4.2v115.9c0 2.3 1.9 4.2 4.2 4.2h702.3c2.3 0 4.2-1.9 4.2-4.2V150.8z" fill="" p-id="7508"></path><path d="M126.4 208.8m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7509"></path><path d="M851.5 453.7c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.7-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V453.7z" fill="" p-id="7510"></path><path d="M126.4 512m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7511"></path><path d="M851.5 756.9c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.8-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V756.9z" fill="" p-id="7512"></path><path d="M126.4 815.2m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7513"></path></svg>');
            __publicField(this, "carNumSvg", '<svg t="1747552574854" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3539" width="200" height="200"><path d="M920.337035 447.804932c-6.067182-6.067182-10.918677-11.643178-16.985859-17.71036l48.536436-30.334889-42.469254-109.207238-121.340579 12.134365c-6.067182-6.067182-6.067182-12.134365-12.134365-18.201547-12.134365-12.134365-18.201547-24.267706-24.267706-30.334889-24.26873-36.402071-30.334889-42.469254-54.603619-42.469254H339.116511c-18.201547 0-24.267706 6.067182-54.603619 42.469254-6.067182 6.067182-12.134365 18.201547-24.267706 30.334889 0 0-6.067182 6.067182-12.134365 18.201547l-115.27442-12.134365-48.536436 109.207238 51.090608 24.378223c-6.067182 6.067182-30.334889 34.660404-30.334889 34.660405l-15.542998 22.280446-12.282744 17.018605c-6.067182 12.134365-5.064342 10.868535-5.064342 29.070082v224.480635c0 36.402071 18.201547 60.670801 54.603618 60.670801h115.273397c36.402071 0 54.603619-24.267706 54.603619-54.603619v-18.201547h424.693562v18.201547c0 30.334889 18.201547 54.603619 54.603618 54.603619h115.273397c36.402071 0 60.670801-24.267706 60.670801-60.670801V539.300786c0-42.469254 0.685615-46.662763-11.44875-64.863287-4.731768-6.744611-11.94403-16.196891-20.101827-26.632567z m-35.186383-78.381161l-30.334889 18.201547-12.134365-12.134365c-6.067182-8.899694-12.134365-12.134365-12.134365-18.201547l42.469254-6.067183 12.134365 18.201548z m-533.899776-97.072873h339.755054l78.871325 103.140055H272.378527l78.872349-103.140055zM175.305655 357.290429h36.402071c-6.067182 6.067182-6.067182 12.134365-12.134365 18.201547l-18.201547 6.067183-18.201547-12.134365 12.135388-12.134365z m667.375743 394.35765h-54.603619V678.843936H242.043638v72.804143H132.837424V527.167444c0-12.134365-0.041956-20.662599 1.216711-23.556508 1.258667-2.89391 9.955746-16.924461 21.193695-29.173437l35.722596-38.276768h639.576607l21.917172 20.938891c6.067182 6.067182 21.847587 21.366633 25.712615 28.732392 7.621585 9.996678 6.973832 10.999518 13.041014 23.133883v242.682182h-48.536436zM242.043638 533.234627h133.474944v60.670801H242.043638v-60.670801z m412.559197 0h133.474944v60.670801H654.602835v-60.670801z" p-id="3540"></path></svg>');
            __publicField(this, "downSvg", '<svg t="1747552626242" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4551" width="200" height="200"><path d="M641.6 660l-8.64-64 32-4.32a211.2 211.2 0 0 0-26.72-420.32 215.36 215.36 0 0 0-213.12 192 94.56 94.56 0 0 0 0 11.52v41.28h-64V384v-7.04a153.12 153.12 0 0 1 0-19.52A279.84 279.84 0 0 1 636.16 108H640A275.2 275.2 0 0 1 673.28 656z" fill="#333333" p-id="4552"></path><path d="M490.4 446.24l-7.52-39.84a182.4 182.4 0 0 1 107.52-162.88l29.12-13.28L646.08 288l-29.12 13.28a117.92 117.92 0 0 0-70.08 101.28l6.24 30.4zM392.96 652.32h-78.72A202.24 202.24 0 0 1 256 256l30.72-9.12 18.24 61.28-30.72 9.12a138.24 138.24 0 0 0 39.68 270.72h78.72zM479.2 512h64v320h-64z" fill="#333333" p-id="4553"></path><path d="M510.4 908l-156.32-147.68 43.84-46.4 112.48 106.08 112.8-106.08 43.84 46.56-156.64 147.52z" fill="#333333" p-id="4554"></path></svg>');
        }
        async initCss() {
            return `\n            .box .tags {\n                justify-content: space-between;\n            }\n            .tool-box span{\n                opacity:.3\n            }\n            \n            .tool-box span:hover{\n                opacity:1\n            }\n            ${isJavBus ? ".tool-box .icon{ height: 2rem; width: 2rem; }" : ""}\n        `;
        }
        handle() {
            if (window.isListPage) {
                this.addCopy();
                this.bindClick();
            }
        }
        addCopy() {
            $(this.getSelector().itemSelector).toArray().forEach((ele => {
                let $box = $(ele);
                isJavDb && $box.find(".tags").append(`\n                    <div class="tool-box">\n                        <span class="titleSvg"  title="复制标题" style="margin-right: 15px; color:#c5a45d;">${this.titleSvg}</span>\n                        <span class="carNumSvg" title="复制番号" style="margin-right: 15px; color:#9f2727;">${this.carNumSvg}</span>\n                        <span class="downSvg"   title="下载封面" style="margin-right: 15px; color:#2ca5c0;">${this.downSvg}</span>\n                    </div>\n                `);
                isJavBus && $box.find(".photo-info").append(`\n                    <div class="tool-box">\n                        <span class="titleSvg"  title="复制标题" style="margin-right: 15px; color:#c5a45d;">${this.titleSvg}</span>\n                        <span class="carNumSvg" title="复制番号" style="margin-right: 15px; color:#9f2727;">${this.carNumSvg}</span>\n                        <span class="downSvg"   title="下载封面" style="margin-right: 15px; color:#2ca5c0;">${this.downSvg}</span>\n                    </div>                \n                `);
            }));
        }
        bindClick() {
            const listPagePlugin = this.getBean("ListPagePlugin");
            $(this.getSelector().boxSelector).on("click", ".titleSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                navigator.clipboard.writeText(title).then((() => {
                    show.info("标题已复制到剪切板, " + title);
                })).catch((err => {
                    console.error("复制失败: ", err);
                }));
            })).on("click", ".carNumSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                navigator.clipboard.writeText(carNum).then((() => {
                    show.info("番号已复制到剪切板, " + carNum);
                })).catch((err => {
                    console.error("复制失败: ", err);
                }));
            })).on("click", ".downSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                let $img = $box.find(".cover img");
                isJavBus && ($img = $box.find(".photo-frame img"));
                const url = $img.attr("src");
                console.log(url);
                GM_download({
                    url: url,
                    name: title + ".jpg"
                });
            }));
        }
    }
    utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/layer.min.css");
    utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css");
    window.onload = async function() {
        window.isDetailPage = function() {
            let href = window.location.href;
            return href.includes("javdb") ? href.includes("/v/") : !!href.includes("javbus") && $("#magnet-table").length > 0;
        }();
        window.isListPage = function() {
            let href = window.location.href;
            return href.includes("javdb") ? $(".movie-list").length > 0 : !!href.includes("javbus") && $(".masonry > div .item").length > 0;
        }();
        !function() {
            const pluginManager = new PluginManager;
            let hostname = window.location.hostname;
            if (hostname.includes("javdb")) {
                pluginManager.register(ListPagePlugin);
                pluginManager.register(AutoPagePlugin);
                pluginManager.register(Fc2Plugin);
                pluginManager.register(FoldCategoryPlugin);
                pluginManager.register(ListPageButtonPlugin);
                pluginManager.register(HistoryPlugin);
                pluginManager.register(SettingPlugin);
                pluginManager.register(NavBarPlugin);
                pluginManager.register(HitShowPlugin);
                pluginManager.register(TOP250Plugin);
                pluginManager.register(SyncDataPlugin);
                pluginManager.register(SearchByImagePlugin);
                pluginManager.register(CopyTitleOrDownImgPlugin);
                pluginManager.register(DetailPagePlugin);
                pluginManager.register(ReviewPlugin);
                pluginManager.register(RelatedPlugin);
                pluginManager.register(DetailPageButtonPlugin);
                pluginManager.register(HighlightMagnetPlugin);
                pluginManager.register(PreviewVideoPlugin);
                pluginManager.register(FilterTitleKeywordPlugin);
                pluginManager.register(ActressInfoPlugin);
                pluginManager.register(OtherSitePlugin);
                pluginManager.register(WantAndWatchedVideosPlugin);
            }
            if (hostname.includes("javbus")) {
                pluginManager.register(ListPagePlugin);
                pluginManager.register(ListPageButtonPlugin);
                pluginManager.register(SettingPlugin);
                pluginManager.register(HistoryPlugin);
                pluginManager.register(SyncDataPlugin);
                pluginManager.register(AutoPagePlugin);
                pluginManager.register(SearchByImagePlugin);
                pluginManager.register(BusNavBarPlugin);
                pluginManager.register(CopyTitleOrDownImgPlugin);
                pluginManager.register(BusDetailPagePlugin);
                pluginManager.register(DetailPageButtonPlugin);
                pluginManager.register(ReviewPlugin);
                pluginManager.register(FilterTitleKeywordPlugin);
                pluginManager.register(HighlightMagnetPlugin);
                pluginManager.register(BusPreviewVideoPlugin);
            }
            hostname.includes("javtrailers") && pluginManager.register(JavTrailersPlugin);
            hostname.includes("subtitlecat") && pluginManager.register(SubTitleCatPlugin);
            hostname.includes("aliyundrive") && pluginManager.register(AliyunPanPlugin);
            pluginManager.process().then();
        }();
    };
}();