// ==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} <span class="score-stars">${starsHtml}</span> <span class="time">${item.created_at.replace("T", " ").replace(".000Z", "")}</span> 点赞:${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", "*"))));
}();
})();