JAV-JHS

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

2025-06-11 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name         JAV-JHS
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version      2.0.4
// @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://javdb*.com/*
// @include      https://www.javbus.com/*
// @include      https://*sehuatang.*/*
// @include      https://javtrailers.com/*
// @include      https://subtitlecat.com/*
// @include      https://www.aliyundrive.com/*
// @include      https://5masterzzz.site/*
// @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%3A%3Abefore%20%7B%20content%3A%20%22%22%3B%20position%3A%20fixed%3B%20top%3A%200%3B%20left%3A%200%3B%20width%3A%20100%25%3B%20height%3A%20100%25%3B%20z-index%3A%209999999999%3B%20pointer-events%3A%20auto%3B%20display%3A%20block%3B%20%7D%20body.script-ready%3A%3Abefore%20%7B%20display%3A%20none%3B%20pointer-events%3A%20none%3B%20%7D%20%60%3B%20document.head.appendChild(initialHideStyle)%3B%20if%20(window.location.href.includes(%22hideNav%3D1%22))%20%7B%20const%20pollInterval%20%3D%20setInterval(()%20%3D%3E%20%7B%20const%20searchBar%20%3D%20document.querySelector(%22%23search-bar-container%22)%3B%20if%20(searchBar%20%26%26%20window.getComputedStyle(searchBar).display%20%3D%3D%3D%20%22none%22)%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20clearInterval(pollInterval)%3B%20%7D%20const%20navBarDefault%20%3D%20document.querySelector(%22.navbar-default%22)%3B%20if%20(navBarDefault%20%26%26%20window.getComputedStyle(navBarDefault).display%20%3D%3D%3D%20%22none%22)%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20clearInterval(pollInterval)%3B%20%7D%20%7D%2C%20200)%3B%20%7D%20else%20%7B%20setTimeout(()%20%3D%3E%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20%7D%2C%201e3)%3B%20%7D%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      xunlei.com
// @connect      geilijiasu.com
// @connect      aliyundrive.com
// @connect      aliyundrive.net
// @connect      ja.wikipedia.org
// @connect      beta.magnet.pics
// @connect      jdforrepam.com
// @connect      cc3001.dmm.co.jp
// @connect      cc3001.dmm.com
// @connect      www.dmm.co.jp
// @connect      special.dmm.co.jp
// @connect      adult.contents.fc2.com
// @connect      fc2ppvdb.com
// @connect      123av.com
// @connect      u3c3.com
// @connect      btsow.pics
// @connect      3xplanet.com
// @connect      memojav.com
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @run-at       document-start
// ==/UserScript==

var _StorageManager_instances, autoCleanup_fn, saveFilterItem_fn, _SettingPlugin_instances, handleSyncData_fn, __defProp = Object.defineProperty, __typeError = msg => {
    throw TypeError(msg);
}, __publicField = (obj, key, value) => ((obj, key, value) => key in obj ? __defProp(obj, key, {
    enumerable: !0,
    configurable: !0,
    writable: !0,
    value: value
}) : obj[key] = value)(obj, "symbol" != typeof key ? key + "" : key, value), __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) => (((obj, member, msg) => {
    member.has(obj) || __typeError("Cannot " + msg);
})(obj, member, "access private method"), method);

const currentHref = window.location.href, isJavDb = currentHref.includes("javdb"), isJavBus = currentHref.includes("javbus"), isSearchPage = currentHref.includes("/search?q") || currentHref.includes("/search/") || currentHref.includes("/users/"), Status_FILTER = "filter", Status_FAVORITE = "favorite", Status_HAS_DOWN = "hasDown", Status_HAS_WATCH = "hasWatch";

let detailPageCss$1 = "";

window.location.href.includes("hideNav=1") && (detailPageCss$1 = "\n         .navbar-default {\n            display: none !important;\n        }\n        body {\n            padding-top:0px!important;\n        }\n    ");

const javBusStyle = `\n<style>\n    \n    ${detailPageCss$1}\n\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        overflow: inherit !important;\n    }\n    .masonry .movie-box .photo-frame {\n        height: 70% !important;\n        margin: 0 !important;\n        position:relative; /* 方便预览视频定位*/\n    }\n    .masonry .movie-box img {\n        max-height: 300px;\n        min-height: 300px;\n        height: 100% !important;\n        object-fit: cover; /* 保持比例,裁剪多余部分 */\n        object-position: top 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`;

let detailPageCss = "";

window.location.href.includes("hideNav=1") && (detailPageCss = "\n        .main-nav,#search-bar-container {\n            display: none !important;\n        }\n        \n        html {\n            padding-top:0px!important;\n        }\n    ");

let imgObjectPosition = "100% 50% !important;";

window.location.href.includes("/advanced_search?type=100") && (imgObjectPosition = "50% 50% !important;");

const javdbStyle = `\n<style>\n    ${detailPageCss}\n    \n    .navbar {\n        z-index: 12345679 !important;\n        padding: 0 0;\n    }\n    \n    .navbar-link:not(.is-arrowless) {\n        padding-right: 33px;\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    .cover {\n        min-height: 300px !important; /*控制列多时的高度*/\n        overflow: hidden !important;\n    }\n    \n    .cover img{\n        object-fit: cover !important;\n        object-position: ${imgObjectPosition}\n    }\n</style>\n`;

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

isJavDb && insertStyle(javdbStyle);

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, "title_filter_keyword_key", "title_filter_keyword");
        __publicField(this, "review_filter_keyword_key", "review_filter_keyword");
        __publicField(this, "setting_key", "setting");
        __publicField(this, "filter_actress_car_list_key", "car_list_actress_");
        __publicField(this, "filter_actor_car_list_key", "car_list_actor_");
        __publicField(this, "filter_actor_actress_info_list_key", "filter_actor_actress_info_list");
        __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-JHS",
            version: 1,
            storeName: "appData"
        }));
        __publicField(this, "interceptedKeys", [ this.car_list_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();
        this.migrateData().then();
    }
    async migrateData() {
        var dbName;
        if (!(await (dbName = "JAV-JSH", new Promise((resolve => {
            const req = indexedDB.open(dbName);
            req.onerror = () => resolve(!1);
            req.onsuccess = () => {
                req.result.close();
                resolve(!0);
            };
            req.onupgradeneeded = e => {
                e.target.transaction.abort();
                resolve(!1);
            };
        }))))) return;
        const oldStore = localforage.createInstance({
            driver: localforage.INDEXEDDB,
            name: "JAV-JSH",
            version: 1,
            storeName: "appData"
        }), newStore = localforage.createInstance({
            driver: localforage.INDEXEDDB,
            name: "JAV-JHS",
            version: 1,
            storeName: "appData"
        });
        oldStore.keys().then((keys => {
            const promises = keys.map((key => oldStore.getItem(key).then((value => newStore.setItem(key, value)))));
            return Promise.all(promises);
        })).then((() => {
            oldStore.dropInstance({
                name: "JAV-JSH"
            }).then((() => {}));
        })).catch((err => {
            console.error(err);
        }));
    }
    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 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) {
        settingObj ? await this.forage.setItem(this.setting_key, settingObj) : show.error("设置对象为空");
    }
    async saveSettingItem(key, value) {
        if (!key) {
            show.error("key 不能为空");
            return;
        }
        let settingObj = await this.getSetting();
        settingObj[key] = value;
        await this.saveSetting(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);
        actress && (actress = actress.trim());
        const carList = await this.forage.getItem(this.car_list_key) || [];
        let carData = carList.find((item => item.carNum === carNum));
        if (carData) {
            carData.url = url;
            carData.actress = actress;
            carData.updateDate = utils.getNowStr();
        } else {
            carData = {
                carNum: carNum,
                url: url,
                actress: actress,
                status: "",
                updateDate: utils.getNowStr()
            };
            carList.push(carData);
        }
        delete carData.createDate;
        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;

          case Status_HAS_WATCH:
            carData.status = Status_HAS_WATCH;
            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.updateDate ? new Date(a.updateDate).getTime() : 0;
            return (b.updateDate ? new Date(b.updateDate).getTime() : 0) - dateA;
        }));
    }
    async getCar(carNum) {
        return (await this.getCarList()).find((item => item.carNum === carNum));
    }
    async getActorFilterCarList(key) {
        return (await this.forage.getItem(key) || []).sort(((a, b) => {
            if (!a || !b) return 0;
            const dateA = a.updateDate ? new Date(a.updateDate).getTime() : 0;
            return (b.updateDate ? new Date(b.updateDate).getTime() : 0) - dateA;
        }));
    }
    async getAllActorFilterCarList() {
        const carList = [];
        await this.forage.iterate(((value, key) => {
            key.startsWith("car_list_") && carList.push(...value);
        }));
        return carList;
    }
    async getActorFilterCarMap() {
        const carListMap = {};
        await this.forage.iterate(((value, key) => {
            key.startsWith(this.filter_actor_car_list_key) && (carListMap[key] = value);
        }));
        return carListMap;
    }
    async getActressFilterCarMap() {
        const carListMap = {};
        await this.forage.iterate(((value, key) => {
            key.startsWith(this.filter_actress_car_list_key) && (carListMap[key] = value);
        }));
        return carListMap;
    }
    async getActorFilterCar(key, carNum) {
        return (await this.getActorFilterCarList(key)).find((item => item.carNum === carNum));
    }
    async saveActorFilterCar(key, carNum, url, actress) {
        if (!carNum) {
            show.error("番号为空!");
            throw new Error("番号为空!");
        }
        if (!url) {
            show.error("url为空!");
            throw new Error("url为空!");
        }
        url.includes("http") || (url = window.location.origin + url);
        actress && (actress = actress.trim());
        const carList = await this.forage.getItem(key) || [];
        let carData = carList.find((item => item.carNum === carNum));
        if (!carData) {
            carData = {
                carNum: carNum,
                url: url,
                actress: actress,
                status: Status_FILTER,
                updateDate: utils.getNowStr()
            };
            carList.push(carData);
            await this.forage.setItem(key, carList);
        }
    }
    async removeActorFilter(key) {
        if (!key.includes("car_list_")) throw new Error("非法操作:" + key);
        await this.forage.removeItem(key);
    }
    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 ? "..." : ""}`);
        newList.forEach((car => {
            if (!("updateDate" in car)) if ("createDate" in car) {
                car.updateDate = car.createDate;
                delete car.createDate;
            } else car.updateDate = "2025-04-23 08:14:33";
        }));
        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.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.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);
        for (const key of Object.keys(dataJson)) if (key.startsWith("car_list_")) {
            console.log(key);
            await this.forage.setItem(key, dataJson[key]);
        }
    }
    async exportData() {
        return {
            car_list: await this.getCarList(),
            title_filter_keyword: await this.getTitleFilterKeyword(),
            review_filter_keyword: await this.getReviewFilterKeywordList(),
            setting: await this.getSetting(),
            ...await this.getActressFilterCarMap(),
            ...await this.getActorFilterCarMap()
        };
    }
};

class Utils {
    constructor() {
        __publicField(this, "intervalContainer", {});
        __publicField(this, "mimeTypes", {
            txt: "text/plain",
            html: "text/html",
            css: "text/css",
            csv: "text/csv",
            json: "application/json",
            xml: "application/xml",
            jpg: "image/jpeg",
            jpeg: "image/jpeg",
            png: "image/png",
            gif: "image/gif",
            webp: "image/webp",
            svg: "image/svg+xml",
            pdf: "application/pdf",
            doc: "application/msword",
            docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            xls: "application/vnd.ms-excel",
            xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ppt: "application/vnd.ms-powerpoint",
            pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
            zip: "application/zip",
            rar: "application/x-rar-compressed",
            "7z": "application/x-7z-compressed",
            mp3: "audio/mpeg",
            wav: "audio/wav",
            mp4: "video/mp4",
            webm: "video/webm",
            ogg: "audio/ogg"
        });
        __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)) {
            GM_openInTab(url.includes("http") ? url : window.location.origin + url, {
                insert: 0
            });
            return;
        }
        const finalUrl = url.includes("?") ? `${url}&hideNav=1` : `${url}?hideNav=1`;
        layer.open({
            type: 2,
            title: title,
            content: finalUrl,
            scrollbar: !1,
            shadeClose: shadeClose,
            area: [ "80%", "90%" ],
            isOutAnim: !1,
            anim: -1
        });
    }
    closePage() {
        storageManager.getSetting("needClosePage", "yes").then((needClosePage => {
            if ("yes" !== needClosePage) return;
            parent.document.documentElement.style.overflow = "auto";
            [ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
                const elements = parent.document.querySelectorAll(selector);
                if (elements.length > 0) {
                    const elementToRemove = elements.length > 1 ? elements[elements.length - 1] : elements[0];
                    elementToRemove.parentNode.removeChild(elementToRemove);
                }
            }));
            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: [ "确定", "取消" ],
            shade: 0,
            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) {
        show.info("开始请求下载...");
        const fileExtension = fileName.split(".").pop().toLowerCase();
        let blob, mimeType = this.mimeTypes[fileExtension] || "application/octet-stream";
        if (data instanceof Blob) {
            console.log("blob类型");
            blob = data;
        } else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
            console.log("ArrayBuffer");
            blob = new Blob([ data ], {
                type: mimeType
            });
        } else if ("string" == typeof data && data.startsWith("data:")) {
            console.log("base64");
            const byteString = atob(data.split(",")[1]), arrayBuffer = new ArrayBuffer(byteString.length), uintArray = new Uint8Array(arrayBuffer);
            for (let i = 0; i < byteString.length; i++) uintArray[i] = byteString.charCodeAt(i);
            blob = new Blob([ uintArray ], {
                type: mimeType
            });
        } else {
            console.log("其他情况按文本处理");
            blob = new Blob([ data ], {
                type: mimeType
            });
        }
        const 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;
        }
    }
    setHrefParam(key, val) {
        const newUrl = new URL(window.location.href);
        newUrl.searchParams.set(key, val);
        window.history.pushState({}, "", newUrl.toString());
    }
    getResponsiveArea(defaultArea) {
        const screenWidth = window.innerWidth;
        return screenWidth >= 1920 ? defaultArea || [ "60%", "80%" ] : screenWidth >= 1200 ? [ "60%", "85%" ] : screenWidth >= 768 ? [ "70%", "90%" ] : [ "95%", "95%" ];
    }
    isMobile() {
        const userAgent = navigator.userAgent.toLowerCase();
        return [ "iphone", "ipod", "ipad", "android", "blackberry", "windows phone", "nokia", "webos", "opera mini", "mobile", "mobi", "tablet" ].some((keyword => userAgent.includes(keyword)));
    }
    copyToClipboard(type, text) {
        navigator.clipboard.writeText(text).then((() => show.info(`${type}已复制到剪切板, ${text}`))).catch((err => console.error("复制失败: ", err)));
    }
}

class SeHuaTangStorageManager {
    constructor() {
        __publicField(this, "forage", localforage.createInstance({
            driver: localforage.INDEXEDDB,
            name: "JAV-JHS-SeHuaTang",
            version: 1,
            storeName: "appData"
        }));
        __publicField(this, "article_list_key", "article_list");
        if (SeHuaTangStorageManager.instance) throw new Error("SeHuaTangStorageManager已被实例化过了!");
        SeHuaTangStorageManager.instance = this;
    }
    async saveArticle(articleId, url, title, actionType) {
        if (!articleId) {
            show.error("articleId为空!");
            throw new Error("articleId为空!");
        }
        if (!title) {
            show.error("title为空!");
            throw new Error("title为空!");
        }
        if (!url) {
            show.error("url为空!");
            throw new Error("url为空!");
        }
        url.includes("http") || (url = window.location.origin + url);
        const articleList = await this.forage.getItem(this.article_list_key) || [];
        let articleData = articleList.find((item => item.articleId === articleId));
        if (articleData) articleData.updateDate = utils.getNowStr(); else {
            articleData = {
                articleId: articleId,
                url: url,
                status: "",
                updateDate: utils.getNowStr()
            };
            articleList.push(articleData);
        }
        switch (actionType) {
          case Status_FILTER:
            if (articleData.status === Status_FILTER) {
                const msg2 = `${articleId} 已在屏蔽列表中`;
                show.error(msg2);
                throw new Error(msg2);
            }
            articleData.status = Status_FILTER;
            break;

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

          default:
            const msg = "actionType错误";
            show.error(msg);
            throw new Error(msg);
        }
        await this.forage.setItem(this.article_list_key, articleList);
    }
    async getArticleList() {
        return await this.forage.getItem(this.article_list_key) || [];
    }
    async getArticle(articleId) {
        return (await this.forage.getItem(this.article_list_key) || []).find((item => item.articleId === articleId));
    }
}

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,
                timeout: 1e4,
                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);
    }
    checkUrlStatus(url, headers = {}, timeout) {
        return new Promise(((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "HEAD",
                url: url,
                headers: headers,
                timeout: timeout || 1e4,
                onload: response => {
                    resolve(response.status);
                },
                onerror: error => {
                    reject(new Error(`请求失败: ${error}`));
                },
                ontimeout: () => {
                    reject(new Error(`请求超时(${timeout}ms)`));
                }
            });
        }));
    }
    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,
                timeout: 1e4,
                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 {
                            console.error("请求失败,状态码:", response.status);
                            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;

window.seHuaTangStorageManager = new SeHuaTangStorageManager;

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-container {\n                flex: 1; /* 自动填充剩余空间 */\n                overflow-y: auto; /* 保留滚动条 */\n                border: 1px solid #e2e8f0;\n            }\n            \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            \n            \n            /* 滚动条美化 */\n            .data-table-container::-webkit-scrollbar {\n                width: 8px;\n                height: 8px;\n            }\n            \n            .data-table-container::-webkit-scrollbar-track {\n                background: #f1f1f1;\n            }\n            \n            .data-table-container::-webkit-scrollbar-thumb {\n                background: #c1c1c1;\n                border-radius: 4px;\n            }\n            \n            .data-table-container::-webkit-scrollbar-thumb:hover {\n                background: #a8a8a8;\n            }\n            \n            /* 最后一行底部边框 */\n            .data-table tbody tr:last-child td {\n                border-bottom: 1px solid #f1f5f9;\n            }\n            \n            .table-pagination {\n                display: flex;\n                align-items: center;\n                justify-content: flex-end;\n                padding: 20px 20px 0;\n                font-size: 14px;\n                flex-shrink: 0; /* 防止分页区域被压缩 */\n            }\n            \n            .pagination-info {\n                margin-right: auto;\n                color: #666;\n            }\n            \n            .pagination-controls {\n                display: flex;\n                align-items: center;\n                margin: 0 15px;\n            }\n            \n            .pagination-controls button {\n                padding: 5px 12px;\n                margin: 0 5px;\n                border: 1px solid #ddd;\n                background: #fff;\n                cursor: pointer;\n                border-radius: 4px;\n            }\n            \n            .pagination-controls button:disabled {\n                color: #ccc;\n                cursor: not-allowed;\n            }\n            \n            .pagination-current {\n                margin: 0 10px;\n            }\n            \n            .pagination-size-select {\n                padding: 5px;\n                border: 1px solid #ddd;\n                border-radius: 4px;\n            }\n        </style>\n    ");
    window.TableGenerator = class {
        constructor(options) {
            this.defaults = {
                tableClass: "data-table",
                showBorder: !1,
                buttons: [],
                pagination: {
                    enable: !1,
                    pageSize: 10,
                    pageSizeOptions: [ 10, 20, 50, 100 ],
                    currentPage: 1,
                    showTotal: !0,
                    showSizeChanger: !0,
                    showQuickJumper: !0
                }
            };
            this.config = {
                ...this.defaults,
                ...options,
                pagination: {
                    ...this.defaults.pagination,
                    ...options.pagination || {}
                }
            };
            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.container.style.display = "flex";
            this.container.style.flexDirection = "column";
            this.container.style.height = "90%";
            const tableContainer = document.createElement("div");
            tableContainer.className = "data-table-container";
            this.table = document.createElement("table");
            this.table.className = this.config.showBorder ? `${this.config.tableClass} show-border` : this.config.tableClass;
            this.createHeader();
            this.createBody();
            tableContainer.appendChild(this.table);
            this.container.appendChild(tableContainer);
            this.config.pagination.enable && this.createPagination();
        }
        createPagination() {
            const paginationContainer = document.createElement("div");
            paginationContainer.className = "table-pagination";
            const totalPages = Math.ceil(this.config.data.length / this.config.pagination.pageSize);
            paginationContainer.innerHTML = `\n                <div class="pagination-info">\n                    共 ${this.config.data.length} 条记录\n                </div>\n                <div class="pagination-controls">\n                    <button class="pagination-prev" ${this.config.pagination.currentPage <= 1 ? "disabled" : ""}>上一页</button>\n                    <span class="pagination-current">${this.config.pagination.currentPage}/${totalPages}</span>\n                    <button class="pagination-next" ${this.config.pagination.currentPage >= totalPages ? "disabled" : ""}>下一页</button>\n                </div>\n                ${this.config.pagination.showSizeChanger ? `\n                <div class="pagination-size">\n                    <select class="pagination-size-select">\n                        ${this.config.pagination.pageSizeOptions.map((size => `<option value="${size}" ${size === this.config.pagination.pageSize ? "selected" : ""}>${size}条/页</option>`)).join("")}\n                    </select>\n                </div>\n                ` : ""}\n            `;
            paginationContainer.querySelector(".pagination-prev").addEventListener("click", (() => {
                if (this.config.pagination.currentPage > 1) {
                    this.config.pagination.currentPage--;
                    this.update(this.config.data);
                }
            }));
            paginationContainer.querySelector(".pagination-next").addEventListener("click", (() => {
                if (this.config.pagination.currentPage < totalPages) {
                    this.config.pagination.currentPage++;
                    this.update(this.config.data);
                }
            }));
            this.config.pagination.showSizeChanger && paginationContainer.querySelector(".pagination-size-select").addEventListener("change", (e => {
                this.config.pagination.pageSize = parseInt(e.target.value);
                this.config.pagination.currentPage = 1;
                this.update(this.config.data);
            }));
            this.container.appendChild(paginationContainer);
        }
        createHeader() {
            const thead = document.createElement("thead"), headerRow = document.createElement("tr");
            this.config.columns.forEach((column => {
                if ("_index" === column.key) {
                    const indexTh = document.createElement("th");
                    indexTh.textContent = "序号";
                    indexTh.style.width = column.width ? column.width : "80px";
                    headerRow.appendChild(indexTh);
                    return;
                }
                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) {
            let displayData = this.config.data;
            if (this.config.pagination.enable) {
                const start = (this.config.pagination.currentPage - 1) * this.config.pagination.pageSize, end = start + this.config.pagination.pageSize;
                displayData = this.config.data.slice(start, end);
            }
            displayData.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 => {
                if ("_index" === column.key) {
                    const indexTd = document.createElement("td"), currentPage = this.config.pagination.currentPage || 1, pageSize = this.config.pagination.pageSize || 10;
                    indexTd.textContent = (currentPage - 1) * pageSize + rowIndex + 1;
                    tr.appendChild(indexTd);
                    return;
                }
                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, currentPage) {
            this.config.data = newData;
            currentPage && (this.config.pagination.currentPage = currentPage);
            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, currentHref2 = window.location.href;
        if (isJavDb) {
            carNum = $('a[title="複製番號"]').attr("data-clipboard-text");
            url = currentHref2.split("?")[0].split("#")[0];
            actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" ");
            actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" ");
        }
        if (isJavBus) {
            url = currentHref2.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
        };
    }
    getSelector() {
        if (isJavDb) return {
            boxSelector: ".movie-list",
            itemSelector: ".movie-list .item",
            coverImgSelector: ".cover img",
            requestDomItemSelector: ".movie-list .item",
            nextPageSelector: ".pagination-next"
        };
        if (isJavBus) return {
            boxSelector: ".masonry",
            itemSelector: ".masonry .item",
            coverImgSelector: ".photo-frame img",
            requestDomItemSelector: "#waterfall .item",
            nextPageSelector: "#next"
        };
        throw new Error("类型错误");
    }
    parseMovieId(href) {
        return href.split("/").pop().split(/[?#]/)[0];
    }
}

class DetailPagePlugin extends BasePlugin {
    constructor() {
        super();
    }
    handle() {
        window.isDetailPage && $(".video-meta-panel a").attr("target", "_blank");
    }
}

const getDmmVideo = async carNum => {
    const cacheKey = `dmm_video_urls_${carNum}`, cachedUrls = sessionStorage.getItem(cacheKey);
    if (cachedUrls) return JSON.parse(cachedUrls);
    const url = `https://www.dmm.co.jp/search/=/searchstr=${carNum}`, html = await gmHttp.get(url);
    if (html.includes("このサービスはお住まいの地域からは")) {
        show.error("节点不可用,请分流到日本ip");
        return null;
    }
    let matchVideoHref = $(html).find('a[href*="litevideo/freepv"]:first').attr("href");
    if (!matchVideoHref) {
        show.error("解析dmm失败, 该番号可能没有预览视频, " + url);
        return null;
    }
    let parts = matchVideoHref.split("/"), videoId = parts[parts.length - 2];
    const firstChar = videoId.charAt(0);
    let idPrefix = videoId.substring(0, 3);
    const videoTypes = [ "hhb", "hmb", "mhb", "mmb" ], videoMap = {};
    for (const type of videoTypes) videoMap[type] = `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoId}/${videoId}${type}.mp4`;
    const errorMap = {
        403: "节点不可用,请分流到日本ip",
        404: "其它画质资源不存在",
        null: "网络错误"
    };
    try {
        const checkPromises = Object.entries(videoMap).map((([type, url2]) => gmHttp.checkUrlStatus(url2).then((status => ({
            type: type,
            url: url2,
            status: status
        }))).catch((e => {
            console.error(e);
            return {
                type: type,
                url: url2,
                status: null
            };
        })))), results = await Promise.all(checkPromises), finalMap = {}, errorMessages = new Set;
        for (const {type: type, url: url2, status: status} of results) if (200 === status) finalMap[type] = url2; else {
            const msg = errorMap[status] || `未知错误状态码: ${status}`;
            errorMessages.add(msg);
        }
        let finalMapLength = Object.keys(finalMap).length;
        0 === finalMapLength && errorMessages.size > 0 && errorMessages.forEach((msg => show.error(msg)));
        Object.values(finalMap).some((url2 => "" !== url2)) && sessionStorage.setItem(cacheKey, JSON.stringify(finalMap));
        return finalMapLength > 0 ? finalMap : null;
    } catch (error) {
        console.error("并行检查URL时出错:", error);
        show.error(errorMap.null);
        return null;
    }
};

class PreviewVideoPlugin extends BasePlugin {
    async initCss() {
        return "\n            .video-control-btn {\n                position: absolute;\n                z-index: 99999999999;\n                min-width:120px;\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();
            }));
        }));
        let href = window.location.href;
        (href.includes("gallery-1") || href.includes("gallery-2")) && utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
            $(".fancybox-content #preview-video").length > 0 && this.handleVideo().then();
        }));
        href.includes("autoPlay=1") && $preview[0].click();
    }
    async handleVideo() {
        const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $videoContainer = $videoEl.parent();
        $videoContainer.css("position", "relative");
        if (!$videoEl.length || !$previewSource.length) return;
        $previewSource.attr("src");
        const videoEl = $videoEl[0];
        videoEl.muted = !1;
        videoEl.play();
        let carNum = this.getPageInfo().carNum;
        const dmmVideoMap = await getDmmVideo(carNum);
        let buttonsHtml = "";
        if (dmmVideoMap) {
            let defaultVideoQuality = await storageManager.getSetting("videoQuality") || "hhb";
            dmmVideoMap[defaultVideoQuality] || (defaultVideoQuality = Object.keys(dmmVideoMap)[0]);
            let defaultVideoUrl = dmmVideoMap[defaultVideoQuality];
            $previewSource.attr("src", defaultVideoUrl);
            let activeIndex = 0;
            [ {
                id: "video-mmb",
                quality: "mmb",
                text: "中画质 (432p)"
            }, {
                id: "video-mhb",
                quality: "mhb",
                text: "高画质 (576p)"
            }, {
                id: "video-hmb",
                quality: "hmb",
                text: "HD (720p)"
            }, {
                id: "video-hhb",
                quality: "hhb",
                text: "FullHD (1080p)"
            } ].forEach((option => {
                let dmmVideoUrl = dmmVideoMap[option.quality];
                if (dmmVideoUrl) {
                    const isActive = defaultVideoQuality === option.quality;
                    buttonsHtml += `\n                    <button class="video-control-btn${isActive ? " active" : ""}" \n                            id="${option.id}" \n                            data-quality="${option.quality}"\n                            data-video-src="${dmmVideoUrl}"\n                            style="bottom: ${50 * activeIndex}px; right: -133px;">\n                        ${option.text}\n                    </button>\n                `;
                    activeIndex++;
                }
            }));
        }
        let dmmVideoMapLength = dmmVideoMap ? Object.keys(dmmVideoMap).length : 0;
        buttonsHtml = `<button class="menu-btn" id="speed-btn" style="position: absolute; min-width: 120px; background-color:#76b45d;bottom: ${50 * (dmmVideoMapLength + 2)}px; right: -133px;">快进(z)</button>` + buttonsHtml;
        buttonsHtml = `<button class="menu-btn" id="video-filterBtn" style="position: absolute; min-width: 120px; background-color:#de3333;bottom: ${50 * (dmmVideoMapLength + 1)}px; right: -133px;">屏蔽(a)</button>` + buttonsHtml;
        buttonsHtml = `<button class="menu-btn" id="video-favoriteBtn" style="position: absolute; min-width: 120px; background-color:#25b1dc;bottom: ${50 * dmmVideoMapLength}px; right: -133px;">收藏(s)</button>` + buttonsHtml;
        $videoContainer.append(buttonsHtml);
        const $buttons = $videoContainer.find(".video-control-btn");
        $videoContainer.on("click", ".video-control-btn", (async e => {
            const $button = $(e.currentTarget), videoSrc = $button.data("video-src");
            if (!$button.hasClass("active")) try {
                $previewSource.attr("src", videoSrc);
                videoEl.load();
                videoEl.muted = !1;
                await videoEl.play();
                $buttons.removeClass("active");
                $button.addClass("active");
            } catch (error) {
                console.error("切换画质失败:", error);
            }
        }));
        $("#speed-btn").on("click", (() => {
            this.getBean("DetailPageButtonPlugin").speedVideo();
        }));
        utils.rightClick($("#speed-btn"), (event2 => {
            this.getBean("DetailPageButtonPlugin").filterOne(event2);
        }));
        $("#video-filterBtn").on("click", (event2 => {
            this.getBean("DetailPageButtonPlugin").filterOne(event2);
        }));
        $("#video-favoriteBtn").on("click", (event2 => {
            this.getBean("DetailPageButtonPlugin").favoriteOne(event2);
        }));
    }
}

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) {
            console.log("番号无法匹配, 跳搜索");
            let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
            window.location.href = "/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) {
                console.log("点击搜索页的第一个");
                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() {
        console.log("进入");
        if (!this.hasBand) {
            utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
                setTimeout((() => {
                    this.hasBand = !0;
                    let videoEl = document.getElementById("vjs_video_3_html5_api");
                    console.log(videoEl);
                    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"
                    });
                }), 100);
            }));
            utils.loopDetector((() => $("#vjs_video_3 canvas").length > 0), (() => {
                0 !== $("#vjs_video_3 canvas").length && $("#vjs_video_3 canvas").css({
                    position: "fixed",
                    width: "100vw",
                    height: "100vh",
                    objectFit: "cover",
                    top: "0",
                    right: "0",
                    zIndex: "999999998"
                });
            }));
        }
    }
}

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")) {
            $("section").html("");
            const urlParams = new URLSearchParams(window.location.search);
            let movieId = urlParams.get("movieId"), carNum = urlParams.get("carNum"), url = urlParams.get("url");
            movieId && carNum && url && this.openFc2Dialog(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        ";
    }
    openFc2Dialog(movieId, carNum, href) {
        if (href.includes("123av")) {
            this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum, href);
            return;
        }
        layer.open({
            type: 1,
            title: carNum,
            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="filterBtn" class="menu-btn" style="background-color:#de3333"><span>🚫 屏蔽</span></a>\n                        <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>⭐ 收藏</span></a>\n                        <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>📥️ 已下载</span></a>\n                        <a id="hasWatchBtn" class="menu-btn" style="background-color:#d7a80c;"><span>🔍 已观看</span></a>\n                        \n\x3c!--                        <a id="enable-magnets-filter" class="menu-btn fr-btn" style="background-color:#c2bd4c">--\x3e\n\x3c!--                            <span id="magnets-span">关闭磁力过滤</span>--\x3e\n\x3c!--                        </a>--\x3e\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                    <div class="message video-panel" style="margin-top:20px">\n                        <div id="magnets-content" class="magnet-links" style="margin: 0 0.75rem">\n                            <div class="search-loading">加载中...</div>\n                        </div>\n                    </div>\n                    <div id="reviews-content">\n                    </div>\n                    <div id="related-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();
                }));
                $("#hasWatchBtn").on("click", (async event2 => {
                    const actress = $("#data-actress").text();
                    await storageManager.saveCar(carNum, href, actress, Status_HAS_WATCH);
                    window.refresh();
                    layer.closeAll();
                }));
                $("#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.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum)));
            },
            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();
        this.getBean("RelatedPlugin").showRelated($("#related-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-meta">\n                    <span>\n                        站点: \n                        <a href="https://fc2ppvdb.com/articles/${res.carNum.replace("FC2-", "")}" target="_blank">fc2ppvdb</a>\n                        <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${res.carNum.replace("FC2-", "")}/" target="_blank">fc2电子市场</a>\n                    </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);
            this.getBean("HighlightMagnetPlugin").handleDb();
        })).catch((err => {
            console.error(err);
            $("#magnets-content").html(`\n                <div class="movie-error">加载失败: ${err.message}</div>\n            `);
        }));
    }
    async handleVideo(searchKeyword) {
        const fc2By123AvPlugin = this.getBean("Fc2By123AvPlugin");
        let loadObj = loading();
        try {
            const baseUrl = await fc2By123AvPlugin.getBaseUrl();
            let url = `${baseUrl}/search?keyword=${searchKeyword}`;
            const html = await gmHttp.get(url);
            const itemList = $(html).find(".box-item");
            if (0 === itemList.length) throw new Error("搜索无结果");
            for (let i = 0; i < itemList.length; i++) {
                const $item = $(itemList[i]);
                let title = $item.find("img").attr("title");
                const link = $item.find(".detail a").attr("href"), href = baseUrl + (link.startsWith("/") ? link : "/" + link);
                if (title && title.includes(searchKeyword)) {
                    const {id: id, publishDate: publishDate, title: title2, moviePoster: moviePoster} = await fc2By123AvPlugin.get123AvVideoInfo(href), movieList = await fc2By123AvPlugin.getMovie(id, moviePoster);
                    if (movieList.length > 0) {
                        $(".movie-trailer").attr("src", movieList[0].url);
                        let html2 = '\n                            <div class="movie-gallery" style="margin-bottom: 10px"> \n                            <span>影片: </span> \n                            <div class="movie-parts-list">\n                        ';
                        movieList.forEach(((movie, index) => {
                            html2 += `\n                                <a class="movie-part a-outline" data-url="${movie.url}" style="margin-left: 0">\n                                    部分 ${index + 1}\n                                </a>\n                            `;
                        }));
                        html2 += "</div> </div> ";
                        $(".movie-gallery").after(html2);
                        $(".movie-parts-list").on("click", ".movie-part", (function() {
                            const url2 = $(this).data("url");
                            $(".movie-trailer").attr("src", url2);
                        }));
                        break;
                    }
                }
            }
        } catch (e) {
            console.error(e);
            const missAvUrl = (await storageManager.getSetting()).missAvUrl || "https://missav.live";
            $(".movie-poster-container").html(`\n                <div class="movie-not-found">\n                    <i class="icon-warning"></i>\n                    <h3>未找到相关视频信息</h3>\n                    <p>123Av 中没有找到与当前番号相关的影片信息</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="${missAvUrl}/dm3/fc2-ppv-${searchKeyword}" target="_blank">missav</a></p>\n                </div>\n            `);
            $(".movie-trailer").hide();
        } finally {
            loadObj.close();
        }
    }
    async openFc2Page(movieId, carNum, url) {
        let javDbUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com");
        window.open(`${javDbUrl}/users/collection_codes?movieId=${movieId}&carNum=${carNum}&url=${url}`);
    }
}

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('a[title="誕生日"]').parent().parent().find("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");
        "5" === currentPage.toString() && $(".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) ? GM_openInTab(window.location.origin + url, {
            insert: 0
        }) : 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();
        this.hookOldSearch();
        this.toggleOtherNavItem();
        $(window).resize(this.toggleOtherNavItem);
        if (window.location.href.includes("/search?q")) {
            const urlParams = new URLSearchParams(window.location.search);
            let q = urlParams.get("q"), f = urlParams.get("f");
            $("#search-keyword").val(q);
            $("#search-type").val(f);
        }
    }
    hookSearch() {
        $("#navbar-menu-hero").after('\n            <div class="navbar-menu" id="search-box">\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();
        }));
    }
    hookOldSearch() {
        const searchImage = document.querySelector(".search-image");
        if (!searchImage) return;
        const clonedImage = searchImage.cloneNode(!0);
        searchImage.parentNode.replaceChild(clonedImage, searchImage);
        $("#button-search-image").attr("data-tooltip", "以图识图");
        $(".search-image").on("click", (event2 => {
            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        ');
    }
    toggleOtherNavItem() {
        let $searchBox = $("#search-box"), $oldSearchBox = $("#search-bar-container");
        if ($(window).width() < 1600 && $(window).width() > 1023) {
            $searchBox.hide();
            $oldSearchBox.show();
        }
        if ($(window).width() > 1600) {
            $searchBox.show();
            $oldSearchBox.hide();
        }
    }
}

class OtherSitePlugin extends BasePlugin {
    async handle() {
        let carNum = this.getPageInfo().carNum;
        const settingObj = await storageManager.getSetting(), missAvUrl = settingObj.missAvUrl || "https://missav.live", jableAvUrl = settingObj.jableAvUrl || "https://jable.tv", avgleUrl = settingObj.avgleUrl || "https://www.av.gl", javTrailersUrl = settingObj.javTrailersUrl || "https://javtrailers.com";
        let html = `\n            <div style="${isJavDb ? "margin-top:20px;margin-left: -16px;" : "margin-top:10px;"}">\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="${jableAvUrl}/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="${missAvUrl}/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="${avgleUrl}/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);
        $("#sample-waterfall").before(html);
        $("#javTrailersBtn").on("click", (event2 => utils.openPage(`${javTrailersUrl}/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 "";
    }
    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());
            }
        }
        $(".genre a").attr("target", "_blank");
    }
}

class DetailPageButtonPlugin extends BasePlugin {
    constructor() {
        super();
        this.answerCount = 1;
    }
    handle() {
        this.bindHotkey();
        this.hideVideoControls();
        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; color:white">\n                    <span>🚫 屏蔽(a)</span>\n                </a>\n                <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc; color:white">\n                    <span>⭐ 收藏(s)</span>\n                </a>\n                <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b; color:white">\n                    <span>📥️ 已下载</span>\n                </a>\n                <a id="hasWatchBtn" class="menu-btn" style="background-color:#d7a80c; color:white">\n                    <span>🔍 已观看</span>\n                </a>\n\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                <a id="magnetSearchBtn" class="menu-btn fr-btn" style="background:linear-gradient(to right, rgb(245,140,1), rgb(84,161,29))">\n                    <span>磁力搜索</span>\n                </a>\n                <a id="enable-magnets-filter" class="menu-btn fr-btn" style="background-color:#c2bd4c">\n                    <span id="magnets-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);
            this.showStatus(carNum).then();
            window.refresh();
            utils.closePage();
        }));
        $("#hasWatchBtn").on("click", (async () => {
            await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_HAS_WATCH);
            this.showStatus(carNum).then();
            window.refresh();
            utils.closePage();
        }));
        $("#magnetSearchBtn").on("click", (() => {
            let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(pageInfo.carNum);
            layer.open({
                type: 1,
                title: "磁力搜索",
                content: '<div id="magnetHubBox"></div>',
                area: utils.getResponsiveArea(),
                scrollbar: !1,
                success: () => {
                    $("#magnetHubBox").append(magnetHub);
                }
            });
        }));
        $("#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) {
        const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span");
        $filterBtn.text("🚫 屏蔽(a)");
        $favoriteBtn.text("⭐ 收藏(s)");
        $hasDownBtn.text("📥️ 已下载");
        $hasWatchBtn.text("🔍 已观看");
        const 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("📥️ 已标记下载");
            break;

          case Status_HAS_WATCH:
            $hasWatchBtn.text("🔍 已标记观看");
        }
    }
    async favoriteOne() {
        let pageInfo = this.getPageInfo();
        await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FAVORITE);
        this.showStatus(pageInfo.carNum).then();
        window.refresh();
        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 => {
                                let url = item.url, name = carNum + "." + item.ext;
                                this.previewSubtitle(url, name);
                            }
                        }, {
                            text: "下载",
                            class: "a-success",
                            onClick: async item => {
                                let url = item.url, name = carNum + "." + item.ext, content = await gmHttp.get(url);
                                utils.download(content, name);
                            }
                        } ]
                    });
                }
            }) : show.error("迅雷中找不到相关字幕!");
        })).catch((e => {
            console.error(e);
            show.error(e);
        })).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);
            this.showStatus(pageInfo.carNum).then();
            window.refresh();
            utils.closePage();
        } else utils.q(event2, `是否屏蔽${pageInfo.carNum}?`, (async () => {
            await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
            this.showStatus(pageInfo.carNum).then();
            window.refresh();
            utils.closePage();
        }), (() => {
            this.answerCount = 1;
        }));
    }
    speedVideo() {
        if ($("#preview-video").is(":visible")) {
            const videoEl = document.getElementById("preview-video");
            if (videoEl) {
                videoEl.muted = !1;
                videoEl.controls = !1;
                if (videoEl.currentTime + 5 < videoEl.duration) videoEl.currentTime += 5; else {
                    show.info("预览视频结束, 已回到开头");
                    videoEl.currentTime = 1;
                }
            }
            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();
    }
    hideVideoControls() {
        $(document).on("mouseenter", "#preview-video", (function() {
            $(this).prop("controls", !0);
        }));
    }
    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, (event2 => {
                const activeElement = document.activeElement;
                "INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable || (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);
        }));
    }
    previewSubtitle(url, name) {
        if (!url) {
            console.error("未提供文件URL");
            return;
        }
        const fileExt = url.split(".").pop().toLowerCase();
        "ass" === fileExt || "srt" === fileExt ? gmHttp.get(url).then((text => {
            let content = text, title = "字幕预览";
            if ("ass" === fileExt) {
                title = "ASS字幕预览 - " + name;
                const eventsSection = text.match(/\[Events][\s\S]*?(?=\[|$)/i);
                eventsSection && (content = eventsSection[0]);
            } else "srt" === fileExt && (title = "SRT字幕预览 - " + name);
            layer.open({
                type: 1,
                title: title,
                area: [ "80%", "80%" ],
                scrollbar: !1,
                content: `<div style="padding:15px;background:#1E1E1E;color:#FFF;font-family:Consolas,Monaco,monospace;white-space:pre-wrap;overflow:auto;height:100%;">${content}</div>`,
                btn: [ "下载", "关闭" ],
                btn1: function(index, layero, that) {
                    utils.download(text, name);
                    return !1;
                }
            });
        })).catch((error => {
            show.error(`预览失败: ${error.message}`);
            console.error("预览字幕文件出错:", error);
        })) : alert("仅支持预览ASS和SRT字幕文件");
    }
}

class HistoryPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "dataType", "all");
        __publicField(this, "tableObj", null);
    }
    handle() {
        if (isJavDb) {
            let handleResize2 = function() {
                if ($(".navbar-search").is(":hidden")) {
                    $(".historyBtnBox").show();
                    $(".miniHistoryBtnBox").hide();
                } else {
                    $(".historyBtnBox").hide();
                    $(".miniHistoryBtnBox").show();
                }
            };
            $(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable historyBtnBox">\n                    <a id="historyBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n                        历史列表\n                    </a>\n                </div>');
            $(".navbar-search").css("margin-left", "0").before('\n                <div class="navbar-item miniHistoryBtnBox">\n                    <a id="miniHistoryBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-left:0 !important;padding-right:0 !important;">\n                        历史列表\n                    </a>\n                </div>\n            ');
            handleResize2();
            $(window).resize(handleResize2);
        }
        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,#miniHistoryBtn").on("click", (event2 => this.openHistory()));
    }
    openHistory() {
        layer.open({
            type: 1,
            title: "历史列表",
            content: '\n            <div style="margin: 10px;display: flex;gap: 5px;">\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:#de3333">🚫 已屏蔽</a>\n                <a class="menu-btn history-btn" data-action="favorite" style="background-color:#25b1dc;">⭐ 已收藏</a>\n                <a class="menu-btn history-btn" data-action="hasDown" style="background-color:#7bc73b;">📥️ 已下载</a>\n                <a class="menu-btn history-btn" data-action="hasWatch" style="background-color:#d7a80c;">🔍 已观看</a>\n                <input id="searchCarNum" type="text" placeholder="搜索番号" style="padding: 4px 5px;margin-left: auto; margin-right: 0">\n                <a id="clearSearchbtn" class="a-dark" style="margin-left: 0">重置</a>\n            </div>\n            <div id="table-container"></div>\n        ',
            scrollbar: !1,
            area: utils.getResponsiveArea([ "60%", "90%" ]),
            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");
                    await this.reloadTable();
                })).on("click", "#clearSearchbtn", (async event2 => {
                    $("#searchCarNum").val("");
                    await this.reloadTable();
                })).on("keydown", "#searchCarNum", (async event2 => {
                    await this.reloadTable();
                }));
            },
            end: async () => window.refresh()
        });
    }
    async handleClickDetail(event2, data) {
        if (isJavDb) if (data.carNum.includes("FC2-")) {
            const movieId = this.parseMovieId(data.url);
            this.getBean("fc2Plugin").openFc2Dialog(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 movieId = this.parseMovieId(url);
                await this.getBean("Fc2Plugin").openFc2Page(movieId, data.carNum, url);
            } 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, 1);
    }
    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;
        this.hasWatchCount = 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++;
                break;

              case Status_HAS_WATCH:
                this.hasWatchCount++;
            }
        }));
        $('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})`);
        $('a[data-action="hasWatch"]').text(`🔍 已观看 (${this.hasWatchCount})`);
        const searchCarNum = $("#searchCarNum").val().trim();
        return searchCarNum ? dataList.filter((item => item.carNum.toLowerCase().includes(searchCarNum.toLowerCase()))) : "all" === this.dataType ? dataList : dataList.filter((item => item.status === this.dataType));
    }
    loadTableData(dataList) {
        this.tableObj = new TableGenerator({
            containerId: "table-container",
            columns: [ {
                title: "序号",
                key: "_index",
                width: "70px"
            }, {
                key: "carNum",
                title: "番号"
            }, {
                key: "actress",
                title: "演员",
                width: "250px"
            }, {
                key: "updateDate",
                title: "操作日期",
                width: "185px",
                render: item => "updateDate" in item ? item.updateDate : item.createDate || ""
            }, {
                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>' : url.includes("123av") ? '<span style="color:#eaa813">123Av</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 = "📥️ 已下载";
                        break;

                      case "hasWatch":
                        color = "#8ebd6e";
                        text = "🔍 已观看";
                    }
                    return `<span style="color:${color}">${text}</span>`;
                }
            } ],
            data: dataList,
            buttons: [ {
                text: "移除",
                class: "a-danger",
                onClick: (event2, item) => {
                    this.handleDelete(event2, item);
                }
            }, {
                text: "详情页",
                class: "a-info",
                onClick: (event2, item) => {
                    this.handleClickDetail(event2, item).then();
                }
            } ],
            pagination: {
                enable: !0,
                pageSize: 10,
                pageSizeOptions: [ 10, 20, 50, 100 ],
                currentPage: 1,
                showTotal: !0,
                showSizeChanger: !0,
                showQuickJumper: !0
            }
        });
    }
}

class ReviewPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "floorIndex", 1);
    }
    async handle() {
        if (window.isDetailPage) {
            if (isJavDb) {
                const movieId = this.parseMovieId(window.location.href);
                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失败, 该视频可能不存在, 无法获取评论数据");
                    return;
                }
                this.showReview(movieId, $("#sample-waterfall")).then();
            }
        }
    }
    async showReview(movieId, $eleBox) {
        const $magnets = $eleBox || $("#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="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", (event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            const $text = $("#reviewsFold .toggle-text"), $icon = $("#reviewsFold .toggle-icon"), isFolded = "展开" === $text.text();
            $text.text(isFolded ? "折叠" : "展开");
            $icon.text(isFolded ? "▲" : "▼");
            if (isFolded) {
                $("#reviewsContainer").show();
                $("#reviewsFooter").show();
            } else {
                $("#reviewsContainer").hide();
                $("#reviewsFooter").hide();
            }
        }));
        $magnets.append('<div id="reviewsContainer"></div>');
        $magnets.append('<div id="reviewsFooter"></div>');
        await this.fetchAndDisplayReviews(movieId);
    }
    async fetchAndDisplayReviews(movieId) {
        const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter");
        $reviewsContainer.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
        const reviewCount = await storageManager.getSetting("reviewCount", 20);
        let dataList = null;
        try {
            dataList = await getReviews(movieId, 1, reviewCount);
        } catch (e) {
            console.error("获取评论失败:", e);
        } finally {
            $("#reviewsLoading").remove();
        }
        if (!dataList) {
            $reviewsContainer.append('\n                <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n                    获取评论失败\n                    <a id="retryFetchReviews" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n                </div>\n            ');
            $("#retryFetchReviews").on("click", (async () => {
                $("#retryFetchReviews").parent().remove();
                await this.fetchAndDisplayReviews(movieId);
            }));
            return;
        }
        if (0 === dataList.length) {
            $reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>');
            return;
        }
        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 = $("#loadMoreReviews");
            $loadMoreReviews.on("click", (async () => {
                $loadMoreReviews.text("加载中...").prop("disabled", !0);
                currentPage++;
                let moreData;
                try {
                    moreData = await getReviews(movieId, currentPage, reviewCount);
                } catch (e) {
                    console.error("加载更多评论失败:", e);
                } finally {
                    $loadMoreReviews.text("加载失败, 请点击重试").prop("disabled", !1);
                }
                if (moreData) {
                    this.displayReviews(moreData, $reviewsContainer, reviewKeywordList);
                    if (moreData.length < reviewCount) {
                        $loadMoreReviews.remove();
                        $("#reviewsEnd").show();
                    } else $loadMoreReviews.text("加载更多评论").prop("disabled", !1);
                }
            }));
        } else $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 => {
                if (reviewKeywordList.some((keyword => item.content.includes(keyword)))) return;
                const starsHtml = Array(item.score).fill('<i class="icon-star"></i>').join(""), content = item.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (match => `<a href="${match}" class="a-primary" style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${match}</a>`)), 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">${utils.formatDate(item.created_at)}</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"), (async event2 => {
                const selectedText = window.getSelection().toString();
                if (selectedText) {
                    event2.preventDefault();
                    if (await utils.q(event2, `是否将 '${selectedText}' 加入评论区关键词?`)) {
                        await storageManager.saveReviewFilterKeyword(selectedText);
                        show.ok("操作成功, 刷新页面后生效");
                    }
                }
            }));
        }
    }
}

class FilterTitleKeywordPlugin extends BasePlugin {
    handle() {
        if (!window.isDetailPage) return;
        let $titles;
        if (isJavDb) {
            $titles = $("h2");
            $(".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();
                }));
            }
        }));
    }
}

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                ');
                isSearchPage || $(".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 if (window.location.href.includes("advanced_search")) {
                let $section = $("h2.section-title");
                $section.css({
                    display: "grid",
                    "grid-template-columns": "auto auto 1fr",
                    width: "100%"
                });
                $section.append(`\n                    <div>\n                        <a class="menu-btn" id="waitCheckBtn" \n                           style="background-color:#56c938 !important;; margin-left: 10px;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;border-bottom:none !important; border-radius:3px;">\n                          <span>打开已收藏</span>\n                        </a>\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; float: right">\n                          当前排序方式: ${"rateCount" === localStorage.getItem("sortMethod") ? "评价人数" : "date" === localStorage.getItem("sortMethod") ? "时间" : "默认"}\n                        </a>\n                    </div>\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                ');
                isSearchPage || $(".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() {
        if (isSearchPage) return;
        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 = this.getSelector();
        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.tag:contains('${text}')`).length > 0))) return;
            const {carNum: carNum, aHref: aHref, title: title} = this.getBean("ListPagePlugin").findCarNumAndHref($el);
            if (carNum.includes("FC2-")) {
                const movieId = this.parseMovieId(aHref);
                this.getBean("Fc2Plugin").openFc2Page(movieId, carNum, aHref);
            } else {
                let url = aHref + (aHref.includes("?") ? "&autoPlay=1" : "?autoPlay=1");
                window.open(url);
            }
            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 movieId = this.parseMovieId(url);
                await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum, url);
            } else window.open(url);
        }
    }
}

class ListPagePlugin extends BasePlugin {
    async handle() {
        this.cleanRepeatId();
        this.replaceHdImg();
        await this.doFilter();
        this.showNoItem();
        this.bindClick().then();
        new BroadcastChannel("channel-refresh").addEventListener("message", (async event2 => {
            "refresh" === event2.data.type && await this.doFilter();
        }));
        $(this.getSelector().itemSelector + " a").attr("target", "_blank");
        this.checkDom();
    }
    checkDom() {
        if (!window.isListPage) return;
        const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector), observer = new MutationObserver((mutations => {
            observer.disconnect();
            try {
                this.replaceHdImg();
                this.doFilter().then();
                this.getBean("ListPageButtonPlugin").sortItems();
                this.showNoItem();
                this.getBean("CopyTitleOrDownImgPlugin").addSvgBtn();
                $(this.getSelector().itemSelector + " a").attr("target", "_blank");
            } finally {
                observer.observe(targetNode, config);
            }
        })), config = {
            childList: !0,
            subtree: !1
        };
        observer.observe(targetNode, config);
    }
    showNoItem() {
        if ($(this.getSelector().itemSelector).length > 0) {
            0 === $(this.getSelector().itemSelector).filter((function() {
                return "none" !== $(this).css("display");
            })).length && show.info("当前内容已标记, 无内容可鉴定, 请进入下一页");
        }
    }
    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)), hasWatchCarNums = carList.filter((item => item.status === Status_HAS_WATCH)).map((item => item.carNum)), allActorFilterCarNums = (await storageManager.getAllActorFilterCarList()).map((item => item.carNum));
        let hideFilterItem = await storageManager.getSetting("hideFilterItem", "yes");
        isSearchPage && (hideFilterItem = "no");
        movieList.forEach((ele => {
            let $box = $(ele);
            if (isJavBus && $box.find(".avatar-box").length > 0) return;
            const {carNum: carNum, aHref: aHref, title: title} = this.findCarNumAndHref($box), hideKey = `${carNum}-hide`, keywordHideKey = `${carNum}-keywordHide`;
            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;
            }
            if (hasWatchCarNums.includes(carNum) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                $box.hide();
                $box.attr("data-hide", hideKey);
                return;
            }
            if (allActorFilterCarNums.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 = "#de3333";
            } else if (favoriteCarNums.includes(carNum)) {
                tagText = "⭐ 已收藏";
                color = "#25b1dc";
            } else if (hasDownCarNums.includes(carNum)) {
                tagText = "📥️ 已下载";
                color = "#7bc73b";
            } else if (hasWatchCarNums.includes(carNum)) {
                tagText = "🔍 已观看";
                color = "#d7a80c";
            }
            $box.find(".status-tag").remove();
            if (tagText) {
                isJavDb && $box.find(".tags").append(`\n                    <span class="tag is-success status-tag" \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 status-tag" 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 class="tag">${tagText}</span></a>`;
                    $box.find(".item-tag").append(tagHtml);
                }
            }
        }));
        $("#waitDownBtn span").text(`打开已收藏 (${favoriteCarNums.length})`);
    }
    async bindClick() {
        let selector = this.getSelector();
        $(selector.boxSelector).on("click", ".item img", (async event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            if ($(event2.target).closest("div.meta-buttons").length) return;
            const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
            let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", "yes");
            if (carNum.includes("FC2-")) {
                let movieId = this.parseMovieId(aHref);
                this.getBean("fc2Plugin").openFc2Dialog(movieId, carNum, aHref);
            } else "yes" === dialogOpenDetail ? utils.openPage(aHref, carNum, !1, event2) : window.open(aHref);
        }));
        $(selector.boxSelector).on("click", ".item video", (async event2 => {
            const video = event2.currentTarget;
            video.paused ? video.play().catch((e => console.error("播放失败:", e))) : video.pause();
            event2.preventDefault();
            event2.stopPropagation();
        }));
        $(selector.boxSelector).on("click", ".item .video-title", (async event2 => {
            const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
            if (carNum.includes("FC2-")) {
                event2.preventDefault();
                let movieId = this.parseMovieId(aHref);
                this.getBean("fc2Plugin").openFc2Dialog(movieId, carNum, aHref);
            }
        }));
        $(selector.boxSelector).on("contextmenu", ".item img, .item video", (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");
        }
        if (!carNum) {
            const msg = "提取番号信息失败";
            show.error(msg);
            throw new Error(msg);
        }
        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;
    }
    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();
        }));
    }
    async parsePage(href) {
        let loadObj = loading();
        try {
            const html = await http.get(href), dom = (new DOMParser).parseFromString(html, "text/html");
            isJavBus && $(dom).find(".avatar-box").length > 0 && $(dom).find(".avatar-box").parent().remove();
            let itemList = dom.querySelectorAll(this.getSelector().requestDomItemSelector), pagination = dom.querySelectorAll(".pagination");
            const listPagePlugin = this.getBean("listPagePlugin");
            await listPagePlugin.filterMovieList(itemList);
            let coverImgNodeList = dom.querySelectorAll(this.getSelector().coverImgSelector);
            listPagePlugin.replaceHdImg(coverImgNodeList);
            let $movieList = $(this.getSelector().boxSelector);
            $movieList.fadeOut(300, (() => {
                $movieList.html(itemList).fadeIn(300, (async () => {}));
            }));
            $(".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|star\/.*?)\/(\d+)/);
        return match ? parseInt(match[2], 10) : null;
    }
    shouldDisablePaging() {
        return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100" ].some((path => window.location.href.includes(path)));
    }
    async handlePaging() {
        if (this.shouldDisablePaging()) return;
        if ($("#no-page").length) return;
        if (0 === $(this.getSelector().boxSelector).length) return;
        if (!window.isListPage) return;
        if (this.paging) return;
        let needPaging = !0;
        $(`${this.getSelector().itemSelector}:visible`).each(((i, el) => {
            0 === $(el).find("span.tag:contains(🚫 已屏蔽)").length && 0 === $(el).find("span.tag:contains(⭐ 已收藏)").length && 0 === $(el).find("span.tag:contains(📥️ 已下载)").length && 0 === $(el).find("span.tag:contains(🔍 已观看)").length && (needPaging = !1);
        }));
        if (!needPaging) return;
        if ("yes" !== await storageManager.getSetting("autoPage", "no")) return;
        let nextBtn = null;
        isJavDb && (nextBtn = $(".pagination-next"));
        isJavBus && (nextBtn = $("#next"));
        if (nextBtn && 0 !== nextBtn.length) {
            this.paging = !0;
            show.info("下一页....", {
                duration: 100,
                callback: () => {
                    nextBtn[0].click();
                    this.paging = !1;
                }
            });
        }
    }
}

class HighlightMagnetPlugin extends BasePlugin {
    handle() {
        this.handleDb();
        this.handleBus();
    }
    handleDb() {
        let magnetNameList = $("#magnets-content .name").toArray();
        if (0 === magnetNameList.length) return;
        let has4k_C_UC = !1;
        magnetNameList.forEach((el => {
            let item = $(el), text = item.text().toLowerCase();
            text.includes("4k") && item.css("color", "#f40");
            (text.includes("-c") || text.includes("-u") || text.includes("-uc") || text.includes("4k")) && (has4k_C_UC = !0);
        }));
        has4k_C_UC ? magnetNameList.forEach((el => {
            let item = $(el), text = item.text().toLowerCase();
            text.includes("-c") || text.includes("-u") || text.includes("-uc") || text.includes("4k") || 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.includes("4k") && item.css("color", "#f40");
                (text.includes("-c") || text.includes("-u") || text.includes("-uc") || text.includes("4k")) && (has4k_C_UC = !0);
            }));
            has4k_C_UC ? magnetNameList.forEach((el => {
                let item = $(el), text = item.text().toLowerCase();
                text.includes("-c") || text.includes("-u") || text.includes("-uc") || text.includes("4k") || 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();
        return await gmHttp.post(url, data, headers);
    }
    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", "JHS-数据备份");
    }
    getDefaultGridColumns() {
        return window.innerWidth < 600 ? 1 : window.innerWidth < 900 ? 2 : window.innerWidth < 1e3 ? 3 : window.innerWidth < 1200 ? 4 : 5;
    }
    async initCss() {
        let containerWidth = await storageManager.getSetting("containerWidth", "100"), containerColumns = await storageManager.getSetting("containerColumns", this.getDefaultGridColumns()), 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                    font-size: 14px;\n                    min-width: 180px;\n                    font-weight: bold;\n                    margin-right: 10px;\n                }\n                .form-content{\n                    max-width: 160px;\n                    min-width: 160px;\n                }\n                .form-content * {\n                    width: 100%;\n                    padding: 5px;\n                    margin-right: 10px;\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,#moreBtn {\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                #moreBtn{\n                    background-color: #ad8731;\n                    color: white;\n                }\n                #moreBtn:hover {\n                    background-color: #dc9f11;\n                }\n                \n                .simple-setting, .mini-simple-setting {\n                    display: none; /* 默认隐藏 */\n                    background: rgba(255,255,255,0.9); \n                    position: absolute;\n                    top: 35px; /* 在按钮正下方显示 */\n                    right: -200%;\n                    z-index: 1000;\n                    border: 1px solid #ddd;\n                    border-radius: 4px;\n                    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n                    padding: 0;\n                    margin-top: 5px; /* 稍微拉开一点距离 */\n                    color: #363131;\n                }\n                \n                .mini-switch {\n                  appearance: none;\n                  -webkit-appearance: none;\n                  width: 40px;\n                  height: 20px;\n                  background: #e0e0e0;\n                  border-radius: 20px;\n                  position: relative;\n                  cursor: pointer;\n                  outline: none;\n                  /*transition: all 0.2s ease;*/\n                }\n                \n                .mini-switch:checked {\n                  background: #4CAF50;\n                }\n                \n                .mini-switch::before {\n                  content: "";\n                  position: absolute;\n                  width: 16px;\n                  height: 16px;\n                  border-radius: 50%;\n                  background: white;\n                  top: 2px;\n                  left: 2px;\n                  box-shadow: 0 1px 3px rgba(0,0,0,0.2);\n                  /*transition: all 0.2s ease;*/\n                }\n                \n                .mini-switch:checked::before {\n                  left: calc(100% - 18px);\n                }\n            </style\n        `;
    }
    handle() {
        if (isJavDb) {
            let handleResize2 = function() {
                if ($(".navbar-search").is(":hidden")) {
                    $(".mini-setting-box").hide();
                    $(".setting-box").show();
                } else {
                    $(".mini-setting-box").show();
                    $(".setting-box").hide();
                }
            };
            $("#navbar-menu-user .navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable setting-box" style="position:relative;">\n                    <a id="setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-right:15px !important;">\n                        设置\n                    </a>\n                    <div class="simple-setting"></div>\n                </div>');
            utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => {
                $(".miniHistoryBtnBox").before('\n                    <div class="navbar-item mini-setting-box" style="position:relative;margin-left: auto;">\n                        <a id="mini-setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-left:0 !important;padding-right:0 !important;">\n                            设置\n                        </a>\n                        <div class="mini-simple-setting"></div>\n                    </div>\n                ');
                handleResize2();
            }));
            $(window).resize(handleResize2);
        }
        isJavBus && $("#navbar").append(`\n                <ul class="nav navbar-nav navbar-right setting-box">\n                    <li><a id="setting-btn" style="color: #ff8400 !important;padding-right:15px !important;" role="button">设置</a><div class="simple-setting">${this.simpleSetting()}</div></li>\n                </ul>\n           `);
        $(".main-nav, .top-bar").on("click", "#setting-btn, #mini-setting-btn", (() => {
            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">域名-MissAv:</span>\n                        <div class="form-content">\n                            <input id="missAvUrl" >\n                        </div>\n                    </div>\n                    <div class="setting-item">\n                        <span class="setting-label">域名-Jable:</span>\n                        <div class="form-content">\n                            <input id="jableUrl" >\n                        </div>\n                    </div>\n                    <div class="setting-item">\n                        <span class="setting-label">域名-Avgle:</span>\n                        <div class="form-content">\n                            <input id="avgleUrl" >\n                        </div>\n                    </div>\n                    <div class="setting-item">\n                        <span class="setting-label">域名-JavTrailer:</span>\n                        <div class="form-content">\n                            <input id="javTrailersUrl" >\n                        </div>\n                    </div>\n                     <div class="setting-item">\n                        <span class="setting-label">域名-123Av:</span>\n                        <div class="form-content">\n                            <input id="av123Url" >\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="videoQuality">\n                                <option value="mmb">中画质 (432p)</option>\n                                <option value="mhb">高画质 (576p)</option>\n                                <option value="hmb">HD (720p)</option>\n                                <option value="hhb">FullHD (1080p)</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">\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">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>\n                    </div>\n                    \n                    <div class="setting-item">\n                        <span class="setting-label">屏蔽女演员:</span>\n                        <div id="filterActressContainer" style="max-width: 50%;min-width: 50%;">\n                            <div class="tag-box">\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: utils.getResponsiveArea(),
                scrollbar: !1,
                success: (layero, index) => {
                    $(layero).find(".layui-layer-content").css("position", "relative");
                    this.loadForm();
                    this.bindClick();
                }
            });
        }));
        $(".main-nav, .top-bar").on("mouseenter", ".setting-box", (() => {
            $(".simple-setting").html(this.simpleSetting()).show();
            this.initSimpleSettingForm().then();
        })).on("mouseleave", ".setting-box", (() => {
            $(".simple-setting").html("").hide();
        }));
        $(".main-nav, .top-bar").on("mouseenter", ".mini-setting-box", (() => {
            $(".mini-simple-setting").html(this.simpleSetting()).show();
            this.initSimpleSettingForm().then();
        })).on("mouseleave", ".mini-setting-box", (() => {
            $(".mini-simple-setting").html("").hide();
        }));
    }
    simpleSetting() {
        return '\n             <div style="display: flex; flex-direction: column; height: 100%;margin-top:20px">\n                <div style=" flex: 1; overflow-y: auto; margin: 0 10px; ">\n                    <div class="setting-item">\n                        <span class="setting-label">隐藏已鉴定内容:</span>\n                        <div class="form-content">\n                            <input type="checkbox" id="hideFilterItem" class="mini-switch">\n                        </div>\n                    </div>\n                    \n                    <div class="setting-item">\n                        <span class="setting-label">弹窗方式打开页面:</span>\n                        <div class="form-content">\n                             <input type="checkbox" id="dialogOpenDetail" class="mini-switch">\n                        </div>\n                    </div>      \n                    \n                    <div class="setting-item">\n                        <span class="setting-label">鉴定后立即关闭当前页面:</span>\n                        <div class="form-content">\n                            <input type="checkbox" id="needClosePage" class="mini-switch">\n                        </div>\n                    </div>\n                    \n                    <div class="setting-item">\n                        <span class="setting-label">自动翻页(当前页全鉴定完时触发):</span>\n                        <div class="form-content">\n                            <input type="checkbox" id="autoPage" class="mini-switch">\n                        </div>\n                    </div>\n       \n                                    \n                    <div class="setting-item">\n                        <span class="setting-label">页面列数: <span id="showContainerColumns"></span></span>\n                        <div class="form-content">\n                            <input type="range" id="containerColumns" min="3" max="10" step="1" style="padding:5px 0">\n                        </div>\n                    </div>\n                    \n                    <div class="setting-item">\n                        <span class="setting-label">页面宽度: <span id="showContainerWidth"></span></span>\n                        <div class="form-content">\n                            <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0">\n                        </div>\n                    </div>\n                </div>\n                <div style="flex-shrink: 0; padding: 0 20px 15px; text-align: right; border-top: 1px solid #eee;">   \n                    <button id="moreBtn">更多设置</button>\n                </div>\n            </div>\n        ';
    }
    async loadForm() {
        let settingObj = await storageManager.getSetting();
        $("#videoQuality").val(settingObj.videoQuality || "hhb");
        $("#reviewCount").val(settingObj.reviewCount || 20);
        $("#waitCheckCount").val(settingObj.waitCheckCount || 5);
        $("#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 || "");
        $("#missAvUrl").val(settingObj.missAvUrl || "https://missav.live");
        $("#jableUrl").val(settingObj.jableAvUrl || "https://jable.tv");
        $("#avgleUrl").val(settingObj.avgleUrl || "https://www.av.gl");
        $("#javTrailersUrl").val(settingObj.javTrailersUrl || "https://javtrailers.com");
        $("#av123Url").val(settingObj.av123Url || "https://123av.com");
        const carListMap = await storageManager.getActressFilterCarMap();
        Object.keys(carListMap).forEach((key => {
            let length = carListMap[key].length;
            this.addLabelTag("#filterActressContainer", key.split("_").filter(Boolean).pop() + ` (${length})`, `当前已屏蔽数量:${length}`, key);
        }));
        const actorCarListMap = await storageManager.getActorFilterCarMap();
        Object.keys(actorCarListMap).forEach((key => {
            let length = actorCarListMap[key].length;
            this.addLabelTag("#filterActorContainer", key.split("_").filter(Boolean).pop() + ` (${length})`, `当前已屏蔽数量:${length}`, key);
        }));
        let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword();
        reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => {
            this.addLabelTag("#reviewKeywordContainer", reviewKeyword);
        }));
        filterKeywordList && filterKeywordList.forEach((reviewKeyword => {
            this.addLabelTag("#filterKeywordContainer", reviewKeyword);
        }));
        [ "#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer", "#filterActressContainer" ].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);
            }));
        }));
    }
    async initSimpleSettingForm() {
        let settingObj = await storageManager.getSetting();
        $("#hideFilterItem").prop("checked", !settingObj.hideFilterItem || "yes" === settingObj.hideFilterItem);
        $("#containerColumns").val(settingObj.containerColumns || 4);
        $("#showContainerColumns").text(settingObj.containerColumns || 4);
        $("#containerWidth").val((settingObj.containerWidth || 100) - 70);
        $("#showContainerWidth").text((settingObj.containerWidth || 100) + "%");
        $("#dialogOpenDetail").prop("checked", !settingObj.dialogOpenDetail || "yes" === settingObj.dialogOpenDetail);
        $("#needClosePage").prop("checked", !settingObj.needClosePage || "yes" === settingObj.needClosePage);
        $("#autoPage").prop("checked", !!settingObj.autoPage && "yes" === settingObj.autoPage);
        $("#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))`;
            }
            storageManager.saveSettingItem("containerColumns", columns);
        }));
        $("#containerWidth").on("input", (event2 => {
            let containerWidth = parseInt($(event2.target).val());
            const value = containerWidth + 70 + "%";
            $("#showContainerWidth").text(value);
            if (isJavDb) {
                document.querySelector("section .container").style.minWidth = value;
            }
            if (isJavBus) {
                document.querySelector(".container-fluid .row").style.minWidth = value;
            }
            storageManager.saveSettingItem("containerWidth", containerWidth + 70);
        }));
        $("#dialogOpenDetail").on("change", (event2 => {
            let dialogOpenDetail = $("#dialogOpenDetail").is(":checked") ? "yes" : "no";
            storageManager.saveSettingItem("dialogOpenDetail", dialogOpenDetail);
        }));
        $("#hideFilterItem").on("change", (event2 => {
            let hideFilterItem = $("#hideFilterItem").is(":checked") ? "yes" : "no";
            storageManager.saveSettingItem("hideFilterItem", hideFilterItem);
            window.refresh();
        }));
        $("#needClosePage").on("change", (event2 => {
            storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? "yes" : "no");
            window.refresh();
        }));
        $("#autoPage").on("change", (async event2 => {
            const autoPage = $("#autoPage").is(":checked") ? "yes" : "no";
            await storageManager.saveSettingItem("autoPage", autoPage);
            "yes" === autoPage && await this.getBean("autoPagePlugin").handlePaging();
        }));
        $("#moreBtn").on("click", (() => {
            $(".simple-setting").html("").hide();
            $("#setting-btn")[0].click();
        }));
    }
    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);
            }
        })));
        $("#saveBtn").on("click", (() => this.saveForm()));
    }
    async saveForm() {
        let settingObj = await storageManager.getSetting();
        settingObj.videoQuality = $("#videoQuality").val();
        settingObj.reviewCount = $("#reviewCount").val();
        settingObj.waitCheckCount = $("#waitCheckCount").val();
        settingObj.refresh_token = $("#refresh_token").val();
        settingObj.webDavUrl = $("#webDavUrl").val();
        settingObj.webDavUsername = $("#webDavUsername").val();
        settingObj.webDavPassword = $("#webDavPassword").val();
        settingObj.javDbUrl = new URL($("#javDbUrl").val()).origin;
        settingObj.missAvUrl = $("#missAvUrl").val();
        settingObj.jableAvUrl = $("#jableAvUrl").val();
        settingObj.avgleUrl = $("#avgleUrl").val();
        settingObj.javTrailersUrl = $("#javTrailersUrl").val();
        settingObj.av123Url = $("#av123Url").val();
        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);
        show.ok("保存成功");
        window.refresh();
    }
    addLabelTag(containerId, keyword, title, storageKey) {
        const $tagBox = $(`${containerId} .tag-box`), $label = $(`\n            <div class="keyword-label" data-keyword="${keyword}" data-key="${storageKey}" style="background-color: #c5b9a0" title="${title || ""}">\n                ${keyword}\n                <span class="keyword-remove">×</span>\n            </div>\n        `);
        $label.find(".keyword-remove").click((event2 => {
            const $targetEl = $(event2.currentTarget);
            if ($targetEl.closest("#filterActressContainer, #filterActorContainer").length > 0) {
                let $label2 = $targetEl.closest(".keyword-label");
                const dataKeyword = $label2.attr("data-keyword").split(" ")[0], key = $label2.attr("data-key");
                utils.q(event2, `是否移除对 ${dataKeyword} 的屏蔽?  <br/>注意:该操作即时生效, 无需保存设置`, (async () => {
                    await storageManager.removeActorFilter(key);
                    $targetEl.parent().remove();
                }));
            } else $targetEl.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 || [], targetTitleFilterKeyword = resData.titleFilterKeyword || [], targetReviewFilterKeyword = resData.reviewFilterKeyword || [], targetSetting = resData.setting || {}, selfCarList = await storageManager.getCarList() || [], 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 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.saveTitleFilterKeyword(newTitleFilterKeyword);
        await storageManager.saveReviewFilterKeyword(newReviewFilterKeyword);
        await storageManager.saveSetting(newSetting);
        const selfActressFilterCarMap = await storageManager.getActressFilterCarMap(), selfActorFilterCarMap = await storageManager.getActorFilterCarMap(), selfAllActressActorFilterCarMap = {
            ...selfActressFilterCarMap,
            ...selfActorFilterCarMap
        };
        for (const key of Object.keys(resData)) if (key.startsWith("car_list_")) {
            let newActressActorFilterCarList = [];
            if (selfAllActressActorFilterCarMap[key] && selfAllActressActorFilterCarMap[key].length > 0) {
                newActressActorFilterCarList = [ ...selfAllActressActorFilterCarMap[key] ];
                resData[key].forEach((targetCar => {
                    selfCarList.some((selfCar => selfCar.carNum === targetCar.carNum)) || newActressActorFilterCarList.push(targetCar);
                }));
            } else newActressActorFilterCarList = resData[key];
            await storageManager.setItem(key, newActressActorFilterCarList);
        }
        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(), titleFilterKeyword = await storageManager.getTitleFilterKeyword(), reviewFilterKeyword = await storageManager.getReviewFilterKeywordList(), setting = await storageManager.getSetting(), actressFilterCarMap = await storageManager.getActressFilterCarMap(), actorFilterCarMap = await storageManager.getActorFilterCarMap();
                event2.source.postMessage({
                    carList: carList,
                    titleFilterKeyword: titleFilterKeyword,
                    reviewFilterKeyword: reviewFilterKeyword,
                    setting: setting,
                    ...actressFilterCarMap,
                    ...actorFilterCarMap
                }, 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 isHandlingVideo = !1, $preview = $(".preview-video-container");
        $preview.on("click", (async event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            if (isHandlingVideo) show.info("正在加载中, 勿重复点击"); else {
                isHandlingVideo = !0;
                try {
                    await this.handleVideo();
                } finally {
                    isHandlingVideo = !1;
                }
            }
        }));
        window.location.href.includes("autoPlay=1") && $preview[0].click();
    }
    async handleVideo() {
        const $previewVideo = $("#preview-video");
        if ($previewVideo.length > 0) {
            if ($previewVideo.is(":visible")) {
                $("#videoBox").hide();
                $previewVideo[0].pause();
            } else {
                $("#videoBox").show();
                $previewVideo[0].play().catch((e => console.error("切换播放失败:", e)));
            }
            return;
        }
        let carNum = this.getPageInfo().carNum;
        const dmmVideoMap = await getDmmVideo(carNum);
        await this.createQualityBtn(dmmVideoMap);
        const element = document.getElementById("preview-video");
        if (element) {
            const rect = element.getBoundingClientRect();
            window.scrollTo({
                top: window.scrollY + rect.top - 100,
                behavior: "smooth"
            });
        }
    }
    async createQualityBtn(dmmVideoMap) {
        let defaultVideoQuality = await storageManager.getSetting("videoQuality") || "hhb";
        dmmVideoMap[defaultVideoQuality] || (defaultVideoQuality = Object.keys(dmmVideoMap)[0]);
        let defaultVideoUrl = dmmVideoMap[defaultVideoQuality];
        $("#magneturlpost").next().after(`<div id="videoBox"><video id="preview-video" controls style="width: 100%;margin-top: 5px;"><source src="${defaultVideoUrl}" /></video></div>`);
        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();
        let buttonsHtml = "";
        [ {
            id: "video-mmb",
            quality: "mmb",
            text: "中画质 (432p)"
        }, {
            id: "video-mhb",
            quality: "mhb",
            text: "高画质 (576p)"
        }, {
            id: "video-hmb",
            quality: "hmb",
            text: "HD (720p)"
        }, {
            id: "video-hhb",
            quality: "hhb",
            text: "FullHD (1080p)"
        } ].forEach(((option, index) => {
            let dmmVideoUrl = dmmVideoMap[option.quality];
            if (dmmVideoUrl) {
                const isActive = defaultVideoQuality === option.quality;
                buttonsHtml += `\n                    <button class="video-control-btn${isActive ? " active" : ""}" \n                            id="${option.id}" \n                            data-quality="${option.quality}"\n                            data-video-src = "${dmmVideoUrl}"\n                            style="bottom: ${40 * index}px; right: -105px;">\n                        ${option.text}\n                    </button>\n                `;
            }
        }));
        $videoContainer.append(buttonsHtml);
        const $buttons = $videoContainer.find(".video-control-btn");
        $videoContainer.on("click", ".video-control-btn", (async e => {
            try {
                const $button = $(e.currentTarget);
                if ($button.hasClass("active")) return;
                let videoSrc = $button.attr("data-video-src");
                $previewSource.attr("src", videoSrc);
                videoEl.load();
                videoEl.muted = !1;
                await videoEl.play();
                $buttons.removeClass("active");
                $button.addClass("active");
            } catch (error) {
                show.error("切换画质失败");
                console.error("切换画质失败:", error);
            }
        }));
    }
}

class SearchByImagePlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "siteList", [ {
            name: "TinEye",
            url: "https://tineye.com/search?url={占位符}",
            ico: "https://www.google.com/s2/favicons?sz=64&domain=tineye.com"
        }, {
            name: "Bing",
            url: "https://www.bing.com/images/search?q=imgurl:{占位符}&view=detailv2&iss=sbi",
            ico: "https://www.bing.com/favicon.ico"
        }, {
            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"
        } ]);
        __publicField(this, "isUploading", !1);
    }
    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: utils.isMobile() ? utils.getResponsiveArea() : [ "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", (async () => {
            const imageSrc = $previewImage.attr("src");
            if (imageSrc) {
                if (!this.isUploading) {
                    this.isUploading = !0;
                    try {
                        const imgUrl = await this.searchByImage(imageSrc);
                        $actionBtns.hide();
                        $searchResults.show();
                        $siteBtnsContainer.empty();
                        const selectedSites = JSON.parse(localStorage.getItem("selectedSites") || "{}");
                        this.siteList.forEach((site => {
                            const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl)), isChecked = !1 !== selectedSites[site.name];
                            $siteBtnsContainer.append(`\n                        <a href="${siteUrl}" class="site-btn" target="_blank" title="${site.name}">\n                        <input type="checkbox" \n                               class="site-checkbox" \n                               data-site-name="${site.name}" \n                               style="margin-right: 5px"\n                               ${isChecked ? "checked" : ""}>\n                            <img src="${site.ico}" alt="${site.name}">\n                            <span>${site.name}</span>\n                        </a>\n                    `);
                        }));
                        $siteBtnsContainer.on("change", ".site-checkbox", (function() {
                            const siteName = $(this).data("site-name");
                            selectedSites[siteName] = $(this).is(":checked");
                            localStorage.setItem("selectedSites", JSON.stringify(selectedSites));
                        }));
                        $siteBtnsContainer.show();
                    } finally {
                        this.isUploading = !1;
                    }
                }
            } else 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").each((function() {
                $(this).find(".site-checkbox").is(":checked") && window.open($(this).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";
            $("#handle-btn")[0].click();
        };
        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);
        __publicField(this, "isInit", !1);
    }
    async showRelated($eleBox) {
        const $magnets = $eleBox || $("#magnets-content");
        let movieId = this.parseMovieId(window.location.href);
        $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        ');
        $("#relatedFold").on("click", (event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            const $text = $("#relatedFold .toggle-text"), $icon = $("#relatedFold .toggle-icon"), isFolded = "展开" === $text.text();
            $text.text(isFolded ? "折叠" : "展开");
            $icon.text(isFolded ? "▲" : "▼");
            if (isFolded) {
                $("#relatedContainer").show();
                $("#relatedFooter").show();
                if (!this.isInit) {
                    this.fetchAndDisplayRelateds(movieId);
                    this.isInit = !0;
                }
            } else {
                $("#relatedContainer").hide();
                $("#relatedFooter").hide();
            }
        }));
        $magnets.append('<div id="relatedContainer"></div>');
        $magnets.append('<div id="relatedFooter"></div>');
    }
    async fetchAndDisplayRelateds(movieId) {
        const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter");
        $relatedContainer.append('<div id="relatedLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单中...</div>');
        let dataList = null;
        try {
            dataList = await related(movieId, 1, 20);
        } catch (e) {
            console.error("获取清单失败:", e);
        } finally {
            $("#relatedLoading").remove();
        }
        if (dataList) if (0 !== dataList.length) {
            this.displayRelateds(dataList, $relatedContainer);
            if (20 === dataList.length) {
                $relatedFooter.html('\n                <button id="loadMoreRelateds" 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="relatedEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>\n            ');
                let currentPage = 1, $loadMoreRelateds = $("#loadMoreRelateds");
                $loadMoreRelateds.on("click", (async () => {
                    $loadMoreRelateds.text("加载中...").prop("disabled", !0);
                    currentPage++;
                    let moreData;
                    try {
                        moreData = await related(movieId, currentPage, 20);
                    } catch (e) {
                        console.error("加载更多清单失败:", e);
                    } finally {
                        $loadMoreRelateds.text("加载失败, 请点击重试").prop("disabled", !1);
                    }
                    if (moreData) {
                        this.displayRelateds(moreData, $relatedContainer);
                        if (moreData.length < 20) {
                            $loadMoreRelateds.remove();
                            $("#relatedEnd").show();
                        } else $loadMoreRelateds.text("加载更多清单").prop("disabled", !1);
                    }
                }));
            } else $relatedFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>');
        } else $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>'); else {
            $relatedContainer.append('\n                <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n                    获取清单失败\n                    <a id="retryFetchRelateds" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n                </div>\n            ');
            $("#retryFetchRelateds").on("click", (async () => {
                $("#retryFetchRelateds").parent().remove();
                await this.fetchAndDisplayRelateds(movieId);
            }));
        }
    }
    displayRelateds(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.movieCount}</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;">导入至 JHS</a>');
            $("#wantWatchBtn").on("click", (event2 => {
                this.type = Status_FAVORITE;
                this.importWantWatchVideos(event2, "是否将 想看的影片 导入到 JHS-收藏?");
            }));
        }
        if (window.location.href.includes("/watched_videos")) {
            $("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JHS</a>');
            $("#wantWatchBtn").on("click", (event2 => {
                this.type = Status_HAS_DOWN;
                this.importWantWatchVideos(event2, "是否将 看过的影片 导入到 JHS-已下载?");
            }));
        }
    }
    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 SeHuaTangPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "currentImageIndex", 0);
        __publicField(this, "currentImageGroup", []);
        __publicField(this, "processedArticles", new Set);
    }
    async initCss() {
        return "\n            <style>\n                /*.icn{\n                    width: 85px !important;\n                }*/\n                .xst{\n                    font-size: 15px;\n                    color: #090909;\n                }\n                #threadlisttableid em{\n                    font-size: 15px;\n                }\n            </style>\n        ";
    }
    async handle() {
        let $enter = $(".enter-btn");
        $enter.length > 0 && $enter[0].click();
        if (!window.location.href.includes("viewthread")) {
            this.parseArticleImg().then();
            this.checkDom();
            this.bindClick();
            this.handleImg();
        }
    }
    getInfo($tr) {
        let articleId, $a = $tr.find("a.xst"), title = $a.text().trim(), url = $a.attr("href");
        articleId = url.includes("tid=") ? url.match(/tid=(\d+)/)[1] : url.split("-")[1];
        return {
            articleId: articleId,
            url: url,
            title: title
        };
    }
    bindClick() {
        $("#threadlisttableid").on("click", ".block-btn", (async () => {
            let $row = $(event.target).closest("tr");
            const {articleId: articleId, url: url, title: title} = this.getInfo($row);
            await seHuaTangStorageManager.saveArticle(articleId, url, title, Status_FILTER);
            show.error("屏蔽成功!");
            this.doFilter().then();
        })).on("click", ".fav-btn", (async event2 => {
            let $row = $(event2.target).closest("tr");
            const {articleId: articleId, url: url, title: title} = this.getInfo($row);
            await seHuaTangStorageManager.saveArticle(articleId, url, title, Status_FAVORITE);
            show.ok("收藏成功!");
            this.doFilter().then();
        }));
    }
    async doFilter() {
        const articleList = await seHuaTangStorageManager.getArticleList();
        $('.icn a[title="新窗口打开"], .icn a[title="有新回复 - 新窗口打开"]').toArray().forEach((item => {
            $(item).hide();
            let $td = $(item).parent();
            $td.find(".fav-btn").length || $td.prepend('\n                    <a class="block-btn" style="color: #d99c1c; cursor: pointer; display: inline-block;min-width: 37px;">屏蔽</a>\n                    <a class="fav-btn" style="color: #1cd925; cursor: pointer; display: inline-block;min-width: 37px;">收藏</a>\n                ');
            let $tr = $td.parent();
            const {articleId: articleId, url: url, title: title} = this.getInfo($tr), article = articleList.find((item2 => item2.articleId === articleId));
            article && article.status === Status_FAVORITE && $tr.find(".common em a").text("已收藏").css("color", "#14e097");
            if (article && article.status === Status_FILTER) {
                $tr.find(".common em a").text("已屏蔽").css("color", "#c72222");
                $tr.parent().hide();
            }
        }));
    }
    checkDom() {
        const targetNode = document.querySelector("#threadlisttableid"), observer = new MutationObserver((async mutations => {
            observer.disconnect();
            try {
                await this.doFilter();
                this.parseArticleImg().then();
            } finally {
                observer.observe(targetNode, config);
            }
        })), config = {
            childList: !0,
            subtree: !1
        };
        observer.observe(targetNode, config);
    }
    async parseArticleImg() {
        $(".s.xst").each((async (index, ele) => {
            const articleUrl = $(ele).attr("href");
            if (!this.processedArticles.has(articleUrl)) {
                this.processedArticles.add(articleUrl);
                try {
                    const $tbody = $(ele).closest("tbody");
                    if ($tbody.find(".imageBox").length) return;
                    if (!$tbody.is(":visible")) return;
                    const res = await fetch(articleUrl);
                    if (!res.ok) return;
                    const imgs = $($.parseHTML(await res.text())).find("img.zoom[file]:not([file*='static'], [file*='hrline'])").slice(0, 5);
                    if (!imgs.length) return;
                    const imgHTML = imgs.map(((_, img) => `<img src="${$(img).attr("file")}" style="width:300px;height:auto;max-width:300px;max-height:300px;object-fit:contain" onclick="zoom(this,this.src,0,0,0)" alt="">`)).get().join("");
                    $tbody.append(`\n                    <tr class="imageBox">\n                        <td colspan="5">\n                            <div style="display:flex;gap:10px;overflow-x:auto;padding:5px 0">${imgHTML}</div>\n                        </td>\n                    </tr>\n            `);
                } catch (e) {
                    console.error("Error:", articleUrl, e);
                }
            }
        }));
    }
    handleImg() {
        document.addEventListener("click", (event2 => {
            if ("IMG" === event2.target.tagName && event2.target.closest(".imageBox")) {
                const previewTbody = event2.target.closest(".imageBox");
                this.currentImageGroup = Array.from(previewTbody.querySelectorAll("img"));
                this.currentImageIndex = this.currentImageGroup.indexOf(event2.target);
                this.createNavigateBtn();
            }
        }));
    }
    navigateImage(direction) {
        this.currentImageIndex = (this.currentImageIndex + direction + this.currentImageGroup.length) % this.currentImageGroup.length;
        const img = this.currentImageGroup[this.currentImageIndex];
        zoom(img, img.src, 0, 0, 0);
        this.createNavigateBtn();
    }
    createNavigateBtn() {
        utils.loopDetector((() => $("#imgzoom_picpage").length > 0), (() => {
            if (0 === $("#imgzoom_picpage").length) return;
            const zoomContainer = document.getElementById("imgzoom_picpage");
            console.log("zoomContainer", zoomContainer);
            if (!zoomContainer) return;
            zoomContainer.querySelectorAll("#zimg_prev, #zimg_next").forEach((btn => btn.remove()));
            const prevBtn = document.createElement("div");
            prevBtn.id = "zimg_prev";
            prevBtn.className = "zimg_prev";
            prevBtn.onclick = () => this.navigateImage(-1);
            const nextBtn = document.createElement("div");
            nextBtn.id = "zimg_next";
            nextBtn.className = "zimg_next";
            nextBtn.onclick = () => this.navigateImage(1);
            zoomContainer.append(prevBtn, nextBtn);
        }));
    }
}

class CopyTitleOrDownImgPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "moreSvg", '<svg t="1749017229420" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9184" width="200" height="200"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667z m0 810.666666c-204.8 0-373.333333-168.533333-373.333333-373.333333S307.2 138.666667 512 138.666667 885.333333 307.2 885.333333 512 716.8 885.333333 512 885.333333z" fill="#666666" p-id="9185"></path><path d="M512 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 1 0-85.333334 0Z" fill="#666666" p-id="9186"></path><path d="M341.333333 512m-42.666666 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9187"></path><path d="M682.666667 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9188"></path></svg>');
        __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>');
        __publicField(this, "handleSvg", '<svg t="1749106236917" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2628" width="200" height="200"><path d="M838 989.48a32 32 0 0 1-22.5-9.22L519.3 687.6 207.48 980.8a32 32 0 0 1-54-23.32V136.52A98.54 98.54 0 0 1 252 38.1h519.6A98.52 98.52 0 0 1 870 136.52v820.96a32 32 0 0 1-32 32zM252 102.1a34.46 34.46 0 0 0-34.42 34.42v746.96L498 619.84a32 32 0 0 1 44.42 0.56L806 880.88V136.52a34.46 34.46 0 0 0-34.4-34.42z" p-id="2629"></path><path d="M648 604.92a28 28 0 0 1-16.46-5.34l-112.84-82-112.84 82a28 28 0 0 1-43.08-31.32l43.1-132.64-112.84-82a28 28 0 0 1 16.46-50.66h139.48L492 170.34a28 28 0 0 1 53.26 0l43.1 132.64h139.48a28 28 0 0 1 16.46 50.66l-112.84 82 43.1 132.64A28 28 0 0 1 648 604.92z m-129.3-150a27.86 27.86 0 0 1 16.46 5.36l59.58 43.28-22.76-70a28 28 0 0 1 10.02-31.28l59.58-43.3H568a28 28 0 0 1-26.64-19.34l-22.76-70-22.76 70a28 28 0 0 1-26.62 19.34h-73.64l59.58 43.3a28 28 0 0 1 10.16 31.3l-22.76 70 59.58-43.28a28 28 0 0 1 16.46-5.32z" p-id="2630"></path></svg>');
        __publicField(this, "siteSvg", '<svg t="1749107903569" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12439" width="200" height="200"><path d="M882.758621 133.674884C882.758621 59.84828 822.91034 0 749.083736 0 675.25715 0 615.40887 59.84828 615.40887 133.674884 615.40887 163.358402 625.152318 191.656395 642.813352 214.773283L670.872117 193.336726 648.314739 166.170836 253.911693 493.666092 276.469054 520.831982 302.371681 496.834595C277.256669 469.725608 241.995388 453.990153 204.295574 453.990153 130.46897 453.990153 70.62069 513.838433 70.62069 587.66502 70.62069 661.491624 130.46897 721.339904 204.295574 721.339904 255.555319 721.339904 301.619094 692.208675 324.036714 647.136344L276.646223 663.002394 706.082022 877.440106 721.856794 845.849335 690.37312 829.861888C680.932829 848.452414 675.940882 869.068818 675.940882 890.325116 675.940882 964.15172 735.789162 1024 809.615766 1024 883.442353 1024 943.290633 964.15172 943.290633 890.325116 943.290633 874.050807 940.36533 858.125365 934.723584 843.16446L868.645076 868.0826C871.294817 875.109252 872.669943 882.595452 872.669943 890.325116 872.669943 925.14899 844.439623 953.37931 809.615766 953.37931 774.791892 953.37931 746.561571 925.14899 746.561571 890.325116 746.561571 880.245089 748.902894 870.575616 753.340487 861.836782L769.436089 830.140063 737.631567 814.258564 308.195769 599.820853 276.554929 584.02108 260.805279 615.686903C250.212352 636.984797 228.494795 650.719214 204.295574 650.719214 169.4717 650.719214 141.241379 622.488894 141.241379 587.66502 141.241379 552.841163 169.4717 524.610842 204.295574 524.610842 222.12269 524.610842 238.680594 531.99985 250.566444 544.829369L273.29589 569.363385 299.026432 547.997855 693.429478 220.502616 719.514606 198.84265 698.930882 171.900169C690.596687 160.991373 686.029559 147.727007 686.029559 133.674884 686.029559 98.85101 714.25988 70.62069 749.083736 70.62069 783.90761 70.62069 812.137931 98.85101 812.137931 133.674884 812.137931 148.208022 807.249885 161.899255 798.379608 172.996785L853.543883 217.089695C872.331935 193.584128 882.758621 164.379366 882.758621 133.674884ZM749.083736 196.729062C729.149334 196.729062 710.818745 187.460449 698.930882 171.900169L642.813352 214.773283C667.922573 247.639305 706.904064 267.349751 749.083736 267.349751 790.225902 267.349751 828.357809 248.599782 853.543883 217.089695L798.379608 172.996785C786.455411 187.915034 768.530291 196.729062 749.083736 196.729062ZM337.970441 587.66502C337.970441 553.551854 325.093782 521.360666 302.371681 496.834595L250.566444 544.829369C261.309069 556.424898 267.349751 571.526356 267.349751 587.66502 267.349751 597.565263 265.091478 607.069184 260.805279 615.686903L324.036714 647.136344C333.156105 628.801148 337.970441 608.540036 337.970441 587.66502ZM809.615766 756.650249C758.753986 756.650249 712.986006 785.330865 690.37312 829.861888L753.340487 861.836782C764.027215 840.791658 785.603302 827.270938 809.615766 827.270938 836.08553 827.270938 859.461862 843.730308 868.645076 868.0826L934.723584 843.16446C915.252259 791.529949 865.714547 756.650249 809.615766 756.650249Z" fill="#389BFF" p-id="12440"></path></svg>');
        __publicField(this, "videoSvg", '<svg t="1749003664455" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1952" width="200" height="200"><path d="M825.6 153.6H198.4C124.5 153.6 64 214.1 64 288v448c0 73.9 60.5 134.4 134.4 134.4h627.2c73.9 0 134.4-60.5 134.4-134.4V288c0-73.9-60.5-134.4-134.4-134.4z m-138.2 44.8l112 112H706l-112-112h93.4z m-156.8 0l112 112H526.7l-112-112h115.9z m-179.2 0l112 112H347.5l-112-112h115.9zM108.8 288c0-41.4 28.4-76.1 66.7-86.3l108.7 108.7H108.8V288z m806.4 448c0 49.4-40.2 89.6-89.6 89.6H198.4c-49.4 0-89.6-40.2-89.6-89.6V355.2h806.4V736z m0-425.6h-52.5l-112-112h74.9c49.4 0 89.6 40.2 89.6 89.6v22.4z" p-id="1953"></path><path d="M454 687.2l149.3-77.6c27.5-13.8 27.5-53 0-66.8L468 472.2c-31.2-15.6-68 7.1-68 42v139.6c0 27.8 29.2 45.8 54 33.4zM444.8 512l134.4 67.2-134.4 67.2V512z" p-id="1954"></path></svg>');
        __publicField(this, "recoveryVideoSvg", '<svg t="1749003779161" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8204" width="200" height="200"><path d="M938.666667 553.92V768c0 64.8-52.533333 117.333333-117.333334 117.333333H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V256c0-64.8 52.533333-117.333333 117.333334-117.333333h618.666666c64.8 0 117.333333 52.533333 117.333334 117.333333v297.92z m-64-74.624V256a53.333333 53.333333 0 0 0-53.333334-53.333333H202.666667a53.333333 53.333333 0 0 0-53.333334 53.333333v344.48A290.090667 290.090667 0 0 1 192 597.333333a286.88 286.88 0 0 1 183.296 65.845334C427.029333 528.384 556.906667 437.333333 704 437.333333c65.706667 0 126.997333 16.778667 170.666667 41.962667z m0 82.24c-5.333333-8.32-21.130667-21.653333-43.648-32.917333C796.768 511.488 753.045333 501.333333 704 501.333333c-121.770667 0-229.130667 76.266667-270.432 188.693334-2.730667 7.445333-7.402667 20.32-13.994667 38.581333-7.68 21.301333-34.453333 28.106667-51.370666 13.056-16.437333-14.634667-28.554667-25.066667-36.138667-31.146667A222.890667 222.890667 0 0 0 192 661.333333c-14.464 0-28.725333 1.365333-42.666667 4.053334V768a53.333333 53.333333 0 0 0 53.333334 53.333333h618.666666a53.333333 53.333333 0 0 0 53.333334-53.333333V561.525333zM320 480a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0-64a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" fill="#000000" p-id="8205"></path></svg>');
    }
    async initCss() {
        return `\n            <style>\n                .box .tags {\n                    justify-content: space-between;\n                }\n                .tool-box span{\n                    opacity:.3\n                }\n                .tool-box span:hover{\n                    opacity:1\n                }\n                ${isJavBus ? ".tool-box .icon{ height: 24px; width: 24px; }" : ""}\n                .tool-box svg path {\n                  fill: blue;\n                }\n                [data-theme="dark"] .tool-box svg path {\n                  fill: white;\n                }\n                \n                \n                /* 鼠标移入时的弹性动画 */\n                .elastic-in {\n                    animation: elasticIn 0.2s ease-out forwards;  /* 动画名称 | 时长 | 缓动函数 | 保持最终状态 */\n                }\n                \n                /* 鼠标移出时的弹性动画 */\n                .elastic-out {\n                    animation: elasticOut 0.2s ease-in forwards;\n                }\n                /* 弹性进入动画(像果冻弹入) */\n                @keyframes elasticIn {\n                    0% {\n                        opacity: 0;\n                        transform: scale(0.8);  /* 起始状态:80% 大小 */\n                    }\n                    50% {\n                        opacity: 1;\n                        transform: scale(1.1);  /* 弹到 110%(超调一点) */\n                    }\n                    70% {\n                        transform: scale(0.95); /* 回弹到 95%(模拟弹性阻尼) */\n                    }\n                    100% {\n                        opacity: 1;\n                        transform: scale(1);    /* 最终恢复正常大小 */\n                    }\n                }\n                /* 弹性离开动画(像果冻弹出) */\n                @keyframes elasticOut {\n                    0% {\n                        opacity: 1;\n                        transform: scale(1);    /* 起始状态:正常大小 */\n                    }\n                    30% {\n                        transform: scale(1.05); /* 先弹大一点(105%) */\n                    }\n                    100% {\n                        opacity: 0;\n                        transform: scale(0.8);  /* 最终缩小并消失 */\n                    }\n                }\n                \n                \n                .loading {\n                    opacity: 0.7;\n                    filter: blur(1px);\n                }\n                .loading-spinner {\n                    position: absolute;\n                    top: 50%;\n                    left: 50%;\n                    transform: translate(-50%, -50%);\n                    width: 40px;\n                    height: 40px;\n                    border: 3px solid rgba(255,255,255,.3);\n                    border-radius: 50%;\n                    border-top-color: #fff;\n                    animation: spin 1s ease-in-out infinite;\n                    z-index: 20;\n                }\n                @keyframes spin {\n                    to { transform: translate(-50%, -50%) rotate(360deg); }\n                }\n            </style>\n        `;
    }
    handle() {
        if (window.isListPage) {
            this.addSvgBtn();
            this.bindClick().then();
        }
    }
    addSvgBtn() {
        $(this.getSelector().itemSelector).toArray().forEach((ele => {
            let $box = $(ele);
            if (!($box.find(".tool-box").length > 0)) {
                isJavDb && $box.find(".tags").append(`\n                    <div class="tool-box" style="margin-left: auto; display: flex; align-items: center">\n                        <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span>\n                        \n                        <div class="more-tools-container" style="position: relative; margin-right: 15px;">\n                            <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div>\n                            \n                            <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n                                background-color: rgba(255, 255, 255, 0);z-index: 10;">\n                                <a class="menu-btn hasWatchBtn" style="background-color:#d7a80c;color:white;margin-bottom: 5px"><span style="opacity: 1;">🔍 已观看</span></a>\n                                <a class="menu-btn hasDownBtn" style="background-color:#7bc73b; color:white;margin-bottom: 5px"><span style="opacity: 1;">📥️ 已下载</span></a>\n                                <a class="menu-btn favoriteBtn" style="background-color:#25b1dc; color:white;margin-bottom: 5px"><span style="opacity: 1;">⭐ 收藏</span></a>\n                                <a class="menu-btn filterBtn" style="background-color:#de3333;   color:white;margin-bottom: 5px"><span style="opacity: 1;">🚫 屏蔽</span></a>\n                            </div>\n                        </div>\n                        \n                        <div class="more-tools-container" style="position: relative; margin-right: 15px;">\n                            <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div>\n                            \n                             <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n                                background-color: rgba(255, 255, 255, 0);z-index: 10;">\n                                <a class="menu-btn site-jable" style="color:white;margin-bottom: 5px; background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))">\n                                    <span style="opacity: 1;">Jable</span>\n                                </a>\n                                <a class="menu-btn site-avgle" style="margin-bottom: 5px; background:linear-gradient(to right, #000000, rgb(238,164,238))">\n                                    <span style="opacity: 1;">Avgle</span>\n                                </a>\n                                <a class="menu-btn site-miss-av" style="color:white;margin-bottom: 5px; background:linear-gradient(to right, #d29494, rgb(254,98,142))">\n                                    <span style="opacity: 1;">MissAv</span>\n                                </a>\n                            </div>\n                        </div>\n                        \n                        <div class="more-tools-container" style="position: relative; margin-right: 15px;">\n                            <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.moreSvg}</div>\n                            \n                            <div class="more-tools" style="\n                                position: absolute;\n                                bottom: 20px;\n                                right: -10px;\n                                display: none;\n                                background: white;\n                                box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n                                border-radius: 20px;\n                                padding: 10px 0;\n                                margin-bottom: 15px;\n                                z-index: 10;\n                            ">\n                                <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;">${this.carNumSvg}</span>\n                                <span class="titleSvg" title="复制标题" style="padding: 5px 10px; white-space: nowrap;">${this.titleSvg}</span>\n                                <span class="downSvg" title="下载封面" style="padding: 5px 10px; white-space: nowrap;">${this.downSvg}</span>\n                            </div>\n                        </div>\n                    </div>\n                `);
                if (isJavBus) {
                    if ($box.find(".avatar-box").length > 0) return;
                    $box.find(".photo-info").append(`\n                    <div class="tool-box" style="display: flex; align-items: center;justify-content: flex-end">\n                        <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span>\n                        \n                        <div class="more-tools-container" style="position: relative; margin-right: 15px;">\n                            <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div>\n                            \n                            <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n                                background-color: rgba(255, 255, 255, 0);z-index: 10;">\n                                <a class="menu-btn hasWatchBtn" style="background-color:#d7a80c;color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline">🔍 已观看</span></a>\n                                <a class="menu-btn hasDownBtn" style="background-color:#7bc73b; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline">📥️ 已下载</span></a>\n                                <a class="menu-btn favoriteBtn" style="background-color:#25b1dc; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline">⭐ 收藏</span></a>\n                                <a class="menu-btn filterBtn" style="background-color:#de3333;   color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline">🚫 屏蔽</span></a>\n                            </div>\n                        </div>\n                        \n                        <div class="more-tools-container" style="position: relative; margin-right: 15px;">\n                            <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div>\n                            \n                             <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n                                background-color: rgba(255, 255, 255, 0);z-index: 10;">\n                                <a class="menu-btn site-jable" style="color:white;margin-bottom: 5px; background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))">\n                                    <span style="opacity: 1;display: inline">Jable</span>\n                                </a>\n                                <a class="menu-btn site-avgle" style="margin-bottom: 5px; background:linear-gradient(to right, #000000, rgb(238,164,238))">\n                                    <span style="opacity: 1;display: inline">Avgle</span>\n                                </a>\n                                <a class="menu-btn site-miss-av" style="color:white;margin-bottom: 5px; background:linear-gradient(to right, #d29494, rgb(254,98,142))">\n                                    <span style="opacity: 1;display: inline">MissAv</span>\n                                </a>\n                            </div>\n                        </div>\n                      \n                        <div class="more-tools-container" style="position: relative;">\n                            <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.moreSvg}</div>\n                            \n                            <div class="more-tools" style="\n                                max-width: 44px;\n                                position: absolute;\n                                bottom: 20px;\n                                right: -10px;\n                                display: none;\n                                background: white;\n                                box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n                                border-radius: 20px;\n                                padding: 10px 0;\n                                margin-bottom: 15px;\n                                z-index: 10;\n                                text-align: center;\n                            ">\n                                <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.carNumSvg}</span>\n                                <span class="titleSvg" title="复制标题"  style="padding: 5px 10px; white-space: nowrap;display: inline">${this.titleSvg}</span>\n                                <span class="downSvg" title="下载封面"   style="padding: 5px 10px; white-space: nowrap;display: inline">${this.downSvg}</span>\n                            </div>\n                        </div>\n                    </div>\n                `);
                }
            }
        }));
    }
    async bindClick() {
        const selector = this.getSelector(), listPagePlugin = this.getBean("ListPagePlugin");
        $(document).on("click", ".more-tools-container", (event2 => {
            event2.preventDefault();
            var $currentTools = $(event2.target).closest(".more-tools-container").find(".more-tools");
            $(".more-tools").not($currentTools).stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
            $currentTools.is(":visible") ? $currentTools.stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide() : $currentTools.stop(!0, !0).removeClass("elastic-out").addClass("elastic-in").show();
        }));
        $(document).on("click", (function(event2) {
            $(event2.target).closest(".more-tools-container").length || $(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
        }));
        $(document).on("click", ".videoSvg", (event2 => {
            event2.preventDefault();
            $('.videoSvg[title!="播放视频"]').each(((index, element) => {
                const $otherSvgElement = $(element);
                let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find(selector.coverImgSelector), {carNum: carNum} = listPagePlugin.findCarNumAndHref($otherBox);
                this.showImg($otherSvgElement, $otherImg, carNum);
                $otherSvgElement.html(this.videoSvg).attr("title", "播放视频");
            }));
            const $currentBox = $(event2.target).closest(".item"), $svgElement = $currentBox.find(".videoSvg");
            if ("播放视频" === $svgElement.attr("title")) {
                $svgElement.html(this.recoveryVideoSvg).attr("title", "切回封面");
                const {carNum: carNum} = listPagePlugin.findCarNumAndHref($currentBox);
                let $img = $currentBox.find(selector.coverImgSelector);
                $img.length || show.error("没有找到图片");
                this.showVideo($svgElement, $img, carNum).then();
            }
        }));
        $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", (event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            const $btn = $(event2.target).closest(".menu-btn"), $box = $btn.closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box), handleAction = async status => {
                await storageManager.saveCar(carNum, aHref, null, status);
                window.refresh();
            };
            $btn.hasClass("filterBtn") ? utils.q(event2, `是否屏蔽${carNum}?`, (() => handleAction(Status_FILTER))) : $btn.hasClass("favoriteBtn") ? handleAction(Status_FAVORITE).then() : $btn.hasClass("hasDownBtn") ? handleAction(Status_HAS_DOWN).then() : $btn.hasClass("hasWatchBtn") && handleAction(Status_HAS_WATCH).then();
        }));
        const settingObj = await storageManager.getSetting(), missAvUrl = settingObj.missAvUrl || "https://missav.live", jableAvUrl = settingObj.jableAvUrl || "https://jable.tv", avgleUrl = settingObj.avgleUrl || "https://www.av.gl";
        settingObj.javTrailersUrl;
        $(document).on("click", ".site-jable, .site-avgle, .site-miss-av", (event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            const $currentTarget = $(event2.currentTarget), $box = $currentTarget.closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
            $currentTarget.hasClass("site-jable") ? window.open(`${jableAvUrl}/videos/${carNum}/`, "_blank") : $currentTarget.hasClass("site-avgle") ? window.open(`${avgleUrl}/vod/search.html?wd=${carNum}`, "_blank") : $currentTarget.hasClass("site-miss-av") && window.open(`${missAvUrl}/search/${carNum}`, "_blank");
        }));
        $(document).on("click", ".titleSvg, .carNumSvg, .downSvg", (event2 => {
            event2.preventDefault();
            event2.stopPropagation();
            const $box = $(event2.currentTarget).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box), $img = $box.find(isJavBus ? ".photo-frame img" : ".cover img");
            $(event2.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", title) : $(event2.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", carNum) : $(event2.currentTarget).hasClass("downSvg") && fetch($img.attr("src")).then((response => response.blob())).then((blob => {
                utils.download(blob, title + ".jpg");
            }));
        }));
    }
    showImg($svgElement, $img, carNum) {
        $svgElement.html(this.videoSvg).attr("title", "播放视频");
        let $video = $(`#${`${carNum}_preview_video`}`);
        if ($video.length > 0) {
            $video[0].pause();
            $video.parent().hide();
        }
        $img.show();
        $img.removeClass("loading");
        $img.next(".loading-spinner").remove();
    }
    async showVideo($svgElement, $img, carNum) {
        const id = `${carNum}_preview_video`;
        let $video = $(`#${id}`);
        if ($video.length > 0) {
            $video.parent().show();
            $video[0].play();
            $img.hide();
            return;
        }
        $img.addClass("loading");
        $img.after('<div class="loading-spinner"></div>');
        const poster = $img.attr("src"), dmmVideoMap = await getDmmVideo(carNum);
        if (!dmmVideoMap) {
            show.error("获取预览视频地址失败");
            this.showImg($svgElement, $img, carNum);
            return;
        }
        let defaultVideoQuality = await storageManager.getSetting("videoQuality") || "hhb";
        dmmVideoMap[defaultVideoQuality] || (defaultVideoQuality = Object.keys(dmmVideoMap)[0]);
        const videoHtml = `\n            <div style="display: flex; justify-content: center; align-items: center; position: absolute; top:0; left:0; height: 100%; width: 100%; z-index: 10; overflow: hidden">\n                <video \n                    src="${dmmVideoMap[defaultVideoQuality]}" \n                    poster="${poster}" \n                    id="${id}" \n                    controls \n                    loop \n                    muted \n                    playsinline\n                    style="max-height: 100%; max-width: 100%; object-fit: contain"\n                ></video>\n            </div>\n        `;
        $img.parent().append(videoHtml);
        $img.hide();
        $img.removeClass("loading");
        $img.next(".loading-spinner").remove();
        $video = $(`#${id}`);
        let videoElement = $video[0];
        videoElement.load();
        videoElement.muted = !1;
        videoElement.play();
        $video.trigger("focus");
    }
}

class Fc2By123AvPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "$contentBox", $(".section .container"));
        __publicField(this, "urlParams", new URLSearchParams(window.location.search));
        __publicField(this, "sortVal", this.urlParams.get("sort") || "release_date");
        __publicField(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1);
        __publicField(this, "maxPage", null);
        __publicField(this, "keyword", this.urlParams.get("keyword") || null);
    }
    async getBaseUrl() {
        return ((await storageManager.getSetting()).av123Url || "https://123av.com") + "/ja";
    }
    handle() {
        $("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('<a class="navbar-item" href="/advanced_search?type=100&released_start=2099-09">123Av-Fc2</a>');
        $('.tabs li:contains("FC2")').after('<li><a href="/advanced_search?type=100&released_start=2099-09"><span>123Av-Fc2</span></a></li>');
        if (currentHref.includes("/advanced_search?type=100")) {
            this.hookPage();
            this.handleQuery().then();
        }
    }
    hookPage() {
        let $h2 = $("h2.section-title");
        $h2.contents().first().replaceWith("123Av");
        $h2.css("marginBottom", "0");
        $h2.append('\n            <div style="margin-left: 100px; width: 400px;">\n                <input id="search-123av-keyword" type="text" placeholder="搜索123Av Fc2ppv内容" style="padding: 4px 5px;margin-right: 0">\n                <a id="search-123av-btn" class="a-primary" style="margin-left: 0">搜索</a>\n                <a id="clear-123av-btn" class="a-dark" style="margin-left: 0">重置</a>\n            </div>\n        ');
        $("#search-123av-keyword").val(this.keyword);
        $("#search-123av-btn").on("click", (async () => {
            let keyword = $("#search-123av-keyword").val().trim();
            if (keyword) {
                this.keyword = keyword;
                utils.setHrefParam("keyword", keyword);
                await this.handleQuery();
            }
        }));
        $("#clear-123av-btn").on("click", (async () => {
            $("#search-123av-keyword").val("");
            this.keyword = "";
            utils.setHrefParam("keyword", "");
            $(".page-box").show();
            $(".tool-box").show();
            await this.handleQuery();
        }));
        $(".empty-message").remove();
        $("#foldCategoryBtn").remove();
        $(".section .container .box").remove();
        $("#sort-toggle-btn").remove();
        this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>');
        this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>');
        this.$contentBox.append('<div class="page-box"></div>');
        $(".tool-box").append('\n            <div class="button-group">\n                <div class="buttons has-addons" id="conditionBox">\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="release_date">发布日期</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="recent_update">最近更新</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="trending">热门</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_today">今天最多观看</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_week">本周最多观看</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_month">本月最多观看</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed">最多观看</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_favourited">最受欢迎</a>\n                </div>\n            </div>\n        ');
        $(`#conditionBox a[data-sort="${this.sortVal}"]`).addClass("is-info");
        utils.setHrefParam("sort", this.sortVal);
        utils.setHrefParam("page", this.currentPage);
        $("#conditionBox").on("click", "a.button", (e => {
            let $target = $(e.target);
            this.sortVal = $target.data("sort");
            utils.setHrefParam("sort", this.sortVal);
            $target.siblings().removeClass("is-info");
            $target.addClass("is-info");
            this.handleQuery();
        }));
        $(".page-box").append('\n            <nav class="pagination">\n                <a class="pagination-previous">上一页</a>\n                <ul class="pagination-list"></ul>\n                <a class="pagination-next">下一页</a>\n            </nav>\n        ');
        $(document).on("click", ".pagination-link", (e => {
            e.preventDefault();
            this.currentPage = parseInt($(e.target).data("page"));
            utils.setHrefParam("page", this.currentPage);
            this.renderPagination();
            this.handleQuery();
        }));
        $(".pagination-previous").on("click", (e => {
            e.preventDefault();
            if (this.currentPage > 1) {
                this.currentPage--;
                utils.setHrefParam("page", this.currentPage);
                this.renderPagination();
                this.handleQuery();
            }
        }));
        $(".pagination-next").on("click", (e => {
            e.preventDefault();
            if (this.currentPage < this.maxPage) {
                this.currentPage++;
                utils.setHrefParam("page", this.currentPage);
                this.renderPagination();
                this.handleQuery();
            }
        }));
    }
    renderPagination() {
        const $paginationList = $(".pagination-list");
        $paginationList.empty();
        let startPage = Math.max(1, this.currentPage - 2), endPage = Math.min(this.maxPage, this.currentPage + 2);
        this.currentPage <= 3 ? endPage = Math.min(6, this.maxPage) : this.currentPage >= this.maxPage - 2 && (startPage = Math.max(this.maxPage - 5, 1));
        if (startPage > 1) {
            $paginationList.append('<li><a class="pagination-link" data-page="1">1</a></li>');
            startPage > 2 && $paginationList.append('<li><span class="pagination-ellipsis">…</span></li>');
        }
        for (let i = startPage; i <= endPage; i++) {
            const activeClass = i === this.currentPage ? " is-current" : "";
            $paginationList.append(`<li><a class="pagination-link${activeClass}" data-page="${i}">${i}</a></li>`);
        }
        if (endPage < this.maxPage) {
            endPage < this.maxPage - 1 && $paginationList.append('<li><span class="pagination-ellipsis">…</span></li>');
            $paginationList.append(`<li><a class="pagination-link" data-page="${this.maxPage}">${this.maxPage}</a></li>`);
        }
    }
    async handleQuery() {
        let loadObj = loading();
        try {
            let pagesToFetch = [];
            pagesToFetch = 1 === this.currentPage ? [ 1, 2 ] : [ 2 * this.currentPage - 1, 2 * this.currentPage ];
            if (this.keyword) {
                pagesToFetch = [ 1 ];
                $(".page-box").hide();
                $(".tool-box").hide();
            }
            const baseUrl = await this.getBaseUrl(), fetchPromises = pagesToFetch.map((page => {
                let url = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}&page=${page}`;
                this.keyword && (url = `${baseUrl}/search?keyword=${this.keyword}`);
                return gmHttp.get(url);
            })), htmlResults = await Promise.all(fetchPromises);
            let dataList = [];
            for (const html of htmlResults) {
                let $dom = $(html);
                $dom.find(".box-item").each(((index, element) => {
                    const $item = $(element), imgSrc = $item.find("img").attr("data-src");
                    let carNum = $item.find("img").attr("title");
                    const detailLink = $item.find(".detail a"), link = detailLink.attr("href"), href = baseUrl + (link.startsWith("/") ? link : "/" + link), title = detailLink.text().trim().replace(carNum + " - ", "");
                    carNum = carNum.replace("FC2-PPV", "FC2");
                    dataList.push({
                        imgSrc: imgSrc,
                        carNum: carNum,
                        href: href,
                        title: title
                    });
                }));
                if (!this.maxPage) {
                    let rawMaxPage, lastPageItem = $dom.find(".page-item:not(.disabled)").last();
                    if (lastPageItem.find("a.page-link").length) {
                        let href = lastPageItem.find("a.page-link").attr("href");
                        rawMaxPage = parseInt(href.split("page=")[1]);
                    } else rawMaxPage = parseInt(lastPageItem.find("span.page-link").text());
                    this.maxPage = Math.ceil(rawMaxPage / 2);
                    this.renderPagination();
                }
            }
            if (0 === dataList.length) {
                show.error("无结果");
                let errorUrl = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}`;
                this.keyword && (errorUrl = `${baseUrl}/search?keyword=${this.keyword}`);
                console.error("获取数据失败!", errorUrl);
            }
            let movieHtml = this.markDataListHtml(dataList);
            $(".movie-list").html(movieHtml);
            await utils.smoothScrollToTop();
        } catch (e) {
            console.error(e);
        } finally {
            loadObj.close();
        }
    }
    open123AvFc2Dialog(carNum, href) {
        layer.open({
            type: 1,
            title: carNum,
            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="filterBtn" class="menu-btn" style="background-color:#de3333"><span>🚫 屏蔽</span></a>\n                        <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>⭐ 收藏</span></a>\n                        <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>📥️ 已下载</span></a>\n                        <a id="hasWatchBtn" class="menu-btn" style="background-color:#d7a80c;"><span>🔍 已观看</span></a>\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                    <div class="message video-panel" style="margin-top:20px">\n                        <div id="magnets-content" class="magnet-links">\n                        </div>\n                    </div>\n                    <div id="reviews-content">\n                    </div>\n                    <div id="related-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(carNum, href);
                let keyword = carNum.replace("FC2-", "");
                $("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword));
                $("#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();
                }));
                $("#hasWatchBtn").on("click", (async event2 => {
                    const actress = $("#data-actress").text();
                    await storageManager.saveCar(carNum, href, actress, Status_HAS_WATCH);
                    window.refresh();
                    layer.closeAll();
                }));
                $("#search-subtitle-btn").on("click", (event2 => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum}`, carNum, !1, event2)));
                $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum)));
            }
        });
    }
    async loadData(carNum, href) {
        let loadObj = loading();
        try {
            const {id: id, publishDate: publishDate, title: title, moviePoster: moviePoster} = await this.get123AvVideoInfo(href);
            $(".movie-info-container").html(`\n                    <h3 class="movie-title" style="margin-bottom: 10px">${title || "无标题"}</h3>\n                    <div class="movie-meta" style="margin-bottom: 10px">\n                        <span>番号: ${carNum || "未知"}</span>\n                        <span>年份: ${publishDate || "未知"}</span>\n                        <span>\n                            站点: \n                            <a href="https://fc2ppvdb.com/articles/${carNum.replace("FC2-", "")}" target="_blank">fc2ppvdb</a>\n                            <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${carNum.replace("FC2-", "")}/" target="_blank">fc2电子市场</a>\n                        </span>\n                    </div>\n                    <div class="movie-actors" style="margin-bottom: 10px">\n                        <div class="actor-list">主演: </div>\n                    </div>\n                    <div class="movie-seller" style="margin-bottom: 10px">\n                        <span>販売者: </span>\n                    </div>\n                    <div class="movie-gallery" style="margin-bottom: 10px">\n                        <h4>剧照: </h4>\n                        <div class="image-list"></div>\n                    </div>\n                `);
            this.getMovie(id, moviePoster).then((movieList => {
                $(".movie-trailer").attr("src", movieList[0].url);
                let html = '\n                    <div class="movie-gallery" style="margin-bottom: 10px"> \n                    <span>影片: </span> \n                    <div class="movie-parts-list">\n                ';
                movieList.forEach(((movie, index) => {
                    html += `\n                        <a class="movie-part a-outline" data-url="${movie.url}" style="margin-left: 0">\n                            部分 ${index + 1}\n                        </a>\n                    `;
                }));
                html += "</div> </div> ";
                $(".movie-gallery").after(html);
                $(".movie-parts-list").on("click", ".movie-part", (function() {
                    const url = $(this).data("url");
                    $(".movie-trailer").attr("src", url);
                }));
            }));
            this.getImgList(carNum).then();
            this.getActressInfo(carNum).then();
        } catch (e) {
            console.error(e);
        } finally {
            loadObj.close();
        }
    }
    async get123AvVideoInfo(href) {
        const html = await gmHttp.get(href), match = html.match(/v-scope="Movie\({id:\s*(\d+),/), id = match ? match[1] : null, $dom = $(html);
        return {
            id: id,
            publishDate: $dom.find('span:contains("リリース日:")').next("span").text(),
            title: $dom.find("h1").text().trim(),
            moviePoster: $dom.find("#player").attr("data-poster")
        };
    }
    async getActressInfo(fc2Num) {
        let url = `https://fc2ppvdb.com/articles/${fc2Num.replace("FC2-", "")}`;
        const html = await gmHttp.get(url), $dom = $(html), actressNodeList = $dom.find("div").filter((function() {
            return 0 === $(this).text().trim().indexOf("女優:");
        }));
        if (0 === actressNodeList.length || actressNodeList.length > 1) {
            show.error("解析女优信息失败");
            return;
        }
        const $actress = $(actressNodeList[0]).find("a");
        let actorsHtml = "主演: ";
        if ($actress.length > 0) {
            let actress = "";
            $actress.each(((index, ele) => {
                let $actor = $(ele), name = $actor.text(), actressHref = $actor.attr("href");
                actorsHtml += `<span class="actor-tag"><a href="https://fc2ppvdb.com${actressHref}" target="_blank">${name}</a></span>`;
                actress += name + " ";
            }));
            $("#data-actress").text(actress);
        } else actorsHtml += "<span>暂无演员信息</span>";
        $(".actor-list").html(actorsHtml);
        const sellerNodeList = $dom.find("div").filter((function() {
            return 0 === $(this).text().trim().indexOf("販売者:");
        }));
        if (sellerNodeList.length > 0) {
            const $sellerA = $(sellerNodeList[0]).find("a");
            if ($sellerA.length > 0) {
                const $seller = $($sellerA[0]);
                let name = $seller.text(), sellerHref = $seller.attr("href");
                $(".movie-seller").html(`<span> 販売者: <a href="https://fc2ppvdb.com${sellerHref}" target="_blank">${name}</a></span>`);
            }
        }
    }
    async getImgList(fc2Num) {
        let url = `https://adult.contents.fc2.com/article/${fc2Num.replace("FC2-", "")}/`;
        const html = await gmHttp.get(url, null, {
            referer: url
        });
        let imgList = $(html).find(".items_article_SampleImagesArea img").map((function() {
            return $(this).attr("src");
        })).get(), imagesHtml = "";
        Array.isArray(imgList) && imgList.length > 0 ? imagesHtml = 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("") : $(".movie-gallery").html("<h4>剧照: 暂无剧照</h4>");
        $(".image-list").html(imagesHtml);
    }
    async getMovie(id, moviePoster) {
        let url = `${await this.getBaseUrl()}/ajax/v/${id}/videos`, loadObj = loading();
        try {
            let movieList = (await gmHttp.get(url)).result.watch;
            if (movieList.length > 0) {
                movieList.forEach((movieItem => {
                    movieItem.url = movieItem.url + "?poster=" + moviePoster;
                }));
                return movieList;
            }
            return null;
        } catch (e) {
            console.error(e);
        } finally {
            loadObj.close();
        }
    }
    markDataListHtml(movies) {
        let moviesHtml = "";
        movies.forEach((movie => {
            moviesHtml += `\n                <div class="item">\n                    <a href="${movie.href}" class="box" title="${movie.title}">\n                        <div class="cover ">\n                            <img loading="lazy" src="${movie.imgSrc.replace("/s360", "")}" alt="">\n                        </div>\n                        <div class="video-title"><strong>${movie.carNum}</strong> ${movie.title}</div>\n                        <div class="score">\n                        </div>\n                        <div class="meta">\n                        </div>\n                        <div class="tags has-addons">\n                        </div>\n                    </a>\n                </div>\n            `;
        }));
        return moviesHtml;
    }
}

class video123AvPlugin extends BasePlugin {
    async handle() {
        if (!currentHref.includes("5masterzzz")) return;
        localStorage.setItem("__pul", Date.now().toString());
        setInterval((() => {
            localStorage.setItem("__pul", Date.now().toString());
        }), 5e3);
        document.querySelector("video").play().then();
    }
}

class MagnetHubPlugin extends BasePlugin {
    constructor() {
        super(...arguments);
        __publicField(this, "currentEngine", null);
        __publicField(this, "searchEngines", [ {
            name: "U3C3",
            id: "u3c3",
            url: "https://u3c3.com/?search2=eelj1a3lfe1a1&search={keyword}",
            parse: this.parseU3C3
        }, {
            name: "BTSOW",
            id: "BTSOW",
            url: "https://btsow.pics/search/{keyword}",
            parse: this.parseBTSOW
        } ]);
    }
    async initCss() {
        return "\n            <style>\n                .magnet-container {\n                    margin: 20px auto;\n                    width: 100%;\n                    font-family: Arial, sans-serif;\n                }\n                .magnet-tabs {\n                    display: flex;\n                    border-bottom: 1px solid #ddd;\n                    margin-bottom: 15px;\n                }\n                .magnet-tab {\n                    padding: 5px 12px;\n                    cursor: pointer;\n                    border: 1px solid transparent;\n                    border-bottom: none;\n                    margin-right: 5px;\n                    background: #f5f5f5;\n                    border-radius: 5px 5px 0 0;\n                }\n                .magnet-tab.active {\n                    background: #fff;\n                    border-color: #ddd;\n                    border-bottom: 1px solid #fff;\n                    margin-bottom: -1px;\n                    font-weight: bold;\n                }\n                .magnet-tab:hover:not(.active) {\n                    background: #e9e9e9;\n                }\n                \n                .magnet-results {\n                    min-height: 200px;\n                }\n                .magnet-result {\n                    padding: 15px;\n                    border-bottom: 1px solid #eee;\n                    position: relative; \n                }\n                .magnet-result:hover {\n                    background-color: #f9f9f9;\n                }\n                .magnet-title {\n                    font-weight: bold;\n                    margin-bottom: 5px;\n                    white-space: nowrap;\n                    overflow: hidden; \n                    text-overflow: ellipsis;\n                    padding-right: 80px; \n                }\n                .magnet-info {\n                    display: flex;\n                    justify-content: space-between;\n                    font-size: 12px;\n                    color: #666;\n                    margin-bottom: 5px;\n                }\n                .magnet-loading {\n                    text-align: center;\n                    padding: 20px;\n                }\n                .magnet-error {\n                    color: #f44336;\n                    padding: 10px;\n                }\n                \n                .magnet-copy {\n                position: absolute;\n                right: 15px;\n                top: 12px;\n            }\n                .copy-btn {\n                    background-color: #f0f0f0;\n                    color: #555;\n                    border: 1px solid #ddd;\n                    padding: 3px 8px;\n                    border-radius: 3px;\n                    cursor: pointer;\n                    font-size: 12px;\n                    transition: all 0.2s;\n                }\n                .copy-btn:hover {\n                    background-color: #e0e0e0;\n                    border-color: #ccc;\n                }\n                .copy-btn.copied {\n                    background-color: #4CAF50;\n                    color: white;\n                    border-color: #4CAF50;\n                }\n            </style>\n        ";
    }
    createMagnetHub(keyword) {
        const $container = $('<div class="magnet-container"></div>'), $tabs = $('<div class="magnet-tabs"></div>'), savedEngineId = localStorage.getItem("magnetHub_selectedEngine");
        let defaultEngineIndex = 0;
        this.searchEngines.forEach(((engine, index) => {
            const $tab = $(`<div class="magnet-tab" data-engine="${engine.id}">${engine.name}</div>`);
            if (savedEngineId && engine.id === savedEngineId) {
                $tab.addClass("active");
                this.currentEngine = engine;
                defaultEngineIndex = index;
            } else if (0 === index && !savedEngineId) {
                $tab.addClass("active");
                this.currentEngine = engine;
            }
            $tabs.append($tab);
        }));
        $container.append($tabs);
        const $resultsContainer = $('<div class="magnet-results"></div>');
        $container.append($resultsContainer);
        $container.on("click", ".magnet-tab", (e => {
            const engineId = $(e.target).data("engine");
            this.currentEngine = this.searchEngines.find((engine => engine.id === engineId));
            localStorage.setItem("magnetHub_selectedEngine", engineId);
            $container.find(".magnet-tab").removeClass("active");
            $(e.target).addClass("active");
            this.searchEngine($resultsContainer, this.currentEngine, keyword);
        }));
        this.searchEngine($resultsContainer, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword);
        return $container;
    }
    searchEngine($container, engine, keyword) {
        $container.html(`<div class="magnet-loading">正在从 ${engine.name} 搜索 "${keyword}"...</div>`);
        const cacheKey = `${engine.name}_${keyword}`, cachedResult = sessionStorage.getItem(cacheKey);
        if (cachedResult) try {
            const results = JSON.parse(cachedResult);
            this.displayResults($container, results, engine.name);
            return;
        } catch (e) {
            $container.html(`<div class="magnet-error">解析 ${engine.name} 缓存结果失败: ${e.message}</div>`);
        }
        const url = engine.url.replace("{keyword}", encodeURIComponent(keyword));
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: response => {
                try {
                    const results = engine.parse.call(this, response.responseText);
                    results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results));
                    this.displayResults($container, results, engine.name);
                } catch (e) {
                    $container.html(`<div class="magnet-error">解析 ${engine.name} 结果失败: ${e.message}</div>`);
                }
            },
            onerror: error => {
                $container.html(`<div class="magnet-error">从 ${engine.name} 获取数据失败: ${error.statusText}</div>`);
            }
        });
    }
    displayResults($container, results, engineName) {
        $container.empty();
        if (0 !== results.length) {
            results.forEach((result => {
                const $result = $(`\n                <div class="magnet-result">\n                    <div class="magnet-title"><a href="${result.magnet}">${result.title}</a></div>\n                    <div class="magnet-info">\n                        <span>大小: ${result.size || "未知"}</span>\n                        <span>日期: ${result.date || "未知"}</span>\n                    </div>\n                    <div class="magnet-copy">\n                        <button class="copy-btn" data-magnet="${result.magnet}">复制链接</button>\n                    </div>\n                </div>\n            `);
                $container.append($result);
            }));
            $container.on("click", ".copy-btn", (function() {
                const $btn = $(this), magnet = $btn.data("magnet");
                navigator.clipboard ? navigator.clipboard.writeText(magnet).then((() => {
                    showCopiedFeedback($btn);
                })).catch((err => {
                    fallbackCopy(magnet, $btn);
                })) : fallbackCopy(magnet, $btn);
            }));
        } else $container.append('<div class="magnet-error">没有找到相关结果</div>');
        function showCopiedFeedback($btn) {
            const originalText = $btn.text();
            $btn.addClass("copied").text("已复制");
            setTimeout((() => {
                $btn.removeClass("copied").text(originalText);
            }), 2e3);
        }
        function fallbackCopy(text, $btn) {
            const textarea = document.createElement("textarea");
            textarea.value = text;
            textarea.style.position = "fixed";
            document.body.appendChild(textarea);
            textarea.select();
            try {
                document.execCommand("copy");
                showCopiedFeedback($btn);
            } catch (err) {
                console.error("复制失败:", err);
                alert("复制失败,请手动复制链接");
            }
            document.body.removeChild(textarea);
        }
    }
    parseBTSOW(html) {
        const $dom = $(html), results = [];
        $dom.find(".data-list .row").each(((i, el) => {
            const $el = $(el);
            let $a = $el.find("a");
            if (0 === $a.length) return;
            const title = $a.attr("title"), magnet = "magnet:?xt=urn:btih:" + $a.attr("href").split("/").pop(), size = $el.find(".size").text(), date = $el.find(".date").text();
            results.push({
                title: title,
                magnet: magnet,
                size: size,
                date: date
            });
        }));
        return results;
    }
    parseU3C3(html) {
        const $dom = $(html), results = [];
        $dom.find(".torrent-list tbody tr").each(((i, el) => {
            const $el = $(el);
            if ($el.text().includes("置顶")) return;
            const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim(), magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim();
            magnet && results.push({
                title: title,
                magnet: magnet,
                size: size,
                date: date
            });
        }));
        return results;
    }
}

class ScreenShotPlugin extends BasePlugin {
    async handle() {
        if (!isDetailPage) return;
        let carNum = this.getPageInfo().carNum;
        const cacheKey = `ScreenShot_${carNum}`, cachedUrl = sessionStorage.getItem(cacheKey);
        cachedUrl ? this.addImg("缩略图", cachedUrl) : Promise.any([ this.img_3xplanet(carNum), this.img_memojav(carNum) ]).then((imgUrl => {
            if (imgUrl) {
                sessionStorage.setItem(cacheKey, imgUrl);
                this.addImg("缩略图", imgUrl);
            }
        })).catch((e => {
            console.error(e);
        }));
    }
    addImg(title, imgUrl) {
        if (!imgUrl) return;
        let simpleId = utils.simpleId();
        isJavDb && $(".preview-images").append(`\n                <a class="tile-item" data-fancybox="gallery" data-caption="${title}" data-src="#${simpleId}" style="overflow:hidden;max-height: 100px;">\n                  <img src="${imgUrl}" alt="${title}" loading="lazy" style="width: 100%;">\n                </a> \n                <div id="${simpleId}" style="display: none;"><img src="${imgUrl}" alt="${title}" loading="lazy" style="width: 85vw;"></div>\n            `);
        if (isJavBus) {
            $("#sample-waterfall .sample-box:last").after(`\n                <a class="sample-box" style="height: 110px; overflow:hidden;" id="${simpleId}" href="${imgUrl}"><div class="photo-frame"><img src="${imgUrl}" style="height: inherit;width: 100%;" title="${title}" alt="${title}"></div></a>\n            `);
            $(`#${simpleId}`).on("click", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                layer.photos({
                    photos: {
                        title: title,
                        start: 0,
                        data: [ {
                            alt: title,
                            pid: 0,
                            src: imgUrl
                        } ]
                    },
                    footer: !1,
                    success: function(layero) {
                        layero.find('[toolbar-event="zoom"][data-option="0.1"]').attr("data-option", .5);
                        layero.find('[toolbar-event="zoom"][data-option="-0.1"]').attr("data-option", -.5);
                    }
                });
            }));
        }
    }
    async img_3xplanet(carNum) {
        let url = `https://3xplanet.com/?s=${carNum}`, html = await gmHttp.get(url);
        const href = $(html).find(".td-image-wrap").first().attr("href");
        if (!href) {
            console.error("解析3xplanet搜索页失败:", url);
            throw new Error("解析3xplanet搜索页失败");
        }
        const detailPageHtml = await gmHttp.get(href);
        let imgUrl = $(detailPageHtml).find('img[src*="s200"]').attr("src");
        if (!imgUrl) {
            console.error("解析3xplanet缩略图失败:", url);
            throw new Error("解析3xplanet缩略图失败");
        }
        imgUrl = imgUrl.replace("s200", "s0");
        return imgUrl;
    }
    async img_memojav(carNum) {
        let url = `https://memojav.com/image/screenshot/${carNum}.jpg`;
        try {
            await gmHttp.gmRequest("HEAD", url);
            return url;
        } catch (e) {
            console.error("memojav缩略图不可用:", e);
            throw e;
        }
    }
}

class FilterActorVideoPlugin extends BasePlugin {
    async handle() {
        currentHref.includes("/actors/") && $("h2").append('<a class="a-danger" id="filterActorVideo" style="padding:8px;">屏蔽该演员所有作品</a>');
        currentHref.includes("/star/") && $("#waitDownBtn").after(' \n                <a id="filterActorVideo" class="menu-btn" style="background-color:#b91c1c !important;margin-left: 5px;border-bottom:none !important;border-radius:3px;">\n                    <span>屏蔽该演员所有作品</span>\n                </a>\n            ');
        $("#filterActorVideo").on("click", (event2 => {
            let tempEvent = {
                clientX: event2.clientX,
                clientY: event2.clientY + 80
            };
            utils.q(tempEvent, "是否屏蔽该演员下的所有作品?", (async () => {
                this.loadObj = loading();
                try {
                    await storageManager.saveSettingItem("autoPage", "yes");
                    let $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
                    if (0 === $actor.length) {
                        show.error("获取演员名称失败");
                        return;
                    }
                    let key, actorName = $actor.text().trim().split(",")[0], isActor = $(".section-meta:contains('男優')").length > 0;
                    key = isActor ? storageManager.filter_actor_car_list_key + actorName : storageManager.filter_actress_car_list_key + actorName;
                    const filterActorActressInfoList = await storageManager.getSetting(storageManager.filter_actor_actress_info_list_key, []), currentStarUrl = this.getCurrentStarUrl();
                    if (!filterActorActressInfoList.some((item => item.name === actorName || item.url === currentStarUrl))) {
                        filterActorActressInfoList.push({
                            name: actorName,
                            key: key,
                            url: currentStarUrl,
                            isActor: isActor,
                            checkTime: ""
                        });
                        await storageManager.saveSettingItem(storageManager.filter_actor_actress_info_list_key, filterActorActressInfoList);
                    }
                    await this.filterActorVideo(key, actorName);
                } catch (e) {
                    console.error(e);
                    this.loadObj.close();
                } finally {
                    this.loadObj.close();
                }
            }));
        }));
        this.checkNewActressActorFilterCar();
    }
    getCurrentStarUrl() {
        let urlWithoutPageNumber = currentHref.replace(/\/\d+(\?|$)/, "$1");
        urlWithoutPageNumber = urlWithoutPageNumber.replace(/([&?])page=\d+&?/, "$1");
        urlWithoutPageNumber = urlWithoutPageNumber.replace(/[&?]$/, "");
        urlWithoutPageNumber = urlWithoutPageNumber.replace(/\?&/, "?");
        return urlWithoutPageNumber;
    }
    async filterActorVideo(key, actorName, $dom) {
        let nextPageLink = await this.parseAndSaveFilterInfo($dom, key, actorName);
        if (nextPageLink) {
            show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink);
            await new Promise((resolve => setTimeout(resolve, 500)));
            const html = await http.get(nextPageLink), parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html"));
            await this.filterActorVideo(key, actorName, next$dom);
        } else {
            show.ok("执行结束!");
            await storageManager.saveSettingItem("autoPage", "no");
            window.refresh();
        }
    }
    async parseAndSaveFilterInfo($dom, key, actorName) {
        let movieList, nextPageLink;
        if ($dom) {
            isJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove();
            movieList = $dom.find(this.getSelector().requestDomItemSelector);
            nextPageLink = $dom.find(this.getSelector().nextPageSelector).attr("href");
        } else {
            movieList = $(this.getSelector().itemSelector);
            nextPageLink = $(this.getSelector().nextPageSelector).attr("href");
        }
        if (nextPageLink && 0 === movieList.length) {
            show.error("解析列表失败");
            throw new Error("解析列表失败");
        }
        for (const element of movieList) {
            const $item = $(element), {carNum: carNum, aHref: aHref} = this.getBean("ListPagePlugin").findCarNumAndHref($item);
            if (aHref && carNum) try {
                if (await storageManager.getActorFilterCar(key, carNum)) continue;
                await storageManager.saveActorFilterCar(key, carNum, aHref, actorName);
                console.log("屏蔽女优番号", actorName, carNum);
            } catch (error) {
                console.error(`保存失败 [${carNum}]:`, error);
            }
        }
        return nextPageLink;
    }
    async checkNewActressActorFilterCar() {
        const filterActorActressInfoList = await storageManager.getSetting(storageManager.filter_actor_actress_info_list_key, []), actressActorFilterCarMap = {
            ...await storageManager.getActressFilterCarMap(),
            ...await storageManager.getActorFilterCarMap()
        }, actressActorFilterCarKeys = Object.keys(actressActorFilterCarMap);
        for (const key of actressActorFilterCarKeys) try {
            const name = key.split("_").pop(), item = filterActorActressInfoList.find((item2 => item2.name === name));
            if (!item) {
                console.log("演员详情页不存在, 需重新屏蔽: ", name);
                continue;
            }
            let url = item.url, checkTime = item.checkTime;
            if (checkTime && this.isToday(checkTime)) {
                console.log("今日已检测,跳过", name);
                continue;
            }
            const html = await http.get(url), $dom = $(html);
            console.log("检测屏蔽演员最新番号:", name, url);
            await this.parseAndSaveFilterInfo($dom, key, name);
            item.checkTime = utils.getNowStr();
        } catch (e) {
            console.error("检测屏蔽演员信息, 发生错误:", e);
        } finally {
            await storageManager.saveSettingItem(storageManager.filter_actor_actress_info_list_key, filterActorActressInfoList);
        }
    }
    isToday(dateStr) {
        return (new Date).toISOString().split("T")[0] === dateStr.split(" ")[0];
    }
}

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.split("?")[0].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("advanced_search") : !!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(Fc2By123AvPlugin);
            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);
            pluginManager.register(MagnetHubPlugin);
            pluginManager.register(ScreenShotPlugin);
            pluginManager.register(FilterActorVideoPlugin);
        }
        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);
            pluginManager.register(MagnetHubPlugin);
            pluginManager.register(ScreenShotPlugin);
            pluginManager.register(OtherSitePlugin);
            pluginManager.register(FilterActorVideoPlugin);
        }
        hostname.includes("sehuatang") && pluginManager.register(SeHuaTangPlugin);
        hostname.includes("javtrailers") && pluginManager.register(JavTrailersPlugin);
        hostname.includes("subtitlecat") && pluginManager.register(SubTitleCatPlugin);
        hostname.includes("aliyundrive") && pluginManager.register(AliyunPanPlugin);
        hostname.includes("5masterzzz") && pluginManager.register(video123AvPlugin);
        pluginManager.process().then();
    }();
};