// ==UserScript==
// @name JAV-JHS
// @namespace https://sleazyfork.org/zh-CN/scripts/533695
// @version 1.1.1
// @author fuajofkewmrw
// @description Jav-鉴黄师 收藏,屏蔽,标记已下载,在线预览,fc2ppv
// @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/layui.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js
// @connect *
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @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)
})(" .container{min-width:85%}.navbar{z-index:12345679!important}.movie-list.h{grid-template-columns:repeat(5,minmax(0,1fr))!important}.sub-header,#footer,.search-recent-keywords,.app-desktop-banner,div[data-controller=movie-tab] .tabs,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 (n, a) {
'use strict';
var t = Object.defineProperty, e = (e, n, a) => ((e, n, a) => n in e ? t(e, n, {
enumerable: true,
configurable: true,
writable: true,
value: a
}) : e[n] = a)(e, "symbol" != typeof n ? n + "" : n, a);
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 n = Array.isArray(t) ? t : [t];
this.list(n, [], [], e);
},
error(t, e = {}) {
const n = Array.isArray(t) ? t : [t];
this.list([], n, [], e);
},
info(t, e = {}) {
const n = Array.isArray(t) ? t : [t];
this.list([], [], n, e);
},
list(t = [], e = [], n = [], a = {}) {
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(n, {
prefix: "ℹ",
color: "#d7c88b"
});
let s = 0;
e.length > 0 && 0 === t.length && 0 === n.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 = {}, n = {}, a) {
return this.jqueryRequest("GET", t, null, e, n, a);
},
post(t, e = {}, n = {}, a) {
return this.jqueryRequest("POST", t, e, null, n, a);
},
put(t, e = {}, n = {}, a) {
return this.jqueryRequest("PUT", t, e, null, n, a);
},
del(t, e = {}, n = {}, a) {
return this.jqueryRequest("DELETE", t, null, e, n, a);
},
jqueryRequest(t, e, n = {}, a = {}, 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 ? a : JSON.stringify(n),
headers: i,
success: t => this.handleResponse(t, s, o),
error: t => o(t)
});
}));
},
handleResponse(t, e, n) {
const a = t;
if (200 === a.code) e(a); else if (401 === a.code) window.location.reload(); else {
const t = a.msg || "请求失败";
this.alertFun ? this.alertFun(t) : console.error(t), n(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, n = 20, a = 1e4, i = true) {
let r = false;
const s = Math.random(), o = (new Date).getTime();
this.intervalContainer[s] = setInterval((() => {
(new Date).getTime() - o > a && (console.warn("loopDetector timeout!", t, e), r = i),
(t() || r) && (clearInterval(this.intervalContainer[s]), e && e(), delete this.intervalContainer[s]);
}), n);
}
rightClick(t, e) {
t.jquery && (t = t[0]), t ? t.addEventListener("contextmenu", (t => {
t.preventDefault(), e(t);
})) : console.error("rightClick(), 找不到元素");
}
q(t, e, n, a) {
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 () {
n(), layer.closeAll();
}), (function () {
a && a();
}));
}
getNowStr(t = "-", e = ":") {
const n = new Date, a = n.getFullYear(), i = String(n.getMonth() + 1).padStart(2, "0"), r = String(n.getDate()).padStart(2, "0"), s = String(n.getHours()).padStart(2, "0"), o = String(n.getMinutes()).padStart(2, "0"), l = String(n.getSeconds()).padStart(2, "0");
return `${[a, 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 n = e ? JSON.parse(e) : t, a = [], i = [], r = [];
for (const s of n.dataList) s.filter ? a.push(s) : s.hasDown ? r.push(s) : s.favorite && i.push(s);
return {
filterList: a,
favoriteList: i,
hasDownList: r,
filterKeywordList: n.filterKeywordList,
filterActorList: n.filterActorList,
data: n
};
}
async saveKeyData(t, e) {
}
async changeData(t, e, n, a) {
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: n,
createDate: i.getNowStr(),
filter: false,
favorite: false,
hasDown: false
}, o.push(l)), "filter" === a) {
if (l.filter) throw new Error(t + " 已在屏蔽列表中");
l.filter = true, l.favorite = false, l.hasDown = false;
} else if ("favorite" === a) {
if (l.favorite) throw new Error(t + " 已在收藏列表中");
l.filter = false, l.favorite = true, l.hasDown = false;
} else {
if ("hasDown" !== a) throw new Error("actionType错误");
l.filter = true, l.favorite = true, l.hasDown = true;
}
localStorage.setItem("appData", JSON.stringify(s));
}
async saveFilterActor(t) {
const e = localStorage.appData, n = JSON.parse(e);
if (n.filterActorList.includes(t)) throw new Error(t + " 已存在");
n.filterActorList.push(t), localStorage.setItem("appData", JSON.stringify(n));
}
async saveFilterKeyword(t) {
const e = localStorage.appData, n = JSON.parse(e);
if (n.filterKeywordList.includes(t)) throw new Error(t + " 已存在");
n.filterKeywordList.push(t), localStorage.setItem("appData", JSON.stringify(n));
}
async removeData(t) {
const e = localStorage.appData, n = JSON.parse(e);
let a = n.dataList;
if (!this.findData(t, a)) throw new Error("未找到该番号信息:" + t);
a = a.filter((e => e.carNum !== t)), n.dataList = a, localStorage.setItem("appData", JSON.stringify(n));
}
}
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 n = e.toLowerCase();
if (this.plugins.has(n)) throw new Error(`插件"${e}"已注册`);
const a = new t;
a.pluginManager = this, this.plugins.set(n, a);
}
getBean(t) {
return this.plugins.get(t.toLowerCase());
}
_initialize() {
if (this.isInitialized) return;
const t = new Map;
for (const [e, n] of this.plugins) "function" == typeof n.injectBean && t.set(e, {
instance: n,
deps: this._getDependencies(n.injectBean)
});
for (const [e, {instance: n, deps: a}] of t) {
const t = a.map((t => {
const n = t.toLowerCase();
if (!this.plugins.has(n)) throw new Error(`插件"${e}"依赖的插件"${t}"未注册`);
return this.plugins.get(n);
}));
n.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 [e, n] of this.plugins) try {
"function" == typeof n.handle && (i.insertStyle(n.initCss()), n.handle());
} catch (t) {
console.error("执行插件发生错误", t);
}
}
}
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, n, a) {
n || (n = true), a && a.ctrlKey ? window.open(t) : layer.open({
type: 2,
title: e,
content: t,
shadeClose: n,
area: ["80%", "90%"],
isOutAnim: false,
anim: -1
});
}
closePage() {
layer.msg("操作成功", {
icon: 1
}), layer.closeAll();
[".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, n, a) {
if (console.log(t), !t) throw layer.error("番号为空!"), new Error("番号为空!");
if (!e) throw layer.error("url为空!"), new Error("url为空!");
await this.storageManager.changeData(t, e, n, a), this.refresh();
}
getSetting(t, e) {
const n = localStorage.getItem("setting");
if (null === n) return e;
const a = JSON.parse(n);
return a && "object" == typeof a && t in a ? a[t] : e;
}
}
class ListPagePlugin extends BasePlugin {
injectBean(autoPagePlugin, fc2Plugin) {
this.autoPagePlugin = autoPagePlugin, this.fc2Plugin = fc2Plugin;
}
handle() {
$('.navbar-item:contains("FC2")').attr("href", "/advanced_search?type=3&score_min=4&d=1"),
$('.tabs a:contains("FC2")').attr("href", "/advanced_search?type=3&score_min=4&d=1"),
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?q")) return;
let t = $(".movie-list .item").toArray();
const e = this.favoriteList.map((t => t.carNum));
let n = this.getSetting("hideFilterItem", "yes");
t.forEach((t => {
let a = $(t), i = a.find("a"), r = i.attr("href"), s = i.attr("title"), o = a.find(".video-title").find("strong").text();
const l = `${o}-hide`, c = `${o}-tag`, d = `${o}-click`;
if ((this.filterList.some((t => t.carNum === o)) && "yes" === n || this.filterKeywordList.some((t => s.includes(t) || o.includes(t)))) && !this.hasHandleList.includes(l)) return a.hide(),
void this.hasHandleList.push(l);
e.includes(o) && !this.hasHandleList.includes(c) && (a.find(".tags").append('<span class="tag is-success" style="margin-right: 5px">待下载</span>'),
this.hasHandleList.push(c)), this.hasHandleList.includes(d) || (a.on("click", (t => {
if (t.preventDefault(), o.includes("FC2-")) {
let t = r.split("/").filter(Boolean).pop();
this.fc2Plugin.openFc2Page(t, o, r);
} else this.openPage(r, o, false, t);
})), this.utils.rightClick(a.find("img"), (t => {
this.utils.q(t, `是否屏蔽番号${o}?`, (() => {
this.changeData(o, r, "", "filter").then((t => layer.msg("操作成功", {
icon: 1
})));
}));
})), this.hasHandleList.push(d));
})), $("#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: 10px 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(), n = $("#search-type option:selected").val();
"" !== e && this.openPage("/search?q=" + e + "&f=" + n, "搜索", 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, n) => {
0 === $(n).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 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);
})));
}));
}
}
function o() {
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 l = "https://api.ffaoa.com/api", c = (t, e = 20) => new Promise(((n, a) => {
$.ajax({
method: "GET",
url: `${l}/v1/movies/${t}/reviews`,
data: {
page: 1,
sort_by: "hotly",
limit: e
},
headers: {
jdSignature: o()
},
success: t => {
const e = t.data.reviews;
n(e);
},
error: t => {
console.error(t), msg.error("发生错误" + t), a(t);
}
});
}));
class ReviewPlugin extends BasePlugin {
async handle() {
if (!this.isDetailPage) return;
const t = window.location.href.split("/"), e = t[t.length - 1].split("#")[0];
let n = $("#magnets-content");
n.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
let a = this.getSetting("reviewCount", 20);
const i = await c(e, a);
$("#reviewsLoading").remove(), 0 === i.length && n.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'),
n.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));"/>'),
i.forEach((t => {
let e = false;
for (let n = 0; n < this.reviewKeyword.length; n++) if (t.content.indexOf(this.reviewKeyword[n]) > -1) {
e = true;
break;
}
if (e) return;
let a = "";
for (let n = 0; n < t.score; n++) a += '<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">${a}</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 `;
n.append(i);
}));
}
}
const d = class _HotkeyManager {
constructor() {
if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(t, e, n = null) {
if (Array.isArray(t)) {
let a = [];
return t.forEach((t => {
if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误");
let i = this.recordHotkey(t, e, n);
a.push(i);
})), a;
}
if (!this.isHotkeyFormat(t)) throw new Error("快捷键格式错误");
return this.recordHotkey(t, e, n);
}
static recordHotkey(t, e, n) {
let a = Math.random().toString(36).substr(2);
return this.registerHotKeyMap.set(a, {
hotkeyString: t,
callback: e,
keyupCallback: n
}), a;
}
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 n = t.toLowerCase().split("+").map((t => t.trim())), a = n.includes("ctrl"), i = n.includes("shift"), r = n.includes("alt"), s = n.find((t => "ctrl" !== t && "shift" !== t && "alt" !== t));
return (this.isMac ? e.metaKey : e.ctrlKey) === a && e.shiftKey === i && e.altKey === r && e.key.toLowerCase() === s;
}
};
e(d, "isMac", 0 === navigator.platform.indexOf("Mac")), e(d, "registerHotKeyMap", new Map),
e(d, "handleKeydown", (t => {
for (const [e, n] of d.registerHotKeyMap) {
let e = n.hotkeyString, a = n.callback;
d.judgeHotkey(e, t) && (t.preventDefault(), a(t));
}
})), e(d, "handleKeyup", (t => {
for (const [e, n] of d.registerHotKeyMap) {
let e = n.hotkeyString, a = n.keyupCallback;
a && (d.judgeHotkey(e, t) && (t.preventDefault(), a(t)));
}
}));
let h = d;
document.addEventListener("keydown", (t => {
h.handleKeydown(t);
})), document.addEventListener("keyup", (t => {
h.handleKeyup(t);
}));
class DetailPageMenuPlugin extends BasePlugin {
constructor() {
super(), this.allowRepeatDown = false;
}
injectBean() {
}
initCss() {
return "\n .fr-btn {\n float: right;\n margin-left: 4px !important;\n }\n ";
}
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, n = [{
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)
}];
n.sort(((t, e) => t.sort - e.sort));
const a = `\n <div style="transform: translateY(-50%);">\n ${n.map((t => t.html())).join("\n")}\n </div>\n `;
$(".tabs").after(a), n.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 n = this.getPageInfo();
e ? this.changeData(n.carNum, n.url, n.actress, "filter").then((t => this.closePage())) : this.utils.q(t, `是否屏蔽${n.carNum}?`, (() => {
this.changeData(n.carNum, n.url, n.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) => {
h.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, n]) => {
e(t, n);
}));
}
}
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 ";
}
createMenuBtn() {
[].sort(((t, e) => t.sort - e.sort));
$(".tabs ul").append('\n <li class="is-active" id="waitCheckBtn">\n <a class="menu-btn" style="background-color:#82d26d !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:#7cb7e8 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>打开待下载</span>\n </a>\n </li>\n '),
$("#waitCheckBtn").on("click", (t => {
this.openWaitCheck(t);
})), $("#waitDownBtn").on("click", (t => {
this.openFavorite(t);
}));
}
openWaitCheck() {
let t = 0, e = this.getSetting("waitCheckCount", 5);
$(".movie-list .item:visible").each(((n, a) => {
if (t >= e) return false;
if (0 === $(a).find("span:contains('待下载')").length) {
const e = $(a).find("a").attr("href");
e && (window.open(e), t++);
}
}));
}
openFavorite() {
let t = this.getSetting("waitCheckCount", 10);
for (let e = 0; e < t; e++) {
if (e >= this.favoriteList.length) return;
window.open(this.favoriteList[e].url);
}
}
archiveFile() {
this.storageManager.archiveFile().then((t => {
let e = t.successMsgList, n = t.errorMsgList;
msg.list(e, n), e.length || n.length || layer.msg("没有可归档文件");
}));
}
checkSubTitle() {
this.storageManager.checkSubTitle().then((t => {
let e = t.data;
if (0 === e.length) return void layer.msg("视频字幕完整");
let n = '<table class="data-table">';
n += "<thead><tr>", n += "<th>番号</th>", n += "<th>文件路径</th>", n += "<th>操作</th>",
n += "</tr></thead>", n += "<tbody>", $.each(e, (function (t, e) {
n += "<tr>", n += "<td>" + e.carNum + "</td>", n += "<td>" + e.filePath + "</td>",
n += `<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>`,
n += "</tr>";
})), n += "</tbody>", n += "</table>", layer.open({
type: 1,
title: "检查字幕",
content: n,
area: ["1000px", "400px"]
});
}));
}
}
class HighlightMagnetPlugin extends BasePlugin {
handle() {
let t = $("#magnets-content .name").toArray(), e = false;
t.forEach((t => {
let n = $(t), a = n.text().toLowerCase();
a.indexOf("4k") > -1 && n.css("color", "#f40"), (a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1) && (e = true);
})), e && t.forEach((t => {
let e = $(t), n = e.text().toLowerCase();
n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.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" === this.getSetting("autoPlay", "no") && $("#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 n = {
clientX: t.clientX,
clientY: t.clientY + 120
};
this.utils.q(n, `是否屏蔽关键词${e}?`, (() => {
this.saveFilterKeyword(e).then((t => {
this.closePage();
}));
}));
}
})), $(".male").prev().toArray().forEach((t => {
this.utils.rightClick($(t), (e => {
let n = $(t).text().trim();
this.utils.q(e, `是否屏蔽演员${n}?`, (() => {
this.saveFilterActor(n).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(), n = t.find((t => $(t).find(".vid-title").text().toLowerCase().includes(e)));
if (n) return void (window.location.href = $(n).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);
})), h.registerHotkey("z", (() => {
const t = document.getElementById("vjs_video_3_html5_api");
t && (t.currentTime += 5);
})), h.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), h.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 n = $(e);
n.text().toLowerCase().includes(t) || n.parent().parent().hide();
}));
}
}
class JablePlugin extends BasePlugin {
handle() {
window.location.hostname.includes("jable") && ($("#player")[0].play(), $('button[data-plyr="fullscreen"]').click(),
h.registerHotkey("a", (() => window.parent.postMessage("a", "*"))), h.registerHotkey("s", (() => window.parent.postMessage("s", "*"))));
}
}
var g = (() => "undefined" != typeof GM_xmlhttpRequest ? GM_xmlhttpRequest : void 0)();
class Fc2Plugin extends BasePlugin {
injectBean(detailPageMenuPlugin) {
this.detailPageMenuPlugin = detailPageMenuPlugin;
}
handle() {
}
initCss() {
return "\n /* 弹层样式 */\n .movie-detail-layer .layui-layer-title {\n font-size: 18px;\n color: #333;\n background: #f8f8f8;\n }\n \n \n /* 容器样式 */\n .movie-detail-container {\n display: flex;\n height: 100%;\n background: #fff;\n }\n \n .movie-poster-container {\n flex: 0 0 60%;\n padding: 15px;\n }\n \n .right-box {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n }\n \n /* 预告片iframe */\n .movie-trailer {\n width: 100%;\n height: 100%;\n min-height: 400px;\n background: #000;\n border-radius: 4px;\n }\n \n /* 电影信息样式 */\n .movie-title {\n font-size: 24px;\n margin-bottom: 15px;\n color: #333;\n }\n \n .movie-meta {\n margin-bottom: 20px;\n color: #666;\n }\n \n .movie-meta span {\n margin-right: 15px;\n }\n \n /* 演员列表 */\n .actor-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 10px;\n }\n \n .actor-tag {\n padding: 4px 12px;\n background: #f0f0f0;\n border-radius: 15px;\n font-size: 12px;\n color: #555;\n }\n \n /* 图片列表 */\n .image-list {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 10px;\n }\n \n .movie-image-thumb {\n width: 120px;\n height: 80px;\n object-fit: cover;\n border-radius: 4px;\n cursor: pointer;\n transition: transform 0.3s;\n }\n \n .movie-image-thumb:hover {\n transform: scale(1.05);\n }\n \n /* 加载中和错误状态 */\n .search-loading, .movie-error {\n padding: 40px;\n text-align: center;\n color: #999;\n }\n \n .movie-error {\n color: #f56c6c;\n }\n \n .fancybox-container{\n z-index:99999999\n }\n \n \n /* 错误提示样式 */\n .movie-not-found, .movie-error {\n text-align: center;\n padding: 30px;\n color: #666;\n }\n \n .movie-not-found h3, .movie-error h3 {\n color: #f56c6c;\n margin: 15px 0;\n }\n \n .icon-warning, .icon-error {\n font-size: 50px;\n color: #e6a23c;\n }\n \n .icon-error {\n color: #f56c6c;\n }\n\n ";
}
openFc2Page(t, e, n) {
(t => new Promise(((e, n) => {
let a = `https://hohoj.tv/search?text=${t}`;
console.log("请求页面", a), g({
method: "GET",
url: a,
onload: function (n) {
let a = n.responseText, i = null;
if (a.includes("找不到任何影片")) return void e({
pageUrl: i
});
const r = (new DOMParser).parseFromString(a, "text/html");
$(r).find(".video-item a").toArray().forEach((e => {
if ($(e).find(".video-item-title").text().includes(t)) {
let t = $(e).attr("href").split("id=")[1];
i = "https://hohoj.tv/embed?id=" + t;
}
})), console.log("解析成功:", i), e({
pageUrl: i
});
},
onerror: function (t) {
console.error("Request failed:", t);
}
});
})))(e.replace("FC2-", "")).then((t => {
let e = t.pageUrl;
const n = document.querySelector(".movie-poster-container"), a = document.querySelector(".movie-trailer");
document.querySelector(".movie-info-container"), e ? $(a).attr("src", e) : (n.innerHTML = '\n <div class="movie-not-found">\n <i class="icon-warning"></i>\n <h3>未找到相关内容</h3>\n <p>hohoj.tv 中没有找到与当前番号相关的影片信息</p>\n </div>\n ',
a.style.display = "none");
}));
let a = "";
(t => new Promise(((e, n) => {
$.ajax({
method: "GET",
url: `${l}/v4/movies/${t}`,
headers: {
jdSignature: o()
},
success: t => {
t.data || (i.msg.error("发生错误" + t.message), n(t.message));
const a = t.data.movie, r = a.id, s = a.actors, o = a.origin_title, l = a.number, c = a.score, d = a.release_date, h = a.preview_images, g = [];
h.forEach((t => {
g.push(t.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
})), console.log(s), e({
movieId: r,
actors: s,
title: o,
carNum: l,
score: c,
releaseDate: d,
imgList: g
});
},
error: t => {
i.msg.error("发生错误" + t.responseJSON.message), n(t);
}
});
})))(t).then((t => {
const e = t.actors || [], n = t.imgList || [];
let i = "";
if (e.length > 0) for (let s = 0; s < e.length; s++) {
let t = e[s];
i += `<span class="actor-tag"><a href="/actors/${t.id}" target="_blank">${t.name}</a></span>`,
0 === t.gender && (a += t.name);
} else i = '<span class="no-data">暂无演员信息</span>';
let r = "";
r = Array.isArray(n) && n.length > 0 ? n.map(((t, e) => `\n <a href="${t}" data-fancybox="movie-gallery" data-caption="剧照 ${e + 1}">\n <img src="${t}" class="movie-image-thumb" alt=""/>\n </a>\n `)).join("") : '<div class="no-data">暂无剧照</div>',
$(".movie-info-container").html(`\n <h3 class="movie-title">${t.title || "无标题"}</h3>\n <div class="movie-meta">\n <span>番号: ${t.carNum || "未知"}</span>\n <span>年份: ${t.releaseDate || "未知"}</span>\n <span>评分: ${t.score || "无"}</span>\n </div>\n <div class="movie-actors">\n <div class="actor-list">主演: ${i}</div>\n </div>\n <div class="movie-gallery" style="margin-top:10px">\n <h4>剧照: </h4>\n <div class="image-list">${r}</div>\n </div>\n `);
})).catch((t => {
console.error(t), $(".movie-info-container").html(`\n <div class="movie-error">加载失败: ${t.message}</div>\n `);
})), (t => new Promise(((e, n) => {
$.ajax({
method: "GET",
url: `${l}/v1/movies/${t}/magnets`,
headers: {
jdSignature: o()
},
success: t => {
let n = t.data.magnets;
e({
magnetList: n
});
},
error: t => {
i.msg.error("发生错误" + t), n(t);
}
});
})))(t).then((t => {
let e = t.magnetList, n = "";
if (e.length > 0) for (let a = 0; a < e.length; a++) {
let t = e[a], i = "";
a % 2 == 0 && (i = "odd"), n += `\n <div class="item columns is-desktop ${i}">\n <div class="magnet-name column is-four-fifths">\n <a href="magnet:?xt=urn:btih:${t.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n <span class="name">${t.name}.torrent</span>\n <br>\n <span class="meta">\n ${t.hash}, ${t.files_count}個文件\n </span>\n <br>\n <div class="tags">\n ${t.hd ? '<span class="tag is-primary 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:${t.hash}" type="button"> 複製 </button>\n </div>\n <div class="date column"><span class="time">${t.created_at}</span></div>\n </div>\n `;
} else n = '<span class="no-data">暂无磁力信息</span>';
$("#magnets-content").html(n);
})).catch((t => {
console.error(t), $("#magnets-content").html(`\n <div class="movie-error">加载失败: ${t.message}</div>\n `);
}));
let r = this.getSetting("reviewCount", 20);
c(t, r).then((t => {
let e = $("#reviews-content");
if (0 === t.length) return void e.html('<div class="movie-error">无评论</div> ');
let n = '<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));"/>';
t.forEach((t => {
let e = false;
for (let n = 0; n < this.reviewKeyword.length; n++) if (t.content.indexOf(this.reviewKeyword[n]) > -1) {
e = true;
break;
}
if (e) return;
let a = "";
for (let n = 0; n < t.score; n++) a += '<i class="icon-star"></i>';
n += `\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">${a}</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 `;
})), e.html(n);
})).catch((t => {
console.error(t), $("#reviews-content").html(`\n <div class="movie-error">加载失败: ${t.message}</div>\n `);
})), layer.open({
type: 1,
title: "影片详情",
content: '\n <div class="movie-detail-container">\n <div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>\n <div class="right-box">\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n <div id="btn-box">\n <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>收藏</span></a>\n <a id="filterBtn" class="menu-btn" style="background-color:#de3333"><span>屏蔽</span></a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>加入已下载</span></a>\n </div>\n <div class="message video-panel" style="margin-top:20px">\n <div id="magnets-content" class="magnet-links">\n <div class="search-loading">加载中...</div>\n </div>\n </div>\n <div id="reviews-content">\n <div class="search-loading">加载中...</div>\n </div>\n </div>\n </div>\n ',
area: ["80%", "90%"],
skin: "movie-detail-layer",
scrollbar: false,
success: (t, i) => {
$("#favoriteBtn").on("click", (t => {
this.changeData(e, n, a, "favorite").then((t => layer.closeAll()));
})), $("#filterBtn").on("click", (t => {
this.utils.q(t, `是否屏蔽${e}?`, (() => {
this.changeData(e, n, a, "filter").then((t => layer.closeAll()));
}));
})), $("#hasDownBtn").on("click", (t => {
this.changeData(e, n, a, "hasDown").then((t => layer.closeAll()));
}));
}
});
}
}
class FoldCategoryPlugin extends BasePlugin {
handle() {
if (!this.isListPage) return;
let t = $(".tabs ul"), e = $("h2.section-title"), n = "y" === localStorage.foldCategory;
const [a, i] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
let r;
if (t.length > 0) {
if (r = $("#tags"), !$("#tags dl div.tag.is-info").map((function () {
return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
})).get().join(" ")) return;
t.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>${a}</span>\n <i style="margin-left: 10px" class="${i}"></i>\n </a>\n </li>\n `);
}
e.length > 0 && (e.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>${a}</span>\n <i style="margin-left: 10px" class="${i}"></i>\n </a>\n </div>\n `),
r = $("section > div > div.box")), r[n ? "hide" : "show"](), $("#foldCategoryBtn").on("click", (t => {
t.preventDefault(), n = !n, localStorage.foldCategory = n ? "y" : "n";
const [e, a] = n ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"];
$("#foldCategoryBtn").find("span").text(e).end().find("i").attr("class", a), r[n ? "hide" : "show"]();
}));
}
}
class SettingPlugin extends BasePlugin {
initCss() {
return "\n <style>\n .nav-btn::after {\n content:none !important;\n }\n \n .setting-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n padding: 10px;\n border: 1px solid #ddd;\n border-radius: 5px;\n background-color: #f9f9f9;\n }\n .setting-label {\n min-width: 150px;\n font-weight: bold;\n margin-right: 10px;\n }\n .form-content{\n min-width: 100px;\n }\n .form-content * {\n width: 100%;\n padding: 5px;\n margin-right: 10px;\n }\n #saveBtn {\n padding: 8px 20px;\n background-color: #4CAF50;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n margin-top: 10px;\n float:right;\n }\n #saveBtn:hover {\n background-color: #45a049;\n }\n </style\n ";
}
handle() {
$(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-right:15px !important;">\n 设置\n </a>\n </div>');
localStorage.getItem("setting") || localStorage.setItem("setting", JSON.stringify({
hideFilterItem: "yes",
autoPlay: "no",
reviewCount: 20,
waitCheckCount: 5
})), $("#setting-btn").on("click", (() => {
layer.open({
type: 1,
title: "设置",
content: '\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 </div>\n <div style="margin: 20px">\n <div class="setting-item">\n <span class="setting-label">是否隐藏已屏蔽内容:</span>\n <div class="form-content">\n <select id="hideFilterItem">\n <option value="yes">是</option>\n <option value="no">否</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">是否打开预览视频:</span>\n <div class="form-content">\n <select id="autoPlay">\n <option value="yes">是</option>\n <option value="no">否</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">评论区条数:</span>\n <div class="form-content">\n <select id="reviewCount">\n <option value="10">10条</option>\n <option value="20">20条</option>\n <option value="30">30条</option>\n </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 <button id="saveBtn">保存设置</button>\n </div>\n ',
area: ["30%", "80%"],
scrollbar: false,
success: (t, e) => {
this.loadForm(), this.bindClick();
}
});
}));
}
bindClick() {
$("#importBtn").on("click", (t => this.importData(t))), $("#exportBtn").on("click", (t => this.exportData(t)));
}
loadForm() {
const t = JSON.parse(localStorage.getItem("setting")) || {};
void 0 !== t.hideFilterItem && $("#hideFilterItem").val(t.hideFilterItem), void 0 !== t.reviewCount && $("#reviewCount").val(t.reviewCount),
void 0 !== t.waitCheckCount && $("#waitCheckCount").val(t.waitCheckCount), void 0 !== t.autoPlay && $("#autoPlay").val(t.autoPlay),
$("#saveBtn").on("click", (() => {
const t = $("#hideFilterItem").val(), e = $("#autoPlay").val(), n = $("#reviewCount").val(), a = $("#waitCheckCount").val();
let i = JSON.parse(localStorage.getItem("setting")) || {};
i.hideFilterItem = t, i.reviewCount = n, i.waitCheckCount = a, i.autoPlay = e, console.log(i),
localStorage.setItem("setting", JSON.stringify(i)), layer.success("保存成功"), window.location.reload();
}));
}
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 n = new FileReader;
n.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);
}
}, n.onerror = () => {
layer.msg("读取文件时出错", {
icon: 2
});
}, n.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`, n = new Blob([t], {
type: "application/json"
}), a = URL.createObjectURL(n), i = document.createElement("a");
i.href = a, i.download = e, document.body.appendChild(i), i.click(), setTimeout((() => {
document.body.removeChild(i), URL.revokeObjectURL(a);
}), 100), layer.msg("数据导出成功", {
icon: 1
}), console.log("数据导出成功:", e);
} catch (e) {
console.error("导出数据时出错:", e), layer.msg("导出数据时出错: " + e.message, {
icon: 2
});
}
}
}
class HistoryPlugin extends BasePlugin {
initCss() {
return "\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 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 </style>\n ";
}
handle() {
$(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n 历史列表\n </a>\n </div>'),
$("#setting-btn").on("click", (t => {
this.openHistory(t);
}));
}
openHistory(t) {
this.storageManager.getData().then((t => {
console.log(t);
const e = t.data.dataList || [];
e.reverse();
let n = [...e];
const a = {
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 = a.filtered;
return t.hasDown ? e = a.hasDown : t.favorite && (e = a.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) => {
n = (t => "all" === t ? [...e] : e.filter(a[t].condition))(i), $(t).find(".data-table").replaceWith(r(n)),
$(t).find(".history-btn").removeClass("active").filter(`[data-action="${i}"]`).addClass("active");
}, o = (t, e, n) => {
"详情" === t && this.openPage(e.url), "移除" === t && this.utils.q(n, `是否移除${e.carNum}?`, (() => {
this.storageManager.removeData(e.carNum).then((t => {
let n = $(".movie-list .item").toArray();
for (let a = 0; a < n.length; a++) {
let t = $(n[a]), 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(n)}\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();
}
});
}));
}
}
window.$ = window.jQuery = a, i.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/css/layui.min.css"),
layer.info = t => {
layer.msg(t, {
icon: 0
});
}, layer.success = t => {
layer.msg(t, {
icon: 1
});
}, layer.error = t => {
layer.msg(t, {
icon: 2
});
}, function () {
const t = new PluginManager;
t.register(ListPagePlugin), t.register(AutoPagePlugin), t.register(Fc2Plugin), t.register(FoldCategoryPlugin),
t.register(ListPageMenuPlugin), t.register(HistoryPlugin), t.register(SettingPlugin),
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();
}();
})(md5, $);