Sleazy Fork is available in English.

JAV-JHS

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

// ==UserScript==
// @name         JAV-JHS
// @namespace    https://sleazyfork.org/zh-CN/scripts/533695
// @version      1.0.0
// @author       fuajofkewmrw
// @description  Jav-鉴黄师 收藏,屏蔽,标记已下载,在线预览
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=javdb.com
// @match        https://javdb.com/*
// @match        https://javtrailers.com/*
// @match        https://subtitlecat.com/*
// @match        https://jable.tv/videos/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js
// @grant        GM_addStyle
// @grant        window.close
// ==/UserScript==

(t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const a=document.createElement("style");a.textContent=t,document.head.append(a)})(" .fl-btn{float:left}.fr-btn{float:right;margin-left:4px!important}.container{min-width:85%}.navbar{z-index:12345679!important}.movie-list{grid-template-columns:repeat(5,minmax(0,1fr))!important}.sub-header,#footer,.search-recent-keywords,.app-desktop-banner,body>section>div>div.video-detail>div:nth-child(5),body>section>div>div.video-detail>div:nth-child(6),h3.main-title,div.video-meta-panel>div>div:nth-child(2)>nav>div.review-buttons>div:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(3),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(1),.top-meta,.float-buttons{display:none!important}div.tabs.no-bottom,.tabs ul{border-bottom:none!important}.search-bar-container{margin-bottom:0!important}.search-bar-container .column{padding:0 12px!important}.search-bar-wrap{background-color:inherit;padding:0}.menu-box{position:fixed;right:10px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;z-index:1000;gap:6px}.menu-btn{display:inline-block;min-width:80px;padding:7px 12px;border-radius:4px;color:#fff;text-decoration:none;font-weight:700;font-size:12px;text-align:center;cursor:pointer;transition:all .3s ease;box-shadow:0 2px 5px #0000001a;text-shadow:0 1px 1px rgba(0,0,0,.2);border:none;line-height:1.3;margin:0}.menu-btn:hover{transform:translateY(-1px);box-shadow:0 3px 6px #00000026;opacity:.9}.menu-btn:active{transform:translateY(0);box-shadow:0 1px 2px #0000001a}.data-table{width:100%;border-collapse:separate;border-spacing:0;font-family:Helvetica Neue,Arial,sans-serif;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px #00000008;margin:0}.data-table thead tr{background:#f8fafc}.data-table th{padding:16px 20px;text-align:left;color:#64748b;font-weight:500;font-size:14px;text-transform:uppercase;letter-spacing:.5px;border-bottom:1px solid #e2e8f0}.data-table td{padding:14px 20px;color:#334155;font-size:15px;border-bottom:1px solid #f1f5f9}.data-table tbody tr:last-child td{border-bottom:none}.data-table tbody tr{transition:all .2s ease}.data-table tbody tr:hover{background:#f8fafc}.data-table a{display:inline-flex;align-items:center;padding:6px 14px;margin-right:10px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:500;transition:all .2s ease}.data-table a:first-child{background:#f0fdf4;color:#16a34a;border:1px solid #dcfce7}.data-table a:last-child{background:#f0f9ff;color:#0284c7;border:1px solid #e0f2fe}.data-table a:hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000000d}.data-table a:first-child:hover{background:#dcfce7}.data-table a:last-child:hover{background:#e0f2fe} ");

(function () {
    'use strict';

    var __defProp = Object.defineProperty, __publicField = (obj, key, value) => ((obj, key, value) => key in obj ? __defProp(obj, key, {
        enumerable: true,
        configurable: true,
        writable: true,
        value: value
    }) : obj[key] = value)(obj, "symbol" != typeof key ? key + "" : key, value);

    let intervalContainer = {};

    const rightClick = (element, callback) => {
        element.jquery && (element = element[0]), element ? element.addEventListener("contextmenu", (event => {
            event.preventDefault(), callback(event);
        })) : console.error("rightClick(), 找不到元素");
    }, q = (event, msg2, fun, cancelFun) => {
        let x, y;
        event ? (x = event.clientX - 120, y = event.clientY - 120) : (x = window.innerWidth / 2 - 120, 
        y = window.innerHeight / 2 - 120), layer.confirm(msg2, {
            offset: [ y, x ],
            btn: [ "屏蔽", "取消" ],
            zIndex: 99999999999
        }, (function() {
            fun(), layer.closeAll();
        }), (function() {}));
    }, http$1 = {
        alertFun: msg2 => {
            layer.msg(msg2, {
                icon: 2
            });
        },
        get(url, params = {}, headers = {}, async) {
            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);
        },
        getResource: (url, headers = {}) => new Promise(((resolve, reject) => {
            $.ajax({
                method: "GET",
                url: url,
                headers: headers,
                success: response => {
                    resolve(response);
                },
                error: error => {
                    reject(error);
                }
            });
        })),
        jqueryRequest(method, url, data = {}, params = {}, headers = {}) {
            return new Promise(((resolve, reject) => {
                $.ajax({
                    method: method,
                    url: url,
                    data: "GET" === method || "DELETE" === method ? params : data,
                    headers: headers,
                    success: response => this.handleResponse(response, resolve, reject),
                    error: error => reject(error)
                });
            }));
        },
        handleResponse(response, resolve, reject) {
            const data = response;
            if (200 === data.code) resolve(data); else if (401 === data.code) window.location.reload(); else {
                const errorMessage = data.msg || "请求失败";
                this.alertFun ? this.alertFun(errorMessage) : console.error(errorMessage), reject(new Error(errorMessage));
            }
        }
    };

    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 = [];
                return hotkeyString.forEach((hotkey => {
                    if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误");
                    let id = this.recordHotkey(hotkey, callback, keyupCallback);
                    id_list.push(id);
                })), 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);
            return this.registerHotKeyMap.set(id, {
                hotkeyString: hotkeyString,
                callback: callback,
                keyupCallback: keyupCallback
            }), 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, event) {
            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 ? event.metaKey : event.ctrlKey) === ctrl && event.shiftKey === shift && event.altKey === alt && event.key.toLowerCase() === key;
        }
    };

    __publicField(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac")), 
    __publicField(_HotkeyManager, "registerHotKeyMap", new Map), __publicField(_HotkeyManager, "handleKeydown", (event => {
        for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
            let hotkeyString = data.hotkeyString, callback = data.callback;
            _HotkeyManager.judgeHotkey(hotkeyString, event) && (event.preventDefault(), callback(event));
        }
    })), __publicField(_HotkeyManager, "handleKeyup", (event => {
        for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
            let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback;
            keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event) && (event.preventDefault(), 
            keyupCallback(event)));
        }
    }));

    let HotkeyManager = _HotkeyManager;

    document.addEventListener("keydown", (event => {
        HotkeyManager.handleKeydown(event);
    })), document.addEventListener("keyup", (event => {
        HotkeyManager.handleKeyup(event);
    })), function(url) {
        let tag;
        url.indexOf("css") >= 0 ? (tag = document.createElement("link"), tag.setAttribute("rel", "stylesheet"), 
        tag.href = url) : (tag = document.createElement("script"), tag.setAttribute("type", "text/javascript"), 
        tag.src = url), document.documentElement.appendChild(tag);
    }("https://cdn.jsdelivr.net/npm/[email protected]/layer.min.css"), localStorage.storageType || (localStorage.storageType = "local");

    const baseUrl = "http://127.0.0.1:7890", javDbHandler = {
        filterList: [],
        favoriteList: [],
        filterKeywordList: [],
        filterActorList: [],
        hasHandleList: [],
        isDetailPage: window.location.href.includes("javdb") && window.location.href.includes("/v"),
        isListPage: window.location.href.includes("javdb") && !window.location.href.includes("/v"),
        answerCount: 1,
        paging: false,
        allowRepeatDown: "local" === localStorage.storageType,
        reviewKeyword: [ "像", "是你" ],
        storageManager: "local" === localStorage.storageType ? new class {
            findData(carNum, dataList) {
                return dataList.find((item => item.carNum === carNum));
            }
            async getData() {
                const storedData = localStorage.getItem("appData");
                storedData || localStorage.setItem("appData", JSON.stringify({
                    filterKeywordList: [],
                    filterActorList: [],
                    dataList: []
                }));
                const json_data = storedData ? JSON.parse(storedData) : {
                    dataList: [],
                    filterList: [],
                    favoriteList: [],
                    hasDownList: [],
                    filterKeywordList: [],
                    filterActorList: []
                }, filterList = [], favoriteList = [], hasDownList = [];
                for (const item of json_data.dataList) item.filter ? filterList.push(item) : item.hasDown ? hasDownList.push(item) : item.favorite && favoriteList.push(item);
                return json_data.filterList = filterList, json_data.favoriteList = favoriteList, 
                json_data.hasDownList = hasDownList, json_data;
            }
            async saveKeyData(key, value) {}
            async changeData(carNum, url, actress, actionType) {
                url.includes("http") || (url = window.location.origin + url);
                const storedData = localStorage.getItem("appData"), json_data = JSON.parse(storedData);
                let dataList = json_data.dataList, data = this.findData(carNum, dataList);
                if (!data) {
                    const dateTimeOptions = {
                        year: "numeric",
                        month: "2-digit",
                        day: "2-digit",
                        hour: "2-digit",
                        minute: "2-digit",
                        second: "2-digit",
                        hour12: false
                    };
                    data = {
                        carNum: carNum,
                        url: url,
                        actress: actress,
                        createDate: (new Date).toLocaleString("zh-CN", dateTimeOptions).replaceAll("/", "-"),
                        filter: false,
                        favorite: false,
                        hasDown: false
                    }, dataList.push(data);
                }
                if ("filter" === actionType) {
                    if (data.filter) throw new Error(carNum + " 已在屏蔽列表中");
                    data.filter = true, data.favorite = false, data.hasDown = false;
                } else if ("favorite" === actionType) {
                    if (data.favorite) throw new Error(carNum + " 已在收藏列表中");
                    data.filter = false, data.favorite = true, data.hasDown = false;
                } else {
                    if ("hasDown" !== actionType) throw new Error("actionType错误");
                    data.filter = true, data.favorite = true, data.hasDown = true;
                }
                localStorage.setItem("appData", JSON.stringify(json_data));
            }
            async saveFilterActor(keyword) {
                const storedData = localStorage.getItem("appData"), json_data = JSON.parse(storedData);
                if (json_data.filterActorList.includes(keyword)) throw new Error(keyword + " 已存在");
                json_data.filterActorList.push(keyword), localStorage.setItem("appData", JSON.stringify(json_data));
            }
            async saveFilterKeyword(keyword) {
                const storedData = localStorage.getItem("appData"), json_data = JSON.parse(storedData);
                if (json_data.filterKeywordList.includes(keyword)) throw new Error(keyword + " 已存在");
                json_data.filterKeywordList.push(keyword), localStorage.setItem("appData", JSON.stringify(json_data));
            }
            async removeData(carNum) {
                const storedData = localStorage.getItem("appData"), json_data = JSON.parse(storedData);
                let dataList = json_data.dataList;
                if (!this.findData(carNum, dataList)) throw new Error("未找到该番号信息:" + carNum);
                dataList = dataList.filter((item => item.carNum !== carNum)), json_data.dataList = dataList, 
                localStorage.setItem("appData", JSON.stringify(json_data));
            }
        } : new class {
            constructor(baseUrl2) {
                this.baseUrl = baseUrl2;
            }
            async getData() {
                const res = await http$1.get(this.baseUrl + "/getData");
                return {
                    filterList: res.data.filterList,
                    favoriteList: res.data.favoriteList,
                    filterKeywordList: res.data.filterKeywordList,
                    filterActorList: res.data.filterActorList,
                    data: res.data
                };
            }
            async saveKeyData(key, value) {
                return await http$1.post(this.baseUrl + "/saveKeyData", {
                    key: key,
                    value: value
                });
            }
            async changeData(carNum, url, actress, actionType) {
                return url.includes("http") || (url = window.location.origin + url), await http$1.post(this.baseUrl + "/changeData", {
                    carNum: carNum,
                    url: url,
                    actress: actress,
                    actionType: actionType
                });
            }
            async saveFilterActor(keyword) {
                return await http$1.post(this.baseUrl + "/saveFilterActor", {
                    keyword: keyword
                });
            }
            async saveFilterKeyword(keyword) {
                return await http$1.post(this.baseUrl + "/saveFilterKeyword", {
                    keyword: keyword
                });
            }
            async removeData(carNum) {
                return await http$1.post(this.baseUrl + "/removeData", {
                    carNum: carNum
                });
            }
        }(baseUrl),
        async run() {
            const data = await this.storageManager.getData();
            this.filterList = data.filterList, this.favoriteList = data.favoriteList, this.filterKeywordList = data.filterKeywordList, 
            this.filterActorList = data.filterActorList, this.filterMovieList(), this.checkFilterActor(), 
            this.handlePaging();
        },
        handleListPage() {
            this.isListPage && (this.foldCategory(), this.createMenuBtn(), this.changeAutoPage(), 
            this.handleSearch());
        },
        foldCategory() {
            let tabs = $(".tabs ul");
            if (0 === tabs.length) return;
            let isFolded = "y" === localStorage.getItem("foldCategory");
            const [text, icon] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
            tabs.append(`\n        <li class="is-active" id="foldCategoryBtn">\n            <a class="menu-btn" style="background-color:#7bc73b !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n                <span>${text}</span>\n                <i style="margin-left: 10px" class="${icon}"></i>\n            </a>\n        </li>\n    `);
            const $tags = $("#tags");
            $tags[isFolded ? "hide" : "show"](), $("#foldCategoryBtn").on("click", (event => {
                event.preventDefault(), isFolded = !isFolded, localStorage.setItem("foldCategory", isFolded ? "y" : "n");
                const [newText, newIcon] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
                $("#foldCategoryBtn").find("span").text(newText).end().find("i").attr("class", newIcon), 
                $tags[isFolded ? "hide" : "show"]();
            }));
            let checkTagStr = $("#tags dl div.tag.is-info").map((function() {
                return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
            })).get().join(" ");
            tabs.append(`<li  style="margin-left: 50px;float: right"><span>${checkTagStr}</span></li>`);
        },
        createMenuBtn() {
            let $tags = $("#tags");
            const tags = $tags.length ? $tags : $(".tabs");
            if (!tags.length) return;
            const buttons = [ {
                id: "wait-check-btn",
                color: "#dcc45b",
                text: "打开待鉴定",
                action: event => this.openWaitCheck(event)
            }, {
                id: "wait-down-btn",
                color: "#7cb7e8",
                text: "打开待下载",
                action: event => this.openFavorite(event)
            }, {
                id: "archive-btn",
                color: "#d7bf50",
                disable: "local" === localStorage.storageType,
                text: "归档",
                action: event => this.archiveFile(event)
            }, {
                id: "auto-play-btn",
                color: "yes" === localStorage.autoPlay ? "#dc4c5e" : "#65ced2",
                text: "yes" === localStorage.autoPlay ? "关闭自动播放" : "开启自动播放",
                action: event => this.changeAutoPlay(event)
            }, {
                id: "check-subtitle-btn",
                color: "#b9dc4e",
                disable: "local" === localStorage.storageType,
                text: "检查字幕",
                action: event => this.checkSubTitle(event)
            }, {
                id: "historyBtn",
                color: "#aade66",
                text: "历史列表",
                action: event => this.openHistory(event)
            } ], buttonsHtml = `\n            <div class="menu-box">\n                ${buttons.map((btn => btn.disable ? "" : `\n                    <a id="${btn.id}" class="menu-btn" style="background-color:${btn.color} !important;">\n                        <span>${btn.text}</span>\n                    </a>\n                `)).join("")}\n            </div>\n        `;
            tags.after(buttonsHtml), buttons.forEach((btn => {
                btn.action && $(`#${btn.id}`).on("click", (event => {
                    btn.action(event);
                }));
            }));
        },
        changeAutoPage() {
            let initText = "yes" === localStorage.getItem("autoPage") ? "关闭翻页" : "开启翻页";
            $(".pagination").prepend(`<a class='pagination-previous' id='auto-page'>${initText}</a>`), 
            $("#auto-page").on("click", (event => {
                event.preventDefault(), "yes" === localStorage.getItem("autoPage") ? (localStorage.setItem("autoPage", "no"), 
                $("#auto-page").html("开启翻页")) : (localStorage.setItem("autoPage", "yes"), $("#auto-page").html("关闭翻页"), 
                handlePaging());
            }));
        },
        handleSearch() {
            $(".search-input").html('<input id="search-keyword" class="input is-medium" data-type="all" type="text" value="" placeholder="輸入影片番號,演員名等關鍵字進行檢索">'), 
            $("#search-bar-container").prependTo("#tags"), $(".search-submit").html('<button type="button" id="search-btn" class="button is-medium is-info">檢索</button>'), 
            $("#search-btn").on("click", (event => {
                let keyword = $("#search-keyword").val(), searchCurrentType = $("#search-type option:selected").val();
                "" !== keyword && this.openPage("/search?q=" + keyword + "&f=" + searchCurrentType, "搜索", false, event);
            })), $("#search-keyword").on("paste", (event => {
                setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            })).on("keypress", (event => {
                "Enter" === event.key && setTimeout((() => {
                    $("#search-btn").click();
                }), 0);
            }));
        },
        filterMovieList() {
            if (window.location.href.includes("search")) return;
            let movieList = $(".movie-list .item").toArray();
            const favoriteCarNums = this.favoriteList.map((item => item.carNum));
            movieList.forEach((ele => {
                let $box = $(ele), aLink = $box.find("a"), aHref = aLink.attr("href"), aTitle = aLink.attr("title"), carNum = $box.find(".video-title").find("strong").text();
                const hideKey = `${carNum}-hide`, tagKey = `${carNum}-tag`, clickKey = `${carNum}-click`;
                if ((this.filterList.some((item => item.carNum === carNum)) || this.filterKeywordList.some((keyword => aTitle.includes(keyword) || carNum.includes(keyword)))) && !this.hasHandleList.includes(hideKey)) return $box.hide(), 
                void this.hasHandleList.push(hideKey);
                favoriteCarNums.includes(carNum) && !this.hasHandleList.includes(tagKey) && ($box.find(".tags").append('<span class="tag is-success" style="margin-right: 5px">待下载</span>'), 
                this.hasHandleList.push(tagKey)), this.hasHandleList.includes(clickKey) || ($box.on("click", (event => {
                    event.preventDefault(), this.openPage(aHref, carNum, false, event);
                })), rightClick($box.find("img"), (event => {
                    q(event, `是否屏蔽番号${carNum}?`, (() => {
                        this.changeData(carNum, aHref, "", "filter").then((res => layer.msg("操作成功", {
                            icon: 1
                        })));
                    }));
                })), this.hasHandleList.push(clickKey));
            })), $("#wait-down-btn span").text(`打开待下载(${this.favoriteList.length})`);
        },
        checkFilterActor() {
            if (!this.isDetailPage) return;
            let actors = this.getPageInfo().actors;
            this.filterActorList.forEach((item => {
                actors.indexOf(item) > -1 && (this.answerCount++, q(null, "存在xxx演员, 是否屏蔽?", (() => {
                    this.filterOne(null, true);
                })));
            }));
        },
        handlePaging() {
            if (!this.isListPage) return;
            if (this.paging) return;
            let needPaging = true;
            if ($(".movie-list .item:visible").each(((i, el) => {
                0 === $(el).find("span:contains('待下载')").length && (needPaging = false);
            })), !needPaging) return;
            if ("yes" !== localStorage.getItem("autoPage")) return;
            let nextBtn = $(".pagination-next");
            0 !== nextBtn.length && (this.paging = true, layer.msg("下一页....", {
                time: 500,
                end: () => {
                    nextBtn[0].click();
                }
            }));
        },
        refresh() {
            localStorage.setItem("refresh", Date.now().toString()), this.isListPage && this.run();
        },
        bindHotkey() {
            const handlers = {
                a: () => {
                    this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
                },
                s: () => this.favoriteOne(null),
                z: () => this.speedVideo()
            }, registerHotkey = (key, handler) => {
                HotkeyManager.registerHotkey(key, (() => {
                    this.isDetailPage ? handler() : (message => {
                        const childIframe = $(".layui-layer-content iframe");
                        0 !== childIframe.length && childIframe[0].contentWindow.postMessage(message, "*");
                    })(key);
                }));
            };
            this.isDetailPage && window.addEventListener("message", (event => {
                handlers[event.data] && handlers[event.data]();
            })), Object.entries(handlers).forEach((([key, handler]) => {
                registerHotkey(key, handler);
            }));
        },
        speedVideo() {
            const iframe = $('iframe[id^="layui-layer-iframe"]');
            0 === iframe.length ? $("#preview-video-btn").click() : iframe[0].contentWindow.postMessage("speedVideo", "*");
        },
        changeAutoPlay(val) {
            let autoPlay = localStorage.getItem("autoPlay");
            autoPlay !== val && ("yes" === autoPlay ? localStorage.setItem("autoPlay", "no") : localStorage.setItem("autoPlay", "yes"), 
            $("#auto-play-btn").css("background-color", "yes" === localStorage.getItem("autoPlay") ? "#dc4c5e" : "#65ced2"), 
            $("#auto-play-btn span").text("yes" === localStorage.getItem("autoPlay") ? "关闭自动播放" : "开启自动播放"));
        },
        openWaitCheck() {
            this.changeAutoPlay("yes");
            let count = 0;
            this.isListPage && $(".movie-list .item:visible").each(((i, el) => {
                if (count >= 8) return false;
                if (0 === $(el).find("span:contains('待下载')").length) {
                    const link = $(el).find("a").attr("href");
                    link && (window.open(link), count++);
                }
            }));
        },
        openFavorite() {
            this.changeAutoPlay("no");
            for (let i = 0; i < 10; i++) {
                if (i >= this.favoriteList.length) return;
                window.open(this.favoriteList[i].url);
            }
        },
        archiveFile() {
            http.post(baseUrl + "/archiveFile").then((res => {
                let successMsgList = res.successMsgList, errorMsgList = res.errorMsgList;
                msg.list(successMsgList, errorMsgList), successMsgList.length || errorMsgList.length || layer.msg("没有可归档文件");
            }));
        },
        checkSubTitle() {
            http.get(baseUrl + "/checkSubTitle").then((res => {
                let dataList = res.data;
                if (0 === dataList.length) return void layer.msg("视频字幕完整");
                let tableHtml = '<table class="data-table">';
                tableHtml += "<thead><tr>", tableHtml += "<th>番号</th>", tableHtml += "<th>文件路径</th>", 
                tableHtml += "<th>操作</th>", tableHtml += "</tr></thead>", tableHtml += "<tbody>", 
                $.each(dataList, (function(index, item) {
                    tableHtml += "<tr>", tableHtml += "<td>" + item.carNum + "</td>", tableHtml += "<td>" + item.filePath + "</td>", 
                    tableHtml += `<td>\n                            <a href="${"https://subtitlecat.com/index.php?search=" + item.carNum}" target="_blank">搜索字幕</a>\n                            <a href="${item.url}"  target="_blank">详情页</a>\n                         </td>`, 
                    tableHtml += "</tr>";
                })), tableHtml += "</tbody>", tableHtml += "</table>", layer.open({
                    type: 1,
                    title: "检查字幕",
                    content: tableHtml,
                    area: [ "1000px", "400px" ]
                });
            }));
        },
        openHistory(event) {
            this.storageManager.getData().then((res => {
                const dataList = res.dataList || [];
                dataList.reverse();
                let filteredData = [ ...dataList ];
                const statusConfig = {
                    filtered: {
                        text: "已屏蔽",
                        color: "#ec4949",
                        condition: item => !item.hasDown && !item.favorite
                    },
                    favorite: {
                        text: "已收藏",
                        color: "#50adb9",
                        condition: item => item.favorite && !item.hasDown
                    },
                    hasDown: {
                        text: "已下载",
                        color: "#8ebd6e",
                        condition: item => item.hasDown
                    }
                }, generateTableRow = item => {
                    let status = statusConfig.filtered;
                    return item.hasDown ? status = statusConfig.hasDown : item.favorite && (status = statusConfig.favorite), 
                    `\n                <tr>\n                    <td>${item.carNum}</td>\n                        <td>${item.actress ? item.actress : ""}</td>\n                        <td>${item.createDate ? item.createDate : ""}</td>\n                        <td style="color:${status.color}">${status.text}</td>\n                    <td>\n                        <a class="action-remove" data-car-num="${item.carNum}" data-url="${item.url}">移除</a>\n                        <a class="action-detail" data-car-num="${item.carNum}" data-url="${item.url}">详情页</a>\n                    </td>\n                </tr>\n            `;
                }, generateTableContent = data => `\n                <table class="data-table">\n                    <thead>\n                        <tr>\n                            <th>番号</th>\n                            <th width="300px">演员</th>\n                            <th>创建日期</th>\n                            <th>状态</th>\n                            <th>操作</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        ${data.map(generateTableRow).join("")}\n                    </tbody>\n                </table>\n            `, handleButtonClick = (layero, action) => {
                    filteredData = (action => "all" === action ? [ ...dataList ] : dataList.filter(statusConfig[action].condition))(action), 
                    $(layero).find(".data-table").replaceWith(generateTableContent(filteredData)), $(layero).find(".history-btn").removeClass("active").filter(`[data-action="${action}"]`).addClass("active");
                }, handleActionClick = (type, data, event2) => {
                    "详情" === type && this.openPage(data.url), "移除" === type && q(event2, `是否移除${data.carNum}?`, (() => {
                        this.storageManager.removeData(data.carNum).then((res2 => {
                            let movieList = $(".movie-list .item").toArray();
                            for (let i = 0; i < movieList.length; i++) {
                                let box = $(movieList[i]), carNum = box.find(".video-title").find("strong").text();
                                if (carNum === data.carNum) {
                                    box.show();
                                    const hideKey = `${carNum}-hide`;
                                    this.hasHandleList = this.hasHandleList.filter((item => item !== hideKey));
                                    break;
                                }
                            }
                            layer.close(layerIndex), this.openHistory();
                        }));
                    }));
                };
                let layerIndex = layer.open({
                    type: 1,
                    title: "历史列表",
                    content: `\n                <div style="margin: 10px">\n                    ${[ {
                    action: "filtered",
                    text: "已屏蔽",
                    color: "#ec4949"
                }, {
                    action: "favorite",
                    text: "已收藏",
                    color: "#50adb9"
                }, {
                    action: "hasDown",
                    text: "已下载",
                    color: "#8ebd6e"
                }, {
                    action: "all",
                    text: "所有",
                    color: "#d3c8a5"
                } ].map((btn => `\n                <a class="menu-btn history-btn" data-action="${btn.action}" style="background-color:${btn.color} !important;">\n                   ${btn.text}\n                </a>\n            `)).join("")}\n                </div>\n                ${generateTableContent(filteredData)}\n            `,
                    area: [ "60%", "80%" ],
                    success: (layero, index) => {
                        $(layero).on("click", ".history-btn", (function() {
                            handleButtonClick(layero, $(this).data("action"));
                        })).on("click", ".action-remove", (function(e) {
                            e.stopPropagation(), handleActionClick("移除", $(this).data(), e);
                        })).on("click", ".action-detail", (function(e) {
                            e.stopPropagation(), handleActionClick("详情", $(this).data(), e);
                        }));
                    },
                    end: () => {
                        this.refresh();
                    }
                });
            }));
        },
        changeStorage() {
            "local" === localStorage.getItem("storageType") ? localStorage.setItem("storageType", "http") : localStorage.setItem("storageType", "local"), 
            $("#storageBtn span").text("local" === localStorage.getItem("storageType") ? "切换为远程存储" : "切换为浏览器存储"), 
            window.location.reload();
        },
        handleDetailPage() {
            this.isDetailPage && ($(".main-nav").hide(), $("html").css("paddingTop", "0"), $("#search-bar-container").hide(), 
            this.getReviews(), this.createMagnetBtn(), this.highlightMagnets(), this.handlePreviewVideo(), 
            this.checkHasDown(), this.handleRightClickFilterKeyword());
        },
        getReviews() {
            const parts = window.location.href.split("/"), movieId = parts[parts.length - 1].split("#")[0];
            let $magnets = $("#magnets-content");
            $magnets.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>'), 
            $.ajax({
                method: "get",
                url: `https://api.ffaoa.com/api/v1/movies/${movieId}/reviews`,
                data: {
                    page: 1,
                    sort_by: "hotly",
                    limit: 10
                },
                headers: {
                    jdSignature: function() {
                        const curr = Math.floor(Date.now() / 1e3);
                        if (curr - (localStorage.getItem("TS") || 0) <= 20) return localStorage.getItem("SIGN");
                        const sign = `${curr}.lpw6vgqzsp.${md5(`${curr}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
                        return localStorage.setItem("TS", curr), localStorage.setItem("SIGN", sign), sign;
                    }()
                },
                success: res => {
                    $("#reviewsLoading").remove();
                    let dataList = res.data.reviews;
                    0 === dataList.length && $magnets.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'), 
                    $magnets.append('<hr style="border: 0; height: 2px; background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>'), 
                    dataList.forEach((item => {
                        let isReviewKeyword = false;
                        for (let i = 0; i < this.reviewKeyword.length; i++) if (item.content.indexOf(this.reviewKeyword[i]) > -1) {
                            isReviewKeyword = true;
                            break;
                        }
                        if (isReviewKeyword) return;
                        let starsHtml = "";
                        for (let i = 0; i < item.score; i++) starsHtml += '<i class="icon-star"></i>';
                        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;">\n                        ${item.username} &nbsp;&nbsp; <span class="score-stars">${starsHtml}</span> <span class="time">${item.created_at.replace("T", " ").replace(".000Z", "")}</span> &nbsp;&nbsp; 点赞:${item.likes_count}\n                        <p style="margin-top: 5px;">${item.content}</p>\n                    </div>\n                `;
                        $magnets.append(commentHtml);
                    }));
                }
            });
        },
        createMagnetBtn() {
            const pageInfo = this.getPageInfo(), carNum = pageInfo.carNum, buttons = [ {
                id: "favoriteBtn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#25b1dc"><span>收藏(s)</span></a>`;
                },
                action: event => this.favoriteOne()
            }, {
                id: "filterBtn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#de3333"><span>屏蔽(a)</span></a>`;
                },
                action: event => this.filterOne(event)
            }, {
                id: "hasDownBtn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>`;
                },
                action: event => this.changeData(pageInfo.carNum, pageInfo.url, pageInfo.actress, "hasDown").then((res => this.closePage()))
            }, {
                id: "allow-repeat-down",
                disable: "local" === localStorage.storageType,
                html: function() {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#b8d747"><span>关闭重复下载检验</span></a>`;
                },
                action: event => {
                    this.allowRepeatDown = !this.allowRepeatDown, $("#allow-repeat-down span").text(this.allowRepeatDown ? "开启重复下载检验" : "关闭重复下载检验");
                }
            }, {
                id: "enable-magnets-filter",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn" style="background-color:#c2bd4c"><span>关闭磁力过滤</span></a>`;
                },
                action: event => {
                    $("#magnets-content .item").toArray().forEach((el => $(el).show()));
                }
            }, {
                id: "jable-video-btn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>`;
                },
                action: event => this.openPage(`https://jable.tv/videos/${carNum}/`, carNum, false, event)
            }, {
                id: "missav-video-btn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>`;
                },
                action: event => window.open(`https://missav.ws/search/${carNum}`, "_blank")
            }, {
                id: "preview-video-btn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>预览视频(z)</span></a>`;
                },
                action: event => this.openPage(`https://javtrailers.com/video/${carNum.toLowerCase().replace("-", "00")}`, carNum, false, event)
            }, {
                id: "search-subtitle-btn",
                html: function() {
                    return `<a id="${this.id}" class="menu-btn fr-btn" style="background-color: #2196F3"><span>搜索字幕</span></a>`;
                },
                action: event => this.openPage(`https://subtitlecat.com/index.php?search=${carNum}`, carNum, false, event)
            } ], buttonsHtml = `\n            <div style="transform: translateY(-50%);">\n                ${buttons.map((button => button.disable ? "" : button.html())).join("\n")}\n            </div>\n        `;
            $(".tabs").after(buttonsHtml), buttons.forEach((({id: id, action: action}) => {
                $(`#${id}`).on("click", action);
            }));
        },
        highlightMagnets() {
            let magnetNameList = $("#magnets-content .name").toArray(), has4k_C_UC = false;
            magnetNameList.forEach((el => {
                let item = $(el), text = item.text().toLowerCase();
                text.indexOf("4k") > -1 && item.css("color", "#f40"), (text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1) && (has4k_C_UC = true);
            })), has4k_C_UC && magnetNameList.forEach((el => {
                let item = $(el), text = item.text().toLowerCase();
                text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1 || item.parent().parent().parent().hide();
            }));
        },
        handlePreviewVideo() {
            $(".preview-video-container").on("click", (event => {
                event.preventDefault(), $("#preview-video-btn").click();
            })), "yes" === localStorage.getItem("autoPlay") && $("#preview-video-btn").click();
        },
        checkHasDown() {
            let carNum = this.getPageInfo().carNum;
            $("#magnets-content a, #magnets-content button").on("click", (event => {
                this.allowRepeatDown || $.ajax({
                    method: "GET",
                    url: baseUrl + "/checkHasDown?carNum=" + carNum,
                    async: false,
                    success: res => {
                        "yes" === res.data && (event.preventDefault(), event.stopPropagation(), layer.msg(res.msg, {
                            icon: 2
                        }));
                    }
                });
            }));
        },
        handleRightClickFilterKeyword() {
            rightClick($("h2"), (event => {
                const selectedText = window.getSelection().toString();
                if (selectedText) {
                    let tempEvent = {
                        clientX: event.clientX,
                        clientY: event.clientY + 120
                    };
                    q(tempEvent, `是否屏蔽关键词${selectedText}?`, (() => {
                        this.saveFilterKeyword(selectedText).then((r => {
                            console.log(123), this.closePage();
                        }));
                    }));
                }
            })), $(".male").prev().toArray().forEach((el => {
                rightClick($(el), (event => {
                    let text = $(el).text().trim();
                    q(event, `是否屏蔽演员${text}?`, (() => {
                        this.saveFilterActor(text).then((res => {
                            this.filterOne(null, true);
                        }));
                    }));
                }));
            })), rightClick($(".preview-images"), (event => {
                let pageInfo = this.getPageInfo();
                q(event, `是否屏蔽${pageInfo.carNum}?`, (() => {
                    this.changeData(pageInfo.carNum, pageInfo.url, "", "filter").then((res => {
                        this.closePage();
                    }));
                }));
            })), rightClick($(".column-video-cover"), (event => {
                let pageInfo = this.getPageInfo();
                q(event, `是否屏蔽${pageInfo.carNum}?`, (() => {
                    this.changeData(pageInfo.carNum, pageInfo.url, "", "filter").then((res => {
                        this.closePage();
                    }));
                }));
            }));
        },
        getPageInfo: () => ({
            carNum: $('a[title="複製番號"]').attr("data-clipboard-text"),
            url: window.location.href.split("#")[0],
            actress: $(".female").prev().map(((i, el) => $(el).text())).get().join(" "),
            actors: $(".male").prev().map(((i, el) => $(el).text())).get().join(" ")
        }),
        filterOne(event, noAlert) {
            event && event.preventDefault();
            let pageInfo = this.getPageInfo();
            noAlert ? this.changeData(pageInfo.carNum, pageInfo.url, pageInfo.actress, "filter").then((res => this.closePage())) : q(event, `是否屏蔽${pageInfo.carNum}?`, (() => {
                this.changeData(pageInfo.carNum, pageInfo.url, pageInfo.actress, "filter").then((res => this.closePage()));
            }));
        },
        favoriteOne() {
            let pageInfo = this.getPageInfo();
            this.changeData(pageInfo.carNum, pageInfo.url, pageInfo.actress, "favorite").then((res => this.closePage()));
        },
        openPage(url, title, shadeClose, event) {
            shadeClose || (shadeClose = true), event && event.ctrlKey ? window.open(url) : layer.open({
                type: 2,
                title: title,
                content: url,
                shadeClose: shadeClose,
                area: [ "80%", "90%" ],
                isOutAnim: false,
                anim: -1
            });
        },
        closePage() {
            layer.msg("操作成功", {
                icon: 1
            });
            [ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
                parent.document.querySelectorAll(selector).forEach((function(el) {
                    el.parentNode.removeChild(el);
                }));
            })), window.close();
        },
        async changeData(carNum, url, actress, actionType) {
            await this.storageManager.changeData(carNum, url, actress, actionType), this.refresh();
        },
        async saveFilterKeyword(keyword) {
            await this.storageManager.saveFilterKeyword(keyword), this.refresh();
        },
        async saveFilterActor(keyword) {
            await this.storageManager.saveFilterActor(keyword), this.refresh();
        }
    };

    function handleJavTrailers() {
        let hasBand = false;
        function handlePlayJavTrailers() {
            hasBand || ((condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = true) => {
                let run = false;
                const uuid = Math.random(), start2 = (new Date).getTime();
                intervalContainer[uuid] = setInterval((() => {
                    (new Date).getTime() - start2 > timeout && (console.warn("loopDetector timeout!", condition, after), 
                    run = runWhenTimeout), (condition() || run) && (clearInterval(intervalContainer[uuid]), 
                    after && after(), delete intervalContainer[uuid]);
                }), detectInterval);
            })((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
                setTimeout((() => {
                    hasBand = true;
                    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);
            }));
        }
        if ($("h1:contains('Page not found')").length > 0) {
            let keyword = window.location.href.split("video/")[1].toLowerCase().replace("00", "-");
            return void (window.location.href = "https://javtrailers.com/search/" + keyword);
        }
        let findList = $(".videos-list .video-link").toArray();
        if (findList.length) {
            const keyword = window.location.href.split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword)));
            if (matchedLink) return void (window.location.href = $(matchedLink).attr("href"));
        }
        handlePlayJavTrailers(), $("#videoPlayerContainer").on("click", handlePlayJavTrailers), 
        window.addEventListener("message", (event => {
            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", "*")));
    }

    !function() {
        let hostname = window.location.hostname;
        hostname.includes("javdb") ? (javDbHandler.run().then((r => {})), javDbHandler.handleDetailPage(), 
        javDbHandler.handleListPage(), window.addEventListener("storage", (event => {
            "refresh" === event.key && javDbHandler.refresh();
        })), javDbHandler.bindHotkey()) : hostname.includes("javtrailers") ? handleJavTrailers() : hostname.includes("subtitlecat") ? function() {
            $(".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();
            }));
        }() : hostname.includes("jable") && ($("#player")[0].play(), $('button[data-plyr="fullscreen"]').click(), 
        HotkeyManager.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), 
        HotkeyManager.registerHotkey("s", (() => window.parent.postMessage("s", "*"))));
    }();

})();