// ==UserScript==
// @name JAV-JHS
// @namespace https://sleazyfork.org/zh-CN/scripts/533695
// @version 1.0.1
// @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.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 i = document.createElement("style");
i.textContent = t, document.head.append(i)
})(" .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} ");
(function (a, layuiLayer, n) {
'use strict';
var t = Object.defineProperty, e = (e, a, n) => ((e, a, n) => a in e ? t(e, a, {
enumerable: true,
configurable: true,
writable: true,
value: n
}) : e[a] = n)(e, "symbol" != typeof a ? a + "" : a, n);
class Utils {
constructor() {
return e(this, "insertStyle", (t => {
t && (-1 === t.indexOf("<style>") && (t = "<style>" + t + "</style>"), $("head").append(t));
})), e(this, "msg", {
success(t, e = {}) {
const a = Array.isArray(t) ? t : [t];
this.list(a, [], [], e);
},
error(t, e = {}) {
const a = Array.isArray(t) ? t : [t];
this.list([], a, [], e);
},
info(t, e = {}) {
const a = Array.isArray(t) ? t : [t];
this.list([], [], a, e);
},
list(t = [], e = [], a = [], n = {}) {
let i = "";
const r = (t, e) => {
t && 0 !== t.length && (i += `<div style="color:${e.color};margin-bottom:10px;">`,
t.forEach((t => i += `${e.prefix} ${t}<br/>`)), i += "</div>");
};
r(t, {
prefix: "✓",
color: "#76d25e"
}), r(e, {
prefix: "✗",
color: "#dc4b64"
}), r(a, {
prefix: "ℹ",
color: "#d7c88b"
});
let s = 0;
e.length > 0 && 0 === t.length && 0 === a.length ? s = 2 : t.length > 0 && 0 === e.length && (s = 1),
layer.msg(i, {
icon: s,
time: 2e3
});
}
}), e(this, "http", {
alertFun: t => {
layer.msg(t, {
icon: 2
});
},
get(t, e = {}, a = {}, n) {
return this.jqueryRequest("GET", t, null, e, a, n);
},
post(t, e = {}, a = {}, n) {
return this.jqueryRequest("POST", t, e, null, a, n);
},
put(t, e = {}, a = {}, n) {
return this.jqueryRequest("PUT", t, e, null, a, n);
},
del(t, e = {}, a = {}, n) {
return this.jqueryRequest("DELETE", t, null, e, a, n);
},
jqueryRequest(t, e, a = {}, n = {}, i = {}, r = true) {
return "POST" === t && (i = {
"Content-Type": "application/json",
...i
}), new Promise(((s, o) => {
$.ajax({
method: t,
url: e,
async: r,
data: "GET" === t || "DELETE" === t ? n : JSON.stringify(a),
headers: i,
success: t => this.handleResponse(t, s, o),
error: t => o(t)
});
}));
},
handleResponse(t, e, a) {
const n = t;
if (200 === n.code) e(n); else if (401 === n.code) window.location.reload(); else {
const t = n.msg || "请求失败";
this.alertFun ? this.alertFun(t) : console.error(t), a(new Error(t));
}
}
}), Utils.instance || (Utils.instance = this, this.intervalContainer = {}), Utils.instance;
}
importResource(t) {
let e;
t.indexOf("css") >= 0 ? (e = document.createElement("link"), e.setAttribute("rel", "stylesheet"),
e.href = t) : (e = document.createElement("script"), e.setAttribute("type", "text/javascript"),
e.src = t), document.documentElement.appendChild(e);
}
loopDetector(t, e, a = 20, n = 1e4, i = true) {
let r = false;
const s = Math.random(), o = (new Date).getTime();
this.intervalContainer[s] = setInterval((() => {
(new Date).getTime() - o > n && (console.warn("loopDetector timeout!", t, e), r = i),
(t() || r) && (clearInterval(this.intervalContainer[s]), e && e(), delete this.intervalContainer[s]);
}), a);
}
rightClick(t, e) {
t.jquery && (t = t[0]), t ? t.addEventListener("contextmenu", (t => {
t.preventDefault(), e(t);
})) : console.error("rightClick(), 找不到元素");
}
q(t, e, a, n) {
let i, r;
t ? (i = t.clientX - 120, r = t.clientY - 120) : (i = window.innerWidth / 2 - 120,
r = window.innerHeight / 2 - 120), layer.confirm(e, {
offset: [r, i],
btn: ["屏蔽", "取消"],
zIndex: 99999999999
}, (function () {
a(), layer.closeAll();
}), (function () {
n && n();
}));
}
getNowStr(t = "-", e = ":") {
const a = new Date, n = a.getFullYear(), i = String(a.getMonth() + 1).padStart(2, "0"), r = String(a.getDate()).padStart(2, "0"), s = String(a.getHours()).padStart(2, "0"), o = String(a.getMinutes()).padStart(2, "0"), l = String(a.getSeconds()).padStart(2, "0");
return `${[n, i, r].join(t)} ${[s, o, l].join(e)}`;
}
}
const i = new Utils;
class LocalStorageManager {
constructor() {
return LocalStorageManager.instance || (LocalStorageManager.instance = this), LocalStorageManager.instance;
}
findData(t, e) {
return e.find((e => e.carNum === t));
}
async getData() {
const t = {
dataList: [],
filterKeywordList: [],
filterActorList: []
}, e = localStorage.appData;
e || (localStorage.appData = JSON.stringify(t));
const a = e ? JSON.parse(e) : t, n = [], i = [], r = [];
for (const s of a.dataList) s.filter ? n.push(s) : s.hasDown ? r.push(s) : s.favorite && i.push(s);
return {
filterList: n,
favoriteList: i,
hasDownList: r,
filterKeywordList: a.filterKeywordList,
filterActorList: a.filterActorList,
data: a
};
}
async saveKeyData(t, e) {
}
async changeData(t, e, a, n) {
e.includes("http") || (e = window.location.origin + e);
const r = localStorage.appData, s = JSON.parse(r);
let o = s.dataList, l = this.findData(t, o);
if (l || (l = {
carNum: t,
url: e,
actress: a,
createDate: i.getNowStr(),
filter: false,
favorite: false,
hasDown: false
}, o.push(l)), "filter" === n) {
if (l.filter) return void i.msg.error(t + " 已在屏蔽列表中");
l.filter = true, l.favorite = false, l.hasDown = false;
} else if ("favorite" === n) {
if (l.favorite) return void i.msg.error(t + " 已在收藏列表中");
l.filter = false, l.favorite = true, l.hasDown = false;
} else {
if ("hasDown" !== n) return void i.msg.error("actionType错误");
l.filter = true, l.favorite = true, l.hasDown = true;
}
localStorage.setItem("appData", JSON.stringify(s));
}
async saveFilterActor(t) {
const e = localStorage.appData, a = JSON.parse(e);
if (a.filterActorList.includes(t)) throw new Error(t + " 已存在");
a.filterActorList.push(t), localStorage.setItem("appData", JSON.stringify(a));
}
async saveFilterKeyword(t) {
const e = localStorage.appData, a = JSON.parse(e);
if (a.filterKeywordList.includes(t)) throw new Error(t + " 已存在");
a.filterKeywordList.push(t), localStorage.setItem("appData", JSON.stringify(a));
}
async removeData(t) {
const e = localStorage.appData, a = JSON.parse(e);
let n = a.dataList;
if (!this.findData(t, n)) throw new Error("未找到该番号信息:" + t);
n = n.filter((e => e.carNum !== t)), a.dataList = n, localStorage.setItem("appData", JSON.stringify(a));
}
}
const r = new LocalStorageManager;
class PluginManager {
constructor() {
this.plugins = new Map, this.isInitialized = false;
}
register(t) {
if ("function" != typeof t) throw new Error("插件必须是一个类");
const e = t.name;
if (!e) throw new Error("类必须要有名称");
const a = e.toLowerCase();
if (this.plugins.has(a)) throw new Error(`插件"${e}"已注册`);
const n = new t;
n.pluginManager = this, this.plugins.set(a, n);
}
getBean(t) {
return this.plugins.get(t.toLowerCase());
}
_initialize() {
if (this.isInitialized) return;
const t = new Map;
for (const [e, a] of this.plugins) "function" == typeof a.injectBean && t.set(e, {
instance: a,
deps: this._getDependencies(a.injectBean)
});
for (const [e, {instance: a, deps: n}] of t) {
const t = n.map((t => {
const a = t.toLowerCase();
if (!this.plugins.has(a)) throw new Error(`插件"${e}"依赖的插件"${t}"未注册`);
return this.plugins.get(a);
}));
a.injectBean(...t);
}
this.isInitialized = true;
}
_getDependencies(t) {
const e = t.toString();
return e.slice(e.indexOf("(") + 1, e.indexOf(")")).split(",").map((t => t.trim())).filter((t => t));
}
process() {
this.isInitialized || this._initialize();
for (const [t, e] of this.plugins) "function" == typeof e.handle && (i.insertStyle(e.initCss()),
e.handle());
}
}
const s = {
filterList: [],
favoriteList: [],
filterKeywordList: [],
filterActorList: [],
hasHandleList: [],
answerCount: 1,
reviewKeyword: ["像", "是你"]
};
class BasePlugin {
constructor() {
this.pluginManager = null, this.utils = i, this.isDetailPage = window.location.href.includes("javdb") && window.location.href.includes("/v"),
this.isListPage = window.location.href.includes("javdb") && !window.location.href.includes("/v"),
this.storageManager = r, Object.keys(s).forEach((t => {
Object.defineProperty(this, t, {
get: () => s[t],
set(e) {
s[t] = e;
},
enumerable: true,
configurable: true
});
}));
}
injectBean() {
}
initCss() {
}
handle() {
}
openPage(t, e, a, n) {
a || (a = true), n && n.ctrlKey ? window.open(t) : layer.open({
type: 2,
title: e,
content: t,
shadeClose: a,
area: ["80%", "90%"],
isOutAnim: false,
anim: -1
});
}
closePage() {
layer.msg("操作成功", {
icon: 1
});
[".layui-layer-shade", ".layui-layer-move", ".layui-layer"].forEach((function (t) {
parent.document.querySelectorAll(t).forEach((function (t) {
t.parentNode.removeChild(t);
}));
})), window.close();
}
getPageInfo() {
return {
carNum: $('a[title="複製番號"]').attr("data-clipboard-text"),
url: window.location.href.split("#")[0],
actress: $(".female").prev().map(((t, e) => $(e).text())).get().join(" "),
actors: $(".male").prev().map(((t, e) => $(e).text())).get().join(" ")
};
}
refresh() {
localStorage.refresh = Date.now().toString(), this.isListPage && this.pluginManager.getBean("ListPagePlugin").refresh();
}
async changeData(t, e, a, n) {
await this.storageManager.changeData(t, e, a, n), this.refresh();
}
}
class ListPagePlugin extends BasePlugin {
injectBean(autoPagePlugin) {
this.autoPagePlugin = autoPagePlugin;
}
handle() {
this.refresh(), window.addEventListener("storage", (t => {
"refresh" === t.key && this.refresh();
}));
}
async refresh() {
if (!this.isListPage) return;
const t = await this.storageManager.getData();
this.filterList = t.filterList, this.favoriteList = t.favoriteList, this.filterKeywordList = t.filterKeywordList,
this.filterActorList = t.filterActorList, this.filterMovieList(), this.autoPagePlugin.handlePaging();
}
filterMovieList() {
if (window.location.href.includes("search")) return;
let t = $(".movie-list .item").toArray();
const e = this.favoriteList.map((t => t.carNum));
t.forEach((t => {
let a = $(t), n = a.find("a"), i = n.attr("href"), r = n.attr("title"), s = a.find(".video-title").find("strong").text();
const o = `${s}-hide`, l = `${s}-tag`, c = `${s}-click`;
if ((this.filterList.some((t => t.carNum === s)) || this.filterKeywordList.some((t => r.includes(t) || s.includes(t)))) && !this.hasHandleList.includes(o)) return a.hide(),
void this.hasHandleList.push(o);
e.includes(s) && !this.hasHandleList.includes(l) && (a.find(".tags").append('<span class="tag is-success" style="margin-right: 5px">待下载</span>'),
this.hasHandleList.push(l)), this.hasHandleList.includes(c) || (a.on("click", (t => {
t.preventDefault(), this.openPage(i, s, false, t);
})), this.utils.rightClick(a.find("img"), (t => {
this.utils.q(t, `是否屏蔽番号${s}?`, (() => {
this.changeData(s, i, "", "filter").then((t => layer.msg("操作成功", {
icon: 1
})));
}));
})), this.hasHandleList.push(c));
})), $("#wait-down-btn span").text(`打开待下载(${this.favoriteList.length})`);
}
}
class SearchPlugin extends BasePlugin {
initCss() {
return "\n .search-bar-container {\n margin-bottom: 0 !important;\n }\n \n .search-bar-container .column {\n padding: 0 12px !important;\n }\n \n .search-bar-wrap {\n background-color: inherit;\n padding: 0;\n }\n ";
}
handle() {
this.isListPage && ($(".search-input").html('<input id="search-keyword" class="input is-medium" data-type="all" type="text" value="" placeholder="輸入影片番號,演員名等關鍵字進行檢索">'),
$("#search-bar-container").prependTo("#tags"), $(".search-submit").html('<button type="button" id="search-btn" class="button is-medium is-info">檢索</button>'),
$("#search-btn").on("click", (t => {
let e = $("#search-keyword").val(), a = $("#search-type option:selected").val();
"" !== e && this.openPage("/search?q=" + e + "&f=" + a, "搜索", false, t);
})), $("#search-keyword").on("paste", (t => {
setTimeout((() => {
$("#search-btn").click();
}), 0);
})).on("keypress", (t => {
"Enter" === t.key && setTimeout((() => {
$("#search-btn").click();
}), 0);
})));
}
}
class AutoPagePlugin extends BasePlugin {
constructor() {
super(), this.paging = false;
}
handle() {
if (!this.isListPage) return;
let t = "yes" === localStorage.autoPage ? "关闭自动翻页" : "开启自动翻页";
$(".pagination").prepend(`<a class='pagination-previous' id='auto-page'>${t}</a>`),
$("#auto-page").on("click", (t => {
t.preventDefault(), "yes" === localStorage.autoPage ? (localStorage.autoPage = "no",
$("#auto-page").html("开启自动翻页")) : (localStorage.autoPage = "yes", $("#auto-page").html("关闭自动翻页"),
this.handlePaging());
}));
}
handlePaging() {
if (!this.isListPage) return;
if (this.paging) return;
let t = true;
if ($(".movie-list .item:visible").each(((e, a) => {
0 === $(a).find("span:contains('待下载')").length && (t = false);
})), !t) return;
if ("yes" !== localStorage.autoPage) return;
let e = $(".pagination-next");
0 !== e.length && (this.paging = true, layer.msg("下一页....", {
time: 500,
end: () => {
e[0].click();
}
}));
}
}
class FoldCategoryPlugin extends BasePlugin {
handle() {
if (!this.isListPage) return;
let t = $(".tabs ul");
if (0 === t.length) return;
let e = "y" === localStorage.getItem("foldCategory");
const [a, n] = e ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
t.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>${a}</span>\n <i style="margin-left: 10px" class="${n}"></i>\n </a>\n </li>\n `);
const i = $("#tags");
i[e ? "hide" : "show"](), $("#foldCategoryBtn").on("click", (t => {
t.preventDefault(), e = !e, localStorage.setItem("foldCategory", e ? "y" : "n");
const [a, n] = e ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
$("#foldCategoryBtn").find("span").text(a).end().find("i").attr("class", n), i[e ? "hide" : "show"]();
}));
let r = $("#tags dl div.tag.is-info").map((function () {
return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
})).get().join(" ");
t.append(`<li style="margin-left: 50px;float: right"><span>${r}</span></li>`);
}
}
class DetailPagePlugin extends BasePlugin {
injectBean(detailPageMenuPlugin) {
this.detailPageMenuPlugin = detailPageMenuPlugin;
}
initCss() {
return this.isDetailPage ? "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n " : "";
}
async handle() {
if (!this.isDetailPage) return;
const t = await this.storageManager.getData();
this.filterList = t.filterList, this.favoriteList = t.favoriteList, this.filterKeywordList = t.filterKeywordList,
this.filterActorList = t.filterActorList, this.checkFilterActor();
}
checkFilterActor() {
if (!this.isDetailPage) return;
let t = this.getPageInfo().actors;
this.filterActorList.forEach((e => {
t.indexOf(e) > -1 && (this.answerCount++, this.utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
this.detailPageMenuPlugin.filterOne(null, true);
})));
}));
}
}
class ReviewPlugin extends BasePlugin {
async handle() {
if (!this.isDetailPage) return;
const t = window.location.href.split("/"), e = t[t.length - 1].split("#")[0];
let a = $("#magnets-content");
a.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/${e}/reviews`,
data: {
page: 1,
sort_by: "hotly",
limit: 20
},
headers: {
jdSignature: this.buildSignature()
},
success: t => {
$("#reviewsLoading").remove();
let e = t.data.reviews;
0 === e.length && a.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'),
a.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));"/>'),
e.forEach((t => {
let e = false;
for (let a = 0; a < this.reviewKeyword.length; a++) if (t.content.indexOf(this.reviewKeyword[a]) > -1) {
e = true;
break;
}
if (e) return;
let n = "";
for (let a = 0; a < t.score; a++) n += '<i class="icon-star"></i>';
let i = `\n <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;">\n ${t.username} <span class="score-stars">${n}</span> <span class="time">${t.created_at.replace("T", " ").replace(".000Z", "")}</span> 点赞:${t.likes_count}\n <p style="margin-top: 5px;">${t.content}</p>\n </div>\n `;
a.append(i);
}));
}
});
}
buildSignature() {
const t = Math.floor(Date.now() / 1e3);
if (t - (localStorage.review_ts || 0) <= 20) return localStorage.review_sign;
const e = `${t}.lpw6vgqzsp.${n(`${t}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
return localStorage.review_ts = t, localStorage.review_sign = e, e;
}
}
const o = class _HotkeyManager {
constructor() {
if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(t, e, a = null) {
if (Array.isArray(t)) {
let n = [];
return t.forEach((t => {
if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误");
let i = this.recordHotkey(t, e, a);
n.push(i);
})), n;
}
if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误");
return this.recordHotkey(t, e, a);
}
static recordHotkey(t, e, a) {
let n = Math.random().toString(36).substr(2);
return this.registerHotKeyMap.set(n, {
hotkeyString: t,
callback: e,
keyupCallback: a
}), n;
}
static unregisterHotkey(t) {
this.registerHotKeyMap.has(t) && this.registerHotKeyMap.delete(t);
}
static isHotkeyFormat(t) {
return t.toLowerCase().split("+").map((t => t.trim())).every((t => ["ctrl", "shift", "alt"].includes(t) || 1 === t.length));
}
static judgeHotkey(t, e) {
const a = t.toLowerCase().split("+").map((t => t.trim())), n = a.includes("ctrl"), i = a.includes("shift"), r = a.includes("alt"), s = a.find((t => "ctrl" !== t && "shift" !== t && "alt" !== t));
return (this.isMac ? e.metaKey : e.ctrlKey) === n && e.shiftKey === i && e.altKey === r && e.key.toLowerCase() === s;
}
};
e(o, "isMac", 0 === navigator.platform.indexOf("Mac")), e(o, "registerHotKeyMap", new Map),
e(o, "handleKeydown", (t => {
for (const [e, a] of o.registerHotKeyMap) {
let e = a.hotkeyString, n = a.callback;
o.judgeHotkey(e, t) && (t.preventDefault(), n(t));
}
})), e(o, "handleKeyup", (t => {
for (const [e, a] of o.registerHotKeyMap) {
let e = a.hotkeyString, n = a.keyupCallback;
n && (o.judgeHotkey(e, t) && (t.preventDefault(), n(t)));
}
}));
let l = o;
document.addEventListener("keydown", (t => {
l.handleKeydown(t);
})), document.addEventListener("keyup", (t => {
l.handleKeyup(t);
}));
class DetailPageMenuPlugin extends BasePlugin {
constructor() {
super(), this.allowRepeatDown = false;
}
injectBean() {
}
handle() {
this.bindHotkey(), this.isDetailPage && this.createMenuBtn();
}
hotkey() {
if (this.isDetailPage) return [{
hotkey: ["a"],
callback: () => {
this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
}
}, {
hotkey: ["s"],
callback: () => this.favoriteOne(null)
}, {
hotkey: ["z"],
callback: () => this.speedVideo()
}];
}
createMenuBtn() {
const t = this.getPageInfo(), e = t.carNum, a = [{
id: "favoriteBtn",
sort: 1,
html: function () {
return `<a id="${this.id}" class="menu-btn" style="background-color:#25b1dc"><span>收藏(s)</span></a>`;
},
action: t => this.favoriteOne()
}, {
id: "filterBtn",
sort: 2,
html: function () {
return `<a id="${this.id}" class="menu-btn" style="background-color:#de3333"><span>屏蔽(a)</span></a>`;
},
action: t => this.filterOne(t)
}, {
id: "hasDownBtn",
sort: 3,
html: function () {
return `<a id="${this.id}" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>`;
},
action: e => this.changeData(t.carNum, t.url, t.actress, "hasDown").then((t => this.closePage()))
}, {
id: "enable-magnets-filter",
sort: 5,
html: function () {
return `<a id="${this.id}" class="menu-btn" style="background-color:#c2bd4c"><span>关闭磁力过滤</span></a>`;
},
action: t => {
$("#magnets-content .item").toArray().forEach((t => $(t).show()));
}
}, {
id: "jable-video-btn",
sort: 6,
html: function () {
return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>`;
},
action: t => this.openPage(`https://jable.tv/videos/${e}/`, e, false, t)
}, {
id: "missav-video-btn",
sort: 7,
html: function () {
return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>`;
},
action: t => window.open(`https://missav.ws/search/${e}`, "_blank")
}, {
id: "preview-video-btn",
sort: 8,
html: function () {
return `<a id="${this.id}" class="menu-btn fr-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>预览视频(z)</span></a>`;
},
action: t => this.openPage(`https://javtrailers.com/video/${e.toLowerCase().replace("-", "00")}`, e, false, t)
}, {
id: "search-subtitle-btn",
sort: 9,
html: function () {
return `<a id="${this.id}" class="menu-btn fr-btn" style="background-color: #2196F3"><span>搜索字幕</span></a>`;
},
action: t => this.openPage(`https://subtitlecat.com/index.php?search=${e}`, e, false, t)
}];
a.sort(((t, e) => t.sort - e.sort));
const n = `\n <div style="transform: translateY(-50%);">\n ${a.map((t => t.html())).join("\n")}\n </div>\n `;
$(".tabs").after(n), a.forEach((({id: t, action: e}) => {
$(`#${t}`).on("click", e);
}));
}
favoriteOne() {
let t = this.getPageInfo();
this.changeData(t.carNum, t.url, t.actress, "favorite").then((t => this.closePage()));
}
filterOne(t, e) {
t && t.preventDefault();
let a = this.getPageInfo();
e ? this.changeData(a.carNum, a.url, a.actress, "filter").then((t => this.closePage())) : this.utils.q(t, `是否屏蔽${a.carNum}?`, (() => {
this.changeData(a.carNum, a.url, a.actress, "filter").then((t => this.closePage()));
}));
}
checkHasDown() {
let t = this.getPageInfo().carNum;
$("#magnets-content a, #magnets-content button").on("click", (e => {
this.allowRepeatDown || this.storageManager.checkHasDown(t).then((t => {
"yes" === t.data && (e.preventDefault(), e.stopPropagation(), layer.msg(t.msg, {
icon: 2
}));
}));
}));
}
speedVideo() {
const t = $('iframe[id^="layui-layer-iframe"]');
0 === t.length ? $("#preview-video-btn").click() : t[0].contentWindow.postMessage("speedVideo", "*");
}
bindHotkey() {
const t = {
a: () => {
this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null), this.answerCount++;
},
s: () => this.favoriteOne(null),
z: () => this.speedVideo()
}, e = (t, e) => {
l.registerHotkey(t, (() => {
this.isDetailPage ? e() : (t => {
const e = $(".layui-layer-content iframe");
0 !== e.length && e[0].contentWindow.postMessage(t, "*");
})(t);
}));
};
this.isDetailPage && window.addEventListener("message", (e => {
t[e.data] && t[e.data]();
})), Object.entries(t).forEach((([t, a]) => {
e(t, a);
}));
}
}
class ListPageMenuPlugin extends BasePlugin {
constructor() {
super(), this.buttons = [];
}
handle() {
this.isListPage && this.createMenuBtn();
}
initCss() {
return "\n .menu-box {\n position: fixed;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n display: flex;\n flex-direction: column;\n z-index: 1000;\n gap: 6px;\n }\n \n .menu-btn {\n display: inline-block;\n min-width: 80px;\n padding: 7px 12px;\n border-radius: 4px;\n color: white;\n text-decoration: none;\n font-weight: bold;\n font-size: 12px;\n text-align: center;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n border: none;\n line-height: 1.3;\n margin: 0;\n }\n \n .menu-btn:hover {\n transform: translateY(-1px);\n box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);\n opacity: 0.9;\n }\n \n .menu-btn:active {\n transform: translateY(0);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\n }\n \n \n /*检查字幕表格*/\n .data-table {\n width: 100%;\n border-collapse: separate;\n border-spacing: 0;\n font-family: 'Helvetica Neue', Arial, sans-serif;\n background: #fff;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);\n margin: 0;\n }\n \n .data-table thead tr {\n background: #f8fafc;\n }\n \n .data-table th {\n padding: 16px 20px;\n text-align: left;\n color: #64748b;\n font-weight: 500;\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n border-bottom: 1px solid #e2e8f0;\n }\n \n .data-table td {\n padding: 14px 20px;\n color: #334155;\n font-size: 15px;\n border-bottom: 1px solid #f1f5f9;\n }\n \n .data-table tbody tr:last-child td {\n border-bottom: none;\n }\n \n .data-table tbody tr {\n transition: all 0.2s ease;\n }\n \n .data-table tbody tr:hover {\n background: #f8fafc;\n }\n \n .data-table a {\n display: inline-flex;\n align-items: center;\n padding: 6px 14px;\n margin-right: 10px;\n border-radius: 6px;\n text-decoration: none;\n font-size: 13px;\n font-weight: 500;\n transition: all 0.2s ease;\n }\n \n .data-table a:first-child {\n background: #f0fdf4;\n color: #16a34a;\n border: 1px solid #dcfce7;\n }\n \n .data-table a:last-child {\n background: #f0f9ff;\n color: #0284c7;\n border: 1px solid #e0f2fe;\n }\n \n .data-table a:hover {\n transform: translateY(-1px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n }\n \n .data-table a:first-child:hover {\n background: #dcfce7;\n }\n \n .data-table a:last-child:hover {\n background: #e0f2fe;\n }\n\n ";
}
createMenuBtn() {
let t = $("#tags");
const e = t.length ? t : $(".tabs");
if (!e.length) return;
const a = [{
id: "wait-check-btn",
style: "background-color:#dcc45b",
text: "打开待鉴定",
sort: 1,
action: t => this.openWaitCheck(t)
}, {
id: "wait-down-btn",
style: "background-color:#7cb7e8",
text: "打开待下载",
sort: 2,
action: t => this.openFavorite(t)
}, {
id: "auto-play-btn",
style: "background-color:" + ("yes" === localStorage.autoPlay ? "#dc4c5e" : "#65ced2"),
text: "yes" === localStorage.autoPlay ? "关闭自动播放" : "开启自动播放",
sort: 4,
action: t => this.changeAutoPlay(t)
}, {
id: "historyBtn",
style: "background-color:#aade66",
text: "历史列表",
sort: 6,
action: t => this.openHistory(t)
}, {
id: "importBtn",
style: "background-color:#d2668d",
text: "导入数据",
sort: 98,
action: t => this.importData(t)
}, {
id: "exportBtn",
style: "background-color:#85d0a3",
text: "导出数据",
sort: 99,
action: t => this.exportData(t)
}];
a.sort(((t, e) => t.sort - e.sort));
const n = `\n <div class="menu-box">\n ${a.map((t => t.disable ? "" : `\n <a id="${t.id}" class="menu-btn" style="${t.style}">\n <span>${t.text}</span>\n </a>\n `)).join("")}\n </div>\n `;
e.after(n), a.forEach((t => {
t.action && $(`#${t.id}`).on("click", (e => {
t.action(e);
}));
}));
}
openWaitCheck() {
this.changeAutoPlay("yes");
let t = 0;
$(".movie-list .item:visible").each(((e, a) => {
if (t >= 8) return false;
if (0 === $(a).find("span:contains('待下载')").length) {
const e = $(a).find("a").attr("href");
e && (window.open(e), t++);
}
}));
}
openFavorite() {
this.changeAutoPlay("no");
for (let t = 0; t < 10; t++) {
if (t >= this.favoriteList.length) return;
window.open(this.favoriteList[t].url);
}
}
changeAutoPlay(t) {
let e = localStorage.autoPlay;
e !== t && (localStorage.autoPlay = "yes" === e ? "no" : "yes", $("#auto-play-btn").css("background-color", "yes" === localStorage.autoPlay ? "#dc4c5e" : "#65ced2"),
$("#auto-play-btn span").text("yes" === localStorage.autoPlay ? "关闭自动播放" : "开启自动播放"));
}
openHistory(t) {
this.storageManager.getData().then((t => {
console.log(t);
const e = t.data.dataList || [];
e.reverse();
let a = [...e];
const n = {
filtered: {
text: "已屏蔽",
color: "#ec4949",
condition: t => !t.hasDown && !t.favorite
},
favorite: {
text: "已收藏",
color: "#50adb9",
condition: t => t.favorite && !t.hasDown
},
hasDown: {
text: "已下载",
color: "#8ebd6e",
condition: t => t.hasDown
}
}, i = t => {
let e = n.filtered;
return t.hasDown ? e = n.hasDown : t.favorite && (e = n.favorite), `\n <tr>\n <td>${t.carNum}</td>\n <td>${t.actress ? t.actress : ""}</td>\n <td>${t.createDate ? t.createDate : ""}</td>\n <td style="color:${e.color}">${e.text}</td>\n <td>\n <a class="action-remove" data-car-num="${t.carNum}" data-url="${t.url}">移除</a>\n <a class="action-detail" data-car-num="${t.carNum}" data-url="${t.url}">详情页</a>\n </td>\n </tr>\n `;
}, r = t => `\n <table class="data-table">\n <thead>\n <tr>\n <th>番号</th>\n <th width="300px">演员</th>\n <th>创建日期</th>\n <th>状态</th>\n <th>操作</th>\n </tr>\n </thead>\n <tbody>\n ${t.map(i).join("")}\n </tbody>\n </table>\n `, s = (t, i) => {
a = (t => "all" === t ? [...e] : e.filter(n[t].condition))(i), $(t).find(".data-table").replaceWith(r(a)),
$(t).find(".history-btn").removeClass("active").filter(`[data-action="${i}"]`).addClass("active");
}, o = (t, e, a) => {
"详情" === t && this.openPage(e.url), "移除" === t && this.utils.q(a, `是否移除${e.carNum}?`, (() => {
this.storageManager.removeData(e.carNum).then((t => {
let a = $(".movie-list .item").toArray();
for (let n = 0; n < a.length; n++) {
let t = $(a[n]), i = t.find(".video-title").find("strong").text();
if (i === e.carNum) {
t.show();
const e = `${i}-hide`;
this.hasHandleList = this.hasHandleList.filter((t => t !== e));
break;
}
}
layer.close(l), this.openHistory();
}));
}));
};
let l = layer.open({
type: 1,
title: "历史列表",
content: `\n <div style="margin: 10px">\n ${[{
action: "filtered",
text: "已屏蔽",
color: "#ec4949"
}, {
action: "favorite",
text: "已收藏",
color: "#50adb9"
}, {
action: "hasDown",
text: "已下载",
color: "#8ebd6e"
}, {
action: "all",
text: "所有",
color: "#d3c8a5"
}].map((t => `\n <a class="menu-btn history-btn" data-action="${t.action}" style="background-color:${t.color} !important;">\n ${t.text}\n </a>\n `)).join("")}\n </div>\n ${r(a)}\n `,
area: ["60%", "80%"],
success: (t, e) => {
$(t).on("click", ".history-btn", (function () {
s(t, $(this).data("action"));
})).on("click", ".action-remove", (function (t) {
t.stopPropagation(), o("移除", $(this).data(), t);
})).on("click", ".action-detail", (function (t) {
t.stopPropagation(), o("详情", $(this).data(), t);
}));
},
end: () => {
this.refresh();
}
});
}));
}
importData() {
try {
const t = document.createElement("input");
t.type = "file", t.accept = ".json", t.onchange = t => {
const e = t.target.files[0];
if (!e) return;
const a = new FileReader;
a.onload = t => {
try {
const e = t.target.result;
JSON.parse(e);
localStorage.appData ? layer.confirm("当前已有数据,确定要覆盖吗?", {
icon: 3,
title: "确认覆盖",
btn: ["确定", "取消"]
}, (function (t) {
localStorage.setItem("appData", e), layer.msg("数据导入成功", {
icon: 1
}), layer.close(t);
}), (function (t) {
layer.msg("已取消导入", {
icon: 2
}), layer.close(t);
})) : (localStorage.setItem("appData", e), layer.msg("数据导入成功", {
icon: 1
}));
} catch (e) {
layer.msg("导入失败:文件内容不是有效的JSON格式", {
icon: 2
}), console.error("导入JSON解析错误:", e);
}
}, a.onerror = () => {
layer.msg("读取文件时出错", {
icon: 2
});
}, a.readAsText(e);
}, document.body.appendChild(t), t.click(), setTimeout((() => document.body.removeChild(t)), 1e3);
} catch (t) {
console.error("导入数据时出错:", t), layer.msg("导入数据时出错: " + t.message, {
icon: 2
});
}
}
exportData(t) {
try {
const t = localStorage.appData;
if (!t) return void layer.msg("没有找到可导出的appData数据", {
icon: 2
});
const e = `${this.utils.getNowStr("_", "_")}_appData.json`, a = new Blob([t], {
type: "application/json"
}), n = URL.createObjectURL(a), i = document.createElement("a");
i.href = n, i.download = e, document.body.appendChild(i), i.click(), setTimeout((() => {
document.body.removeChild(i), URL.revokeObjectURL(n);
}), 100), layer.msg("数据导出成功", {
icon: 1
}), console.log("数据导出成功:", e);
} catch (e) {
console.error("导出数据时出错:", e), layer.msg("导出数据时出错: " + e.message, {
icon: 2
});
}
}
archiveFile() {
this.storageManager.archiveFile().then((t => {
let e = t.successMsgList, a = t.errorMsgList;
msg.list(e, a), e.length || a.length || layer.msg("没有可归档文件");
}));
}
checkSubTitle() {
this.storageManager.checkSubTitle().then((t => {
let e = t.data;
if (0 === e.length) return void layer.msg("视频字幕完整");
let a = '<table class="data-table">';
a += "<thead><tr>", a += "<th>番号</th>", a += "<th>文件路径</th>", a += "<th>操作</th>",
a += "</tr></thead>", a += "<tbody>", $.each(e, (function (t, e) {
a += "<tr>", a += "<td>" + e.carNum + "</td>", a += "<td>" + e.filePath + "</td>",
a += `<td>\n <a href="${"https://subtitlecat.com/index.php?search=" + e.carNum}" target="_blank">搜索字幕</a>\n <a href="${e.url}" target="_blank">详情页</a>\n </td>`,
a += "</tr>";
})), a += "</tbody>", a += "</table>", layer.open({
type: 1,
title: "检查字幕",
content: a,
area: ["1000px", "400px"]
});
}));
}
}
class HighlightMagnetPlugin extends BasePlugin {
handle() {
let t = $("#magnets-content .name").toArray(), e = false;
t.forEach((t => {
let a = $(t), n = a.text().toLowerCase();
n.indexOf("4k") > -1 && a.css("color", "#f40"), (n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.indexOf("4k") > -1) && (e = true);
})), e && t.forEach((t => {
let e = $(t), a = e.text().toLowerCase();
a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1 || e.parent().parent().parent().hide();
}));
}
}
class PreviewVideoPlugin extends BasePlugin {
handle() {
$(".preview-video-container").on("click", (t => {
t.preventDefault(), $("#preview-video-btn").click();
})), "yes" === localStorage.getItem("autoPlay") && $("#preview-video-btn").click();
}
}
class SelectTextFilterPlugin extends BasePlugin {
injectBean(detailPageMenuPlugin) {
this.detailPageMenuPlugin = detailPageMenuPlugin;
}
handle() {
this.isDetailPage && (this.utils.rightClick($("h2"), (t => {
const e = window.getSelection().toString();
if (e) {
let a = {
clientX: t.clientX,
clientY: t.clientY + 120
};
this.utils.q(a, `是否屏蔽关键词${e}?`, (() => {
this.saveFilterKeyword(e).then((t => {
this.closePage();
}));
}));
}
})), $(".male").prev().toArray().forEach((t => {
this.utils.rightClick($(t), (e => {
let a = $(t).text().trim();
this.utils.q(e, `是否屏蔽演员${a}?`, (() => {
this.saveFilterActor(a).then((t => {
this.detailPageMenuPlugin.filterOne(null, true);
}));
}));
}));
})), this.utils.rightClick($(".preview-images"), (t => {
let e = this.getPageInfo();
this.utils.q(t, `是否屏蔽${e.carNum}?`, (() => {
this.changeData(e.carNum, e.url, "", "filter").then((t => {
this.closePage();
}));
}));
})), this.utils.rightClick($(".column-video-cover"), (t => {
let e = this.getPageInfo();
this.utils.q(t, `是否屏蔽${e.carNum}?`, (() => {
this.changeData(e.carNum, e.url, "", "filter").then((t => {
this.closePage();
}));
}));
})));
}
async saveFilterKeyword(t) {
await this.storageManager.saveFilterKeyword(t), this.refresh();
}
async saveFilterActor(t) {
await this.storageManager.saveFilterActor(t), this.refresh();
}
}
class JavTrailersPlugin extends BasePlugin {
constructor() {
super(), this.hasBand = false;
}
handlePlayJavTrailers() {
this.hasBand || this.utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
setTimeout((() => {
this.hasBand = true;
let t = document.getElementById("vjs_video_3_html5_api");
t.play(), t.currentTime = 5, t.addEventListener("timeupdate", (function () {
t.currentTime >= 14 && t.currentTime < 16 && (t.currentTime += 2);
})), $("#vjs_video_3_html5_api").css({
position: "fixed",
width: "100vw",
height: "100vh",
objectFit: "cover",
zIndex: "999999999"
}), $(".vjs-control-bar").css({
position: "fixed",
bottom: "20px",
zIndex: "999999999"
});
}), 0);
}));
}
handle() {
if (!window.location.hostname.includes("javtrailers")) return;
if ($("h1:contains('Page not found')").length > 0) {
let t = window.location.href.split("video/")[1].toLowerCase().replace("00", "-");
return void (window.location.href = "https://javtrailers.com/search/" + t);
}
let t = $(".videos-list .video-link").toArray();
if (t.length) {
const e = window.location.href.split("search/")[1].toLowerCase(), a = t.find((t => $(t).find(".vid-title").text().toLowerCase().includes(e)));
if (a) return void (window.location.href = $(a).attr("href"));
}
this.handlePlayJavTrailers(), $("#videoPlayerContainer").on("click", (() => {
this.handlePlayJavTrailers();
})), window.addEventListener("message", (t => {
let e = document.getElementById("vjs_video_3_html5_api");
e && (e.currentTime += 5);
})), l.registerHotkey("z", (() => {
const t = document.getElementById("vjs_video_3_html5_api");
t && (t.currentTime += 5);
})), l.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), l.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
}
}
class SubTitleCatPlugin extends BasePlugin {
handle() {
if (!window.location.hostname.includes("subtitlecat")) return;
$(".t-banner-inner").hide(), $("#navbar").hide();
let t = window.location.href.split("=")[1].toLowerCase();
$(".sub-table tr td a").toArray().forEach((e => {
let a = $(e);
a.text().toLowerCase().includes(t) || a.parent().parent().hide();
}));
}
}
class JablePlugin extends BasePlugin {
handle() {
window.location.hostname.includes("jable") && ($("#player")[0].play(), $('button[data-plyr="fullscreen"]').click(),
l.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), l.registerHotkey("s", (() => window.parent.postMessage("s", "*"))));
}
}
window.$ = a, window.jQuery = a, window.layer = layer, i.importResource("https://cdn.jsdelivr.net/npm/[email protected]/layer.min.css"),
function () {
const t = new PluginManager;
t.register(AutoPagePlugin), t.register(FoldCategoryPlugin), t.register(ListPageMenuPlugin),
t.register(ListPagePlugin), t.register(SearchPlugin), t.register(DetailPagePlugin),
t.register(ReviewPlugin), t.register(DetailPageMenuPlugin), t.register(HighlightMagnetPlugin),
t.register(PreviewVideoPlugin), t.register(SelectTextFilterPlugin), t.register(JavTrailersPlugin),
t.register(SubTitleCatPlugin), t.register(JablePlugin), t.process();
}();
})($, layer, md5);