JAV-JHS

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

Fra og med 04.06.2025. Se den nyeste version.

// ==UserScript==
// @name         JAV-JHS
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version      1.9.3
// @author       xie bro
// @description  Jav-鉴黄师 收藏、屏蔽、标记已下载; 免VIP查看热榜、Top250排行榜、Fc2ppv等数据; 可查看所有评论信息; 支持云盘备份; 以图识图; 字幕搜索;
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=javdb.com
// @include      https://javdb*.com/*
// @include      https://www.javbus.com/*
// @include      https://*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%20%7B%20opacity%3A%200%20!important%3B%20visibility%3A%20hidden%20!important%3B%20%7D%20body.script-ready%20%7B%20opacity%3A%201%20!important%3B%20visibility%3A%20visible%20!important%3B%20%7D%20%60%3B%20document.head.appendChild(initialHideStyle)%3B%20setTimeout(()%20%3D%3E%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20%7D%2C%203e3)%3B%20%7D)()%3B
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js
// @connect      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      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
// @run-at       document-start
// ==/UserScript==

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

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

              case Status_FAVORITE:
                if (carData.status === Status_FAVORITE) {
                    const msg2 = `${carNum2} 已在收藏列表中`;
                    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(carNum2) {
            return (await this.getCarList()).find((item => item.carNum === carNum2));
        }
        async removeCar(carNum2) {
            const carList = await this.getCarList(), initialLength = carList.length, updatedList = carList.filter((car => car.carNum !== carNum2));
            if (updatedList.length === initialLength) {
                show.error(`${carNum2} 不存在`);
                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.filterActorList;
            Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
            arrayData = dataJson.reviewKeywordList;
            Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
            dataJson.dataList && await this.overrideCarList(dataJson.dataList);
            arrayData = dataJson[this.title_filter_keyword_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.title_filter_keyword_key, arrayData);
            arrayData = dataJson[this.filter_actor_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
            arrayData = dataJson[this.review_filter_keyword_key];
            Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
            dataJson[this.car_list_key] && await this.overrideCarList(dataJson[this.car_list_key]);
            dataJson.setting && await this.saveSetting(dataJson.setting);
        }
        async exportData() {
            return {
                car_list: await this.getCarList(),
                filter_actor: await this.getFilterActorList(),
                title_filter_keyword: await this.getTitleFilterKeyword(),
                review_filter_keyword: await this.getReviewFilterKeywordList(),
                setting: await this.getSetting()
            };
        }
    };
    class Utils {
        constructor() {
            __publicField(this, "intervalContainer", {});
            __publicField(this, "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)) return window.open(url);
            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() {
            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 mimeType = this.mimeTypes[fileExtension] || "application/octet-stream";
            const blob = new Blob([ data ], {
                type: mimeType
            }), 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() {
            const screenWidth = window.innerWidth;
            return screenWidth >= 1920 ? [ "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)));
        }
    }
    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 {\n                width: 100%;\n                border-collapse: separate;\n                border-spacing: 0;\n                font-family: 'Helvetica Neue', Arial, sans-serif;\n                background: #fff;\n                overflow: hidden;\n                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);\n                margin: 0 auto; /* 表格整体水平居中 */\n            }\n    \n            .data-table thead tr {\n                background: #f8fafc;\n            }\n            \n            /* 表头居中 */\n            .data-table th {\n                padding: 16px 20px;\n                text-align: center !important; /* 表头文字居中 */\n                color: #64748b;\n                font-weight: 500;\n                font-size: 14px;\n                text-transform: uppercase;\n                letter-spacing: 0.5px;\n                border-bottom: 1px solid #e2e8f0;\n            }\n            \n            /* 单元格内容居中 */\n            .data-table td {\n                padding: 14px 20px;\n                color: #334155;\n                font-size: 15px;\n                border-bottom: 1px solid #f1f5f9;\n                text-align: center !important; /* 单元格文字居中 */\n                vertical-align: middle; /* 垂直居中 */\n            }\n            \n            .data-table tbody tr:last-child td {\n                border-bottom: none;\n            }\n            \n            /* 行hover 变色*/\n            .data-table tbody tr {\n                transition: all 0.2s ease;\n            }\n            \n            .data-table tbody tr:hover {\n                background: #f8fafc;\n            }\n            \n            /* 可选:特定列左对齐/右对齐的示例 */\n            .data-table .text-left {\n                text-align: left;\n            }\n            \n            .data-table .text-right {\n                text-align: right;\n            }\n            \n            /* 添加.show-border时显示边框 */\n            .data-table.show-border {\n                border: 1px solid #e2e8f0;\n            }\n            \n            .data-table.show-border th,\n            .data-table.show-border td {\n            border: 1px solid #e2e8f0;\n        }\n        </style>\n    ");
        window.TableGenerator = class {
            constructor(options) {
                this.defaults = {
                    tableClass: "data-table",
                    showBorder: !1,
                    buttons: []
                };
                this.config = {
                    ...this.defaults,
                    ...options
                };
                this.validateConfig() && this.init();
            }
            validateConfig() {
                if (!(this.config.containerId && this.config.columns && Array.isArray(this.config.columns) && Array.isArray(this.config.data))) {
                    console.error("缺少必要参数或参数类型不正确");
                    return !1;
                }
                this.container = document.getElementById(this.config.containerId);
                if (!this.container) {
                    console.error(`未找到ID为${this.config.containerId}的容器`);
                    return !1;
                }
                return !0;
            }
            init() {
                this.container.innerHTML = "";
                this.table = document.createElement("table");
                this.table.className = this.config.showBorder ? `${this.config.tableClass} show-border` : this.config.tableClass;
                this.createHeader();
                this.createBody();
                this.container.appendChild(this.table);
            }
            createHeader() {
                const thead = document.createElement("thead"), headerRow = document.createElement("tr");
                this.config.columns.forEach((column => {
                    const th = document.createElement("th");
                    th.textContent = column.title || column.key;
                    column.width && (th.style.width = column.width);
                    column.headerClass && (th.className = column.headerClass);
                    headerRow.appendChild(th);
                }));
                if (this.config.buttons && this.config.buttons.length > 0) {
                    const th = document.createElement("th");
                    th.textContent = "操作";
                    this.config.buttonColumnWidth && (th.style.width = this.config.buttonColumnWidth);
                    headerRow.appendChild(th);
                }
                thead.appendChild(headerRow);
                this.table.appendChild(thead);
            }
            createBody() {
                const tbody = document.createElement("tbody");
                0 === this.config.data.length ? this.renderEmptyData(tbody) : this.renderDataRows(tbody);
                this.table.appendChild(tbody);
            }
            renderEmptyData(tbody) {
                const tr = document.createElement("tr"), td = document.createElement("td");
                td.colSpan = this.config.columns.length + (this.config.buttons.length > 0 ? 1 : 0);
                td.textContent = "暂无数据";
                td.style.textAlign = "center";
                tr.appendChild(td);
                tbody.appendChild(tr);
            }
            renderDataRows(tbody) {
                this.config.data.forEach(((item, rowIndex) => {
                    const tr = document.createElement("tr");
                    this.renderDataCells(tr, item, rowIndex);
                    this.config.buttons && this.config.buttons.length > 0 && this.renderButtonCells(tr, item, rowIndex);
                    tbody.appendChild(tr);
                }));
            }
            renderDataCells(tr, item, rowIndex) {
                this.config.columns.forEach((column => {
                    const td = document.createElement("td");
                    column.render ? td.innerHTML = column.render(item, rowIndex) : td.textContent = item[column.key] || "";
                    column.cellClass && (td.className = column.cellClass);
                    tr.appendChild(td);
                }));
            }
            renderButtonCells(tr, item, rowIndex) {
                const td = document.createElement("td");
                this.config.buttons.forEach((button => {
                    const btn = document.createElement("a");
                    btn.textContent = button.text;
                    btn.className = button.class || "a-primary";
                    btn.addEventListener("click", (event2 => {
                        if (button.onClick) {
                            const paramLength = button.onClick.length;
                            3 === paramLength ? button.onClick(event2, item, rowIndex) : 2 === paramLength ? button.onClick(event2, item) : button.onClick(item);
                        }
                    }));
                    td.appendChild(btn);
                }));
                tr.appendChild(td);
            }
            update(newData) {
                this.config.data = newData;
                this.init();
            }
            getTableElement() {
                return this.table;
            }
        };
    }();
    !function() {
        const showMessage = (msg, type, gravityOrOptions, positionOrOptions, options) => {
            let finalOptions;
            if ("object" == typeof gravityOrOptions) finalOptions = gravityOrOptions; else {
                finalOptions = "object" == typeof positionOrOptions ? positionOrOptions : options || {};
                finalOptions.gravity = gravityOrOptions || "top";
                finalOptions.position = "string" == typeof positionOrOptions ? positionOrOptions : "center";
            }
            finalOptions.gravity && "center" !== finalOptions.gravity || (finalOptions.offset = {
                y: "calc(50vh - 150px)"
            });
            const colors_infoStart = "#60A5FA", colors_infoEnd = "#93C5FD", colors_successStart = "#10B981", colors_successEnd = "#6EE7B7", colors_errorStart = "#EF4444", colors_errorEnd = "#FCA5A5", commonStyles = {
                borderRadius: "12px",
                color: "white",
                padding: "12px 16px",
                boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
                minWidth: "150px",
                textAlign: "center",
                zIndex: 999999999
            }, defaultConfig = {
                text: msg,
                duration: 2e3,
                close: !1,
                gravity: "top",
                position: "center",
                style: {
                    info: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_infoStart}, ${colors_infoEnd})`
                    },
                    success: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_successStart}, ${colors_successEnd})`
                    },
                    error: {
                        ...commonStyles,
                        background: `linear-gradient(to right, ${colors_errorStart}, ${colors_errorEnd})`
                    }
                }[type],
                stopOnFocus: !0,
                oldestFirst: !1,
                ...finalOptions
            };
            Toastify(defaultConfig).showToast();
        };
        window.show = {
            ok: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "success", gravityOrOptions, positionOrOptions, options);
            },
            error: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "error", gravityOrOptions, positionOrOptions, options);
            },
            info: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
                showMessage(msg, "info", gravityOrOptions, positionOrOptions, options);
            }
        };
    }();
    class PluginManager {
        constructor() {
            this.plugins = new Map;
        }
        register(pluginClass) {
            if ("function" != typeof pluginClass) throw new Error("插件必须是一个类");
            const name = pluginClass.name;
            if (!name) throw new Error("类必须要有名称");
            const lowerName = name.toLowerCase();
            if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`);
            const instance = new pluginClass;
            instance.pluginManager = this;
            this.plugins.set(lowerName, instance);
        }
        getBean(name) {
            return this.plugins.get(name.toLowerCase());
        }
        _getDependencies(func) {
            const fnStr = func.toString();
            return fnStr.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")")).split(",").map((arg => arg.trim())).filter((arg => arg));
        }
        async process() {
            const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name, instance]) => {
                try {
                    if ("function" == typeof instance.handle) {
                        const css = await instance.initCss();
                        utils.insertStyle(css);
                        await instance.handle();
                        return {
                            name: name,
                            status: "fulfilled"
                        };
                    }
                    console.log("加载插件", name);
                } catch (e) {
                    console.error(`插件 ${name} 执行失败`, e);
                    return {
                        name: name,
                        status: "rejected",
                        error: e
                    };
                }
            })))).filter((r => "rejected" === r.status));
            failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map((p => p.name)));
            document.body.classList.add("script-ready");
        }
    }
    class BasePlugin {
        constructor() {
            __publicField(this, "pluginManager", null);
        }
        getBean(name) {
            let bean = this.pluginManager.getBean(name);
            if (!bean) {
                let msg = "容器中不存在: " + name;
                show.error(msg);
                throw new Error(msg);
            }
            return bean;
        }
        async initCss() {
            return "";
        }
        async handle() {}
        getPageInfo() {
            let carNum2, url, actress, actors, currentHref2 = window.location.href;
            if (isJavDb) {
                carNum2 = $('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];
                carNum2 = url.split("/").filter(Boolean).pop();
                actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ");
                actors = "";
            }
            return {
                carNum: carNum2,
                url: url,
                actress: actress,
                actors: actors
            };
        }
        getSelector() {
            return isJavDb ? Db : isJavBus ? Bus : null;
        }
        parseMovieId(href) {
            return href.split("/").pop().split(/[?#]/)[0];
        }
    }
    class DetailPagePlugin extends BasePlugin {
        constructor() {
            super();
        }
        async initCss() {
            return window.isDetailPage && window.location.href.includes("hideNav=1") ? "\n                .main-nav,#search-bar-container {\n                    display: none !important;\n                }\n                \n                html {\n                    padding-top:0px!important;\n                }\n            " : "";
        }
        handle() {
            if (window.isDetailPage) {
                this.checkFilterActor().then();
                $(".video-meta-panel a").attr("target", "_blank");
            }
        }
        async checkFilterActor() {
            if (!window.isDetailPage) return;
            if (await storageManager.getCar(carNum)) return;
            const filterActorList = await storageManager.getFilterActorList();
            let actors = this.getPageInfo().actors;
            filterActorList.forEach((item => {
                if (actors.indexOf(item) > -1) {
                    const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
                    detailPageButtonPlugin.answerCount++;
                    utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
                        detailPageButtonPlugin.filterOne(null, !0);
                    }), (() => {
                        detailPageButtonPlugin.answerCount = 1;
                    }));
                }
            }));
        }
    }
    const getDmmVideo = async carNum2 => {
        const cacheKey = `dmm_video_urls_${carNum2}`, cachedUrls = sessionStorage.getItem(cacheKey);
        if (cachedUrls) return JSON.parse(cachedUrls);
        const url = `https://www.dmm.co.jp/search/=/searchstr=${carNum2}`, html = await gmHttp.get(url);
        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");
            $previewSource.removeAttr("src");
            const videoEl = $videoEl[0];
            let carNum2 = this.getPageInfo().carNum;
            const dmmVideoMap = await getDmmVideo(carNum2);
            if (!dmmVideoMap) return null;
            let defaultVideoQuality = await storageManager.getSetting("videoQuality") || "hhb";
            dmmVideoMap[defaultVideoQuality] || (defaultVideoQuality = Object.keys(dmmVideoMap)[0]);
            let defaultVideoUrl = dmmVideoMap[defaultVideoQuality];
            $previewSource.attr("src", defaultVideoUrl);
            let buttonsHtml = "";
            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: -123px;">\n                        ${option.text}\n                    </button>\n                `;
                    activeIndex++;
                }
            }));
            let dmmVideoMapLength = Object.keys(dmmVideoMap).length;
            buttonsHtml = `<button class="menu-btn" id="speed-btn" style="position: absolute; min-width: 120px; background-color:#76b45d;bottom: ${50 * (dmmVideoMapLength + 2)}px; right: -123px;">快进(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: -123px;">屏蔽(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: -123px;">收藏(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), quality = $button.data("quality");
                if (!$button.hasClass("active")) try {
                    const newSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), quality);
                    $previewSource.attr("src", newSrc);
                    videoEl.load();
                    videoEl.muted = !1;
                    await videoEl.play();
                    $buttons.removeClass("active");
                    $button.addClass("active");
                } catch (error) {
                    console.error("切换画质失败:", error);
                }
            }));
            $("#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) {
                let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
                window.location.href = "https://javtrailers.com/search/" + keyword + "?handle=1";
                return;
            }
            let findList = $(".videos-list .video-link").toArray();
            if (findList.length) {
                const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword)));
                if (matchedLink) {
                    window.location.href = $(matchedLink).attr("href") + "?handle=1";
                    return;
                }
            }
            this.handlePlayJavTrailers();
            $("#videoPlayerContainer").on("click", (() => {
                this.handlePlayJavTrailers();
            }));
            window.addEventListener("message", (event2 => {
                let videoEl = document.getElementById("vjs_video_3_html5_api");
                videoEl && (videoEl.currentTime += 5);
            }));
            HotkeyManager.registerHotkey("z", (() => {
                const videoEl = document.getElementById("vjs_video_3_html5_api");
                videoEl && (videoEl.currentTime += 5);
            }));
            HotkeyManager.registerHotkey("a", (() => window.parent.postMessage("a", "*")));
            HotkeyManager.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
        }
        handlePlayJavTrailers() {
            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");
                        videoEl.play();
                        videoEl.currentTime = 5;
                        videoEl.addEventListener("timeupdate", (function() {
                            videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.currentTime += 2);
                        }));
                        $("#vjs_video_3_html5_api").css({
                            position: "fixed",
                            width: "100vw",
                            height: "100vh",
                            objectFit: "cover",
                            zIndex: "999999999"
                        });
                        $(".vjs-control-bar").css({
                            position: "fixed",
                            bottom: "20px",
                            zIndex: "999999999"
                        });
                    }), 0);
                }));
                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"), carNum2 = urlParams.get("carNum"), url = urlParams.get("url");
                movieId && carNum2 && url && this.openFc2Dialog(movieId, carNum2, 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, carNum2, href) {
            if (href.includes("123av")) {
                this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum2, href);
                return;
            }
            layer.open({
                type: 1,
                title: carNum2,
                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, carNum2);
                    $("#favoriteBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum2, href, actress, Status_FAVORITE);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#filterBtn").on("click", (event2 => {
                        utils.q(event2, `是否屏蔽${carNum2}?`, (async () => {
                            const actress = $("#data-actress").text();
                            await storageManager.saveCar(carNum2, 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(carNum2, href, actress, Status_HAS_DOWN);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#hasWatchBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum2, 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=${carNum2}`, carNum2, !1, event2)));
                    $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2)));
                },
                end() {
                    window.location.href.includes("collection_codes?movieId") && utils.closePage();
                }
            });
        }
        loadData(movieId, carNum2) {
            this.handleVideo(carNum2.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-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 urls = [ `${fc2By123AvPlugin.baseUrl}/dm4/v/fc2-ppv-${searchKeyword}`, `${fc2By123AvPlugin.baseUrl}/v/fc2-ppv-${searchKeyword}`, `${fc2By123AvPlugin.baseUrl}/dm1/v/fc2-ppv-${searchKeyword}` ];
                let success = !1;
                for (const url of urls) try {
                    const {id: id, moviePoster: moviePoster} = await fc2By123AvPlugin.get123AvVideoInfo(url), pageUrl = await fc2By123AvPlugin.getMovie(id, moviePoster);
                    if (pageUrl) {
                        $(".movie-trailer").attr("src", pageUrl);
                        success = !0;
                        break;
                    }
                } catch (e) {
                    console.log("尝试失败:", url, e.message);
                }
                if (!success) throw new Error("所有地址尝试失败");
            } catch (e) {
                console.error(e);
                $(".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="https://missav.ws/dm3/fc2-ppv-${searchKeyword}" target="_blank">missav</a></p>\n                </div>\n            `);
                $(".movie-trailer").hide();
            } finally {
                loadObj.close();
            }
        }
        async openFc2Page(movieId, carNum2, url) {
            let javDbUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com");
            window.open(`${javDbUrl}/users/collection_codes?movieId=${movieId}&carNum=${carNum2}&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");
            if ("5" === currentPage.toString()) {
                $("#auto-page").remove();
                $(".pagination-next").remove();
            }
            $(".pagination-ellipsis").closest("li").remove();
            $(".pagination-list li a").each((function() {
                parseInt($(this).text()) > 5 && $(this).closest("li").remove();
            }));
            let yearHtml = "";
            for (let year = (new Date).getFullYear(); year >= 2008; year--) yearHtml += `\n                <a style="padding:18px 18px !important;" \n                   class="button is-small ${type_value === year.toString() ? "is-info" : ""}" \n                   href="/?handleTop=1&type=year&type_value=${year}&has_cnsub=${this.has_cnsub}">\n                  ${year}\n                </a>\n            `;
            let conditionHtml = `\n            <div class="button-group">\n                <div class="buttons has-addons" id="conditionBox" style="margin-bottom: 0!important;">\n                    <a style="padding:18px 18px !important;" class="button is-small ${"all" === type ? "is-info" : ""}" href="/?handleTop=1&type=all&type_value=&has_cnsub=${this.has_cnsub}">全部</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"0" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=0&has_cnsub=${this.has_cnsub}">有码</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"1" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=1&has_cnsub=${this.has_cnsub}">无码</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"2" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=2&has_cnsub=${this.has_cnsub}">欧美</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"3" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=3&has_cnsub=${this.has_cnsub}">Fc2</a>\n                    \n                    <a style="padding:18px 18px !important;margin-left: 50px" class="button is-small ${"1" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="1">含中字磁鏈</a>\n                    <a style="padding:18px 18px !important;" class="button is-small ${"0" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="0">无字幕</a>\n                    <a style="padding:18px 18px !important;" class="button is-small" data-cnsub-value="">重置</a>\n                </div>\n                \n                <div class="buttons has-addons" id="conditionBox">\n                    ${yearHtml}\n                </div>\n            </div>\n        `;
            $(".toolbar").html(conditionHtml);
            $("a[data-cnsub-value]").on("click", (event2 => {
                const cnsubValue = $(event2.currentTarget).data("cnsub-value");
                this.has_cnsub = cnsubValue.toString();
                $("a[data-cnsub-value]").removeClass("is-info");
                $(event2.currentTarget).addClass("is-info");
                $(".toolbar a.button").not("[data-cnsub-value]").each(((index, element) => {
                    const $link = $(element), url = new URL($link.attr("href"), window.location.origin);
                    url.searchParams.set("has_cnsub", cnsubValue);
                    $link.attr("href", url.toString());
                }));
                const newUrl = new URL(window.location.href);
                newUrl.searchParams.set("has_cnsub", cnsubValue);
                history.pushState({}, "", newUrl.toString());
                if ("1" === this.has_cnsub) {
                    $(".item:contains('含中字磁鏈')").show();
                    $(".item:contains('含磁鏈')").hide();
                } else if ("0" === this.has_cnsub) {
                    $(".item:contains('含中字磁鏈')").hide();
                    $(".item:contains('含磁鏈')").show();
                } else {
                    $(".item:contains('含中字磁鏈')").show();
                    $(".item:contains('含磁鏈')").show();
                }
                this.getBean("hitShowPlugin").loadScore(this.movies);
            }));
        }
        async checkLogin(event2, urlParams) {
            if (!(await storageManager.getItem("appAuthorization"))) {
                show.error("未登录手机端接口, 无法查看");
                this.openLoginDialog();
                return;
            }
            let type = "all", type_value = "", t = urlParams.get("t") || "";
            if (/^y\d+$/.test(t)) {
                type = "year";
                type_value = t.substring(1);
            } else if ("" !== t) {
                type = "video_type";
                type_value = t;
            }
            let url = `/?handleTop=1&type=${type}&type_value=${type_value}`;
            event2 && (event2.ctrlKey || event2.metaKey) ? window.open(url, "_blank") : window.location.href = url;
        }
        openLoginDialog() {
            layer.open({
                type: 1,
                title: "JavDB",
                closeBtn: 1,
                area: [ "360px", "auto" ],
                shadeClose: !1,
                content: '\n                <div style="padding: 30px; font-family: \'Helvetica Neue\', Arial, sans-serif;">\n                    <div style="margin-bottom: 25px;">\n                        <input type="text" id="username" name="username" \n                            style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n                                   box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n                                   background: #f9f9f9; color: #333;"\n                            placeholder="用户名 | 邮箱"\n                            onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n                            onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n                    </div>\n                    \n                    <div style="margin-bottom: 15px;">\n                        <input type="password" id="password" name="password" \n                            style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n                                   box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n                                   background: #f9f9f9; color: #333;"\n                            placeholder="密码"\n                            onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n                            onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n                    </div>\n                    \n                    <button id="loginBtn" \n                            style="width: 100%; padding: 12px; background: #4a8bfc; color: white; \n                                   border: none; border-radius: 4px; font-size: 15px; cursor: pointer;\n                                   transition: background 0.3s;"\n                            onmouseover="this.style.background=\'#3a7be0\'"\n                            onmouseout="this.style.background=\'#4a8bfc\'">\n                        登录\n                    </button>\n                </div>\n            ',
                success: (layero, index) => {
                    $("#loginBtn").click((function() {
                        const username = $("#username").val(), password = $("#password").val();
                        if (!username || !password) {
                            show.error("请输入用户名和密码");
                            return;
                        }
                        let loadObj = loading();
                        (async (username, password) => {
                            let url = `${apiUrl}//v1/sessions?username=${username}&password=${password}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, headers = {
                                "user-agent": "Dart/3.5 (dart:io)",
                                "accept-language": "zh-TW",
                                "content-type": "multipart/form-data; boundary=--dio-boundary-2210433284",
                                jdsignature: await buildSignature()
                            };
                            return await gmHttp.post(url, null, headers);
                        })(username, password).then((async res => {
                            let success = res.success;
                            if (0 === success) show.error(res.message); else {
                                if (1 !== success) {
                                    console.error("登录失败", res);
                                    throw new Error(res.message);
                                }
                                {
                                    let token = res.data.token;
                                    await storageManager.setItem("appAuthorization", token);
                                    await storageManager.setItem("appUser", res.data);
                                    show.ok("登录成功");
                                    layer.close(index);
                                    window.location.href = "/?handleTop=1&period=daily";
                                }
                            }
                        })).catch((err => {
                            console.error("登录异常:", err);
                            show.error(err.message);
                        })).finally((() => {
                            loadObj.close();
                        }));
                    }));
                }
            });
        }
    }
    class NavBarPlugin extends BasePlugin {
        handle() {
            this.margeNav();
            this.hookSearch();
            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 {
        handle() {
            let carNum2 = this.getPageInfo().carNum, html = `\n            <div style="margin-top:20px;margin-left: -16px;">\n                <div style="display: flex;gap: 5px">\n                    <a id="javTrailersBtn" class="menu-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>JavTrailers</span></a>\n                    <a href="https://jable.tv/videos/${carNum2}/" target="_blank" class="menu-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>\n                    <a href="https://missav.ws/search/${carNum2}" target="_blank" class="menu-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>\n                    <a href="https://www.av.gl/vod/search.html?wd=${carNum2}" target="_blank" class="menu-btn" style="color:#f40 !important;background:linear-gradient(to bottom, rgb(238,164,238), #fff)"><span>Avgle</span></a>\n                </div>\n            </div>\n        `;
            $(".column-video-cover").append(html);
            $("#javTrailersBtn").on("click", (event2 => utils.openPage(`https://javtrailers.com/video/${carNum2.toLowerCase().replace("-", "00")}?handle=1`, carNum2, !1, event2)));
        }
    }
    class BusDetailPagePlugin extends BasePlugin {
        async initCss() {
            if (!window.isDetailPage) return "";
            $("h4:contains('論壇熱帖')").hide();
            $("h4:contains('同類影片')").hide();
            $("h4:contains('推薦')").hide();
            return window.location.href.includes("hideNav=1") ? "\n                .navbar-default {\n                    display: none !important;\n                }\n                body {\n                    padding-top:0px!important;\n                }\n            " : "";
        }
        async handle() {
            if (window.location.href.includes("/star/")) {
                const $avatarBox = $(".avatar-box");
                if ($avatarBox.length > 0) {
                    let parent2 = $avatarBox.parent();
                    parent2.css("position", "initial");
                    parent2.insertBefore(parent2.parent());
                }
            }
        }
    }
    class DetailPageButtonPlugin extends BasePlugin {
        constructor() {
            super();
            this.answerCount = 1;
        }
        handle() {
            this.bindHotkey();
            this.hideVideoControls();
            window.isDetailPage && this.createMenuBtn();
        }
        createMenuBtn() {
            const pageInfo = this.getPageInfo(), carNum2 = 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);
                window.refresh();
                show.ok("操作成功", {
                    duration: 50,
                    callback: () => {
                        utils.closePage();
                    }
                });
            }));
            $("#hasWatchBtn").on("click", (async () => {
                await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_HAS_WATCH);
                window.refresh();
                show.ok("操作成功", {
                    duration: 50,
                    callback: () => {
                        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=${carNum2}`, carNum2, !1, event2)));
            $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2)));
            this.showStatus(carNum2).then();
        }
        async showStatus(carNum2) {
            let car = await storageManager.getCar(carNum2);
            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);
            window.refresh();
            show.ok("操作成功", {
                duration: 50,
                callback: () => {
                    utils.closePage();
                }
            });
        }
        searchXunLeiSubtitle(carNum2) {
            let loadObj = loading();
            gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${carNum2}`).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 = carNum2 + "." + item.ext;
                                    this.previewSubtitle(url, name);
                                }
                            }, {
                                text: "下载",
                                class: "a-success",
                                onClick: async item => {
                                    let url = item.url, name = carNum2 + "." + 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);
                window.refresh();
                show.ok("操作成功", {
                    duration: 50,
                    callback: () => {
                        utils.closePage();
                    }
                });
            } else utils.q(event2, `是否屏蔽${pageInfo.carNum}?`, (async () => {
                await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
                window.refresh();
                show.ok("操作成功", {
                    duration: 50,
                    callback: () => {
                        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 handleResize = 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            ');
                handleResize();
                $(window).resize(handleResize);
            }
            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">\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            </div>\n            <div id="table-container"></div>\n        ',
                area: utils.getResponsiveArea(),
                success: async layero => {
                    const dataList = await this.getDataList();
                    this.loadTableData(dataList);
                    $(".layui-layer-content").on("click", ".history-btn", (async event2 => {
                        this.dataType = $(event2.target).data("action");
                        this.reloadTable();
                    }));
                },
                end: async () => window.refresh()
            });
        }
        async handleClickDetail(event2, data) {
            if (isJavDb) if (data.carNum.includes("FC2-")) {
                const 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);
        }
        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})`);
            return "all" === this.dataType ? dataList : dataList.filter((item => item.status === this.dataType));
        }
        loadTableData(dataList) {
            this.tableObj = new TableGenerator({
                containerId: "table-container",
                columns: [ {
                    key: "carNum",
                    title: "番号"
                }, {
                    key: "actress",
                    title: "演员",
                    width: "250px"
                }, {
                    key: "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: item => {
                        this.handleDelete(event, item);
                    }
                }, {
                    text: "详情页",
                    class: "a-info",
                    onClick: item => {
                        this.handleClickDetail(event, item);
                    }
                } ]
            });
        }
    }
    class ReviewPlugin extends BasePlugin {
        constructor() {
            super(...arguments);
            __publicField(this, "floorIndex", 1);
        }
        async handle() {
            if (window.isDetailPage) {
                if (isJavDb) {
                    const movieId = this.parseMovieId(window.location.href);
                    await this.showReview(movieId);
                    await this.getBean("RelatedPlugin").showRelated();
                }
                if (isJavBus) {
                    let carNum2 = 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;
                    })(carNum2);
                    let movieId = null;
                    for (let i = 0; i < movies.length; i++) {
                        let item = movies[i];
                        if (item.number.toLowerCase() === carNum2.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, $male;
            if (isJavDb) {
                $titles = $("h2");
                $male = $(".male").prev();
            }
            isJavBus && ($titles = $("h3"));
            utils.rightClick($titles, (event2 => {
                const selectedText = window.getSelection().toString();
                if (selectedText) {
                    event2.preventDefault();
                    let tempEvent = {
                        clientX: event2.clientX,
                        clientY: event2.clientY + 80
                    };
                    utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => {
                        await storageManager.saveTitleFilterKeyword(selectedText);
                        window.refresh();
                        utils.closePage();
                    }));
                }
            }));
            $male && $male.length > 0 && utils.rightClick($male, (event2 => {
                event2.preventDefault();
                let text = $(event2.target).text().trim();
                utils.q(event2, `是否屏蔽演员${text}?`, (async () => {
                    await storageManager.saveFilterActor(text);
                    window.refresh();
                    const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
                    await detailPageButtonPlugin.filterOne(null, !0);
                }));
            }));
        }
    }
    class ListPageButtonPlugin extends BasePlugin {
        handle() {
            window.isListPage && this.createMenuBtn();
        }
        createMenuBtn() {
            if (isJavDb) {
                if (window.location.href.includes("/actors/")) {
                    $(".toolbar .buttons").append('\n                    <a class="menu-btn" id="waitCheckBtn" \n                       style="background-color:#56c938 !important;; margin-left: 40px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n                        <span>打开待鉴定</span>\n                    </a>\n                    <a class="menu-btn" id="waitDownBtn" \n                       style="background-color:#2caac0 !important;; margin-left: 10px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n                      <span>打开已收藏</span>\n                    </a>\n                ');
                    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:contains('${text}')`).length > 0))) return;
                const {carNum: carNum2, aHref: aHref, title: title} = this.getBean("ListPagePlugin").findCarNumAndHref($el);
                if (carNum2.includes("FC2-")) {
                    const movieId = this.parseMovieId(aHref);
                    this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, 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], carNum2 = data.carNum, url = data.url;
                if (carNum2.includes("FC2-")) {
                    const movieId = this.parseMovieId(url);
                    await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, 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").addCopy();
                    $(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));
            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: carNum2, aHref: aHref, title: title} = this.findCarNumAndHref($box), hideKey = `${carNum2}-hide`, keywordHideKey = `${carNum2}-keywordHide`;
                if ("no" === hideFilterItem && $box.attr("data-hide") === hideKey) {
                    $box.show();
                    $box.removeAttr("data-hide");
                }
                if (filterKeywordList.some((filterKeyword => title.includes(filterKeyword) || carNum2.includes(filterKeyword))) && $box.attr("data-keyword-hide") !== keywordHideKey) {
                    $box.hide();
                    $box.attr("data-keyword-hide", keywordHideKey);
                    return;
                }
                if (filterCarNums.includes(carNum2) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                    $box.hide();
                    $box.attr("data-hide", hideKey);
                    return;
                }
                if (hasDownCarNums.includes(carNum2) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                    $box.hide();
                    $box.attr("data-hide", hideKey);
                    return;
                }
                if (hasWatchCarNums.includes(carNum2) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
                    $box.hide();
                    $box.attr("data-hide", hideKey);
                    return;
                }
                let tagText = "", color = "";
                if (filterCarNums.includes(carNum2)) {
                    tagText = "🚫 已屏蔽";
                    color = "#de3333";
                } else if (favoriteCarNums.includes(carNum2)) {
                    tagText = "⭐ 已收藏";
                    color = "#25b1dc";
                } else if (hasDownCarNums.includes(carNum2)) {
                    tagText = "📥️ 已下载";
                    color = "#7bc73b";
                } else if (hasWatchCarNums.includes(carNum2)) {
                    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>${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: carNum2, aHref: aHref} = this.findCarNumAndHref($box);
                let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", "yes");
                if (carNum2.includes("FC2-")) {
                    let movieId = this.parseMovieId(aHref);
                    this.getBean("fc2Plugin").openFc2Dialog(movieId, carNum2, aHref);
                } else "yes" === dialogOpenDetail ? utils.openPage(aHref, carNum2, !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: carNum2, aHref: aHref} = this.findCarNumAndHref($box);
                if (carNum2.includes("FC2-")) {
                    event2.preventDefault();
                    let movieId = this.parseMovieId(aHref);
                    this.getBean("fc2Plugin").openFc2Dialog(movieId, carNum2, aHref);
                }
            }));
            $(selector.boxSelector).on("contextmenu", ".item img, .item video", (event2 => {
                event2.preventDefault();
                const $box = $(event2.target).closest(".item"), {carNum: carNum2, aHref: aHref} = this.findCarNumAndHref($box);
                utils.q(event2, `是否屏蔽番号 ${carNum2}?`, (async () => {
                    await storageManager.saveCar(carNum2, aHref, "", Status_FILTER);
                    window.refresh();
                    show.ok("操作成功");
                }));
            }));
        }
        findCarNumAndHref($box) {
            let carNum2, title, aHref = $box.find("a").attr("href");
            if (isJavDb) {
                carNum2 = $box.find(".video-title").find("strong").text();
                title = $box.find(".video-title").text();
            }
            if (isJavBus) {
                carNum2 = aHref.split("/").filter(Boolean).pop();
                title = $box.find("img").attr("title");
            }
            if (!carNum2) {
                const msg = "提取番号信息失败";
                show.error(msg);
                throw new Error(msg);
            }
            return {
                carNum: carNum2,
                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();
            }));
            0 === $("#auto-page").length && await this.insertPageBtn();
            $("#auto-page").on("click", (async event2 => {
                event2.preventDefault();
                if ("yes" === await storageManager.getItem(storageManager.auto_page_key)) {
                    await storageManager.setItem(storageManager.auto_page_key, "no");
                    $("#auto-page").html("开启自动翻页");
                } else {
                    await storageManager.setItem(storageManager.auto_page_key, "yes");
                    $("#auto-page").html("关闭自动翻页");
                    await this.handlePaging();
                }
            }));
        }
        async insertPageBtn() {
            let initText = "yes" === await storageManager.getItem(storageManager.auto_page_key) ? "关闭自动翻页" : "开启自动翻页";
            isJavDb && $(".pagination").prepend(`<a style="background-color:#fff; order: 2;padding: calc(.5em - 1px) .75em;" id='auto-page'>${initText}</a>`);
            isJavBus && $(".pagination").append(`<li><a style="margin-left: 20px;cursor: pointer;" id='auto-page'>${initText}</a></li>`);
        }
        async parsePage(href) {
            let loadObj = loading();
            try {
                const html = await http.get(href), dom = (new DOMParser).parseFromString(html, "text/html");
                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 () => {}));
                }));
                await this.insertPageBtn();
                $(".pagination").replaceWith(pagination);
                window.history.pushState({}, "", href);
                if (isJavBus) {
                    const pageNumber = this.getPageNumberFromUrl(href);
                    document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁");
                }
                await utils.smoothScrollToTop();
                await this.bindPageClick();
                await this.handlePaging();
            } finally {
                loadObj.close();
            }
        }
        getPageNumberFromUrl(url) {
            const match = url.match(/\/page\/(\d+)/);
            return match ? parseInt(match[1], 10) : null;
        }
        shouldDisablePaging() {
            return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100" ].some((path => window.location.href.includes(path)));
        }
        async handlePaging() {
            if (this.shouldDisablePaging()) return;
            if ($("#no-page").length) {
                $("#auto-page").remove();
                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:contains(🚫 已屏蔽)").length && 0 === $(el).find("span:contains(⭐ 已收藏)").length && 0 === $(el).find("span:contains(📥️ 已下载)").length && 0 === $(el).find("span:contains(🔍 已观看)").length && (needPaging = !1);
            }));
            if (!needPaging) return;
            if ("yes" !== await storageManager.getItem(storageManager.auto_page_key)) return;
            let nextBtn = null;
            isJavDb && (nextBtn = $(".pagination-next"));
            isJavBus && (nextBtn = $("#next"));
            if (nextBtn && 0 !== nextBtn.length) {
                this.paging = !0;
                show.info("下一页....", {
                    duration: 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 handleResize = 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                ');
                    handleResize();
                }));
                $(window).resize(handleResize);
            }
            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">预览视频默认画质:</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 style="margin-top: 10px;">\n                                <button class="add-tag-btn">添加</button>\n                                <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div style=" flex-shrink: 0; padding: 15px 20px; text-align: right; border-top: 1px solid #eee; background: white; ">   \n                    <button id="saveBtn">保存设置</button>\n                </div>\n            </div>\n        ',
                    area: 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 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 || "");
            let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorList = await storageManager.getFilterActorList();
            reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => {
                this.addLabelTag("#reviewKeywordContainer", reviewKeyword);
            }));
            filterKeywordList && filterKeywordList.forEach((reviewKeyword => {
                this.addLabelTag("#filterKeywordContainer", reviewKeyword);
            }));
            filterActorList && filterActorList.forEach((filterActor => {
                this.addLabelTag("#filterActorContainer", filterActor);
            }));
            [ "#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer" ].forEach((containerId => {
                $(`${containerId} .add-tag-btn`).on("click", (event2 => this.addKeyword(event2, containerId)));
                $(`${containerId} .keyword-input`).on("keypress", (event2 => {
                    "Enter" === event2.key && this.addKeyword(event2, containerId);
                }));
            }));
        }
        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);
            $("#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();
            }));
            $("#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;
            await storageManager.saveSetting(settingObj);
            let reviewKeywordList = [];
            $("#reviewKeywordContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                reviewKeywordList.push(keyword);
            }));
            await storageManager.saveReviewFilterKeyword(reviewKeywordList);
            let filterKeywordList = [];
            $("#filterKeywordContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                filterKeywordList.push(keyword);
            }));
            await storageManager.saveTitleFilterKeyword(filterKeywordList);
            let filterActorList = [];
            $("#filterActorContainer .keyword-label").toArray().forEach((item => {
                let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
                filterActorList.push(keyword);
            }));
            await storageManager.saveFilterActor(filterActorList);
            show.ok("保存成功");
            window.refresh();
        }
        addLabelTag(containerId, keyword) {
            const $tagBox = $(`${containerId} .tag-box`), $label = $(`\n            <div class="keyword-label" style="background-color: #c5b9a0">\n                ${keyword}\n                <span class="keyword-remove">×</span>\n            </div>\n        `);
            $label.find(".keyword-remove").click((event2 => {
                $(event2.target).parent().remove();
            }));
            $tagBox.append($label);
        }
        addKeyword(event2, containerId) {
            let $keywordInput = $(`${containerId} .keyword-input`);
            const keyword = $keywordInput.val().trim();
            if (keyword) {
                this.addLabelTag(containerId, keyword);
                $keywordInput.val("");
            }
        }
        importData() {
            try {
                const input = document.createElement("input");
                input.type = "file";
                input.accept = ".json";
                input.onchange = e => {
                    const file = e.target.files[0];
                    if (!file) return;
                    const reader = new FileReader;
                    reader.onload = event2 => {
                        try {
                            const content = event2.target.result.toString(), updateJsonData = JSON.parse(content);
                            layer.confirm("确定是否要覆盖导入?", {
                                icon: 3,
                                title: "确认覆盖",
                                btn: [ "确定", "取消" ]
                            }, (async function(index) {
                                await storageManager.importData(updateJsonData);
                                show.ok("数据导入成功");
                                layer.close(index);
                                location.reload();
                            }));
                        } catch (err) {
                            console.error(err);
                            show.error("导入失败:文件内容不是有效的JSON格式 " + err);
                        }
                    };
                    reader.onerror = () => {
                        show.error("读取文件时出错");
                    };
                    reader.readAsText(file);
                };
                document.body.appendChild(input);
                input.click();
                setTimeout((() => document.body.removeChild(input)), 1e3);
            } catch (err) {
                console.error(err);
                show.error("导入数据时出错: " + err.message);
            }
        }
        async backupData(event2) {
            const refresh_token = await storageManager.getSetting("refresh_token");
            if (!refresh_token) {
                show.error("请填写refresh_token并保存后, 再试此功能");
                return;
            }
            let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
            uploadContent = simpleEncrypt(uploadContent);
            let loadObj = loading();
            try {
                const aliyunApi = new AliyunApi(refresh_token);
                await aliyunApi.backup(this.folderName, fileName, uploadContent);
                show.ok("备份完成");
            } catch (e) {
                console.error(e);
                show.error(e.toString());
            } finally {
                loadObj.close();
            }
        }
        async backupListBtn(event2) {
            const refresh_token = await storageManager.getSetting("refresh_token");
            if (!refresh_token) {
                show.error("请填写refresh_token并保存后, 再试此功能");
                return;
            }
            let loadObj = loading();
            try {
                const aliyunApi = new AliyunApi(refresh_token), fileList = await aliyunApi.getBackupList(this.folderName);
                this.openFileListDialog(fileList, aliyunApi, "阿里云盘");
            } catch (e) {
                console.error(e);
                show.error(`发生错误: ${e ? e.message : e}`);
            } finally {
                loadObj.close();
            }
        }
        async backupDataByWebDav(event2) {
            const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
            if (!webDavUrl) {
                show.error("请填写webDav服务地址并保存后, 再试此功能");
                return;
            }
            const webDavUsername = settingObj.webDavUsername;
            if (!webDavUsername) {
                show.error("请填写webDav用户名并保存后, 再试此功能");
                return;
            }
            const webDavPassword = settingObj.webDavPassword;
            if (!webDavPassword) {
                show.error("请填写webDav密码并保存后, 再试此功能");
                return;
            }
            let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
            uploadContent = simpleEncrypt(uploadContent);
            let loadObj = loading();
            try {
                const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword);
                await webDavApi.backup(this.folderName, fileName, uploadContent);
                show.ok("备份完成");
            } catch (e) {
                console.error(e);
                show.error(e.toString());
            } finally {
                loadObj.close();
            }
        }
        async backupListBtnByWebDav(event2) {
            const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
            if (!webDavUrl) {
                show.error("请填写webDav服务地址并保存后, 再试此功能");
                return;
            }
            const webDavUsername = settingObj.webDavUsername;
            if (!webDavUsername) {
                show.error("请填写webDav用户名并保存后, 再试此功能");
                return;
            }
            const webDavPassword = settingObj.webDavPassword;
            if (!webDavPassword) {
                show.error("请填写webDav密码并保存后, 再试此功能");
                return;
            }
            let loadObj = loading();
            try {
                const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword), fileList = await webDavApi.getBackupList(this.folderName);
                this.openFileListDialog(fileList, webDavApi, "WebDav");
            } catch (e) {
                console.error(e);
                show.error(`发生错误: ${e ? e.message : e}`);
            } finally {
                loadObj.close();
            }
        }
        openFileListDialog(fileList, api, apiType) {
            layer.open({
                type: 1,
                title: apiType + "备份文件",
                content: '<div id="table-container"></div>',
                area: [ "40%", "70%" ],
                success: layero => {
                    const tableObj = new TableGenerator({
                        containerId: "table-container",
                        columns: [ {
                            key: "name",
                            title: "文件名"
                        }, {
                            key: "createTime",
                            title: "备份日期",
                            render: item => `${utils.getNowStr("-", ":", item.createTime)}`
                        }, {
                            key: "size",
                            title: "文件大小",
                            render: item => {
                                const units = [ "B", "KB", "MB", "GB", "TB", "PB" ];
                                let unitIndex = 0, adjustedSize = item.size;
                                for (;adjustedSize >= 1024 && unitIndex < units.length - 1; ) {
                                    adjustedSize /= 1024;
                                    unitIndex++;
                                }
                                return `${adjustedSize % 1 == 0 ? adjustedSize.toFixed(0) : adjustedSize.toFixed(2)} ${units[unitIndex]}`;
                            }
                        } ],
                        data: fileList,
                        buttons: [ {
                            text: "删除",
                            class: "a-danger",
                            onClick: async (event2, item) => {
                                layer.confirm(`是否删除 ${item.name} ?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: [ "确定", "取消" ]
                                }, (async index => {
                                    layer.close(index);
                                    let loadObj = loading();
                                    try {
                                        await api.deleteFile(item.fileId);
                                        let newFileList = await api.getBackupList(this.folderName);
                                        tableObj.update(newFileList);
                                        "阿里云盘" === apiType ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功");
                                    } catch (e) {
                                        console.error(e);
                                        show.error(`发生错误: ${e ? e.message : e}`);
                                    } finally {
                                        loadObj.close();
                                    }
                                }));
                            }
                        }, {
                            text: "下载",
                            class: "a-primary",
                            onClick: item => {
                                let loadObj = loading();
                                try {
                                    "阿里云盘" === apiType ? api.getDownloadUrl(item.fileId).then((url => {
                                        gmHttp.get(url, null, {
                                            Referer: "https://www.aliyundrive.com/"
                                        }).then((content => {
                                            content = simpleDecrypt(content);
                                            utils.download(content, item.name);
                                        }));
                                    })).catch((e => {
                                        console.error(e);
                                        show.error("下载失败: " + e);
                                    })) : api.getFileContent(item.fileId).then((content => {
                                        content = simpleDecrypt(content);
                                        utils.download(content, item.name);
                                    }));
                                } catch (e) {
                                    console.error(e);
                                    show.error("下载失败: " + e);
                                } finally {
                                    loadObj.close();
                                }
                            }
                        }, {
                            text: "导入",
                            class: "a-success",
                            onClick: item => {
                                layer.confirm(`是否将该云备份数据 ${item.name} 导入?`, {
                                    icon: 3,
                                    title: "提示",
                                    btn: [ "确定", "取消" ]
                                }, (async index => {
                                    layer.close(index);
                                    let loadObj = loading();
                                    try {
                                        let content;
                                        if ("阿里云盘" === apiType) {
                                            const downUrl = await api.getDownloadUrl(item.fileId);
                                            content = await gmHttp.get(downUrl, null, {
                                                Referer: "https://www.aliyundrive.com/"
                                            });
                                        } else content = await api.getFileContent(item.fileId);
                                        content = simpleDecrypt(content);
                                        const updateJsonData = JSON.parse(content);
                                        await storageManager.importData(updateJsonData);
                                        show.ok("导入成功!");
                                        window.location.reload();
                                    } catch (err) {
                                        console.error(err);
                                        show.error(err);
                                    } finally {
                                        loadObj.close();
                                    }
                                }));
                            }
                        } ]
                    });
                }
            });
        }
        async exportData(event2) {
            try {
                const backupData = JSON.stringify(await storageManager.exportData()), fileName = `${utils.getNowStr("_", "_")}.json`;
                utils.download(backupData, fileName);
                show.ok("数据导出成功");
            } catch (err) {
                console.error(err);
                show.error("导出数据时出错: " + err.message);
            }
        }
        async syncData(event2) {
            let msg = null, targetUrl = null;
            if (isJavDb) {
                msg = "是否将JavBus的数据及配置同步到本站中? ";
                targetUrl = "https://www.javbus.com/temp?syncData=1";
            }
            if (isJavBus) {
                msg = "是否将JavDB的数据及配置同步到本站中? ";
                targetUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com") + "/feedbacks/new?syncData=1";
            }
            utils.q(event2, msg, (() => {
                const targetWindow = window.open(targetUrl);
                let targetOrigin = new URL(targetUrl).origin;
                console.log("开始连接接受方:", targetOrigin);
                let pingInterval, retryCount = 0;
                if (!this.hasListenMsg) {
                    window.addEventListener("message", (event3 => {
                        if (event3.origin === targetOrigin) if ("ok" === event3.data) {
                            clearInterval(pingInterval);
                            console.log("连接确认,开始同步数据");
                            targetWindow.postMessage("syncData", targetOrigin);
                        } else {
                            const resData = event3.data;
                            console.log("收到数据", resData);
                            __privateMethod(this, _SettingPlugin_instances, handleSyncData_fn).call(this, resData);
                        }
                    }));
                    this.hasListenMsg = !0;
                }
                const pingTarget = () => {
                    if (retryCount >= 8) {
                        clearInterval(pingInterval);
                        console.log("超过最大重试次数,停止尝试");
                        show.error("同步失败, 目标网站已中断, 请检查是否登录后再试!", {
                            close: !0,
                            duration: -1
                        });
                    } else {
                        console.log(`第 ${retryCount + 1} 次ping...`);
                        targetWindow.postMessage("ping", targetOrigin);
                        retryCount++;
                    }
                };
                pingInterval = setInterval(pingTarget, 1e3);
                pingTarget();
            }));
        }
    }
    _SettingPlugin_instances = new WeakSet;
    handleSyncData_fn = async function(resData) {
        try {
            const targetCarList = resData.carList || [], targetFilterActor = resData.filterActor || [], targetTitleFilterKeyword = resData.titleFilterKeyword || [], targetReviewFilterKeyword = resData.reviewFilterKeyword || [], targetSetting = resData.setting || {}, selfCarList = await storageManager.getCarList() || [], selfFilterActor = await storageManager.getFilterActorList() || [], selfTitleFilterKeyword = await storageManager.getTitleFilterKeyword() || [], selfReviewFilterKeyword = await storageManager.getReviewFilterKeywordList() || [], selfSetting = await storageManager.getSetting() || {}, newCarList = [ ...selfCarList ];
            targetCarList.forEach((targetCar => {
                selfCarList.some((selfCar => selfCar.carNum === targetCar.carNum)) || newCarList.push(targetCar);
            }));
            const newFilterActorKeyword = [ ...new Set([ ...selfFilterActor, ...targetFilterActor ]) ], newTitleFilterKeyword = [ ...new Set([ ...selfTitleFilterKeyword, ...targetTitleFilterKeyword ]) ], newReviewFilterKeyword = [ ...new Set([ ...selfReviewFilterKeyword, ...targetReviewFilterKeyword ]) ], newSetting = {
                ...selfSetting
            };
            Object.keys(targetSetting).forEach((key => {
                key in newSetting && newSetting[key] || (newSetting[key] = targetSetting[key]);
            }));
            await storageManager.overrideCarList(newCarList);
            await storageManager.saveFilterActor(newFilterActorKeyword);
            await storageManager.saveTitleFilterKeyword(newTitleFilterKeyword);
            await storageManager.saveReviewFilterKeyword(newReviewFilterKeyword);
            await storageManager.saveSetting(newSetting);
            show.ok("同步完成, 关闭提示后, 将重载数据", {
                close: !0,
                duration: -1,
                callback: () => {
                    window.location.reload();
                }
            });
        } catch (error) {
            console.error(error);
            show.error("同步数据时出错:", error);
        }
    };
    const SALT = "x7k9p3";
    function simpleEncrypt(str) {
        return (SALT + str + SALT).split("").map((char => {
            const code = char.codePointAt(0);
            return String.fromCodePoint(code + 5);
        })).join("");
    }
    function simpleDecrypt(encryptedStr) {
        return encryptedStr.split("").map((char => {
            const code = char.codePointAt(0);
            return String.fromCodePoint(code - 5);
        })).join("").slice(SALT.length, -SALT.length);
    }
    class SyncDataPlugin extends BasePlugin {
        async handle() {
            if (!window.location.href.includes("syncData=1")) return;
            isJavBus && $("h4").html("临时页面, 用于同步数据");
            let senderOrigin = null;
            isJavDb && (senderOrigin = "https://www.javbus.com");
            isJavBus && (senderOrigin = await storageManager.getSetting("javDbUrl", "https://javdb.com"));
            console.log("等待发送方:", senderOrigin);
            window.addEventListener("message", (async event2 => {
                if (event2.origin === senderOrigin) if ("ping" === event2.data) {
                    console.log("收到 ping,发送确认");
                    event2.source.postMessage("ok", event2.origin);
                } else if ("syncData" === event2.data) {
                    console.log("开始发送数据...");
                    const carList = await storageManager.getCarList(), filterActor = await storageManager.getFilterActorList(), titleFilterKeyword = await storageManager.getTitleFilterKeyword(), reviewFilterKeyword = await storageManager.getReviewFilterKeywordList(), setting = await storageManager.getSetting();
                    event2.source.postMessage({
                        carList: carList,
                        filterActor: filterActor,
                        titleFilterKeyword: titleFilterKeyword,
                        reviewFilterKeyword: reviewFilterKeyword,
                        setting: setting
                    }, event2.origin);
                    show.ok("数据已传输, 即将关闭页面...", {
                        callback: () => {
                            window.close();
                        }
                    });
                }
            }));
        }
    }
    class BusPreviewVideoPlugin extends BasePlugin {
        async initCss() {
            return "\n            .video-control-btn {\n                min-width:100px;\n                padding: 8px 16px;\n                background: rgba(0,0,0,0.7);\n                color: white;\n                border: none;\n                border-radius: 4px;\n                cursor: pointer;\n            }\n            .video-control-btn.active {\n                background-color: #1890ff; /* 选中按钮的背景色 */\n                color: white;             /* 选中按钮的文字颜色 */\n                font-weight: bold;        /* 加粗显示 */\n                border: 2px solid #096dd9; /* 边框样式 */\n            }\n        ";
        }
        handle() {
            if (!isDetailPage) return;
            const firstImageSrc = $("#sample-waterfall a:first").attr("href"), videoPreview = $(`\n            <a class="preview-video-container sample-box" style="cursor: pointer">\n                <div class="photo-frame" style="position:relative;">\n                    <img src="${firstImageSrc}" class="video-cover" alt="">\n                    <div class="play-icon" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); \n                            color:white; font-size:40px; text-shadow:0 0 10px rgba(0,0,0,0.5);">\n                        ▶\n                    </div>\n                </div>\n            </a>`);
            $("#sample-waterfall").prepend(videoPreview);
            let $preview = $(".preview-video-container");
            $preview.on("click", (async event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                this.handleVideo().then();
            }));
            window.location.href.includes("autoPlay=1") && $preview[0].click();
        }
        async handleVideo() {
            if ($("#preview-video").length > 0) return;
            let carNum2 = this.getPageInfo().carNum;
            const dmmVideoMap = await getDmmVideo(carNum2);
            if (!dmmVideoMap) return null;
            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><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 videoSrc2 = $button.attr("data-video-src");
                    $previewSource.attr("src", videoSrc2);
                    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();
                            this.siteList.forEach((site => {
                                const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl));
                                $siteBtnsContainer.append(`\n                            <a href="${siteUrl}" class="site-btn" target="_blank" title="${site.name}">\n                                <img src="${site.ico}" alt="${site.name}">\n                                <span>${site.name}</span>\n                            </a>\n                        `);
                            }));
                            $siteBtnsContainer.show();
                        } 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").toArray().forEach((item => {
                    window.open($(item).attr("href"));
                }));
            }));
        }
        resetSearchUI() {
            $("#action-btns").show();
            $("#search-results").hide();
            $("#site-btns-container").hide().empty();
        }
        handleImageFile(file) {
            const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container");
            if (!file.type.match("image.*")) {
                show.info("请选择图片文件");
                return;
            }
            const reader = new FileReader;
            reader.onload = e => {
                previewImage.src = e.target.result;
                previewArea.style.display = "block";
                urlInputContainer.style.display = "none";
                $("#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"), carNum2 = item.find(".video-title strong").text().trim();
                if (href && carNum2) try {
                    if (await storageManager.getCar(carNum2)) {
                        show.info(`${carNum2} 已存在, 跳过`);
                        continue;
                    }
                    await storageManager.saveCar(carNum2, href, "", this.type);
                } catch (error) {
                    console.error(`保存失败 [${carNum2}]:`, 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, "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: 2rem; width: 2rem; }" : ""}\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.addCopy();
                this.bindClick();
            }
        }
        addCopy() {
            $(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                            \x3c!-- 主按钮 --\x3e\n                            <div style="padding: 5px; margin: -5px;opacity:.3">${this.moreSvg}</div>\n                            \n                            \x3c!-- 下拉菜单 --\x3e\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;">\n                            \x3c!-- 主按钮 --\x3e\n                            <div style="padding: 5px; margin: -5px;opacity:.3">${this.moreSvg}</div>\n                            \n                            \x3c!-- 下拉菜单 --\x3e\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                `);
                    }
                    $box.find(".more-tools-container").on({
                        mouseenter: function() {
                            $(this).find(".more-tools").stop(!0, !0).removeClass("elastic-out").addClass("elastic-in").show();
                        },
                        mouseleave: function() {
                            $(this).find(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").delay(200).queue((function(next) {
                                $(this).hide();
                                next();
                            }));
                        }
                    });
                }
            }));
        }
        bindClick() {
            const selector = this.getSelector(), listPagePlugin = this.getBean("ListPagePlugin");
            $(document).on("click", ".titleSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum2, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                navigator.clipboard.writeText(title).then((() => {
                    show.info("标题已复制到剪切板, " + title);
                })).catch((err => {
                    console.error("复制失败: ", err);
                }));
            })).on("click", ".carNumSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum2, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                navigator.clipboard.writeText(carNum2).then((() => {
                    show.info("番号已复制到剪切板, " + carNum2);
                })).catch((err => {
                    console.error("复制失败: ", err);
                }));
            })).on("click", ".downSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                const $box = $(event2.target).closest(".item"), {carNum: carNum2, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
                let $img = $box.find(".cover img");
                isJavBus && ($img = $box.find(".photo-frame img"));
                const url = $img.attr("src");
                http.get(url).then((content => {
                    utils.download(content, title + ".jpg");
                }));
            })).on("click", ".videoSvg", (event2 => {
                event2.preventDefault();
                event2.stopPropagation();
                $('.videoSvg[title!="播放视频"]').each(((index, element) => {
                    const $otherSvgElement = $(element);
                    let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find(selector.coverImgSelector), {carNum: carNum2} = listPagePlugin.findCarNumAndHref($otherBox);
                    this.showImg($otherSvgElement, $otherImg, carNum2);
                    $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: carNum2} = listPagePlugin.findCarNumAndHref($currentBox);
                    let $img = $currentBox.find(selector.coverImgSelector);
                    $img.length || show.error("没有找到图片");
                    this.showVideo($svgElement, $img, carNum2).then();
                }
            }));
        }
        showImg($svgElement, $img, carNum2) {
            $svgElement.html(this.videoSvg).attr("title", "播放视频");
            let $video = $(`#${`${carNum2}_preview_video`}`);
            if ($video.length > 0) {
                $video[0].pause();
                $video.hide();
            }
            $img.show();
            $img.removeClass("loading");
            $img.next(".loading-spinner").remove();
        }
        async showVideo($svgElement, $img, carNum2) {
            const id = `${carNum2}_preview_video`;
            let $video = $(`#${id}`);
            if ($video.length > 0) {
                $video.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(carNum2);
            if (!dmmVideoMap) {
                show.error("获取预览视频地址失败");
                this.showImg($svgElement, $img, carNum2);
                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, "baseUrl", "https://123av.com/ja");
            __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);
        }
        handle() {
            $('.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 fetchPromises = pagesToFetch.map((page => {
                    let url = `${this.baseUrl}/dm4/tags/fc2?sort=${this.sortVal}&page=${page}`;
                    this.keyword && (url = `${this.baseUrl}/search?keyword=${this.keyword}`);
                    console.log(url);
                    return gmHttp.get(url);
                })), htmlResults = await Promise.all(fetchPromises);
                let dataList = [];
                for (const html of htmlResults) {
                    console.log(html);
                    let $dom = $(html);
                    $dom.find(".box-item").each(((index, element) => {
                        const $item = $(element), imgSrc = $item.find("img").attr("data-src");
                        let carNum2 = $item.find("img").attr("title");
                        const detailLink = $item.find(".detail a"), link = detailLink.attr("href"), href = this.baseUrl + (link.startsWith("/") ? link : "/" + link), title = detailLink.text().trim().replace(carNum2 + " - ", "");
                        carNum2 = carNum2.replace("FC2-PPV", "FC2");
                        dataList.push({
                            imgSrc: imgSrc,
                            carNum: carNum2,
                            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("获取数据失败!");
                    return;
                }
                let movieHtml = this.markDataListHtml(dataList);
                $(".movie-list").html(movieHtml);
                await utils.smoothScrollToTop();
            } catch (e) {
                console.error(e);
            } finally {
                loadObj.close();
            }
        }
        open123AvFc2Dialog(carNum2, href) {
            layer.open({
                type: 1,
                title: carNum2,
                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(carNum2, href);
                    let keyword = carNum2.replace("FC2-", "");
                    $("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword));
                    $("#favoriteBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum2, href, actress, Status_FAVORITE);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#filterBtn").on("click", (event2 => {
                        utils.q(event2, `是否屏蔽${carNum2}?`, (async () => {
                            const actress = $("#data-actress").text();
                            await storageManager.saveCar(carNum2, 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(carNum2, href, actress, Status_HAS_DOWN);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#hasWatchBtn").on("click", (async event2 => {
                        const actress = $("#data-actress").text();
                        await storageManager.saveCar(carNum2, href, actress, Status_HAS_WATCH);
                        window.refresh();
                        layer.closeAll();
                    }));
                    $("#search-subtitle-btn").on("click", (event2 => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event2)));
                    $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2)));
                }
            });
        }
        async loadData(carNum2, 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>番号: ${carNum2 || "未知"}</span>\n                        <span>年份: ${publishDate || "未知"}</span>\n                        <span>\n                            站点: \n                            <a href="https://fc2ppvdb.com/articles/${carNum2.replace("FC2-", "")}" target="_blank">fc2ppvdb</a>\n                            <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${carNum2.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((movieUrl => {
                    $(".movie-trailer").attr("src", movieUrl);
                }));
                this.getImgList(carNum2).then();
                this.getActressInfo(carNum2).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]);
                    console.log($seller);
                    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 = `${this.baseUrl}/ajax/v/${id}/videos`, loadObj = loading();
            try {
                let movieList = (await gmHttp.get(url)).result.watch;
                return movieList.length > 0 ? movieList[0].url + "?poster=" + moviePoster : 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")) {
                localStorage.setItem("__pul", Date.now().toString());
                setInterval((() => {
                    localStorage.setItem("__pul", Date.now().toString());
                }), 5e3);
            }
        }
    }
    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 carNum2 = this.getPageInfo().carNum;
            Promise.any([ this.img_3xplanet(carNum2), this.img_memojav(carNum2) ]).then((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}">\n                  <img src="${imgUrl}" alt="${title}" loading="lazy" style="max-height: 100px">\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(carNum2) {
            let url = `https://3xplanet.com/?s=${carNum2}`, html = await gmHttp.get(url);
            const href = $(html).find(".td-image-wrap").first().attr("href");
            if (!href) {
                console.error("解析3xplanet搜索页失败:", url);
                return;
            }
            const detailPageHtml = await gmHttp.get(href);
            let imgUrl = $(detailPageHtml).find('img[src*="s200"]').attr("src");
            if (imgUrl) {
                imgUrl = imgUrl.replace("s200", "s0");
                return imgUrl;
            }
            console.error("解析3xplanet缩略图失败:", url);
        }
        async img_memojav(carNum2) {
            let url = `https://memojav.com/image/screenshot/${carNum2}.jpg`;
            try {
                await gmHttp.gmRequest("HEAD", url);
                return url;
            } catch (e) {
                console.error("memojav缩略图不可用:", e);
                return null;
            }
        }
    }
    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);
            }
            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);
            }
            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();
        }();
    };
}();