// ==UserScript==
// @name JAV-JSH
// @namespace https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version 1.6.7
// @author xie bro
// @description Jav-鉴黄师 收藏、屏蔽、标记已下载; 免VIP查看热榜、Top250排行榜、Fc2ppv等数据; 可查看所有评论信息; 支持云盘备份; 以图识图
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com
// @include https://javdb*.com/*
// @include https://www.javbus.com/*
// @include https://javtrailers.com/*
// @include https://subtitlecat.com/*
// @include https://www.aliyundrive.com/*
// @exclude https://www.javbus.com/forum/*
// @exclude https://www.javbus.com/*actresses
// @require data:application/javascript,;(function%20hookBody()%20%7B%20if%20(document.readyState%20!%3D%3D%20%22loading%22)%20%7B%20return%3B%20%7D%20const%20initialHideStyle%20%3D%20document.createElement(%22style%22)%3B%20initialHideStyle.textContent%20%3D%20%60%20body%20%7B%20opacity%3A%200%20!important%3B%20visibility%3A%20hidden%20!important%3B%20%7D%20body.script-ready%20%7B%20opacity%3A%201%20!important%3B%20visibility%3A%20visible%20!important%3B%20%7D%20%60%3B%20document.head.appendChild(initialHideStyle)%3B%20setTimeout(()%20%3D%3E%20%7B%20document.body.classList.add(%22script-ready%22)%3B%20%7D%2C%203e3)%3B%20%7D)()%3B
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js
// @connect hohoj.tv
// @connect xunlei.com
// @connect geilijiasu.com
// @connect aliyundrive.com
// @connect aliyundrive.net
// @connect ja.wikipedia.org
// @connect beta.magnet.pics
// @connect jdforrepam.com
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_download
// @run-at document-start
// ==/UserScript==
var __defProp = Object.defineProperty, __typeError = msg => {
throw TypeError(msg);
}, __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
enumerable: !0,
configurable: !0,
writable: !0,
value: value
}) : obj[key] = value, __publicField = (obj, key, value) => __defNormalProp(obj, "symbol" != typeof key ? key + "" : key, value), __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"),
method);
!function() {
"use strict";
var _StorageManager_instances, autoCleanup_fn, saveFilterItem_fn, _SettingPlugin_instances, handleSyncData_fn;
const Bus = {
boxSelector: ".masonry",
itemSelector: ".masonry .item",
coverImgSelector: ".item .photo-frame img",
requestDomItemSelector: "#waterfall .item"
}, Db = {
boxSelector: ".movie-list",
itemSelector: ".movie-list .item",
coverImgSelector: ".cover img",
requestDomItemSelector: ".movie-list .item"
}, isJavDb = window.location.href.includes("javdb"), isJavBus = window.location.href.includes("javbus"), Status_FAVORITE = "favorite", Status_FILTER = "filter", Status_HAS_DOWN = "hasDown";
function insertStyle(css) {
if (css) if (css.includes("<style>")) document.head.insertAdjacentHTML("beforeend", css); else {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
}
}
isJavBus && insertStyle("\n<style>\n .masonry {\n height: 100% !important;\n width: 100% !important;\n padding: 0 15px !important;\n }\n .masonry {\n display: grid;\n column-gap: 10px; /* 列间距*/\n row-gap: 10px; /* 行间距 */\n grid-template-columns: repeat(4, minmax(0, 1fr));\n }\n .masonry .item {\n /*position: initial !important;*/\n top: initial !important;\n left: initial !important;\n float: none !important;\n background-color:#c4b1b1;\n position: relative !important;\n }\n \n .masonry .item:hover {\n box-shadow: 0 .5em 1em -.125em rgba(10, 10, 10, .1), 0 0 0 1px #485fc7;\n }\n .masonry .movie-box{\n width: 100% !important;\n height: 100% !important;\n margin: 0 !important;\n }\n .masonry .movie-box .photo-frame {\n height: 70% !important;\n margin: 0 !important;\n }\n .masonry .movie-box img {\n max-height: 300px;\n height: 100% !important;\n object-fit: cover; /* 保持比例,裁剪多余部分 */\n object-position: top; /* 从中间裁剪(可调整:top, bottom, left, right) */\n }\n .masonry .movie-box img:hover {\n transform: scale(1.04);\n transition: transform 0.3s;\n }\n .masonry .photo-info{\n height: 30% !important;\n }\n .masonry .photo-info span {\n display: inline-block; /* 或者 block */\n max-width: 100%; /* 根据父容器限制宽度 */\n white-space: nowrap; /* 禁止换行 */\n overflow: hidden; /* 隐藏溢出内容 */\n text-overflow: ellipsis; /* 显示省略号 */\n }\n \n /* 无码页面的样式 */\n .photo-frame .mheyzo,\n .photo-frame .mcaribbeancom2{\n margin-left: 0 !important;\n }\n .avatar-box{\n width: 100% !important;\n display: flex !important;\n margin:0 !important;\n }\n .avatar-box .photo-info{\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 30px;\n flex-direction: row;\n background-color:#fff !important;\n }\n /*.photo-info .item-tag{\n position: relative;\n }*/\n footer,#related-waterfall{\n display: none!important;\n }\n</style>\n");
isJavDb && insertStyle('\n<style>\n .navbar {\n z-index: 12345679 !important;\n }\n \n .sub-header,\n #search-bar-container, /*搜索框*/\n #footer,\n .search-recent-keywords, /*搜索框底部热搜词条*/\n .app-desktop-banner,\n div[data-controller="movie-tab"] .tabs,\n h3.main-title,\n div.video-meta-panel > div > div:nth-child(2) > nav > div.review-buttons > div:nth-child(2), /* 下载 订正 按钮*/\n div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(3), /* 相关清单*/\n div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(2), /* 短评按钮*/\n div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(1), /*磁力面板 按钮*/\n .top-meta,\n .float-buttons {\n display: none !important;\n }\n \n div.tabs.no-bottom,\n .tabs ul {\n border-bottom: none !important;\n }\n \n \n /* 视频列表项 相对相对 方便标签绝对定位*/\n .movie-list .item {\n position: relative !important;\n }\n\n</style>\n');
insertStyle("\n<style>\n .a-primary, /* 主按钮 - 浅蓝色 */\n .a-success, /* 成功按钮 - 浅绿色 */\n .a-danger, /* 危险按钮 - 浅粉色 */\n .a-warning, /* 警告按钮 - 浅橙色 */\n .a-info, /* 信息按钮 - 浅青色 */\n .a-dark, /* 深色按钮 - 改为中等灰色(保持浅色系中的对比) */\n .a-outline, /* 轮廓按钮 - 浅灰色边框 */\n .a-disabled /* 禁用按钮 - 极浅灰色 */\n {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px 14px;\n margin-left: 10px;\n border-radius: 6px;\n text-decoration: none;\n font-size: 13px;\n font-weight: 500;\n transition: all 0.2s ease;\n cursor: pointer;\n border: 1px solid rgba(0, 0, 0, 0.08);\n white-space: nowrap;\n }\n \n .a-primary {\n background: #e0f2fe;\n color: #0369a1;\n border-color: #bae6fd;\n }\n \n .a-primary:hover {\n background: #bae6fd;\n }\n \n .a-success {\n background: #dcfce7;\n color: #166534;\n border-color: #bbf7d0;\n }\n \n .a-success:hover {\n background: #bbf7d0;\n }\n \n .a-danger {\n background: #fee2e2;\n color: #b91c1c;\n border-color: #fecaca;\n }\n \n .a-danger:hover {\n background: #fecaca;\n }\n \n .a-warning {\n background: #ffedd5;\n color: #9a3412;\n border-color: #fed7aa;\n }\n \n .a-warning:hover {\n background: #fed7aa;\n }\n \n .a-info {\n background: #ccfbf1;\n color: #0d9488;\n border-color: #99f6e4;\n }\n \n .a-info:hover {\n background: #99f6e4;\n }\n \n .a-dark {\n background: #e2e8f0;\n color: #334155;\n border-color: #cbd5e1;\n }\n \n .a-dark:hover {\n background: #cbd5e1;\n }\n \n .a-outline {\n background: transparent;\n color: #64748b;\n border-color: #cbd5e1;\n }\n \n .a-outline:hover {\n background: #f8fafc;\n }\n \n .a-disabled {\n background: #f1f5f9;\n color: #94a3b8;\n border-color: #e2e8f0;\n cursor: not-allowed;\n }\n \n .a-disabled:hover {\n transform: none;\n box-shadow: none;\n background: #f1f5f9;\n }\n</style>\n");
insertStyle("\n<style>\n /* 全局通用样式 */\n .fr-btn {\n float: right;\n margin-left: 4px !important;\n }\n \n .menu-box {\n position: fixed;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n display: flex;\n flex-direction: column;\n z-index: 1000;\n gap: 6px;\n }\n \n .menu-btn {\n display: inline-block !important;\n min-width: 80px;\n padding: 7px 12px;\n border-radius: 4px;\n color: white !important;\n text-decoration: none;\n font-weight: bold;\n font-size: 12px;\n text-align: center;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n border: none;\n line-height: 1.3;\n margin: 0;\n }\n \n .menu-btn:hover {\n transform: translateY(-1px);\n box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);\n opacity: 0.9;\n }\n \n .menu-btn:active {\n transform: translateY(0);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\n }\n \n .do-hide {\n display: none !important;\n }\n</style>\n");
_StorageManager_instances = new WeakSet;
autoCleanup_fn = async function() {
if (!window.location.hostname.includes("javdb")) return;
(await this.forage.keys()).forEach((k => k.startsWith("SCORE_") && this.forage.removeItem(k)));
const now = Date.now();
try {
const lastCleanupTime = await this.forage.getItem("lastCleanupTime");
if (lastCleanupTime && now - lastCleanupTime < 864e5) return;
const keys = await this.forage.keys();
for (const key of keys) {
if (this.interceptedKeys.includes(key)) continue;
const storedData = await this.forage.getItem(key);
if ("object" == typeof storedData && "expires" in storedData && "expiresStr" in storedData && Date.now() > storedData.expires) {
console.log("清理过期数据:", key);
await this.forage.removeItem(key);
}
}
await this.forage.setItem("lastCleanupTime", now);
} catch (error) {
console.error("[自动清理失败]", error);
await this.forage.setItem("lastCleanupTime", now);
}
};
saveFilterItem_fn = async function(items, storageKey, itemName) {
let itemList;
if (Array.isArray(items)) itemList = [ ...items ]; else {
itemList = await this.forage.getItem(storageKey) || [];
if (itemList.includes(items)) {
const errorMsg = `${items} ${itemName}已存在`;
show.error(errorMsg);
throw new Error(errorMsg);
}
itemList.push(items);
}
await this.forage.setItem(storageKey, itemList);
return itemList;
};
let StorageManager = class _StorageManager {
constructor() {
__privateAdd(this, _StorageManager_instances);
__publicField(this, "car_list_key", "car_list");
__publicField(this, "filter_actor_key", "filter_actor");
__publicField(this, "title_filter_keyword_key", "title_filter_keyword");
__publicField(this, "review_filter_keyword_key", "review_filter_keyword");
__publicField(this, "setting_key", "setting");
__publicField(this, "auto_page_key", "autoPage");
__publicField(this, "fold_category_key", "foldCategory");
__publicField(this, "review_ts_key", "review_ts");
__publicField(this, "review_sign_key", "review_sign");
__publicField(this, "actress_prefix_key", "z_actress_");
__publicField(this, "score_prefix_key", "z_score_");
__publicField(this, "forage", localforage.createInstance({
driver: localforage.INDEXEDDB,
name: "JAV-JSH",
version: 1,
storeName: "appData"
}));
__publicField(this, "interceptedKeys", [ this.car_list_key, this.filter_actor_key, this.title_filter_keyword_key, this.review_filter_keyword_key, this.setting_key ]);
if (_StorageManager.instance) throw new Error("LocalStorageManager已被实例化过了!");
_StorageManager.instance = this;
__privateMethod(this, _StorageManager_instances, autoCleanup_fn).call(this).then();
}
async saveFilterActor(keywords) {
return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_actor_key, "演员");
}
async saveReviewFilterKeyword(keywords) {
return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.review_filter_keyword_key, "评论关键词");
}
async saveTitleFilterKeyword(keywords) {
return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.title_filter_keyword_key, "标题关键词");
}
async getFilterActorList() {
return await this.forage.getItem(this.filter_actor_key) || [];
}
async getTitleFilterKeyword() {
return await this.forage.getItem(this.title_filter_keyword_key) || [];
}
async getSetting(attribute = null, defaultVal) {
const settingObj = await this.forage.getItem(this.setting_key) || {};
if (null === attribute) return settingObj;
const value = settingObj[attribute];
return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || isNaN(Number(value)) ? value : Number(value) : defaultVal;
}
async saveSetting(settingObj) {
await this.forage.setItem(this.setting_key, settingObj);
}
async getReviewFilterKeywordList() {
return await this.forage.getItem(this.review_filter_keyword_key) || [];
}
async saveCar(carNum, url, actress, actionType) {
if (!carNum) {
show.error("番号为空!");
throw new Error("番号为空!");
}
if (!url) {
show.error("url为空!");
throw new Error("url为空!");
}
url.includes("http") || (url = window.location.origin + url);
const carList = await this.forage.getItem(this.car_list_key) || [];
let carData = carList.find((item => item.carNum === carNum));
if (carData) carData.createDate = utils.getNowStr(); else {
carData = {
carNum: carNum,
url: url,
actress: actress,
status: "",
createDate: utils.getNowStr()
};
carList.push(carData);
}
switch (actionType) {
case Status_FILTER:
if (carData.status === Status_FILTER) {
const msg2 = `${carNum} 已在屏蔽列表中`;
show.error(msg2);
throw new Error(msg2);
}
carData.status = Status_FILTER;
break;
case Status_FAVORITE:
if (carData.status === Status_FAVORITE) {
const msg2 = `${carNum} 已在收藏列表中`;
show.error(msg2);
throw new Error(msg2);
}
carData.status = Status_FAVORITE;
break;
case Status_HAS_DOWN:
carData.status = Status_HAS_DOWN;
break;
default:
const msg = "actionType错误";
show.error(msg);
throw new Error(msg);
}
await this.forage.setItem(this.car_list_key, carList);
}
async getCarList() {
return (await this.forage.getItem(this.car_list_key) || []).sort(((a, b) => {
if (!a || !b) return 0;
const dateA = a.createDate ? new Date(a.createDate).getTime() : 0;
return (b.createDate ? new Date(b.createDate).getTime() : 0) - dateA;
}));
}
async getCar(carNum) {
return (await this.getCarList()).find((item => item.carNum === carNum));
}
async removeCar(carNum) {
const carList = await this.getCarList(), initialLength = carList.length, updatedList = carList.filter((car => car.carNum !== carNum));
if (updatedList.length === initialLength) {
show.error(`${carNum} 不存在`);
return !1;
}
await this.forage.setItem(this.car_list_key, updatedList);
return !0;
}
async overrideCarList(newList) {
if (!Array.isArray(newList)) throw new TypeError("必须传入数组类型数据");
const invalidItems = newList.filter((item => !item || "object" != typeof item || !item.carNum));
if (invalidItems.length > 0) throw new Error(`缺少必要字段 carNum 的数据项: ${invalidItems.length} 条`);
const carNums = new Set, duplicates = newList.filter((item => {
if (carNums.has(item.carNum)) return !0;
carNums.add(item.carNum);
return !1;
}));
if (duplicates.length > 0) throw new Error(`发现重复: ${duplicates.slice(0, 3).map((d => d.carNum)).join(", ")}${duplicates.length > 3 ? "..." : ""}`);
await this.forage.setItem(this.car_list_key, newList);
}
async getItem(key) {
if (this.interceptedKeys.includes(key)) {
let errorMsg = `危险操作, 改key已有方法实现获取, 请用内部方法调用! key: ${key}`;
show.error(errorMsg);
throw new Error(errorMsg);
}
const storedData = await this.forage.getItem(key);
if (null == storedData) return null;
if ("object" == typeof storedData && "expires" in storedData && "expiresStr" in storedData) {
if (Date.now() > storedData.expires) {
await this.forage.removeItem(key);
return null;
}
return storedData.value;
}
return storedData;
}
async setItem(key, value, maxAge = null) {
if (this.interceptedKeys.includes(key)) {
let errorMsg = `危险操作, 改key已有方法实现获取, 请用内部方法调用! key: ${key}`;
show.error(errorMsg);
throw new Error(errorMsg);
}
let data = value;
if (null !== maxAge) {
const expires = Date.now() + maxAge;
data = {
value: value,
expires: expires,
expiresStr: utils.formatDate(new Date(expires))
};
}
return await this.forage.setItem(key, data);
}
async removeItem(key) {
if (this.interceptedKeys.includes(key)) {
let errorMsg = `危险操作, 改key不可删除! key: ${key}`;
show.error(errorMsg);
throw new Error(errorMsg);
}
return await this.forage.removeItem(key);
}
async importData(dataJson) {
let arrayData = dataJson.filterKeywordList;
Array.isArray(arrayData) && await this.forage.setItem(this.title_filter_keyword_key, arrayData);
arrayData = dataJson.filterActorList;
Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
arrayData = dataJson.reviewKeywordList;
Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
dataJson.dataList && await this.overrideCarList(dataJson.dataList);
arrayData = dataJson[this.title_filter_keyword_key];
Array.isArray(arrayData) && await this.forage.setItem(this.title_filter_keyword_key, arrayData);
arrayData = dataJson[this.filter_actor_key];
Array.isArray(arrayData) && await this.forage.setItem(this.filter_actor_key, arrayData);
arrayData = dataJson[this.review_filter_keyword_key];
Array.isArray(arrayData) && await this.forage.setItem(this.review_filter_keyword_key, arrayData);
dataJson[this.car_list_key] && await this.overrideCarList(dataJson[this.car_list_key]);
dataJson.setting && await this.saveSetting(dataJson.setting);
}
async exportData() {
return {
car_list: await this.getCarList(),
filter_actor: await this.getFilterActorList(),
title_filter_keyword: await this.getTitleFilterKeyword(),
review_filter_keyword: await this.getReviewFilterKeywordList(),
setting: await this.getSetting()
};
}
};
class Utils {
constructor() {
__publicField(this, "intervalContainer", {});
__publicField(this, "insertStyle", (css => {
if (css) {
-1 === css.indexOf("<style>") && (css = "<style>" + css + "</style>");
$("head").append(css);
}
}));
Utils.instance || (Utils.instance = this);
return Utils.instance;
}
importResource(url) {
let tag;
if (url.indexOf("css") >= 0) {
tag = document.createElement("link");
tag.setAttribute("rel", "stylesheet");
tag.href = url;
} else {
tag = document.createElement("script");
tag.setAttribute("type", "text/javascript");
tag.src = url;
}
document.documentElement.appendChild(tag);
}
openPage(url, title, shadeClose, event2) {
shadeClose || (shadeClose = !0);
if (event2 && (event2.ctrlKey || event2.metaKey)) window.open(url); else {
url.includes("?") ? url += "&hideNav=1" : url += "?hideNav=1";
layer.open({
type: 2,
title: title,
content: url,
scrollbar: !1,
shadeClose: shadeClose,
area: [ "80%", "90%" ],
isOutAnim: !1,
anim: -1
});
}
}
closePage() {
parent.document.documentElement.style.overflow = "auto";
[ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
parent.document.querySelectorAll(selector).forEach((function(el) {
el.parentNode.removeChild(el);
}));
}));
window.close();
}
loopDetector(condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = !0) {
let run = !1;
const uuid = Math.random(), start = (new Date).getTime();
this.intervalContainer[uuid] = setInterval((() => {
if ((new Date).getTime() - start > timeout) {
console.warn("loopDetector timeout!", condition, after);
run = runWhenTimeout;
}
if (condition() || run) {
clearInterval(this.intervalContainer[uuid]);
after && after();
delete this.intervalContainer[uuid];
}
}), detectInterval);
}
rightClick(element, callback) {
if (element) {
element.jquery ? element = element.toArray() : element instanceof HTMLElement ? element = [ element ] : Array.isArray(element) || (element = [ element ]);
element && 0 !== element.length ? element.forEach((el => {
el && el.addEventListener("contextmenu", (event2 => {
callback(event2);
}));
})) : console.error("rightClick(), 找不到元素");
}
}
q(event2, msg, fun, cancelFun) {
let x, y;
if (event2) {
x = event2.clientX - 130;
y = event2.clientY - 120;
} else {
x = window.innerWidth / 2 - 120;
y = window.innerHeight / 2 - 120;
}
let confirmIndex = layer.confirm(msg, {
offset: [ y, x ],
title: "提示",
btn: [ "确定", "取消" ],
zIndex: 999999991
}, (function() {
fun();
layer.close(confirmIndex);
}), (function() {
cancelFun && cancelFun();
}));
}
getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) {
let now;
now = dateString ? new Date(dateString) : new Date;
const year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, "0"), day = String(now.getDate()).padStart(2, "0"), hours = String(now.getHours()).padStart(2, "0"), minutes = String(now.getMinutes()).padStart(2, "0"), seconds = String(now.getSeconds()).padStart(2, "0");
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
}
formatDate(date, dateSplitStr = "-", timeSplitStr = ":") {
let targetDate;
if (date instanceof Date) targetDate = date; else {
if ("string" != typeof date) throw new Error("Invalid date input: must be Date object or date string");
targetDate = new Date(date);
if (isNaN(targetDate.getTime())) throw new Error("Invalid date string");
}
const year = targetDate.getFullYear(), month = String(targetDate.getMonth() + 1).padStart(2, "0"), day = String(targetDate.getDate()).padStart(2, "0"), hours = String(targetDate.getHours()).padStart(2, "0"), minutes = String(targetDate.getMinutes()).padStart(2, "0"), seconds = String(targetDate.getSeconds()).padStart(2, "0");
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
}
download(data, fileName) {
const blob = new Blob([ data ], {
type: "application/json"
}), url = URL.createObjectURL(blob), a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout((() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}), 100);
}
smoothScrollToTop(duration = 500) {
return new Promise((resolve => {
const start = performance.now(), startPosition = window.pageYOffset;
window.requestAnimationFrame((function scrollStep(timestamp) {
const elapsed = timestamp - start, progress = Math.min(elapsed / duration, 1), easeInOutCubic = progress < .5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
window.scrollTo(0, startPosition * (1 - easeInOutCubic));
progress < 1 ? window.requestAnimationFrame(scrollStep) : resolve();
}));
}));
}
simpleId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
}
log(...data) {
console.groupCollapsed("📌", ...data);
const stackLines = (new Error).stack.split("\n").slice(2).map((line => line.trim())).filter((line => line.trim()));
console.log(stackLines.join("\n"));
console.groupEnd();
}
isUrl(urlString) {
try {
new URL(urlString);
return !0;
} catch (_) {
return !1;
}
}
}
window.utils = new Utils;
window.http = new class {
get(url, params = {}, headers = {}) {
return this.jqueryRequest("GET", url, null, params, headers);
}
post(url, data = {}, headers = {}) {
return this.jqueryRequest("POST", url, data, null, headers);
}
put(url, data = {}, headers = {}) {
return this.jqueryRequest("PUT", url, data, null, headers);
}
del(url, params = {}, headers = {}) {
return this.jqueryRequest("DELETE", url, null, params, headers);
}
jqueryRequest(method, url, data = {}, params = {}, headers = {}) {
"POST" === method && (headers = {
"Content-Type": "application/json",
...headers
});
return new Promise(((resolve, reject) => {
$.ajax({
method: method,
url: url,
data: "GET" === method || "DELETE" === method ? params : JSON.stringify(data),
headers: headers,
success: (response, textStatus, xhr) => {
var _a;
if (null == (_a = xhr.getResponseHeader("Content-Type")) ? void 0 : _a.includes("application/json")) try {
resolve("object" == typeof response ? response : JSON.parse(response));
} catch (e) {
resolve(response);
} else resolve(response);
},
error: (xhr, textStatus, errorThrown) => {
let errorMsg = errorThrown;
if (xhr.responseText) try {
const errorResponse = JSON.parse(xhr.responseText);
errorMsg = errorResponse.message || errorResponse.msg || xhr.responseText;
} catch {
errorMsg = xhr.responseText;
}
reject(new Error(errorMsg));
}
});
}));
}
};
window.gmHttp = new class {
get(url, params = {}, headers = {}) {
return this.gmRequest("GET", url, null, params, headers);
}
post(url, data = {}, headers = {}) {
return this.gmRequest("POST", url, data, null, headers);
}
put(url, data = {}, headers = {}) {
return this.gmRequest("PUT", url, data, null, headers);
}
del(url, params = {}, headers = {}) {
return this.gmRequest("DELETE", url, null, params, headers);
}
gmRequest(method, url, data = {}, params = {}, headers = {}) {
if (("GET" === method || "DELETE" === method) && params && Object.keys(params).length) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes("?") ? "&" : "?") + queryString;
}
"POST" !== method && "PUT" !== method || (headers = {
"Content-Type": "application/json",
...headers
});
return new Promise(((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url,
headers: headers,
data: "POST" === method || "PUT" === method ? JSON.stringify(data) : void 0,
onload: response => {
var _a;
try {
if (response.status >= 200 && response.status < 300) if (response.responseText && (null == (_a = response.responseHeaders) ? void 0 : _a.toLowerCase().includes("application/json"))) try {
resolve(JSON.parse(response.responseText));
} catch (e) {
resolve(response.responseText);
} else resolve(response.responseText || response); else if (response.responseText) try {
const errorData = JSON.parse(response.responseText);
reject(errorData);
} catch {
reject(new Error(response.responseText || `HTTP Error ${response.status}`));
} else reject(new Error(`HTTP Error ${response.status}`));
} catch (e) {
reject(e);
}
},
onerror: error => {
reject(new Error(error.error || "Network Error"));
},
ontimeout: () => {
reject(new Error("Request Timeout"));
}
});
}));
}
};
window.storageManager = new StorageManager;
const channel = new BroadcastChannel("channel-refresh");
window.refresh = function() {
channel.postMessage({
type: "refresh"
});
};
!function() {
document.head.insertAdjacentHTML("beforeend", '\n <style>\n .loading-container {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.1);\n z-index: 99999999;\n }\n \n .loading-animation {\n position: relative;\n width: 60px;\n height: 12px;\n background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);\n border-radius: 6px;\n animation: loading-animate 1.8s ease-in-out infinite;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n \n .loading-animation:before,\n .loading-animation:after {\n position: absolute;\n display: block;\n content: "";\n animation: loading-animate 1.8s ease-in-out infinite;\n height: 12px;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n \n .loading-animation:before {\n top: -20px;\n left: 10px;\n width: 40px;\n background: linear-gradient(90deg, #ff758c 0%, #ff7eb3 100%);\n }\n \n .loading-animation:after {\n bottom: -20px;\n width: 35px;\n background: linear-gradient(90deg, #ff9a9e 0%, #fad0c4 100%);\n }\n \n @keyframes loading-animate {\n 0% {\n transform: translateX(40px);\n }\n 50% {\n transform: translateX(-30px);\n }\n 100% {\n transform: translateX(40px);\n }\n }\n </style>\n ');
window.loading = function() {
const container = document.createElement("div");
container.className = "loading-container";
const animation = document.createElement("div");
animation.className = "loading-animation";
container.appendChild(animation);
document.body.appendChild(container);
return {
close: () => {
container && container.parentNode && container.parentNode.removeChild(container);
}
};
};
}();
!function() {
document.head.insertAdjacentHTML("beforeend", "\n <style>\n .data-table {\n width: 100%;\n border-collapse: separate;\n border-spacing: 0;\n font-family: 'Helvetica Neue', Arial, sans-serif;\n background: #fff;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);\n margin: 0 auto; /* 表格整体水平居中 */\n }\n \n .data-table thead tr {\n background: #f8fafc;\n }\n \n /* 表头居中 */\n .data-table th {\n padding: 16px 20px;\n text-align: center !important; /* 表头文字居中 */\n color: #64748b;\n font-weight: 500;\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n border-bottom: 1px solid #e2e8f0;\n }\n \n /* 单元格内容居中 */\n .data-table td {\n padding: 14px 20px;\n color: #334155;\n font-size: 15px;\n border-bottom: 1px solid #f1f5f9;\n text-align: center !important; /* 单元格文字居中 */\n vertical-align: middle; /* 垂直居中 */\n }\n \n .data-table tbody tr:last-child td {\n border-bottom: none;\n }\n \n /* 行hover 变色*/\n .data-table tbody tr {\n transition: all 0.2s ease;\n }\n \n .data-table tbody tr:hover {\n background: #f8fafc;\n }\n \n /* 可选:特定列左对齐/右对齐的示例 */\n .data-table .text-left {\n text-align: left;\n }\n \n .data-table .text-right {\n text-align: right;\n }\n \n /* 添加.show-border时显示边框 */\n .data-table.show-border {\n border: 1px solid #e2e8f0;\n }\n \n .data-table.show-border th,\n .data-table.show-border td {\n border: 1px solid #e2e8f0;\n }\n </style>\n ");
window.TableGenerator = class {
constructor(options) {
this.defaults = {
tableClass: "data-table",
showBorder: !1,
buttons: []
};
this.config = {
...this.defaults,
...options
};
this.validateConfig() && this.init();
}
validateConfig() {
if (!(this.config.containerId && this.config.columns && Array.isArray(this.config.columns) && Array.isArray(this.config.data))) {
console.error("缺少必要参数或参数类型不正确");
return !1;
}
this.container = document.getElementById(this.config.containerId);
if (!this.container) {
console.error(`未找到ID为${this.config.containerId}的容器`);
return !1;
}
return !0;
}
init() {
this.container.innerHTML = "";
this.table = document.createElement("table");
this.table.className = this.config.showBorder ? `${this.config.tableClass} show-border` : this.config.tableClass;
this.createHeader();
this.createBody();
this.container.appendChild(this.table);
}
createHeader() {
const thead = document.createElement("thead"), headerRow = document.createElement("tr");
this.config.columns.forEach((column => {
const th = document.createElement("th");
th.textContent = column.title || column.key;
column.width && (th.style.width = column.width);
column.headerClass && (th.className = column.headerClass);
headerRow.appendChild(th);
}));
if (this.config.buttons && this.config.buttons.length > 0) {
const th = document.createElement("th");
th.textContent = "操作";
this.config.buttonColumnWidth && (th.style.width = this.config.buttonColumnWidth);
headerRow.appendChild(th);
}
thead.appendChild(headerRow);
this.table.appendChild(thead);
}
createBody() {
const tbody = document.createElement("tbody");
0 === this.config.data.length ? this.renderEmptyData(tbody) : this.renderDataRows(tbody);
this.table.appendChild(tbody);
}
renderEmptyData(tbody) {
const tr = document.createElement("tr"), td = document.createElement("td");
td.colSpan = this.config.columns.length + (this.config.buttons.length > 0 ? 1 : 0);
td.textContent = "暂无数据";
td.style.textAlign = "center";
tr.appendChild(td);
tbody.appendChild(tr);
}
renderDataRows(tbody) {
this.config.data.forEach(((item, rowIndex) => {
const tr = document.createElement("tr");
this.renderDataCells(tr, item, rowIndex);
this.config.buttons && this.config.buttons.length > 0 && this.renderButtonCells(tr, item, rowIndex);
tbody.appendChild(tr);
}));
}
renderDataCells(tr, item, rowIndex) {
this.config.columns.forEach((column => {
const td = document.createElement("td");
column.render ? td.innerHTML = column.render(item, rowIndex) : td.textContent = item[column.key] || "";
column.cellClass && (td.className = column.cellClass);
tr.appendChild(td);
}));
}
renderButtonCells(tr, item, rowIndex) {
const td = document.createElement("td");
this.config.buttons.forEach((button => {
const btn = document.createElement("a");
btn.textContent = button.text;
btn.className = button.class || "a-primary";
btn.addEventListener("click", (event2 => {
if (button.onClick) {
const paramLength = button.onClick.length;
3 === paramLength ? button.onClick(event2, item, rowIndex) : 2 === paramLength ? button.onClick(event2, item) : button.onClick(item);
}
}));
td.appendChild(btn);
}));
tr.appendChild(td);
}
update(newData) {
this.config.data = newData;
this.init();
}
getTableElement() {
return this.table;
}
};
}();
!function() {
const showMessage = (msg, type, gravityOrOptions, positionOrOptions, options) => {
let finalOptions;
if ("object" == typeof gravityOrOptions) finalOptions = gravityOrOptions; else {
finalOptions = "object" == typeof positionOrOptions ? positionOrOptions : options || {};
finalOptions.gravity = gravityOrOptions || "top";
finalOptions.position = "string" == typeof positionOrOptions ? positionOrOptions : "center";
}
finalOptions.gravity && "center" !== finalOptions.gravity || (finalOptions.offset = {
y: "calc(50vh - 150px)"
});
const colors_infoStart = "#60A5FA", colors_infoEnd = "#93C5FD", colors_successStart = "#10B981", colors_successEnd = "#6EE7B7", colors_errorStart = "#EF4444", colors_errorEnd = "#FCA5A5", commonStyles = {
borderRadius: "12px",
color: "white",
padding: "12px 16px",
boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
minWidth: "150px",
textAlign: "center",
zIndex: 999999999
}, defaultConfig = {
text: msg,
duration: 2e3,
close: !1,
gravity: "top",
position: "center",
style: {
info: {
...commonStyles,
background: `linear-gradient(to right, ${colors_infoStart}, ${colors_infoEnd})`
},
success: {
...commonStyles,
background: `linear-gradient(to right, ${colors_successStart}, ${colors_successEnd})`
},
error: {
...commonStyles,
background: `linear-gradient(to right, ${colors_errorStart}, ${colors_errorEnd})`
}
}[type],
stopOnFocus: !0,
oldestFirst: !1,
...finalOptions
};
Toastify(defaultConfig).showToast();
};
window.show = {
ok: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
showMessage(msg, "success", gravityOrOptions, positionOrOptions, options);
},
error: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
showMessage(msg, "error", gravityOrOptions, positionOrOptions, options);
},
info: (msg, gravityOrOptions = "center", positionOrOptions, options) => {
showMessage(msg, "info", gravityOrOptions, positionOrOptions, options);
}
};
}();
class PluginManager {
constructor() {
this.plugins = new Map;
}
register(pluginClass) {
if ("function" != typeof pluginClass) throw new Error("插件必须是一个类");
const name = pluginClass.name;
if (!name) throw new Error("类必须要有名称");
const lowerName = name.toLowerCase();
if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`);
const instance = new pluginClass;
instance.pluginManager = this;
this.plugins.set(lowerName, instance);
}
getBean(name) {
return this.plugins.get(name.toLowerCase());
}
_getDependencies(func) {
const fnStr = func.toString();
return fnStr.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")")).split(",").map((arg => arg.trim())).filter((arg => arg));
}
async process() {
const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name, instance]) => {
try {
if ("function" == typeof instance.handle) {
const css = await instance.initCss();
utils.insertStyle(css);
await instance.handle();
return {
name: name,
status: "fulfilled"
};
}
console.log("加载插件", name);
} catch (e) {
console.error(`插件 ${name} 执行失败`, e);
return {
name: name,
status: "rejected",
error: e
};
}
})))).filter((r => "rejected" === r.status));
failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map((p => p.name)));
document.body.classList.add("script-ready");
}
}
class BasePlugin {
constructor() {
__publicField(this, "pluginManager", null);
}
getBean(name) {
let bean = this.pluginManager.getBean(name);
if (!bean) {
let msg = "容器中不存在: " + name;
show.error(msg);
throw new Error(msg);
}
return bean;
}
async initCss() {
return "";
}
async handle() {}
getPageInfo() {
let carNum, url, actress, actors, movieId, currentHref = window.location.href;
if (isJavDb) {
carNum = $('a[title="複製番號"]').attr("data-clipboard-text");
url = currentHref.split("?")[0].split("#")[0];
actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" ");
actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" ");
const parts = window.location.href.split("?")[0].split("/");
movieId = parts[parts.length - 1].split("#")[0];
}
if (isJavBus) {
url = currentHref.split("?")[0];
carNum = url.split("/").filter(Boolean).pop();
actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ");
actors = "";
}
return {
carNum: carNum,
url: url,
actress: actress,
actors: actors,
movieId: movieId
};
}
getSelector() {
return isJavDb ? Db : isJavBus ? Bus : null;
}
}
class DetailPagePlugin extends BasePlugin {
constructor() {
super();
}
async initCss() {
return window.isDetailPage && window.location.href.includes("hideNav=1") ? "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n " : "";
}
handle() {
window.isDetailPage && this.checkFilterActor().then();
}
async checkFilterActor() {
if (!window.isDetailPage) return;
const filterActorList = await storageManager.getFilterActorList();
let actors = this.getPageInfo().actors;
filterActorList.forEach((item => {
if (actors.indexOf(item) > -1) {
const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
detailPageButtonPlugin.answerCount++;
utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
detailPageButtonPlugin.filterOne(null, !0);
}));
}
}));
}
}
class PreviewVideoPlugin extends BasePlugin {
async initCss() {
return "\n .video-control-btn {\n position: absolute;\n bottom: 10px;\n right: 10px;\n z-index: 99999999999;\n min-width:100px;\n padding: 8px 16px;\n background: rgba(0,0,0,0.7);\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n .video-control-btn.active {\n background-color: #1890ff; /* 选中按钮的背景色 */\n color: white; /* 选中按钮的文字颜色 */\n font-weight: bold; /* 加粗显示 */\n border: 2px solid #096dd9; /* 边框样式 */\n }\n ";
}
handle() {
let $preview = $(".preview-video-container");
$preview.on("click", (event2 => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
this.handleVideo().then();
}));
}));
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
$(".fancybox-content #preview-video").length > 0 && this.handleVideo().then();
}));
window.location.href.includes("autoPlay=1") && $preview[0].click();
}
async handleVideo() {
const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $videoContainer = $videoEl.parent();
if (!$videoEl.length || !$previewSource.length) return;
const videoEl = $videoEl[0];
videoEl.muted = !1;
$videoContainer.css("position", "relative");
const videoSrc = $previewSource.attr("src"), qualityLevels = [ "hhb", "hmb", "mhb", "mmb" ], currentQuality = qualityLevels.find((q => videoSrc.includes(q))) || "mhb", qualityOptions = [ {
id: "video-mmb",
text: "低画质",
quality: "mmb"
}, {
id: "video-mhb",
text: "中画质",
quality: "mhb"
}, {
id: "video-hmb",
text: "高画质",
quality: "hmb"
}, {
id: "video-hhb",
text: "超高清",
quality: "hhb"
} ];
const cacheKey = `videoQualities_${this.getPageInfo().carNum}`;
let availableQualities = JSON.parse(sessionStorage.getItem(cacheKey));
if (!availableQualities) {
availableQualities = (await Promise.all(qualityOptions.map((async option => {
const testSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), option.quality);
try {
return (await fetch(testSrc, {
method: "HEAD"
})).ok ? option : null;
} catch {
return null;
}
})))).filter(Boolean);
sessionStorage.setItem(cacheKey, JSON.stringify(availableQualities));
}
if (availableQualities.length <= 1) return;
const buttonsHtml = availableQualities.map(((option, index) => `\n <button class="video-control-btn${option.quality === currentQuality ? " active" : ""}" \n id="${option.id}" \n data-quality="${option.quality}"\n style="bottom: ${50 * index}px; right: -105px;">\n ${option.text}\n </button>\n `)).join("");
$videoContainer.append(buttonsHtml);
const $buttons = $videoContainer.find(".video-control-btn");
$videoContainer.on("click", ".video-control-btn", (async e => {
const $button = $(e.currentTarget), quality = $button.data("quality");
if (!$button.hasClass("active")) try {
const newSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), quality);
$previewSource.attr("src", newSrc);
videoEl.load();
videoEl.muted = !1;
await videoEl.play();
$buttons.removeClass("active");
$button.addClass("active");
} catch (error) {
console.error("切换画质失败:", error);
}
}));
$buttons.last().trigger("click");
}
}
const _HotkeyManager = class _HotkeyManager {
constructor() {
if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(hotkeyString, callback, keyupCallback = null) {
if (Array.isArray(hotkeyString)) {
let id_list = [];
hotkeyString.forEach((hotkey => {
if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误");
let id = this.recordHotkey(hotkey, callback, keyupCallback);
id_list.push(id);
}));
return id_list;
}
if (!this.isHotkeyFormat(hotkeyString)) throw new Error("快捷键格式错误");
return this.recordHotkey(hotkeyString, callback, keyupCallback);
}
static recordHotkey(hotkeyString, callback, keyupCallback) {
let id = Math.random().toString(36).substr(2);
this.registerHotKeyMap.set(id, {
hotkeyString: hotkeyString,
callback: callback,
keyupCallback: keyupCallback
});
return id;
}
static unregisterHotkey(id) {
this.registerHotKeyMap.has(id) && this.registerHotKeyMap.delete(id);
}
static isHotkeyFormat(hotkeyString) {
return hotkeyString.toLowerCase().split("+").map((k => k.trim())).every((k => [ "ctrl", "shift", "alt" ].includes(k) || 1 === k.length));
}
static judgeHotkey(hotkeyString, event2) {
const keyList = hotkeyString.toLowerCase().split("+").map((k => k.trim())), ctrl = keyList.includes("ctrl"), shift = keyList.includes("shift"), alt = keyList.includes("alt"), key = keyList.find((k => "ctrl" !== k && "shift" !== k && "alt" !== k));
return (this.isMac ? event2.metaKey : event2.ctrlKey) === ctrl && event2.shiftKey === shift && event2.altKey === alt && event2.key.toLowerCase() === key;
}
};
__publicField(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac"));
__publicField(_HotkeyManager, "registerHotKeyMap", new Map);
__publicField(_HotkeyManager, "handleKeydown", (event2 => {
for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
let hotkeyString = data.hotkeyString, callback = data.callback;
_HotkeyManager.judgeHotkey(hotkeyString, event2) && callback(event2);
}
}));
__publicField(_HotkeyManager, "handleKeyup", (event2 => {
for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback;
keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event2) && keyupCallback(event2));
}
}));
let HotkeyManager = _HotkeyManager;
document.addEventListener("keydown", (event2 => {
HotkeyManager.handleKeydown(event2);
}));
document.addEventListener("keyup", (event2 => {
HotkeyManager.handleKeyup(event2);
}));
class JavTrailersPlugin extends BasePlugin {
constructor() {
super();
this.hasBand = !1;
}
handle() {
let href = window.location.href;
if (!href.includes("handle=1")) return;
if ($("h1:contains('Page not found')").length) {
let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
window.location.href = "https://javtrailers.com/search/" + keyword + "?handle=1";
return;
}
let findList = $(".videos-list .video-link").toArray();
if (findList.length) {
const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword)));
if (matchedLink) {
window.location.href = $(matchedLink).attr("href") + "?handle=1";
return;
}
}
this.handlePlayJavTrailers();
$("#videoPlayerContainer").on("click", (() => {
this.handlePlayJavTrailers();
}));
window.addEventListener("message", (event2 => {
let videoEl = document.getElementById("vjs_video_3_html5_api");
videoEl && (videoEl.currentTime += 5);
}));
HotkeyManager.registerHotkey("z", (() => {
const videoEl = document.getElementById("vjs_video_3_html5_api");
videoEl && (videoEl.currentTime += 5);
}));
HotkeyManager.registerHotkey("a", (() => window.parent.postMessage("a", "*")));
HotkeyManager.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
}
handlePlayJavTrailers() {
this.hasBand || utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
setTimeout((() => {
this.hasBand = !0;
let videoEl = document.getElementById("vjs_video_3_html5_api");
videoEl.play();
videoEl.currentTime = 5;
videoEl.addEventListener("timeupdate", (function() {
videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.currentTime += 2);
}));
$("#vjs_video_3_html5_api").css({
position: "fixed",
width: "100vw",
height: "100vh",
objectFit: "cover",
zIndex: "999999999"
});
$(".vjs-control-bar").css({
position: "fixed",
bottom: "20px",
zIndex: "999999999"
});
}), 0);
}));
}
}
class SubTitleCatPlugin extends BasePlugin {
handle() {
$(".t-banner-inner").hide();
$("#navbar").hide();
let keyword = window.location.href.split("=")[1].toLowerCase();
$(".sub-table tr td a").toArray().forEach((el => {
let item = $(el);
item.text().toLowerCase().includes(keyword) || item.parent().parent().hide();
}));
}
}
const apiUrl = "https://jdforrepam.com/api";
async function buildSignature() {
const curr = Math.floor(Date.now() / 1e3);
if (curr - (await storageManager.getItem(storageManager.review_ts_key) || 0) <= 20) return await storageManager.getItem(storageManager.review_sign_key);
const sign = `${curr}.lpw6vgqzsp.${md5(`${curr}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
await storageManager.setItem(storageManager.review_ts_key, curr);
await storageManager.setItem(storageManager.review_sign_key, sign);
return sign;
}
const getReviews = async (movieId, pageNum = 1, pageSize = 20) => {
let url = `${apiUrl}/v1/movies/${movieId}/reviews`, headers = {
jdSignature: await buildSignature()
};
return (await http.get(url, {
page: pageNum,
sort_by: "hotly",
limit: pageSize
}, headers)).data.reviews;
}, getMovieDetail = async movieId => {
let url = `${apiUrl}/v4/movies/${movieId}`, headers = {
jdSignature: await buildSignature()
};
const res = await http.get(url, null, headers);
if (!res.data) {
show.error("获取视频详情失败: " + res.message);
throw new Error(res.message);
}
const movie = res.data.movie, preview_images = movie.preview_images, imgList = [];
preview_images.forEach((item => {
imgList.push(item.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
}));
return {
movieId: movie.id,
actors: movie.actors,
title: movie.origin_title,
carNum: movie.number,
score: movie.score,
releaseDate: movie.release_date,
watchedCount: movie.watched_count,
imgList: imgList
};
}, related = async (movieId, page = 1, limit = 20) => {
let url = `${apiUrl}/v1/lists/related?movie_id=${movieId}&page=${page}&limit=${limit}`, headers = {
jdSignature: await buildSignature()
};
const res = await gmHttp.get(url, null, headers), dataList = [];
res.data.lists.forEach((item => {
dataList.push({
relatedId: item.id,
name: item.name,
movieCount: item.movies_count,
collectionCount: item.collections_count,
viewCount: item.views_count,
createTime: utils.formatDate(item.created_at)
});
}));
return dataList;
};
class Fc2Plugin extends BasePlugin {
handle() {
let fc2Url = "/advanced_search?type=3&score_min=3&d=1";
$('.navbar-item:contains("FC2")').attr("href", fc2Url);
$('.tabs a:contains("FC2")').attr("href", fc2Url);
if (window.location.href.includes("collection_codes?movieId")) {
const urlParams = new URLSearchParams(window.location.search);
let movieId = urlParams.get("movieId"), carNum = urlParams.get("carNum"), url = urlParams.get("url");
movieId && carNum && url && this.openFc2Page(movieId, carNum, url);
}
}
async initCss() {
return "\n /* 弹层样式 */\n .movie-detail-layer .layui-layer-title {\n font-size: 18px;\n color: #333;\n background: #f8f8f8;\n }\n \n \n /* 容器样式 */\n .movie-detail-container {\n display: flex;\n height: 100%;\n background: #fff;\n }\n \n .movie-poster-container {\n flex: 0 0 60%;\n padding: 15px;\n }\n \n .right-box {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n }\n \n /* 预告片iframe */\n .movie-trailer {\n width: 100%;\n height: 100%;\n min-height: 400px;\n background: #000;\n border-radius: 4px;\n }\n \n /* 电影信息样式 */\n .movie-title {\n font-size: 24px;\n margin-bottom: 15px;\n color: #333;\n }\n \n .movie-meta {\n margin-bottom: 20px;\n color: #666;\n }\n \n .movie-meta span {\n margin-right: 15px;\n }\n \n /* 演员列表 */\n .actor-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 10px;\n }\n \n .actor-tag {\n padding: 4px 12px;\n background: #f0f0f0;\n border-radius: 15px;\n font-size: 12px;\n color: #555;\n }\n \n /* 图片列表 */\n .image-list {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 10px;\n }\n \n .movie-image-thumb {\n width: 120px;\n height: 80px;\n object-fit: cover;\n border-radius: 4px;\n cursor: pointer;\n transition: transform 0.3s;\n }\n \n .movie-image-thumb:hover {\n transform: scale(1.05);\n }\n \n /* 加载中和错误状态 */\n .search-loading, .movie-error {\n padding: 40px;\n text-align: center;\n color: #999;\n }\n \n .movie-error {\n color: #f56c6c;\n }\n \n .fancybox-container{\n z-index:99999999\n }\n \n \n /* 错误提示样式 */\n .movie-not-found, .movie-error {\n text-align: center;\n padding: 30px;\n color: #666;\n }\n \n .movie-not-found h3, .movie-error h3 {\n color: #f56c6c;\n margin: 15px 0;\n }\n \n .icon-warning, .icon-error {\n font-size: 50px;\n color: #e6a23c;\n }\n \n .icon-error {\n color: #f56c6c;\n }\n\n ";
}
openFc2Page(movieId, carNum, href) {
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 style="margin: 10px 0">\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" style="margin: 0 0.75rem">\n <div class="search-loading">加载中...</div>\n </div>\n </div>\n <div id="reviews-content">\n </div>\n <span id="data-actress" style="display: none"></span>\n </div>\n </div>\n ',
area: [ "80%", "90%" ],
skin: "movie-detail-layer",
scrollbar: !1,
success: (layero, index) => {
this.loadData(movieId, carNum);
$("#favoriteBtn").on("click", (async event2 => {
const actress = $("#data-actress").text();
await storageManager.saveCar(carNum, href, actress, Status_FAVORITE);
window.refresh();
layer.closeAll();
}));
$("#filterBtn").on("click", (event2 => {
utils.q(event2, `是否屏蔽${carNum}?`, (async () => {
const actress = $("#data-actress").text();
await storageManager.saveCar(carNum, href, actress, Status_FILTER);
window.refresh();
layer.closeAll();
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
}));
$("#hasDownBtn").on("click", (async event2 => {
const actress = $("#data-actress").text();
await storageManager.saveCar(carNum, href, actress, Status_HAS_DOWN);
window.refresh();
layer.closeAll();
}));
},
end() {
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}
});
}
loadData(movieId, carNum) {
this.handleVideo(carNum.replace("FC2-", ""));
this.handleMovieDetail(movieId);
this.handleMagnets(movieId);
this.getBean("reviewPlugin").showReview(movieId, $("#reviews-content")).then();
}
handleMovieDetail(movieId) {
getMovieDetail(movieId).then((res => {
const actors = res.actors || [], imgList = res.imgList || [];
let actorsHtml = "";
if (actors.length > 0) {
let actress = "";
for (let i = 0; i < actors.length; i++) {
let actor = actors[i];
actorsHtml += `<span class="actor-tag"><a href="/actors/${actor.id}" target="_blank">${actor.name}</a></span>`;
0 === actor.gender && (actress += actor.name);
}
$("#data-actress").text(actress);
} else actorsHtml = '<span class="no-data">暂无演员信息</span>';
let imagesHtml = "";
imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => `\n <a href="${img}" data-fancybox="movie-gallery" data-caption="剧照 ${index + 1}">\n <img src="${img}" class="movie-image-thumb" alt=""/>\n </a>\n `)).join("") : '<div class="no-data">暂无剧照</div>';
$(".movie-info-container").html(`\n <h3 class="movie-title">${res.title || "无标题"}</h3>\n <div class="movie-meta">\n <span>番号: ${res.carNum || "未知"}</span>\n <span>年份: ${res.releaseDate || "未知"}</span>\n <span>评分: ${res.score || "无"}</span>\n </div>\n <div class="movie-actors">\n <div class="actor-list">主演: ${actorsHtml}</div>\n </div>\n <div class="movie-gallery" style="margin-top:10px">\n <h4>剧照: </h4>\n <div class="image-list">${imagesHtml}</div>\n </div>\n `);
})).catch((err => {
console.error(err);
$(".movie-info-container").html(`\n <div class="movie-error">加载失败: ${err.message}</div>\n `);
}));
}
handleMagnets(movieId) {
(async movieId => {
let url = `${apiUrl}/v1/movies/${movieId}/magnets`, headers = {
jdSignature: await buildSignature()
};
return (await http.get(url, null, headers)).data.magnets;
})(movieId).then((magnetList => {
let magnetsHtml = "";
if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) {
let magnet = magnetList[i], oddClass = "";
i % 2 == 0 && (oddClass = "odd");
magnetsHtml += `\n <div class="item columns is-desktop ${oddClass}">\n <div class="magnet-name column is-four-fifths">\n <a href="magnet:?xt=urn:btih:${magnet.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n <span class="name">${magnet.name}</span>\n <br>\n <span class="meta">\n ${(magnet.size / 1024).toFixed(2)}GB, ${magnet.files_count}個文件 \n </span>\n <br>\n <div class="tags">\n ${magnet.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n ${magnet.cnsub ? '<span class="tag is-warning is-small is-light">字幕</span>' : ""}\n </div>\n </a>\n </div>\n <div class="buttons column">\n <button class="button is-info is-small copy-to-clipboard" data-clipboard-text="magnet:?xt=urn:btih:${magnet.hash}" type="button"> 複製 </button>\n </div>\n <div class="date column"><span class="time">${magnet.created_at}</span></div>\n </div>\n `;
} else magnetsHtml = '<span class="no-data">暂无磁力信息</span>';
$("#magnets-content").html(magnetsHtml);
})).catch((err => {
console.error(err);
$("#magnets-content").html(`\n <div class="movie-error">加载失败: ${err.message}</div>\n `);
}));
}
handleVideo(searchKeyword) {
(async carNum => {
let url = `https://hohoj.tv/search?text=${carNum}`, html = await gmHttp.get(url), pageUrl = null;
if (html.includes("找不到任何影片")) return pageUrl;
const doc = (new DOMParser).parseFromString(html, "text/html");
$(doc).find(".video-item a").toArray().forEach((item => {
if ($(item).find(".video-item-title").text().includes(carNum)) {
let pageId = $(item).attr("href").split("id=")[1];
pageUrl = "https://hohoj.tv/embed?id=" + pageId;
}
}));
return pageUrl;
})(searchKeyword).then((pageUrl => {
const moviePosterContainer = document.querySelector(".movie-poster-container"), iframe = document.querySelector(".movie-trailer");
if (pageUrl) $(iframe).attr("src", pageUrl); else {
moviePosterContainer.innerHTML = `\n <div class="movie-not-found">\n <i class="icon-warning"></i>\n <h3>未找到相关内容</h3>\n <p>hohoj.tv 中没有找到与当前番号相关的影片信息</p>\n <p style="margin:20px">请尝试以下网站</p>\n <p><a class="menu-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))" href="https://missav.ws/dm3/fc2-ppv-${searchKeyword}" target="_blank">missav</a></p>\n </div>\n `;
iframe.style.display = "none";
}
}));
}
}
class FoldCategoryPlugin extends BasePlugin {
async handle() {
if (!window.isListPage) return;
let $subTags, $topTabs = $(".tabs ul");
if ($topTabs.length > 0) {
$subTags = $("#tags");
let checkTagStr = $("#tags dl div.tag.is-info").map((function() {
return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
})).get().join(" ");
if (!checkTagStr) return;
$topTabs.append('\n <li class="is-active" id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span></span>\n <i style="margin-left: 10px"></i>\n </a>\n </li>\n ');
$(".tabs").append(`<div style="padding-top:10px"><span>已选分类: ${checkTagStr}</span></div>`);
}
let $section = $("h2.section-title");
if ($section.length > 0) {
$section.append('\n <div id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span></span>\n <i style="margin-left: 10px"></i>\n </a>\n </div>\n ');
$subTags = $("section > div > div.box");
}
if (!$subTags) return;
let $foldCategoryBtn = $("#foldCategoryBtn"), isFolded = "yes" === await storageManager.getItem(storageManager.fold_category_key), [newText, newIcon] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
$foldCategoryBtn.find("span").text(newText).end().find("i").attr("class", newIcon);
window.location.href.includes("noFold=1") || $subTags[isFolded ? "hide" : "show"]();
$foldCategoryBtn.on("click", (async event2 => {
event2.preventDefault();
isFolded = !isFolded;
await storageManager.setItem(storageManager.fold_category_key, isFolded ? "yes" : "no");
const [newText2, newIcon2] = isFolded ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
$foldCategoryBtn.find("span").text(newText2).end().find("i").attr("class", newIcon2);
$subTags[isFolded ? "hide" : "show"]();
}));
}
}
class ActressInfoPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "apiUrl", "https://ja.wikipedia.org/wiki/");
}
handle() {
this.handleDetailPage().then();
this.handleStarPage().then();
}
async initCss() {
return "\n <style>\n .info-tag {\n background-color: #ecf5ff;\n display: inline-block;\n height: 32px;\n padding: 0 10px;\n line-height: 30px;\n font-size: 12px;\n color: #409eff;\n border: 1px solid #d9ecff;\n border-radius: 4px;\n box-sizing: border-box;\n white-space: nowrap;\n }\n </style>\n ";
}
async handleDetailPage() {
let nameList = $(".female").prev().map(((i, el) => $(el).text().trim())).get();
if (!nameList.length) return;
let result = null, infoHtml = "";
for (let i = 0; i < nameList.length; i++) {
let name = nameList[i];
result = await storageManager.getItem(storageManager.actress_prefix_key + name);
if (!result) try {
result = await this.searchInfo(name);
result && await storageManager.setItem(storageManager.actress_prefix_key + name, result, 2592e6);
} catch (e) {
console.error("该名称查询失败,尝试其它名称");
}
let contentHtml = "";
contentHtml = result ? `\n <div class="panel-block">\n <strong>${name}:</strong>\n <a href="${result.url}" style="margin-left: 5px" target="_blank">\n <span class="info-tag">${result.birthday} ${result.age}</span>\n <span class="info-tag">${result.height} ${result.weight}</span>\n <span class="info-tag">${result.threeSizeText} ${result.braSize}</span>\n </a>\n </div>\n ` : `<div class="panel-block"><a href="${this.apiUrl + name}" target="_blank"><strong>${name}:</strong></a></div> `;
infoHtml += contentHtml;
}
$('strong:contains("演員")').parent().after(infoHtml);
}
async handleStarPage() {
let nameList = [], $actor = $(".actor-section-name");
$actor.length && $actor.text().trim().split(",").forEach((name => {
nameList.push(name.trim());
}));
let $sectionMeta = $(".section-meta:not(:contains('影片'))");
$sectionMeta.length && $sectionMeta.text().trim().split(",").forEach((name => {
nameList.push(name.trim());
}));
if (!nameList.length) return;
let result = null;
for (let i = 0; i < nameList.length; i++) {
let name = nameList[i];
result = await storageManager.getItem(storageManager.actress_prefix_key + name);
if (result) break;
try {
result = await this.searchInfo(name);
} catch (e) {
console.error("该名称查询失败,尝试其它名称");
}
if (result) break;
}
result && nameList.forEach((name => {
storageManager.setItem(storageManager.actress_prefix_key + name, result, 2592e6);
}));
let contentHtml = '<div style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>';
result && (contentHtml = `\n <a href="${result.url}" target="_blank">\n <div style="font-size: 17px; font-weight: normal; margin-top: 5px;">\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">出生日期: ${result.birthday}</span>\n <span style="width: 200px;">年龄: ${result.age}</span>\n <span style="width: 200px;">身高: ${result.height}</span>\n </div>\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">体重: ${result.weight}</span>\n <span style="width: 200px;">三围: ${result.threeSizeText}</span>\n <span style="width: 200px;">罩杯: ${result.braSize}</span>\n </div>\n </div>\n </a>\n `);
$actor.parent().append(contentHtml);
}
async searchInfo(name) {
"三上悠亞" === name && (name = "三上悠亜");
let url = this.apiUrl + name;
const html = await gmHttp.get(url), parser = new DOMParser, $dom = $(parser.parseFromString(html, "text/html"));
let birthday = $dom.find('tr:has(a[title="誕生日"]) td').text().trim(), age = $dom.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt($dom.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", height = $dom.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", weight = $dom.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim();
"― kg" === weight && (weight = "");
return {
birthday: birthday,
age: age,
height: height,
weight: weight,
threeSizeText: $dom.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(),
braSize: $dom.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(),
url: url
};
}
}
class AliyunPanPlugin extends BasePlugin {
handle() {
$("body").append('<a class="a-success" id="refresh-token-btn" style="position:fixed; right: 0; top:50%;z-index:99999">获取refresh_token</a>');
$("#refresh-token-btn").on("click", (event2 => {
let tokenStr = localStorage.getItem("token");
if (!tokenStr) {
alert("请先登录!");
return;
}
let refresh_token = JSON.parse(tokenStr).refresh_token;
navigator.clipboard.writeText(refresh_token).then((() => {
alert("已复制到剪切板 如失败, 请手动复制: " + refresh_token);
})).catch((err => {
console.error("Failed to copy refresh token: ", err);
}));
}));
}
}
class HitShowPlugin extends BasePlugin {
constructor() {
super();
}
handle() {
$('a[href*="rankings/playback"]').on("click", (event2 => {
event2.preventDefault();
event2.stopPropagation();
window.location.href = "/?handlePlayback=1&period=daily";
}));
this.handlePlayback().then();
}
async handlePlayback() {
if (!window.location.href.includes("handlePlayback=1")) return;
let period = new URLSearchParams(window.location.search).get("period");
this.toolBar(period);
let $movieBox = $(".movie-list");
$movieBox.html("");
let loadObj = loading();
try {
const movies = await (async (period = "daily", filter_by = "high_score") => {
let url = `${apiUrl}/v1/rankings/playback?period=${period}&filter_by=${filter_by}`, headers = {
jdSignature: await buildSignature()
};
return (await http.get(url, null, headers)).data.movies;
})(period);
let moviesHtml = this.markDataListHtml(movies);
$movieBox.html(moviesHtml);
window.refresh();
this.loadScore(movies);
} finally {
loadObj.close();
}
}
toolBar(period) {
$(".pagination").remove();
$(".main-tabs ul li").removeClass("is-active");
$(".main-tabs ul li:first").addClass("is-active");
let conditionHtml = `\n <div class="button-group" style="margin-top:18px">\n <div class="buttons has-addons" id="conditionBox">\n <a style="padding:18px 18px !important;" class="button is-small ${"daily" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=daily">日榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=weekly">周榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === period ? "is-info" : ""}" href="/?handlePlayback=1&period=monthly">月榜</a>\n </div>\n </div>\n `;
$(".toolbar").html(conditionHtml);
}
getStarRating(score) {
let stars = "";
const fullStars = Math.floor(score);
for (let i = 0; i < fullStars; i++) stars += '<i class="icon-star"></i>';
for (let i = 0; i < 5 - fullStars; i++) stars += '<i class="icon-star gray"></i>';
return stars;
}
loadScore(movies) {
if (0 === movies.length) return;
(async () => {
const errors = [];
for (const movie of movies) try {
const movieId = movie.id;
if ($(`#${movieId}`).is(":hidden")) continue;
const cached = await storageManager.getItem(storageManager.score_prefix_key + movieId);
if (cached) {
this.appendScoreHtml(movieId, cached);
continue;
}
for (;!document.hasFocus(); ) await new Promise((r => setTimeout(r, 500)));
const res = await getMovieDetail(movieId);
let score = res.score, watchedCount = res.watchedCount, html = `\n <span class="value">\n <span class="score-stars">${this.getStarRating(score)}</span> \n ${score}分,由${watchedCount}人評價\n </span>\n `;
this.appendScoreHtml(movieId, html);
await storageManager.setItem(storageManager.score_prefix_key + movieId, html, 6048e5);
await new Promise((r => setTimeout(r, 1e3)));
} catch (err) {
errors.push({
carNum: movie.number,
error: err.message,
stack: err.stack
});
console.error(`🚨 解析评分数据失败 | 编号: ${movie.number}\n`, `错误详情: ${err.message}\n`, err.stack ? `调用栈:\n${err.stack}` : "");
}
if (errors.length > 0) {
show.error("解析评分数据失败, 个数:", errors.length);
console.table(errors);
}
})();
}
appendScoreHtml(movieId, scoreHtml) {
let $scoreBox = $(`#score_${movieId}`);
"" === $scoreBox.html().trim() && $scoreBox.slideUp(0, (function() {
$(this).html(scoreHtml).slideDown(500);
}));
}
markDataListHtml(movies) {
let moviesHtml = "";
movies.forEach((movie => {
moviesHtml += `\n <div class="item" id="${movie.id}">\n <a href="/v/${movie.id}" class="box" title="${movie.origin_title}">\n <div class="cover ">\n <img loading="lazy" src="${movie.cover_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt="">\n </div>\n <div class="video-title"><strong>${movie.number}</strong> ${movie.origin_title}</div>\n <div class="score" id="score_${movie.id}">\n </div>\n <div class="meta">\n ${movie.release_date}\n </div>\n <div class="tags has-addons">\n ${movie.has_cnsub ? '<span class="tag is-warning">含中字磁鏈</span>' : movie.magnets_count > 0 ? '<span class="tag is-success">含磁鏈</span>' : '<span class="tag is-info">无磁鏈</span>'}\n ${movie.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""}\n </div>\n </a>\n </div>\n `;
}));
return moviesHtml;
}
}
class TOP250Plugin extends BasePlugin {
constructor() {
super();
__publicField(this, "has_cnsub", "");
__publicField(this, "movies", []);
}
handle() {
$('.main-tabs ul li:contains("猜你喜歡")').html('<a href="/rankings/top"><span>Top250</span></a>');
$('a[href*="rankings/top"]').on("click", (event2 => {
event2.preventDefault();
event2.stopPropagation();
const $target = $(event2.target), href = ($target.is("a") ? $target : $target.closest("a")).attr("href");
let queryString = href.includes("?") ? href.split("?")[1] : href;
const urlParams = new URLSearchParams(queryString);
this.checkLogin(event2, urlParams);
}));
this.handleTop().then();
}
async handleTop() {
if (!window.location.href.includes("handleTop=1")) return;
const urlParams = new URLSearchParams(window.location.search);
let type = urlParams.get("type") || "all", type_value = urlParams.get("type_value") || "";
this.has_cnsub = urlParams.get("has_cnsub") || "";
let page = urlParams.get("page") || 1;
this.toolBar(type, type_value, page);
let $movieBox = $(".movie-list");
$movieBox.html("");
let loadObj = loading();
try {
const res = await (async (type = "all", type_value = "", page = 1, limit = 40) => {
let url = `${apiUrl}/v1/movies/top?start_rank=1&type=${type}&type_value=${type_value}&ignore_watched=false&page=${page}&limit=${limit}`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
authorization: "Bearer " + await storageManager.getItem("appAuthorization"),
jdsignature: await buildSignature()
};
return await gmHttp.get(url, null, headers);
})(type, type_value, page, 50);
let success = res.success, message = res.message, action = res.action;
if (1 === success) {
let movies = res.data.movies;
if (0 === movies.length) {
show.error("无数据");
return;
}
this.movies = movies;
const hitShowPlugin = this.getBean("hitShowPlugin");
let moviesHtml = hitShowPlugin.markDataListHtml(movies);
$movieBox.html(moviesHtml);
window.refresh();
if ("1" === this.has_cnsub) {
$(".item:contains('含中字磁鏈')").show();
$(".item:contains('含磁鏈')").hide();
} else if ("0" === this.has_cnsub) {
$(".item:contains('含中字磁鏈')").hide();
$(".item:contains('含磁鏈')").show();
} else {
$(".item:contains('含中字磁鏈')").show();
$(".item:contains('含磁鏈')").show();
}
hitShowPlugin.loadScore(movies);
} else {
console.error(res);
$movieBox.html(`<h3>${message}</h3>`);
show.error(message);
}
if ("JWTVerificationError" === action) {
await storageManager.removeItem("appAuthorization");
await this.checkLogin(null, new URLSearchParams(window.location.search));
}
} catch (e) {
console.error("获取Top数据失败:", e);
show.error(`获取Top数据失败: ${e ? e.message : e}`);
} finally {
loadObj.close();
}
}
toolBar(type, type_value, currentPage) {
$(".main-tabs ul li").removeClass("is-active");
$(".main-tabs ul li:eq(1)").addClass("is-active");
if ("5" === currentPage.toString()) {
$("#auto-page").remove();
$(".pagination-next").remove();
}
$(".pagination-ellipsis").closest("li").remove();
$(".pagination-list li a").each((function() {
parseInt($(this).text()) > 5 && $(this).closest("li").remove();
}));
let yearHtml = "";
for (let year = (new Date).getFullYear(); year >= 2008; year--) yearHtml += `\n <a style="padding:18px 18px !important;" \n class="button is-small ${type_value === year.toString() ? "is-info" : ""}" \n href="/?handleTop=1&type=year&type_value=${year}&has_cnsub=${this.has_cnsub}">\n ${year}\n </a>\n `;
let conditionHtml = `\n <div class="button-group">\n <div class="buttons has-addons" id="conditionBox" style="margin-bottom: 0!important;">\n <a style="padding:18px 18px !important;" class="button is-small ${"all" === type ? "is-info" : ""}" href="/?handleTop=1&type=all&type_value=&has_cnsub=${this.has_cnsub}">全部</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"0" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=0&has_cnsub=${this.has_cnsub}">有码</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"1" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=1&has_cnsub=${this.has_cnsub}">无码</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"2" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=2&has_cnsub=${this.has_cnsub}">欧美</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"3" === type_value ? "is-info" : ""}" href="/?handleTop=1&type=video_type&type_value=3&has_cnsub=${this.has_cnsub}">Fc2</a>\n \n <a style="padding:18px 18px !important;margin-left: 50px" class="button is-small ${"1" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="1">含中字磁鏈</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"0" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="0">无字幕</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-cnsub-value="">重置</a>\n </div>\n \n <div class="buttons has-addons" id="conditionBox">\n ${yearHtml}\n </div>\n </div>\n `;
$(".toolbar").html(conditionHtml);
$("a[data-cnsub-value]").on("click", (event2 => {
const cnsubValue = $(event2.currentTarget).data("cnsub-value");
this.has_cnsub = cnsubValue.toString();
$("a[data-cnsub-value]").removeClass("is-info");
$(event2.currentTarget).addClass("is-info");
$(".toolbar a.button").not("[data-cnsub-value]").each(((index, element) => {
const $link = $(element), url = new URL($link.attr("href"), window.location.origin);
url.searchParams.set("has_cnsub", cnsubValue);
$link.attr("href", url.toString());
}));
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("has_cnsub", cnsubValue);
history.pushState({}, "", newUrl.toString());
if ("1" === this.has_cnsub) {
$(".item:contains('含中字磁鏈')").show();
$(".item:contains('含磁鏈')").hide();
} else if ("0" === this.has_cnsub) {
$(".item:contains('含中字磁鏈')").hide();
$(".item:contains('含磁鏈')").show();
} else {
$(".item:contains('含中字磁鏈')").show();
$(".item:contains('含磁鏈')").show();
}
this.getBean("hitShowPlugin").loadScore(this.movies);
}));
}
async checkLogin(event2, urlParams) {
if (!(await storageManager.getItem("appAuthorization"))) {
show.error("未登录手机端接口, 无法查看");
this.openLoginDialog();
return;
}
let type = "all", type_value = "", t = urlParams.get("t") || "";
if (/^y\d+$/.test(t)) {
type = "year";
type_value = t.substring(1);
} else if ("" !== t) {
type = "video_type";
type_value = t;
}
let url = `/?handleTop=1&type=${type}&type_value=${type_value}`;
event2 && (event2.ctrlKey || event2.metaKey) ? window.open(url, "_blank") : window.location.href = url;
}
openLoginDialog() {
layer.open({
type: 1,
title: "JavDB",
closeBtn: 1,
area: [ "360px", "auto" ],
shadeClose: !1,
content: '\n <div style="padding: 30px; font-family: \'Helvetica Neue\', Arial, sans-serif;">\n <div style="margin-bottom: 25px;">\n <input type="text" id="username" name="username" \n style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n background: #f9f9f9; color: #333;"\n placeholder="用户名 | 邮箱"\n onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n </div>\n \n <div style="margin-bottom: 15px;">\n <input type="password" id="password" name="password" \n style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; \n box-sizing: border-box; transition: all 0.3s; font-size: 14px;\n background: #f9f9f9; color: #333;"\n placeholder="密码"\n onfocus="this.style.borderColor=\'#4a8bfc\'; this.style.background=\'#fff\'"\n onblur="this.style.borderColor=\'#e0e0e0\'; this.style.background=\'#f9f9f9\'">\n </div>\n \n <button id="loginBtn" \n style="width: 100%; padding: 12px; background: #4a8bfc; color: white; \n border: none; border-radius: 4px; font-size: 15px; cursor: pointer;\n transition: background 0.3s;"\n onmouseover="this.style.background=\'#3a7be0\'"\n onmouseout="this.style.background=\'#4a8bfc\'">\n 登录\n </button>\n </div>\n ',
success: (layero, index) => {
$("#loginBtn").click((function() {
const username = $("#username").val(), password = $("#password").val();
if (!username || !password) {
show.error("请输入用户名和密码");
return;
}
let loadObj = loading();
(async (username, password) => {
let url = `${apiUrl}//v1/sessions?username=${username}&password=${password}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
"content-type": "multipart/form-data; boundary=--dio-boundary-2210433284",
jdsignature: await buildSignature()
};
return await gmHttp.post(url, null, headers);
})(username, password).then((async res => {
let success = res.success;
if (0 === success) show.error(res.message); else {
if (1 !== success) {
console.error("登录失败", res);
throw new Error(res.message);
}
{
let token = res.data.token;
await storageManager.setItem("appAuthorization", token);
await storageManager.setItem("appUser", res.data);
show.ok("登录成功");
layer.close(index);
window.location.href = "/?handleTop=1&period=daily";
}
}
})).catch((err => {
console.error("登录异常:", err);
show.error(err.message);
})).finally((() => {
loadObj.close();
}));
}));
}
});
}
}
class NavBarPlugin extends BasePlugin {
handle() {
this.margeNav();
this.hookSearch();
if (window.location.href.includes("/search?q")) {
let q = new URLSearchParams(window.location.search).get("q");
$("#search-keyword").val(q);
}
}
hookSearch() {
$("#navbar-menu-hero").after('\n <div class="navbar-menu">\n <div class="navbar-start" style="display: flex; align-items: center; gap: 5px;">\n <select id="search-type" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; background-color: #333; color: #eee; font-size: 14px; outline: none;">\n <option value="all">影片</option>\n <option value="actor">演員</option>\n <option value="series">系列</option>\n <option value="maker">片商</option>\n <option value="director">導演</option>\n <option value="code">番號</option>\n <option value="list">清單</option>\n </select>\n <input id="search-keyword" type="text" placeholder="輸入影片番號,演員名等關鍵字進行檢索" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; flex-grow: 1; font-size: 14px; background-color: #333; color: #eee; outline: none;">\n <a href="/advanced_search?noFold=1" title="進階檢索" style="padding: 6px 12px; background-color: #444; border-radius: 4px; text-decoration: none; color: #ddd; font-size: 14px; border: 1px solid #555;"><span>...</span></a>\n <a id="search-img-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">识图</a>\n <a id="search-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">檢索</a>\n </div>\n </div>\n ');
$("#search-keyword").on("paste", (event2 => {
setTimeout((() => {
$("#search-btn").click();
}), 0);
})).on("keypress", (event2 => {
"Enter" === event2.key && setTimeout((() => {
$("#search-btn").click();
}), 0);
}));
$("#search-btn").on("click", (event2 => {
let keyword = $("#search-keyword").val(), searchCurrentType = $("#search-type option:selected").val();
"" !== keyword && (window.location.href.includes("/search?q") ? window.location.href = "/search?q=" + keyword + "&f=" + searchCurrentType : window.open("/search?q=" + keyword + "&f=" + searchCurrentType));
}));
$("#search-img-btn").on("click", (() => {
this.getBean("SearchByImagePlugin").open();
}));
}
margeNav() {
$('a[href*="/feedbacks/new"]').remove();
$('a[href*="theporndude.com"]').remove();
$('a.navbar-link[href="/makers"]').parent().after('\n <div class="navbar-item has-dropdown is-hoverable">\n <a class="navbar-link">其它</a>\n <div class="navbar-dropdown is-boxed">\n <a class="navbar-item" href="/feedbacks/new" target="_blank" >反饋</a>\n <a class="navbar-item" rel="nofollow noopener" target="_blank" href="https://theporndude.com/zh">ThePornDude</a>\n </div>\n </div>\n ');
}
}
class OtherSitePlugin extends BasePlugin {
handle() {
let carNum = this.getPageInfo().carNum, html = `\n <div style="margin-top:20px;margin-left: -16px;">\n <div style="display: flex;gap: 5px">\n <a id="javTrailersBtn" class="menu-btn" style="background:linear-gradient(to right, #d7ab91, rgb(255,76,76))"><span>JavTrailers</span></a>\n <a href="https://jable.tv/videos/${carNum}/" target="_blank" class="menu-btn" style="background:linear-gradient(to right, rgb(255,161,0), rgb(0,119,172))"><span>Jable</span></a>\n <a href="https://missav.ws/search/${carNum}" target="_blank" class="menu-btn" style="background:linear-gradient(to right, #d29494, rgb(254,98,142))"><span>MissAv</span></a>\n <a href="https://www.av.gl/vod/search.html?wd=${carNum}" target="_blank" class="menu-btn" style="color:#f40 !important;background:linear-gradient(to bottom, rgb(238,164,238), #fff)"><span>Avgle</span></a>\n </div>\n </div>\n `;
$(".column-video-cover").append(html);
$("#javTrailersBtn").on("click", (event2 => utils.openPage(`https://javtrailers.com/video/${carNum.toLowerCase().replace("-", "00")}?handle=1`, carNum, !1, event2)));
}
}
class BusDetailPagePlugin extends BasePlugin {
async initCss() {
if (!window.isDetailPage) return "";
$("h4:contains('論壇熱帖')").hide();
$("h4:contains('同類影片')").hide();
$("h4:contains('推薦')").hide();
return window.location.href.includes("hideNav=1") ? "\n .navbar-default {\n display: none !important;\n }\n body {\n padding-top:0px!important;\n }\n " : "";
}
async handle() {
if (window.location.href.includes("/star/")) {
const $avatarBox = $(".avatar-box");
if ($avatarBox.length > 0) {
let parent2 = $avatarBox.parent();
parent2.css("position", "initial");
parent2.insertBefore(parent2.parent());
}
}
}
}
class DetailPageButtonPlugin extends BasePlugin {
constructor() {
super();
this.answerCount = 1;
}
handle() {
this.bindHotkey();
window.isDetailPage && this.createMenuBtn();
}
createMenuBtn() {
const pageInfo = this.getPageInfo(), carNum = pageInfo.carNum, buttonsHtml = '\n <div style="margin: 10px auto;">\n <a id="filterBtn" class="menu-btn" style="background-color:#de3333">\n <span>屏蔽(a)</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc">\n <span>收藏(s)</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b">\n <span>加入已下载</span>\n </a>\n <a id="enable-magnets-filter" class="menu-btn" style="background-color:#c2bd4c">\n <span id="magnets-span">关闭磁力过滤</span>\n </a>\n \n\n <a id="search-subtitle-btn" class="menu-btn fr-btn" style="background:linear-gradient(to bottom, #8d5656, rgb(196,159,91))">\n <span>字幕 (SubTitleCat)</span>\n </a>\n <a id="xunLeiSubtitleBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)">\n <span>字幕 (迅雷)</span>\n </a>\n </div>\n ';
isJavDb && $(".tabs").after(buttonsHtml);
isJavBus && $("#mag-submit-show").before(buttonsHtml);
$("#favoriteBtn").on("click", (() => this.favoriteOne()));
$("#filterBtn").on("click", (event2 => this.filterOne(event2)));
$("#hasDownBtn").on("click", (async () => {
await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_HAS_DOWN);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}));
$("#enable-magnets-filter").on("click", (event2 => {
let $span = $("#magnets-span");
const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin");
if ("关闭磁力过滤" === $span.text()) {
highlightMagnetPlugin.showAll();
$span.text("开启磁力过滤");
} else {
highlightMagnetPlugin.handle();
$span.text("关闭磁力过滤");
}
}));
$("#search-subtitle-btn").on("click", (event2 => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum}`, carNum, !1, event2)));
$("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum)));
this.showStatus(carNum).then();
}
async showStatus(carNum) {
let car = await storageManager.getCar(carNum);
if (car) switch (car.status) {
case Status_FILTER:
$("#filterBtn").text("已屏蔽(a)");
break;
case Status_FAVORITE:
$("#favoriteBtn").text("已收藏(s)");
break;
case Status_HAS_DOWN:
$("#hasDownBtn").text("已加入已下载");
}
}
async favoriteOne() {
let pageInfo = this.getPageInfo();
await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FAVORITE);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}
searchXunLeiSubtitle(carNum) {
let loadObj = loading();
gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${carNum}`).then((res => {
let dataList = res.data;
dataList && 0 !== dataList.length ? layer.open({
type: 1,
title: "迅雷字幕",
content: '<div id="table-container"></div>',
area: [ "50%", "70%" ],
success: layero => {
new TableGenerator({
containerId: "table-container",
columns: [ {
key: "name",
title: "文件名"
}, {
key: "ext",
title: "类型"
}, {
key: "extra_name",
title: "来源"
} ],
data: dataList,
buttons: [ {
text: "下载",
class: "a-primary",
onClick: item => {
gmHttp.get(item.url).then((content => {
utils.download(content, carNum + "." + item.ext);
}));
}
} ]
});
}
}) : show.error("迅雷中找不到相关字幕!");
})).finally((() => {
loadObj.close();
}));
}
async filterOne(event2, noAlert) {
event2 && event2.preventDefault();
let pageInfo = this.getPageInfo();
if (noAlert) {
await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
} else utils.q(event2, `是否屏蔽${pageInfo.carNum}?`, (async () => {
await storageManager.saveCar(pageInfo.carNum, pageInfo.url, pageInfo.actress, Status_FILTER);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}));
}
speedVideo() {
if ($("#preview-video").is(":visible")) {
const videoEl = document.getElementById("preview-video");
if (videoEl) {
videoEl.muted = !1;
videoEl.currentTime += 5;
}
return;
}
const iframe = $('iframe[id^="layui-layer-iframe"]');
if (iframe.length > 0) {
iframe[0].contentWindow.postMessage("speedVideo", "*");
return;
}
let $videoPlayBtn = $(".preview-video-container");
if ($videoPlayBtn.length > 0) {
$videoPlayBtn[0].click();
const videoEl = document.getElementById("preview-video");
if (videoEl) {
videoEl.currentTime += 5;
videoEl.muted = !1;
}
} else $("#javTrailersBtn").click();
}
bindHotkey() {
const handlers = {
a: () => {
this.answerCount >= 2 ? this.filterOne(null, !0) : this.filterOne(null);
this.answerCount++;
},
s: () => this.favoriteOne(null),
z: () => this.speedVideo()
}, registerHotkey = (key, handler) => {
HotkeyManager.registerHotkey(key, (() => {
window.isDetailPage ? handler() : (message => {
const childIframe = $(".layui-layer-content iframe");
if (0 === childIframe.length) return !1;
childIframe[0].contentWindow.postMessage(message, "*");
})(key);
}));
};
window.isDetailPage && window.addEventListener("message", (event2 => {
handlers[event2.data] && handlers[event2.data]();
}));
Object.entries(handlers).forEach((([key, handler]) => {
registerHotkey(key, handler);
}));
}
}
class HistoryPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "dataType", "all");
__publicField(this, "tableObj", null);
}
handle() {
isJavDb && $(".navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable">\n <a id="historyBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n 历史列表\n </a>\n </div>');
isJavBus && $("#navbar").append('\n <ul class="nav navbar-nav navbar-right" style="margin-right: 10px">\n <li><a id="historyBtn" style="color: #86e114 !important;padding-right:15px !important;" role="button">历史列表</a></li>\n </ul>\n ');
$("#historyBtn").on("click", (event2 => this.openHistory()));
}
openHistory() {
layer.open({
type: 1,
title: "历史列表",
content: '\n <div style="margin: 10px">\n <a class="menu-btn history-btn" data-action="all" style="background-color:#d3c8a5">所有</a>\n <a class="menu-btn history-btn" data-action="filter" style="background-color:#ec4949">已屏蔽</a>\n <a class="menu-btn history-btn" data-action="favorite" style="background-color:#50adb9;">已收藏</a>\n <a class="menu-btn history-btn" data-action="hasDown" style="background-color:#8ebd6e;">已下载</a>\n </div>\n <div id="table-container"></div>\n ',
area: [ "60%", "80%" ],
success: async layero => {
const dataList = await this.getDataList();
this.loadTableData(dataList);
$(".layui-layer-content").on("click", ".history-btn", (async event2 => {
this.dataType = $(event2.target).data("action");
this.reloadTable();
}));
},
end: async () => window.refresh()
});
}
async handleClickDetail(event2, data) {
if (isJavDb) if (data.carNum.includes("FC2-")) {
const parts = data.url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
this.getBean("fc2Plugin").openFc2Page(movieId, data.carNum, data.url);
} else utils.openPage(data.url, data.carNum, !1, event2);
if (isJavBus) {
let url = data.url;
if (url.includes("javdb")) if (data.carNum.includes("FC2-")) {
const parts = url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
let openUrl = `${await storageManager.getSetting("javDbUrl", "https://javdb.com")}/users/collection_codes?movieId=${movieId}&carNum=${data.carNum}&url=${url}`;
window.open(openUrl, "_blank");
} else window.open(url, "_blank"); else utils.openPage(data.url, data.carNum, !1, event2);
}
}
async reloadTable() {
const dataList = await this.getDataList();
this.tableObj.update(dataList);
}
handleDelete(event2, data) {
utils.q(event2, `是否移除${data.carNum}?`, (async () => {
await storageManager.removeCar(data.carNum);
this.getBean("listPagePlugin").showCarNumBox(data.carNum);
this.reloadTable().then();
}));
}
async getDataList() {
let dataList = await storageManager.getCarList();
this.allCount = dataList.length;
this.filterCount = 0;
this.favoriteCount = 0;
this.hasDownCount = 0;
dataList.forEach((item => {
switch (item.status) {
case Status_FILTER:
this.filterCount++;
break;
case Status_FAVORITE:
this.favoriteCount++;
break;
case Status_HAS_DOWN:
this.hasDownCount++;
}
}));
$('a[data-action="all"]').text(`所有 (${this.allCount})`);
$('a[data-action="filter"]').text(`已屏蔽 (${this.filterCount})`);
$('a[data-action="favorite"]').text(`已收藏 (${this.favoriteCount})`);
$('a[data-action="hasDown"]').text(`已下载 (${this.hasDownCount})`);
return "all" === this.dataType ? dataList : dataList.filter((item => item.status === this.dataType));
}
loadTableData(dataList) {
this.tableObj = new TableGenerator({
containerId: "table-container",
columns: [ {
key: "carNum",
title: "番号"
}, {
key: "actress",
title: "演员",
width: "250px"
}, {
key: "createDate",
title: "操作日期",
width: "185px"
}, {
key: "url",
title: "来源",
render: item => {
let url = item.url;
return url.includes("javdb") ? '<span style="color:#d34f9e">Javdb</span>' : url.includes("javbus") ? '<span style="color:#eaa813">JavBus</span>' : `<span style="color:#050505">${url}</span>`;
}
}, {
key: "status",
title: "状态",
width: "250px",
render: item => {
let color, text = "";
switch (item.status) {
case "filter":
color = "#ec4949";
text = "已屏蔽";
break;
case "favorite":
color = "#50adb9";
text = "已收藏";
break;
case "hasDown":
color = "#8ebd6e";
text = "已下载";
}
return `<span style="color:${color}">${text}</span>`;
}
} ],
data: dataList,
buttons: [ {
text: "移除",
class: "a-danger",
onClick: item => {
this.handleDelete(event, item);
}
}, {
text: "详情页",
class: "a-info",
onClick: item => {
this.handleClickDetail(event, item);
}
} ]
});
}
}
class ReviewPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "floorIndex", 1);
}
async handle() {
if (window.isDetailPage) {
if (isJavDb) {
const parts = window.location.href.split("?")[0].split("/"), movieId = parts[parts.length - 1].split("#")[0];
await this.showReview(movieId);
await this.getBean("RelatedPlugin").showRelated();
}
if (isJavBus) {
let carNum = this.getPageInfo().carNum;
const movies = await (async keyword => {
let url = `${apiUrl}/v2/search`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
jdsignature: await buildSignature()
}, params = {
q: keyword,
page: 1,
type: "movie",
limit: 1,
movie_type: "all",
from_recent: "false",
movie_filter_by: "all",
movie_sort_by: "relevance"
};
return (await gmHttp.get(url, params, headers)).data.movies;
})(carNum);
let movieId = null;
for (let i = 0; i < movies.length; i++) {
let item = movies[i];
if (item.number.toLowerCase() === carNum.toLowerCase()) {
movieId = item.id;
break;
}
}
if (!movieId) {
show.error("解析视频ID失败, 该视频可能在JavDb中不存在, 无法获取评论数据");
return;
}
this.showReview(movieId, $("#sample-waterfall")).then();
}
}
}
async showReview(movieId, $eleBox) {
let $magnets = $("#magnets-content");
$eleBox && ($magnets = $eleBox);
$magnets.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
let reviewCount = await storageManager.getSetting("reviewCount", 20), dataList = null;
try {
dataList = await getReviews(movieId, 1, reviewCount);
} catch (e) {
console.error(e);
}
$("#reviewsLoading").remove();
$magnets.append('\n <div style=" display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px; ">\n <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n <span style="padding: 0 10px;">评论区</span>\n <a id="reviewsFold" style=" margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center; ">\n <span class="toggle-text">折叠</span>\n <span class="toggle-icon" style="margin-left: 4px;">▲</span>\n </a>\n <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n </div>\n ');
$("#reviewsFold").on("click", (() => {
const textSpan = $("#reviewsFold .toggle-text"), iconSpan = $("#reviewsFold .toggle-icon");
if ("展开" === textSpan.text()) {
textSpan.text("折叠");
iconSpan.text("▲");
$reviewsContainer.show();
$reviewsFooter.show();
} else {
textSpan.text("展开");
iconSpan.text("▼");
$reviewsContainer.hide();
$reviewsFooter.hide();
}
}));
$magnets.append('<div id="reviewsContainer"></div>');
$magnets.append('<div id="reviewsFooter"></div>');
const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter");
if (!dataList) {
$reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论失败</div>');
return;
}
0 === dataList.length && $reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>');
const reviewKeywordList = await storageManager.getReviewFilterKeywordList();
this.displayReviews(dataList, $reviewsContainer, reviewKeywordList);
if (dataList.length === reviewCount) {
$reviewsFooter.html('\n <button id="loadMoreReviews" style="width:100%; background-color: #e1f5fe; border:none; padding:10px; margin-top:10px; cursor:pointer; color:#0277bd; font-weight:bold; border-radius:4px;">\n 加载更多评论\n </button>\n <div id="reviewsEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>\n ');
let currentPage = 1;
$("#loadMoreReviews").click((async () => {
$("#loadMoreReviews").text("加载中...").prop("disabled", !0);
currentPage++;
const moreData = await getReviews(movieId, currentPage, reviewCount);
this.displayReviews(moreData, $reviewsContainer, reviewKeywordList);
if (moreData.length < reviewCount) {
$("#loadMoreReviews").remove();
$("#reviewsEnd").show();
} else $("#loadMoreReviews").text("加载更多评论").prop("disabled", !1);
}));
} else dataList.length > 0 && $reviewsFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>');
}
displayReviews(dataList, $container, reviewKeywordList) {
if (dataList.length) {
dataList.forEach((item => {
let isReviewKeyword = !1;
for (let i = 0; i < reviewKeywordList.length; i++) if (item.content.indexOf(reviewKeywordList[i]) > -1) {
isReviewKeyword = !0;
break;
}
if (isReviewKeyword) return;
let starsHtml = "";
for (let i = 0; i < item.score; i++) starsHtml += '<i class="icon-star"></i>';
let content = item.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (match => `<a href="${match}" class="a-primary" \n style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${match}</a>\n\x3c!-- <a class="a-success review-magnet" style="padding:0;margin-left:0">预览</a>--\x3e`)), commentHtml = `\n <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;position:relative;">\n <span style="position:absolute;top:5px;right:10px;color:#999;font-size:12px;">#${this.floorIndex++}楼</span>\n ${item.username} <span class="score-stars">${starsHtml}</span> \n <span class="time">${item.created_at.replace("T", " ").replace(".000Z", "")}</span> \n 点赞:${item.likes_count}\n <p class="review-content" style="margin-top: 5px;"> ${content} </p>\n </div>\n `;
$container.append(commentHtml);
}));
utils.rightClick($(".review-content"), (event2 => {
const selectedText = window.getSelection().toString();
if (selectedText) {
event2.preventDefault();
utils.q(event2, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => {
await storageManager.saveReviewFilterKeyword(selectedText);
show.ok("操作成功, 刷新页面后生效");
}));
}
}));
}
}
}
class FilterTitleKeywordPlugin extends BasePlugin {
handle() {
if (!window.isDetailPage) return;
let $titles, $male;
if (isJavDb) {
$titles = $("h2");
$male = $(".male").prev();
}
isJavBus && ($titles = $("h3"));
utils.rightClick($titles, (event2 => {
const selectedText = window.getSelection().toString();
if (selectedText) {
event2.preventDefault();
let tempEvent = {
clientX: event2.clientX,
clientY: event2.clientY + 80
};
utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => {
await storageManager.saveTitleFilterKeyword(selectedText);
window.refresh();
utils.closePage();
}));
}
}));
$male && $male.length > 0 && utils.rightClick($male, (event2 => {
event2.preventDefault();
let text = $(event2.target).text().trim();
utils.q(event2, `是否屏蔽演员${text}?`, (async () => {
await storageManager.saveFilterActor(text);
window.refresh();
const detailPageButtonPlugin = this.getBean("detailPageButtonPlugin");
await detailPageButtonPlugin.filterOne(null, !0);
}));
}));
}
}
class ListPageButtonPlugin extends BasePlugin {
handle() {
window.isListPage && this.createMenuBtn();
}
createMenuBtn() {
if (isJavDb) {
if (window.location.href.includes("/actors/")) {
$(".toolbar .buttons").append('\n <a class="menu-btn" id="waitCheckBtn" \n style="background-color:#56c938 !important;; margin-left: 40px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n <span>打开待鉴定</span>\n </a>\n <a class="menu-btn" id="waitDownBtn" \n style="background-color:#2caac0 !important;; margin-left: 10px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">\n <span>打开已收藏</span>\n </a>\n ');
$(".toolbar .buttons").append(`\n <a class="menu-btn" id="sort-toggle-btn" \n style="background-color:#8783ab !important; margin-left: 50px;margin-bottom: 8px; border-bottom:none !important; border-radius:3px;">当前排序方式: ${"rateCount" === localStorage.getItem("sortMethod") ? "评价人数" : "date" === localStorage.getItem("sortMethod") ? "时间" : "默认"}</a>\n `);
} else {
$(".tabs ul").append('\n <li class="is-active" id="waitCheckBtn">\n <a class="menu-btn" style="background-color:#56c938 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>打开待鉴定</span>\n </a>\n </li>\n <li class="is-active" id="waitDownBtn">\n <a class="menu-btn" style="background-color:#2caac0 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span>打开已收藏</span>\n </a>\n </li>\n ');
$(".tabs ul").after(`\n <div style="padding:10px">\n <a class="menu-btn" id="sort-toggle-btn" \n style="background-color:#8783ab !important; margin-left: 20px; border-bottom:none !important; border-radius:3px;">\n 当前排序方式: ${"rateCount" === localStorage.getItem("sortMethod") ? "评价人数" : "date" === localStorage.getItem("sortMethod") ? "时间" : "默认"}\n </a>\n </div>\n `);
}
this.sortItems();
}
if (isJavBus) {
const buttonsHtml = '\n <div style="margin-top: 10px">\n <a id="waitCheckBtn" class="menu-btn" style="background-color:#56c938 !important;margin-left: 14px;border-bottom:none !important;border-radius:3px;">\n <span>打开待鉴定</span>\n </a>\n <a id="waitDownBtn" class="menu-btn" style="background-color:#2caac0 !important;margin-left: 5px;border-bottom:none !important;border-radius:3px;">\n <span>打开已收藏</span>\n </a>\n </div>\n ';
$(".masonry").parent().prepend(buttonsHtml);
}
$("#waitCheckBtn").on("click", (event2 => {
this.openWaitCheck(event2).then();
}));
$("#waitDownBtn").on("click", (event2 => {
this.openFavorite(event2).then();
}));
$("#sort-toggle-btn").on("click", (event2 => {
const currentMethod = localStorage.getItem("sortMethod");
let newMethod;
newMethod = currentMethod && "default" !== currentMethod ? "rateCount" === currentMethod ? "date" : "default" : "rateCount";
const methodText = {
default: "默认",
rateCount: "评价人数",
date: "时间"
}[newMethod];
$(event2.target).text(`当前排序方式: ${methodText}`);
localStorage.setItem("sortMethod", newMethod);
this.sortItems();
}));
}
sortItems() {
const method = localStorage.getItem("sortMethod");
if (!method) return;
$(".movie-list .item").each((function(index) {
$(this).attr("data-original-index") || $(this).attr("data-original-index", index);
}));
const $container = $(".movie-list"), $items = $(".item", $container);
if ("default" === method) $items.sort((function(a, b) {
return $(a).data("original-index") - $(b).data("original-index");
})).appendTo($container); else {
const items = $items.get();
items.sort((function(a, b) {
if ("rateCount" === method) {
const getScore = el => {
const match = $(el).find(".score .value").text().match(/由(\d+)人/);
return match ? parseFloat(match[1]) : 0;
};
return getScore(b) - getScore(a);
}
{
const getDate = el => {
const dateStr = $(el).find(".meta").text().trim();
return new Date(dateStr);
};
return getDate(b) - getDate(a);
}
}));
$container.empty().append(items);
}
}
async openWaitCheck() {
let selector;
isJavDb && (selector = Db);
isJavBus && (selector = Bus);
const maxCount = await storageManager.getSetting("waitCheckCount", 5), excludedTexts = [ "已收藏", "已屏蔽", "已下载" ];
let count = 0;
$(`${selector.itemSelector}:visible`).each(((i, el) => {
if (count >= maxCount) return !1;
const $el = $(el);
if (!excludedTexts.some((text => $el.find(`span:contains('${text}')`).length > 0))) {
const $link = $el.find("a");
if ($link.length) {
let href = $link.attr("href");
if (href) {
href += href.includes("?") ? "&autoPlay=1" : "?autoPlay=1";
window.open(href);
count++;
}
}
}
}));
0 === count && show.info("没有需鉴定的视频");
}
async openFavorite() {
let openCount = await storageManager.getSetting("waitCheckCount", 5);
const favoriteList = (await storageManager.getCarList()).filter((item => item.status === Status_FAVORITE));
for (let i = 0; i < openCount; i++) {
if (i >= favoriteList.length) return;
let data = favoriteList[i], carNum = data.carNum, url = data.url;
if (carNum.includes("FC2-")) {
const parts = data.url.split("/"), movieId = parts[parts.length - 1].split("#")[0];
let javDbUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com");
window.open(`${javDbUrl}/users/collection_codes?movieId=${movieId}&carNum=${carNum}&url=${url}`);
} else window.open(url);
}
}
}
class ListPagePlugin extends BasePlugin {
async handle() {
this.cleanRepeatId();
this.replaceHdImg();
await this.doFilter();
this.bindClick().then();
new BroadcastChannel("channel-refresh").addEventListener("message", (async event2 => {
"refresh" === event2.data.type && await this.doFilter();
}));
this.checkDom();
}
checkDom() {
if (!window.isListPage) return;
const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector), observer = new MutationObserver((mutations => {
utils.log("检查");
observer.disconnect();
try {
this.replaceHdImg();
this.doFilter().then();
this.getBean("ListPageButtonPlugin").sortItems();
} finally {
observer.observe(targetNode, config);
}
})), config = {
childList: !0,
subtree: !1
};
observer.observe(targetNode, config);
}
cleanRepeatId() {
if (!isJavBus) return;
$("#waterfall_h").removeAttr("id").attr("id", "no-page");
const $waterfalls = $('[id="waterfall"]');
0 !== $waterfalls.length && $waterfalls.each((function() {
const $current = $(this);
if (!$current.hasClass("masonry")) {
$current.children().insertAfter($current);
$current.remove();
}
}));
}
async doFilter() {
if (!window.isListPage) return;
let movieList = $(this.getSelector().itemSelector).toArray();
await this.filterMovieList(movieList);
await this.getBean("autoPagePlugin").handlePaging();
}
async filterMovieList(movieList) {
const carList = await storageManager.getCarList(), filterKeywordList = await storageManager.getTitleFilterKeyword(), filterCarNums = carList.filter((item => item.status === Status_FILTER)).map((item => item.carNum)), favoriteCarNums = carList.filter((item => item.status === Status_FAVORITE)).map((item => item.carNum)), hasDownCarNums = carList.filter((item => item.status === Status_HAS_DOWN)).map((item => item.carNum));
let hideFilterItem = await storageManager.getSetting("hideFilterItem", "yes"), href = window.location.href;
(href.includes("search?q") || href.includes("/search/") || href.includes("/users/")) && (hideFilterItem = "no");
movieList.forEach((ele => {
let $box = $(ele);
const {carNum: carNum, aHref: aHref, title: title} = this.findCarNumAndHref($box), hideKey = `${carNum}-hide`, keywordHideKey = `${carNum}-keywordHide`, tagKey = `${carNum}-tag`;
if ("no" === hideFilterItem && $box.attr("data-hide") === hideKey) {
$box.show();
$box.removeAttr("data-hide");
}
if (filterKeywordList.some((filterKeyword => title.includes(filterKeyword) || carNum.includes(filterKeyword))) && $box.attr("data-keyword-hide") !== keywordHideKey) {
$box.hide();
$box.attr("data-keyword-hide", keywordHideKey);
return;
}
if (filterCarNums.includes(carNum) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
$box.hide();
$box.attr("data-hide", hideKey);
return;
}
if (hasDownCarNums.includes(carNum) && "yes" === hideFilterItem && $box.attr("data-hide") !== hideKey) {
$box.hide();
$box.attr("data-hide", hideKey);
return;
}
let tagText = "", color = "";
if (filterCarNums.includes(carNum)) {
tagText = "已屏蔽";
color = "#d95427";
} else if (favoriteCarNums.includes(carNum)) {
tagText = "已收藏";
color = "#2caac0";
} else if (hasDownCarNums.includes(carNum)) {
tagText = "已下载";
color = "#58c433";
}
if (tagText && $box.attr("data-tag") !== tagKey) {
isJavDb && $box.find(".tags").append(`\n <span class="tag is-success" \n style="margin-right: 5px; border-radius:10px; position:absolute; right: 0; top:5px;z-index:10;background-color: ${color} !important;">\n ${tagText}\n </span>`);
if (isJavBus) {
let tagHtml = `<a class="a-primary" style="margin-right: 5px; padding: 0 5px;color: #fff; border-radius:10px; position:absolute; right: 0; top:5px;z-index:10;background-color: ${color} !important;"><span>${tagText}</span></a>`;
$box.find(".item-tag").append(tagHtml);
}
$box.attr("data-tag", tagKey);
}
}));
$("#waitDownBtn span").text(`打开已收藏 (${favoriteCarNums.length})`);
}
async bindClick() {
let selector = this.getSelector(), dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", "yes");
$(selector.boxSelector).on("click", ".item img", (event2 => {
event2.preventDefault();
if ($(event2.target).closest("div.meta-buttons").length) return;
const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
if (carNum.includes("FC2-")) {
let movieId = aHref.split("/").filter(Boolean).pop();
this.getBean("fc2Plugin").openFc2Page(movieId, carNum, aHref);
} else "yes" === dialogOpenDetail ? utils.openPage(aHref, carNum, !1, event2) : window.open(aHref);
}));
$(selector.boxSelector).on("contextmenu", ".item img", (event2 => {
event2.preventDefault();
const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref} = this.findCarNumAndHref($box);
utils.q(event2, `是否屏蔽番号 ${carNum}?`, (async () => {
await storageManager.saveCar(carNum, aHref, "", Status_FILTER);
window.refresh();
show.ok("操作成功");
}));
}));
}
findCarNumAndHref($box) {
let carNum, title, aHref = $box.find("a").attr("href");
if (isJavDb) {
carNum = $box.find(".video-title").find("strong").text();
title = $box.find(".video-title").text();
}
if (isJavBus) {
carNum = aHref.split("/").filter(Boolean).pop();
title = $box.find("img").attr("title");
}
return {
carNum: carNum,
aHref: aHref,
title: title
};
}
showCarNumBox(matchCarNum) {
const matchingBox = $(".movie-list .item").toArray().find((item => $(item).find(".video-title strong").text() === matchCarNum));
if (matchingBox) {
const $matchingBox = $(matchingBox);
if ($matchingBox.attr("data-hide") === `${matchCarNum}-hide`) {
$matchingBox.show();
$matchingBox.removeAttr("data-hide");
}
}
}
replaceHdImg(coverImgNodeList) {
coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector));
isJavDb && coverImgNodeList.forEach((img => {
img.src = img.src.replace("thumbs", "covers");
}));
if (isJavBus) {
const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = img => {
if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) {
img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1");
img.dataset.hdReplaced = "true";
img.loading = "lazy";
}
};
coverImgNodeList.forEach((img => {
replaceWithHd(img);
}));
}
}
}
class AutoPagePlugin extends BasePlugin {
constructor() {
super();
this.paging = !1;
this.selector = null;
isJavDb && (this.selector = Db);
isJavBus && (this.selector = Bus);
}
async handle() {
window.isListPage && !this.shouldDisablePaging() && this.bindPageClick().then();
}
async bindPageClick() {
$(".pagination-link, .pagination-next, .pagination-previous, .pagination li a").on("click", (event2 => {
event2.preventDefault();
event2.stopPropagation();
let href = $(event2.target).attr("href");
this.parsePage(href).then();
}));
0 === $("#auto-page").length && await this.insertPageBtn();
$("#auto-page").on("click", (async event2 => {
event2.preventDefault();
if ("yes" === await storageManager.getItem(storageManager.auto_page_key)) {
await storageManager.setItem(storageManager.auto_page_key, "no");
$("#auto-page").html("开启自动翻页");
} else {
await storageManager.setItem(storageManager.auto_page_key, "yes");
$("#auto-page").html("关闭自动翻页");
await this.handlePaging();
}
}));
}
async insertPageBtn() {
let initText = "yes" === await storageManager.getItem(storageManager.auto_page_key) ? "关闭自动翻页" : "开启自动翻页";
isJavDb && $(".pagination").prepend(`<a style="background-color:#fff; order: 2;padding: calc(.5em - 1px) .75em;" id='auto-page'>${initText}</a>`);
isJavBus && $(".pagination").append(`<li><a style="margin-left: 20px;cursor: pointer;" id='auto-page'>${initText}</a></li>`);
}
async parsePage(href) {
let loadObj = loading();
try {
const html = await http.get(href), dom = (new DOMParser).parseFromString(html, "text/html");
let itemList = dom.querySelectorAll(this.selector.requestDomItemSelector), pagination = dom.querySelectorAll(".pagination");
const listPagePlugin = this.getBean("listPagePlugin");
await listPagePlugin.filterMovieList(itemList);
let coverImgNodeList = dom.querySelectorAll(this.selector.coverImgSelector);
listPagePlugin.replaceHdImg(coverImgNodeList);
let $movieList = $(this.selector.boxSelector);
$movieList.fadeOut(300, (() => {
$movieList.html(itemList).fadeIn(300, (async () => {}));
}));
await this.insertPageBtn();
$(".pagination").replaceWith(pagination);
window.history.pushState({}, "", href);
if (isJavBus) {
const pageNumber = this.getPageNumberFromUrl(href);
document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁");
}
await utils.smoothScrollToTop();
await this.bindPageClick();
await this.handlePaging();
} finally {
loadObj.close();
}
}
getPageNumberFromUrl(url) {
const match = url.match(/\/page\/(\d+)/);
return match ? parseInt(match[1], 10) : null;
}
shouldDisablePaging() {
return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos" ].some((path => window.location.href.includes(path)));
}
async handlePaging() {
if (this.shouldDisablePaging()) return;
if ($("#no-page").length) {
$("#auto-page").remove();
return;
}
if (0 === $(this.selector.boxSelector).length) return;
if (!window.isListPage) return;
if (this.paging) return;
let needPaging = !0;
$(`${this.selector.itemSelector}:visible`).each(((i, el) => {
0 === $(el).find("span:contains('已收藏')").length && 0 === $(el).find("span:contains('已屏蔽')").length && 0 === $(el).find("span:contains('已下载')").length && (needPaging = !1);
}));
if (!needPaging) return;
if ("yes" !== await storageManager.getItem(storageManager.auto_page_key)) return;
let nextBtn = null;
isJavDb && (nextBtn = $(".pagination-next"));
isJavBus && (nextBtn = $("#next"));
if (nextBtn && 0 !== nextBtn.length) {
this.paging = !0;
show.info("下一页....", {
duration: 500,
callback: () => {
nextBtn[0].click();
this.paging = !1;
}
});
}
}
}
class HighlightMagnetPlugin extends BasePlugin {
handle() {
this.handleDb();
this.handleBus();
}
handleDb() {
if (!isJavDb || !isDetailPage) return;
let magnetNameList = $("#magnets-content .name").toArray(), has4k_C_UC = !1;
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 = !0);
}));
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();
})) : $("#enable-magnets-filter").addClass("do-hide");
}
handleBus() {
isJavBus && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
let magnetNameList = $("#magnet-table tr td:first-child a:first-child").toArray(), has4k_C_UC = !1;
magnetNameList.forEach((el => {
let item = $(el), text = item.text().toLowerCase();
text.indexOf("4k") > -1 && item.css("color", "#f40");
(text.indexOf("-c") > -1 || text.indexOf("-uc") > -1 || text.indexOf("4k") > -1) && (has4k_C_UC = !0);
}));
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().hide();
})) : $("#enable-magnets-filter").addClass("do-hide");
}));
}
showAll() {
if (isJavDb) {
$("#magnets-content .item").toArray().forEach((el => $(el).show()));
}
isJavBus && $("#magnet-table tr").toArray().forEach((el => $(el).show()));
}
}
class AliyunApi {
constructor(refresh_token) {
this.baseApiUrl = "https://api.aliyundrive.com";
this.refresh_token = refresh_token;
this.authorization = null;
this.default_drive_id = null;
this.backupFolderId = null;
}
async getDefaultDriveId() {
if (this.default_drive_id) return this.default_drive_id;
this.userInfo = await this.getUserInfo();
this.default_drive_id = this.userInfo.default_drive_id;
return this.default_drive_id;
}
async getHeaders() {
if (this.authorization) return {
authorization: this.authorization
};
this.authorization = await this.getAuthorization();
return {
authorization: this.authorization
};
}
async getAuthorization() {
let url = this.baseApiUrl + "/v2/account/token", data = {
refresh_token: this.refresh_token,
grant_type: "refresh_token"
};
try {
return "Bearer " + (await http.post(url, data)).access_token;
} catch (e) {
throw e.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : e;
}
}
async getUserInfo() {
const headers = await this.getHeaders();
let url = this.baseApiUrl + "/v2/user/get";
return await http.post(url, {}, headers);
}
async deleteFile(file_id, drive_id = null) {
if (!file_id) throw new Error("未传入file_id");
drive_id || (drive_id = await this.getDefaultDriveId());
let data = {
file_id: file_id,
drive_id: drive_id
}, url = this.baseApiUrl + "/v2/recyclebin/trash";
const headers = await this.getHeaders();
await gmHttp.post(url, data, headers);
return {};
}
async createFolder(name, drive_id = null, parent_folder_id = "root") {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/adrive/v2/file/createWithFolders", data = {
name: name,
type: "folder",
parent_file_id: parent_folder_id,
check_name_mode: "auto_rename",
content_hash_name: "sha1",
drive_id: drive_id
};
const headers = await this.getHeaders(), result = await gmHttp.post(url, data, headers);
return JSON.parse(result);
}
async getFileList(parent_folder_id = "root", drive_id = null) {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/adrive/v3/file/list";
const data = {
drive_id: drive_id,
parent_file_id: parent_folder_id,
limit: 200,
all: !1,
url_expire_sec: 14400,
image_thumbnail_process: "image/resize,w_256/format,avif",
image_url_process: "image/resize,w_1920/format,avif",
video_thumbnail_process: "video/snapshot,t_120000,f_jpg,m_lfit,w_256,ar_auto,m_fast",
fields: "*",
order_by: "updated_at",
order_direction: "DESC"
}, headers = await this.getHeaders();
return (await gmHttp.post(url, data, headers)).items;
}
async uploadFile(folder_id, fileName, uploadContent, drive_id = null) {
let createFileUrl = this.baseApiUrl + "/adrive/v2/file/createWithFolders";
drive_id || (drive_id = await this.getDefaultDriveId());
let data = {
drive_id: drive_id,
part_info_list: [ {
part_number: 1
} ],
parent_file_id: folder_id,
name: fileName,
type: "file",
check_name_mode: "auto_rename"
};
const headers = await this.getHeaders(), createFileResult = await gmHttp.post(createFileUrl, data, headers), upload_id = createFileResult.upload_id, upload_file_id = createFileResult.file_id, upload_url = createFileResult.part_info_list[0].upload_url;
console.log("创建完成: ", createFileResult);
await this._doUpload(upload_url, uploadContent);
const completeResult = await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", data = {
drive_id: "745851",
file_id: upload_file_id,
upload_id: upload_id
}, headers);
console.log("标记完成:", completeResult);
}
_doUpload(upload_url, uploadContent) {
return new Promise(((resolve, reject) => {
$.ajax({
type: "PUT",
url: upload_url,
data: uploadContent,
contentType: " ",
processData: !1,
success: (res, status, xhr) => {
if (200 === xhr.status) {
console.log("上传成功:", res);
resolve({});
} else reject(xhr);
},
error: xhr => {
console.error("上传失败", xhr.responseText);
reject(xhr);
}
});
}));
}
async getDownloadUrl(file_id, drive_id = null) {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/v2/file/get_download_url";
const headers = await this.getHeaders();
let data = {
file_id: file_id,
drive_id: drive_id
};
return (await gmHttp.post(url, data, headers)).url;
}
async _createBackupFolder(folderName) {
const fileList = await this.getFileList();
let folderObj = null;
for (let i = 0; i < fileList.length; i++) {
let file = fileList[i];
if (file.name === folderName) {
folderObj = file;
break;
}
}
if (!folderObj) {
console.log("不存在目录, 进行创建");
folderObj = await this.createFolder(folderName);
}
this.backupFolderId = folderObj.file_id;
}
async backup(folderName, fileName, uploadContent) {
if (this.backupFolderId) await this.uploadFile(this.backupFolderId, fileName, uploadContent); else {
await this._createBackupFolder(folderName);
await this.uploadFile(this.backupFolderId, fileName, uploadContent);
}
}
async getBackupList(folderName) {
let dataList = null;
if (this.backupFolderId) dataList = await this.getFileList(this.backupFolderId); else {
await this._createBackupFolder(folderName);
dataList = await this.getFileList(this.backupFolderId);
}
const fileList = [];
dataList.forEach((data => {
fileList.push({
name: data.name,
fileId: data.file_id,
createTime: data.created_at,
size: data.size
});
}));
return fileList;
}
}
class WebDavApi {
constructor(davUrl, username, password) {
this.davUrl = davUrl.endsWith("/") ? davUrl : davUrl + "/";
this.username = username;
this.password = password;
this.folderName = null;
}
_getAuthHeaders() {
return {
Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`,
Depth: "1"
};
}
_sendRequest(method, path, headers = {}, data) {
return new Promise(((resolve, reject) => {
const url = this.davUrl + path, allHeaders = {
...this._getAuthHeaders(),
...headers
};
GM_xmlhttpRequest({
method: method,
url: url,
headers: allHeaders,
data: data,
onload: response => {
response.status >= 200 && response.status < 300 ? resolve(response) : reject(new Error(`Request failed with status ${response.status}: ${response.statusText}`));
},
onerror: response => {
console.error("请求WebDav发生错误:", response);
reject(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确"));
}
});
}));
}
async backup(folderName, fileName, uploadContent) {
await this._sendRequest("MKCOL", folderName);
const path = folderName + "/" + fileName;
await this._sendRequest("PUT", path, {
"Content-Type": "text/plain"
}, uploadContent);
}
async getFileList(folderName) {
var _a, _b;
const xmlResponse = (await this._sendRequest("PROPFIND", folderName, {
"Content-Type": "application/xml"
}, '\n <?xml version="1.0"?>\n <d:propfind xmlns:d="DAV:">\n <d:prop>\n <d:displayname />\n <d:getcontentlength />\n <d:creationdate />\n <d:iscollection />\n </d:prop>\n </d:propfind>\n ')).responseText, items = (new DOMParser).parseFromString(xmlResponse, "text/xml").getElementsByTagNameNS("DAV:", "response"), fileList = [];
for (let i = 0; i < items.length; i++) {
if (0 === i) continue;
if ("1" === items[i].getElementsByTagNameNS("DAV:", "iscollection")[0].textContent) continue;
const name = items[i].getElementsByTagNameNS("DAV:", "displayname")[0].textContent, size = (null == (_a = items[i].getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : _a.textContent) || "0", createTime = (null == (_b = items[i].getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : _b.textContent) || "";
fileList.push({
fileId: name,
name: name,
size: size,
createTime: createTime
});
}
fileList.reverse();
return fileList;
}
async deleteFile(fileId) {
let path = this.folderName + "/" + encodeURI(fileId);
await this._sendRequest("DELETE", path, {
"Cache-Control": "no-cache"
});
}
async getBackupList(folderName) {
this.folderName = folderName;
await this._sendRequest("MKCOL", folderName);
return this.getFileList(folderName);
}
async getFileContent(filePath) {
let path = this.folderName + "/" + filePath;
return (await this._sendRequest("GET", path, {
Accept: "application/octet-stream"
})).responseText;
}
}
class SettingPlugin extends BasePlugin {
constructor() {
super(...arguments);
__privateAdd(this, _SettingPlugin_instances);
__publicField(this, "folderName", "JSH-数据备份");
}
async initCss() {
let containerWidth = await storageManager.getSetting("containerWidth", "100"), containerColumns = await storageManager.getSetting("containerColumns", 5), containerWidthCss = `\n section .container{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n }\n .movie-list{\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `;
isJavBus && (containerWidthCss = `\n .container-fluid .row{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n margin: auto auto;\n }\n \n .masonry {\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `);
return `\n <style>\n ${containerWidthCss}\n .nav-btn::after {\n content:none !important;\n }\n \n .setting-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n padding: 10px;\n border: 1px solid #ddd;\n border-radius: 5px;\n background-color: #f9f9f9;\n }\n .setting-label {\n min-width: 250px;\n font-weight: bold;\n margin-right: 10px;\n }\n .form-content{\n max-width: 150px;\n min-width: 150px;\n }\n .form-content * {\n width: 100%;\n padding: 5px;\n margin-right: 10px;\n min-width: 150px;\n text-align: center;\n }\n .keyword-label {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n border-radius: 4px;\n color: white;\n font-size: 14px;\n position: relative;\n margin-left: 8px;\n margin-bottom: 2px;\n }\n \n .keyword-remove {\n margin-left: 6px;\n cursor: pointer;\n font-size: 12px;\n line-height: 1;\n }\n \n .keyword-input {\n padding: 6px 12px;\n border: 1px solid #ccc;\n border-radius: 4px;\n font-size: 14px;\n float:right;\n }\n \n .add-tag-btn {\n padding: 6px 12px;\n background-color: #45d0b6;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n margin-left: 8px;\n float:right;\n }\n \n .add-tag-btn:hover {\n background-color: #3fceb7;\n }\n #saveBtn {\n padding: 8px 20px;\n background-color: #4CAF50;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n margin-top: 10px;\n }\n #saveBtn:hover {\n background-color: #45a049;\n }\n </style\n `;
}
handle() {
isJavDb && $(".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>');
isJavBus ? $("#navbar").append('\n <ul class="nav navbar-nav navbar-right">\n <li><a id="setting-btn" style="color: #ff8400 !important;padding-right:15px !important;" role="button">设置</a></li>\n </ul>\n ') : $("#pt").append('<div class="z" style="float: right">\n <a id="setting-btn" class="a-primary" style="padding: 0px 16px;">\n 设置\n </a>\n </div>');
$("#setting-btn").on("click", (() => {
layer.open({
type: 1,
title: "设置",
content: '\n <div style=" display: flex; flex-direction: column; height: 100%; ">\n <div style="margin: 20px">\n <a id="importBtn" class="menu-btn" style="background-color:#d25a88"><span>导入数据</span></a>\n <a id="exportBtn" class="menu-btn" style="background-color:#85d0a3"><span>导出数据</span></a>\n <a id="syncDataBtn" class="menu-btn" style="background-color:#387ca9"><span>同步数据</span></a>\n <a id="getRefreshTokenBtn" class="menu-btn fr-btn" style="background-color:#c4a35e"><span>获取refresh_token</span></a>\n\n </div>\n <div style=" flex: 1; overflow-y: auto; margin: 0 20px; padding-bottom: 20px; ">\n <div class="setting-item">\n <span class="setting-label">阿里云盘备份</span>\n <div>\n <a id="backupBtn" class="menu-btn" style="background-color:#5d87c2"><span>备份数据</span></a>\n <a id="backupListBtn" class="menu-btn" style="background-color:#48c554"><span>查看备份</span></a>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">refresh_token:</span>\n <div class="form-content">\n <input id="refresh_token" >\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">WebDav备份</span>\n <div>\n <a id="webdavBackupBtn" class="menu-btn" style="background-color:#5d87c2"><span>备份数据</span></a>\n <a id="webdavBackupListBtn" class="menu-btn" style="background-color:#48c554"><span>查看备份</span></a>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">服务地址:</span>\n <div class="form-content">\n <input id="webDavUrl" >\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">用户名:</span>\n <div class="form-content">\n <input id="webDavUsername" >\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">密码:</span>\n <div class="form-content">\n <input id="webDavPassword" >\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">隐藏已屏蔽内容:</span>\n <div class="form-content">\n <select id="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="reviewCount">\n <option value="10">10条</option>\n <option value="20">20条</option>\n <option value="30">30条</option>\n <option value="40">40条</option>\n <option value="50">50条</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">页面列数:</span>\n <div class="form-content" style="position: relative;padding-top: 20px">\n <input type="range" id="containerColumns" min="3" max="10" step="1" style="padding:5px 0">\n <span id="showContainerColumns" style="position:absolute; top:-10px;"></span>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">页面宽度:</span>\n <div class="form-content" style="position: relative;padding-top: 20px">\n <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0">\n <span id="showContainerWidth" style="position:absolute; top:-10px;"></span>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">每次打开待鉴定待下载数量:</span>\n <div class="form-content">\n <input type="number" id="waitCheckCount" min="1" max="20" style="width: 100%;">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">详情页打开方式:</span>\n <div class="form-content">\n <select id="dialogOpenDetail">\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">JavDb地址(用于跟Bus同步数据):</span>\n <div class="form-content">\n <input id="javDbUrl" >\n </div>\n </div>\n \n <hr style="border: 0; height: 2px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">评论区屏蔽词:</span>\n <div id="reviewKeywordContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">视频列表屏蔽词:</span>\n <div id="filterKeywordContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">屏蔽男演员:</span>\n <div id="filterActorContainer" style="max-width: 50%;min-width: 50%;">\n <div class="tag-box">\n </div>\n <div style="margin-top: 10px;">\n <button class="add-tag-btn">添加</button>\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n </div>\n </div>\n </div>\n </div>\n <div style=" flex-shrink: 0; padding: 15px 20px; text-align: right; border-top: 1px solid #eee; background: white; "> \n <button id="saveBtn">保存设置</button>\n </div>\n </div>\n ',
area: [ "50%", "80%" ],
scrollbar: !1,
success: (layero, index) => {
$(layero).find(".layui-layer-content").css("position", "relative");
this.loadForm();
this.bindClick();
}
});
}));
}
async loadForm() {
let settingObj = await storageManager.getSetting();
$("#hideFilterItem").val(settingObj.hideFilterItem || "yes");
$("#dialogOpenDetail").val(settingObj.dialogOpenDetail || "yes");
$("#reviewCount").val(settingObj.reviewCount || 20);
$("#waitCheckCount").val(settingObj.waitCheckCount || 5);
$("#containerWidth").val((settingObj.containerWidth || 100) - 70);
$("#showContainerWidth").text(settingObj.containerWidth + "%");
$("#containerColumns").val(settingObj.containerColumns || 4);
$("#showContainerColumns").text(settingObj.containerColumns || 4);
$("#refresh_token").val(settingObj.refresh_token || "");
$("#javDbUrl").val(settingObj.javDbUrl || "https://javdb.com");
$("#webDavUrl").val(settingObj.webDavUrl || "");
$("#webDavUsername").val(settingObj.webDavUsername || "");
$("#webDavPassword").val(settingObj.webDavPassword || "");
let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorList = await storageManager.getFilterActorList();
reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => {
this.addLabelTag("#reviewKeywordContainer", reviewKeyword);
}));
filterKeywordList && filterKeywordList.forEach((reviewKeyword => {
this.addLabelTag("#filterKeywordContainer", reviewKeyword);
}));
filterActorList && filterActorList.forEach((filterActor => {
this.addLabelTag("#filterActorContainer", filterActor);
}));
[ "#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer" ].forEach((containerId => {
$(`${containerId} .add-tag-btn`).on("click", (event2 => this.addKeyword(event2, containerId)));
$(`${containerId} .keyword-input`).on("keypress", (event2 => {
"Enter" === event2.key && this.addKeyword(event2, containerId);
}));
}));
}
bindClick() {
$("#importBtn").on("click", (event2 => this.importData(event2)));
$("#exportBtn").on("click", (event2 => this.exportData(event2)));
$("#syncDataBtn").on("click", (event2 => this.syncData(event2)));
$("#backupBtn").on("click", (event2 => this.backupData(event2)));
$("#backupListBtn").on("click", (event2 => this.backupListBtn(event2)));
$("#webdavBackupBtn").on("click", (event2 => this.backupDataByWebDav(event2)));
$("#webdavBackupListBtn").on("click", (event2 => this.backupListBtnByWebDav(event2)));
$("#getRefreshTokenBtn").on("click", (event2 => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", {
yes: function(index, layero, that) {
window.open("https://www.aliyundrive.com/drive/home");
layer.close(index);
}
})));
$("#containerWidth").on("input", (event2 => {
const value = parseInt($(event2.target).val()) + 70 + "%";
$("#showContainerWidth").text(value);
if (isJavDb) {
document.querySelector("section .container").style.minWidth = value;
}
if (isJavBus) {
document.querySelector(".container-fluid .row").style.minWidth = value;
}
}));
$("#containerColumns").on("input", (event2 => {
let columns = $("#containerColumns").val();
$("#showContainerColumns").text(columns);
if (isJavDb) {
document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
}
if (isJavBus) {
document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
}
}));
$("#saveBtn").on("click", (() => this.saveForm()));
}
async saveForm() {
const hideFilterValue = $("#hideFilterItem").val(), reviewCount = $("#reviewCount").val(), waitCheckCount = $("#waitCheckCount").val(), containerWidth = parseInt($("#containerWidth").val()), containerColumns = parseInt($("#containerColumns").val()), refresh_token = $("#refresh_token").val();
let settingObj = await storageManager.getSetting();
settingObj.hideFilterItem = hideFilterValue;
settingObj.reviewCount = reviewCount;
settingObj.waitCheckCount = waitCheckCount;
settingObj.containerWidth = containerWidth + 70;
settingObj.containerColumns = containerColumns;
settingObj.refresh_token = refresh_token;
settingObj.webDavUrl = $("#webDavUrl").val();
settingObj.webDavUsername = $("#webDavUsername").val();
settingObj.webDavPassword = $("#webDavPassword").val();
settingObj.dialogOpenDetail = $("#dialogOpenDetail").val();
settingObj.javDbUrl = new URL($("#javDbUrl").val()).origin;
await storageManager.saveSetting(settingObj);
let reviewKeywordList = [];
$("#reviewKeywordContainer .keyword-label").toArray().forEach((item => {
let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
reviewKeywordList.push(keyword);
}));
await storageManager.saveReviewFilterKeyword(reviewKeywordList);
let filterKeywordList = [];
$("#filterKeywordContainer .keyword-label").toArray().forEach((item => {
let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
filterKeywordList.push(keyword);
}));
await storageManager.saveTitleFilterKeyword(filterKeywordList);
let filterActorList = [];
$("#filterActorContainer .keyword-label").toArray().forEach((item => {
let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
filterActorList.push(keyword);
}));
await storageManager.saveFilterActor(filterActorList);
show.ok("保存成功");
window.refresh();
}
addLabelTag(containerId, keyword) {
const $tagBox = $(`${containerId} .tag-box`), $label = $(`\n <div class="keyword-label" style="background-color: #c5b9a0">\n ${keyword}\n <span class="keyword-remove">×</span>\n </div>\n `);
$label.find(".keyword-remove").click((event2 => {
$(event2.target).parent().remove();
}));
$tagBox.append($label);
}
addKeyword(event2, containerId) {
let $keywordInput = $(`${containerId} .keyword-input`);
const keyword = $keywordInput.val().trim();
if (keyword) {
this.addLabelTag(containerId, keyword);
$keywordInput.val("");
}
}
importData() {
try {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader;
reader.onload = event2 => {
try {
const content = event2.target.result.toString(), updateJsonData = JSON.parse(content);
layer.confirm("确定是否要覆盖导入?", {
icon: 3,
title: "确认覆盖",
btn: [ "确定", "取消" ]
}, (async function(index) {
await storageManager.importData(updateJsonData);
show.ok("数据导入成功");
layer.close(index);
location.reload();
}));
} catch (err) {
console.error(err);
show.error("导入失败:文件内容不是有效的JSON格式 " + err);
}
};
reader.onerror = () => {
show.error("读取文件时出错");
};
reader.readAsText(file);
};
document.body.appendChild(input);
input.click();
setTimeout((() => document.body.removeChild(input)), 1e3);
} catch (err) {
console.error(err);
show.error("导入数据时出错: " + err.message);
}
}
async backupData(event2) {
const refresh_token = await storageManager.getSetting("refresh_token");
if (!refresh_token) {
show.error("请填写refresh_token并保存后, 再试此功能");
return;
}
let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
uploadContent = simpleEncrypt(uploadContent);
let loadObj = loading();
try {
const aliyunApi = new AliyunApi(refresh_token);
await aliyunApi.backup(this.folderName, fileName, uploadContent);
show.ok("备份完成");
} catch (e) {
console.error(e);
show.error(e.toString());
} finally {
loadObj.close();
}
}
async backupListBtn(event2) {
const refresh_token = await storageManager.getSetting("refresh_token");
if (!refresh_token) {
show.error("请填写refresh_token并保存后, 再试此功能");
return;
}
let loadObj = loading();
try {
const aliyunApi = new AliyunApi(refresh_token), fileList = await aliyunApi.getBackupList(this.folderName);
this.openFileListDialog(fileList, aliyunApi, "阿里云盘");
} catch (e) {
console.error(e);
show.error(`发生错误: ${e ? e.message : e}`);
} finally {
loadObj.close();
}
}
async backupDataByWebDav(event2) {
const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
if (!webDavUrl) {
show.error("请填写webDav服务地址并保存后, 再试此功能");
return;
}
const webDavUsername = settingObj.webDavUsername;
if (!webDavUsername) {
show.error("请填写webDav用户名并保存后, 再试此功能");
return;
}
const webDavPassword = settingObj.webDavPassword;
if (!webDavPassword) {
show.error("请填写webDav密码并保存后, 再试此功能");
return;
}
let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData());
uploadContent = simpleEncrypt(uploadContent);
let loadObj = loading();
try {
const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword);
await webDavApi.backup(this.folderName, fileName, uploadContent);
show.ok("备份完成");
} catch (e) {
console.error(e);
show.error(e.toString());
} finally {
loadObj.close();
}
}
async backupListBtnByWebDav(event2) {
const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl;
if (!webDavUrl) {
show.error("请填写webDav服务地址并保存后, 再试此功能");
return;
}
const webDavUsername = settingObj.webDavUsername;
if (!webDavUsername) {
show.error("请填写webDav用户名并保存后, 再试此功能");
return;
}
const webDavPassword = settingObj.webDavPassword;
if (!webDavPassword) {
show.error("请填写webDav密码并保存后, 再试此功能");
return;
}
let loadObj = loading();
try {
const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword), fileList = await webDavApi.getBackupList(this.folderName);
this.openFileListDialog(fileList, webDavApi, "WebDav");
} catch (e) {
console.error(e);
show.error(`发生错误: ${e ? e.message : e}`);
} finally {
loadObj.close();
}
}
openFileListDialog(fileList, api, apiType) {
layer.open({
type: 1,
title: apiType + "备份文件",
content: '<div id="table-container"></div>',
area: [ "40%", "70%" ],
success: layero => {
const tableObj = new TableGenerator({
containerId: "table-container",
columns: [ {
key: "name",
title: "文件名"
}, {
key: "createTime",
title: "备份日期",
render: item => `${utils.getNowStr("-", ":", item.createTime)}`
}, {
key: "size",
title: "文件大小",
render: item => {
const units = [ "B", "KB", "MB", "GB", "TB", "PB" ];
let unitIndex = 0, adjustedSize = item.size;
for (;adjustedSize >= 1024 && unitIndex < units.length - 1; ) {
adjustedSize /= 1024;
unitIndex++;
}
return `${adjustedSize % 1 == 0 ? adjustedSize.toFixed(0) : adjustedSize.toFixed(2)} ${units[unitIndex]}`;
}
} ],
data: fileList,
buttons: [ {
text: "删除",
class: "a-danger",
onClick: async (event2, item) => {
layer.confirm(`是否删除 ${item.name} ?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async index => {
layer.close(index);
let loadObj = loading();
try {
await api.deleteFile(item.fileId);
let newFileList = await api.getBackupList(this.folderName);
tableObj.update(newFileList);
"阿里云盘" === apiType ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功");
} catch (e) {
console.error(e);
show.error(`发生错误: ${e ? e.message : e}`);
} finally {
loadObj.close();
}
}));
}
}, {
text: "下载",
class: "a-primary",
onClick: item => {
let loadObj = loading();
try {
"阿里云盘" === apiType ? api.getDownloadUrl(item.fileId).then((url => {
gmHttp.get(url, null, {
Referer: "https://www.aliyundrive.com/"
}).then((content => {
content = simpleDecrypt(content);
utils.download(content, item.name);
}));
})).catch((e => {
console.error(e);
show.error("下载失败: " + e);
})) : api.getFileContent(item.fileId).then((content => {
content = simpleDecrypt(content);
utils.download(content, item.name);
}));
} catch (e) {
console.error(e);
show.error("下载失败: " + e);
} finally {
loadObj.close();
}
}
}, {
text: "导入",
class: "a-success",
onClick: item => {
layer.confirm(`是否将该云备份数据 ${item.name} 导入?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async index => {
layer.close(index);
let loadObj = loading();
try {
let content;
if ("阿里云盘" === apiType) {
const downUrl = await api.getDownloadUrl(item.fileId);
content = await gmHttp.get(downUrl, null, {
Referer: "https://www.aliyundrive.com/"
});
} else content = await api.getFileContent(item.fileId);
content = simpleDecrypt(content);
const updateJsonData = JSON.parse(content);
await storageManager.importData(updateJsonData);
show.ok("导入成功!");
window.location.reload();
} catch (err) {
console.error(err);
show.error(err);
} finally {
loadObj.close();
}
}));
}
} ]
});
}
});
}
async exportData(event2) {
try {
const backupData = JSON.stringify(await storageManager.exportData()), fileName = `${utils.getNowStr("_", "_")}.json`;
utils.download(backupData, fileName);
show.ok("数据导出成功");
} catch (err) {
console.error(err);
show.error("导出数据时出错: " + err.message);
}
}
async syncData(event2) {
let msg = null, targetUrl = null;
if (isJavDb) {
msg = "是否将JavBus的数据及配置同步到本站中? ";
targetUrl = "https://www.javbus.com/temp?syncData=1";
}
if (isJavBus) {
msg = "是否将JavDB的数据及配置同步到本站中? ";
targetUrl = await storageManager.getSetting("javDbUrl", "https://javdb.com") + "/feedbacks/new?syncData=1";
}
utils.q(event2, msg, (() => {
const targetWindow = window.open(targetUrl);
let targetOrigin = new URL(targetUrl).origin;
console.log("开始连接接受方:", targetOrigin);
let pingInterval, retryCount = 0;
if (!this.hasListenMsg) {
window.addEventListener("message", (event3 => {
if (event3.origin === targetOrigin) if ("ok" === event3.data) {
clearInterval(pingInterval);
console.log("连接确认,开始同步数据");
targetWindow.postMessage("syncData", targetOrigin);
} else {
const resData = event3.data;
console.log("收到数据", resData);
__privateMethod(this, _SettingPlugin_instances, handleSyncData_fn).call(this, resData);
}
}));
this.hasListenMsg = !0;
}
const pingTarget = () => {
if (retryCount >= 8) {
clearInterval(pingInterval);
console.log("超过最大重试次数,停止尝试");
show.error("同步失败, 目标网站已中断, 请检查是否登录后再试!", {
close: !0,
duration: -1
});
} else {
console.log(`第 ${retryCount + 1} 次ping...`);
targetWindow.postMessage("ping", targetOrigin);
retryCount++;
}
};
pingInterval = setInterval(pingTarget, 1e3);
pingTarget();
}));
}
}
_SettingPlugin_instances = new WeakSet;
handleSyncData_fn = async function(resData) {
try {
const targetCarList = resData.carList || [], targetFilterActor = resData.filterActor || [], targetTitleFilterKeyword = resData.titleFilterKeyword || [], targetReviewFilterKeyword = resData.reviewFilterKeyword || [], targetSetting = resData.setting || {}, selfCarList = await storageManager.getCarList() || [], selfFilterActor = await storageManager.getFilterActorList() || [], selfTitleFilterKeyword = await storageManager.getTitleFilterKeyword() || [], selfReviewFilterKeyword = await storageManager.getReviewFilterKeywordList() || [], selfSetting = await storageManager.getSetting() || {}, newCarList = [ ...selfCarList ];
targetCarList.forEach((targetCar => {
selfCarList.some((selfCar => selfCar.carNum === targetCar.carNum)) || newCarList.push(targetCar);
}));
const newFilterActorKeyword = [ ...new Set([ ...selfFilterActor, ...targetFilterActor ]) ], newTitleFilterKeyword = [ ...new Set([ ...selfTitleFilterKeyword, ...targetTitleFilterKeyword ]) ], newReviewFilterKeyword = [ ...new Set([ ...selfReviewFilterKeyword, ...targetReviewFilterKeyword ]) ], newSetting = {
...selfSetting
};
Object.keys(targetSetting).forEach((key => {
key in newSetting && newSetting[key] || (newSetting[key] = targetSetting[key]);
}));
await storageManager.overrideCarList(newCarList);
await storageManager.saveFilterActor(newFilterActorKeyword);
await storageManager.saveTitleFilterKeyword(newTitleFilterKeyword);
await storageManager.saveReviewFilterKeyword(newReviewFilterKeyword);
await storageManager.saveSetting(newSetting);
show.ok("同步完成, 关闭提示后, 将重载数据", {
close: !0,
duration: -1,
callback: () => {
window.location.reload();
}
});
} catch (error) {
console.error(error);
show.error("同步数据时出错:", error);
}
};
const SALT = "x7k9p3";
function simpleEncrypt(str) {
return (SALT + str + SALT).split("").map((char => {
const code = char.codePointAt(0);
return String.fromCodePoint(code + 5);
})).join("");
}
function simpleDecrypt(encryptedStr) {
return encryptedStr.split("").map((char => {
const code = char.codePointAt(0);
return String.fromCodePoint(code - 5);
})).join("").slice(SALT.length, -SALT.length);
}
class SyncDataPlugin extends BasePlugin {
async handle() {
if (!window.location.href.includes("syncData=1")) return;
isJavBus && $("h4").html("临时页面, 用于同步数据");
let senderOrigin = null;
isJavDb && (senderOrigin = "https://www.javbus.com");
isJavBus && (senderOrigin = await storageManager.getSetting("javDbUrl", "https://javdb.com"));
console.log("等待发送方:", senderOrigin);
window.addEventListener("message", (async event2 => {
if (event2.origin === senderOrigin) if ("ping" === event2.data) {
console.log("收到 ping,发送确认");
event2.source.postMessage("ok", event2.origin);
} else if ("syncData" === event2.data) {
console.log("开始发送数据...");
const carList = await storageManager.getCarList(), filterActor = await storageManager.getFilterActorList(), titleFilterKeyword = await storageManager.getTitleFilterKeyword(), reviewFilterKeyword = await storageManager.getReviewFilterKeywordList(), setting = await storageManager.getSetting();
event2.source.postMessage({
carList: carList,
filterActor: filterActor,
titleFilterKeyword: titleFilterKeyword,
reviewFilterKeyword: reviewFilterKeyword,
setting: setting
}, event2.origin);
show.ok("数据已传输, 即将关闭页面...", {
callback: () => {
window.close();
}
});
}
}));
}
}
class BusPreviewVideoPlugin extends BasePlugin {
async initCss() {
return "\n .video-control-btn {\n min-width:100px;\n padding: 8px 16px;\n background: rgba(0,0,0,0.7);\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n .video-control-btn.active {\n background-color: #1890ff; /* 选中按钮的背景色 */\n color: white; /* 选中按钮的文字颜色 */\n font-weight: bold; /* 加粗显示 */\n border: 2px solid #096dd9; /* 边框样式 */\n }\n ";
}
handle() {
if (!isDetailPage) return;
const firstImageSrc = $("#sample-waterfall a:first").attr("href"), videoPreview = $(`\n <a class="preview-video-container sample-box" style="cursor: pointer">\n <div class="photo-frame" style="position:relative;">\n <img src="${firstImageSrc}" class="video-cover" alt="">\n <div class="play-icon" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); \n color:white; font-size:40px; text-shadow:0 0 10px rgba(0,0,0,0.5);">\n ▶\n </div>\n </div>\n </a>`);
$("#sample-waterfall").prepend(videoPreview);
let $preview = $(".preview-video-container");
$preview.on("click", (async event2 => {
event2.preventDefault();
event2.stopPropagation();
if (!$("#preview-video").length) {
let videoUrl = await this.parseVideo(firstImageSrc);
console.log("解析播放地址:", videoUrl);
$("#magneturlpost").next().after(`<div><video id="preview-video" controls style="width: 100%;margin-top: 5px;"><source src="${videoUrl}" /></video></div>`);
this.handleVideo().then((() => {
const element = document.getElementById("preview-video");
if (element) {
const rect = element.getBoundingClientRect();
window.scrollTo({
top: window.scrollY + rect.top - 100,
behavior: "smooth"
});
}
}));
}
}));
window.location.href.includes("autoPlay=1") && $preview[0].click();
}
async handleVideo() {
const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $videoContainer = $videoEl.parent();
if (!$videoEl.length || !$previewSource.length) return;
const videoEl = $videoEl[0];
videoEl.muted = !1;
videoEl.play();
$videoContainer.css("position", "relative");
const videoSrc = $previewSource.attr("src"), qualityLevels = [ "hhb", "hmb", "mhb", "mmb" ], currentQuality = qualityLevels.find((q => videoSrc.includes(q))) || "mhb", qualityOptions = [ {
id: "video-mmb",
text: "低画质",
quality: "mmb"
}, {
id: "video-mhb",
text: "中画质",
quality: "mhb"
}, {
id: "video-hmb",
text: "高画质",
quality: "hmb"
}, {
id: "video-hhb",
text: "超高清",
quality: "hhb"
} ];
const cacheKey = `videoQualities_${this.getPageInfo().carNum}`;
let availableQualities = JSON.parse(sessionStorage.getItem(cacheKey));
if (!availableQualities) {
availableQualities = (await Promise.all(qualityOptions.map((async option => {
const testSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), option.quality);
try {
return (await fetch(testSrc, {
method: "HEAD"
})).ok ? option : null;
} catch {
return null;
}
})))).filter(Boolean);
availableQualities.length && sessionStorage.setItem(cacheKey, JSON.stringify(availableQualities));
}
if (availableQualities.length <= 1) return;
const buttonsHtml = availableQualities.map(((option, index) => `\n <button class="video-control-btn${option.quality === currentQuality ? " active" : ""}" \n id="${option.id}" \n data-quality="${option.quality}"\n style="bottom: ${50 * index}px; right: -105px;">\n ${option.text}\n </button>\n `)).join("");
$videoContainer.append(buttonsHtml);
const $buttons = $videoContainer.find(".video-control-btn");
$videoContainer.on("click", ".video-control-btn", (async e => {
const $button = $(e.currentTarget), quality = $button.data("quality");
if (!$button.hasClass("active")) try {
const newSrc = videoSrc.replace(new RegExp(qualityLevels.join("|"), "g"), quality);
$previewSource.attr("src", newSrc);
videoEl.load();
videoEl.muted = !1;
await videoEl.play();
$buttons.removeClass("active");
$button.addClass("active");
} catch (error) {
console.error("切换画质失败:", error);
}
}));
$buttons.last().trigger("click");
}
async parseVideo(imgSrc) {
const cacheKey = `ok_url_${this.getPageInfo().carNum}`;
let ok_url = sessionStorage.getItem(cacheKey);
if (ok_url) return ok_url;
const videoIdMatch = imgSrc.match(/\/digital\/video\/([^\/]+)\//);
if (!videoIdMatch || videoIdMatch.length < 2) {
show.error("解析id错误" + imgSrc + ", 该视频没有对应的dmm视频");
console.error("解析dmm视频id错误", imgSrc);
setTimeout((() => {
$("#preview-video").remove();
}), 1e3);
return null;
}
const videoId = videoIdMatch[1], firstChar = videoId.charAt(0).toLowerCase();
let idPrefix = videoId.substring(0, 3);
const checkVideo = async testSrc => {
try {
console.log("测试视频地址", testSrc);
return (await fetch(testSrc, {
method: "HEAD"
})).ok ? testSrc : null;
} catch {
return null;
}
};
let videoIdNo00 = videoId.replace("00", ""), testSrcList = [ `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoId}/${videoId}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoIdNo00}/${videoIdNo00}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoId}/${videoId}mhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${firstChar}/${idPrefix}/${videoIdNo00}/${videoIdNo00}mhb.mp4` ], okUrl = null;
for (let i = 0; i < testSrcList.length; i++) {
let testUrl = await checkVideo(testSrcList[i]);
if (testUrl) {
console.log("测试成功,", testUrl);
okUrl = testUrl;
break;
}
}
if (!okUrl) {
show.error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
throw new Error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
}
sessionStorage.setItem(cacheKey, okUrl);
return okUrl;
}
}
class SearchByImagePlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "siteList", [ {
name: "Google旧版",
url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d",
ico: "https://www.google.com/favicon.ico"
}, {
name: "Google",
url: "https://lens.google.com/uploadbyurl?url={占位符}",
ico: "https://www.google.com/favicon.ico"
}, {
name: "Yandex",
url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}",
ico: "https://yandex.ru/favicon.ico"
} ]);
}
async initCss() {
return "\n <style>\n #upload-area {\n border: 2px dashed #85af68;\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n margin-bottom: 20px;\n transition: all 0.3s;\n background-color: #f9f9f9;\n }\n #upload-area:hover {\n border-color: #76b947;\n background-color: #f0f0f0;\n }\n /* 拖拽进入 */\n #upload-area.highlight {\n border-color: #2196F3;\n background-color: #e3f2fd;\n }\n \n \n #select-image-btn {\n background-color: #4CAF50;\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n transition: background-color 0.3s;\n }\n #select-image-btn:hover {\n background-color: #45a049;\n }\n \n \n #handle-btn, #cancel-btn {\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n border: none;\n transition: opacity 0.3s;\n }\n #handle-btn {\n background-color: #2196F3;\n color: white;\n }\n #handle-btn:hover {\n opacity: 0.9;\n }\n #cancel-btn {\n background-color: #f44336;\n color: white;\n }\n #cancel-btn:hover {\n opacity: 0.9;\n }\n \n .site-btns-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 15px;\n }\n .site-btn {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: #f5f5f5;\n border-radius: 4px;\n text-decoration: none;\n color: #333;\n transition: all 0.2s;\n font-size: 14px;\n border: 1px solid #ddd;\n }\n .site-btn:hover {\n background-color: #e0e0e0;\n transform: translateY(-2px);\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n .site-btn img {\n width: 16px;\n height: 16px;\n margin-right: 6px;\n }\n .site-btn span {\n white-space: nowrap;\n }\n </style>\n ";
}
open() {
layer.open({
type: 1,
title: "以图识图",
content: '\n <div style="padding: 20px">\n <div id="upload-area">\n <div style="color: #555;margin-bottom: 15px;">\n <p>拖拽图片到此处 或 点击按钮选择图片</p>\n <p>也可以直接 Ctrl+V 粘贴图片或 图片URL</p>\n </div>\n <button id="select-image-btn">选择图片</button>\n <input type="file" style="display: none" id="image-file" accept="image/*">\n </div>\n \n <div id="url-input-container" style="margin-top: 15px;display: none;">\n <input type="text" id="image-url" placeholder="粘贴图片URL地址..." style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">\n </div>\n \n <div id="preview-area" style="margin-bottom: 20px; text-align: center; display: none;">\n <img id="preview-image" alt="" src="" style="max-width: 100%; max-height: 300px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">\n <div style="margin-top: 15px; display: flex; justify-content: center; gap: 10px;" id="action-btns">\n <button id="handle-btn">搜索图片</button>\n <button id="cancel-btn">取消</button>\n </div>\n \n <div id="search-results" style="display: none;">\n <p style="margin: 20px auto">请选择识图网站:<a id="openAll" style="cursor: pointer">全部打开</a></p>\n <div class="site-btns-container" id="site-btns-container"></div>\n </div>\n </div>\n \n </div>\n ',
area: [ "40%", "80%" ],
success: async layero => {
this.initEventListeners();
}
});
}
initEventListeners() {
const $uploadArea = $("#upload-area"), $fileInput = $("#image-file"), $selectBtn = $("#select-image-btn"), $previewArea = $("#preview-area"), $previewImage = $("#preview-image"), $actionBtns = $("#action-btns"), $searchImage = $("#handle-btn"), $cancelBtn = $("#cancel-btn"), $urlInputContainer = $("#url-input-container"), $imageUrlInput = $("#image-url"), $searchResults = $("#search-results"), $siteBtnsContainer = $("#site-btns-container");
$uploadArea.on("dragover", (e => {
e.preventDefault();
$uploadArea.addClass("highlight");
})).on("dragleave", (() => {
$uploadArea.removeClass("highlight");
})).on("drop", (e => {
e.preventDefault();
$uploadArea.removeClass("highlight");
if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) {
this.handleImageFile(e.originalEvent.dataTransfer.files[0]);
this.resetSearchUI();
}
}));
$selectBtn.on("click", (() => {
$fileInput.trigger("click");
}));
$fileInput.on("change", (e => {
if (e.target.files && e.target.files[0]) {
this.handleImageFile(e.target.files[0]);
this.resetSearchUI();
}
}));
$(document).on("paste", (async e => {
const items = e.originalEvent.clipboardData.items;
for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) {
const blob = items[i].getAsFile();
this.handleImageFile(blob);
this.resetSearchUI();
return;
}
const text = e.originalEvent.clipboardData.getData("text");
if (text && utils.isUrl(text)) {
$urlInputContainer.show();
$imageUrlInput.val(text);
$previewImage.attr("src", text);
$previewArea.show();
this.resetSearchUI();
}
}));
$searchImage.on("click", (() => {
const imageSrc = $previewImage.attr("src");
imageSrc ? this.searchByImage(imageSrc).then((imgUrl => {
$actionBtns.hide();
$searchResults.show();
$siteBtnsContainer.empty();
this.siteList.forEach((site => {
const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl));
$siteBtnsContainer.append(`\n <a href="${siteUrl}" class="site-btn" target="_blank" title="${site.name}">\n <img src="${site.ico}" alt="${site.name}">\n <span>${site.name}</span>\n </a>\n `);
}));
$siteBtnsContainer.show();
})) : show.info("请粘贴或上传图片");
}));
$cancelBtn.on("click", (() => {
$previewArea.hide();
$urlInputContainer.hide();
$fileInput.val("");
$imageUrlInput.val("");
}));
$imageUrlInput.on("change", (() => {
if (utils.isUrl($imageUrlInput.val())) {
$previewImage.attr("src", $imageUrlInput.val());
$previewArea.show();
}
}));
$("#openAll").on("click", (() => {
$(".site-btn").toArray().forEach((item => {
window.open($(item).attr("href"));
}));
}));
}
resetSearchUI() {
$("#action-btns").show();
$("#search-results").hide();
$("#site-btns-container").hide().empty();
}
handleImageFile(file) {
const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container");
if (!file.type.match("image.*")) {
show.info("请选择图片文件");
return;
}
const reader = new FileReader;
reader.onload = e => {
previewImage.src = e.target.result;
previewArea.style.display = "block";
urlInputContainer.style.display = "none";
};
reader.readAsDataURL(file);
}
async searchByImage(imageSrc) {
let loadObj = loading();
try {
let imageUrl = imageSrc;
if (imageSrc.startsWith("data:")) {
show.info("开始上传图片...");
const imgurUrl = await async function(base64Data) {
var _a;
const matches = base64Data.match(/^data:(.+);base64,(.+)$/);
if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据");
const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i);
const byteArray = new Uint8Array(byteNumbers), blob = new Blob([ byteArray ], {
type: mimeType
}), formData = new FormData;
formData.append("image", blob);
const response = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: {
Authorization: "Client-ID d70305e7c3ac5c6"
},
body: formData
}), data = await response.json();
if (data.success && data.data && data.data.link) return data.data.link;
throw new Error((null == (_a = data.data) ? void 0 : _a.error) || "上传到Imgur失败");
}(imageSrc);
if (!imgurUrl) {
show.error("上传到失败");
return;
}
imageUrl = imgurUrl;
}
return imageUrl;
} catch (error) {
show.error(`搜索失败: ${error.message}`);
console.error("搜索失败:", error);
} finally {
loadObj.close();
}
}
}
class BusNavBarPlugin extends BasePlugin {
handle() {
$("#navbar > div > div > span").append('\n <button class="btn btn-default" style="color: #0d9488" id="search-img-btn">识图</button>\n ');
$("#search-img-btn").on("click", (() => {
this.getBean("SearchByImagePlugin").open();
}));
}
}
class RelatedPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "floorIndex", 1);
}
async showRelated() {
let movieId = this.getPageInfo().movieId, $magnets = $("#magnets-content");
$magnets.append('\n <div style=" display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px; ">\n <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n <span style="padding: 0 10px;">相关清单</span>\n <a id="relatedFold" style=" margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center; ">\n <span class="toggle-text">展开</span>\n <span class="toggle-icon" style="margin-left: 4px;">▼</span>\n </a>\n <span style=" flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent); "></span>\n </div>\n ');
let dataList = null;
$("#relatedFold").on("click", (async () => {
const textSpan = $("#relatedFold .toggle-text"), iconSpan = $("#relatedFold .toggle-icon");
if ("展开" === textSpan.text()) {
textSpan.text("折叠");
iconSpan.text("▲");
$relatedContainer.show();
$relatedFooter.show();
if (dataList) return;
try {
$relatedContainer.append('<div id="relate-load" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">正在查询中...</div>');
let pageNum = 1, reviewCount = 20;
dataList = await related(movieId, pageNum, reviewCount);
$("#relate-load").remove();
if (!dataList) {
$relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单失败</div>');
return;
}
0 === dataList.length && $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>');
this.displayRelated(dataList, $relatedContainer);
if (dataList.length === reviewCount) {
$relatedFooter.html('\n <button id="loadMoreRelated" style="width:100%; background-color: #e1f5fe; border:none; padding:10px; margin-top:10px; cursor:pointer; color:#0277bd; font-weight:bold; border-radius:4px;">\n 加载更多清单\n </button>\n <div id="reviewsEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>\n ');
let currentPage = 1;
$("#loadMoreRelated").click((async () => {
$("#loadMoreRelated").text("加载中...").prop("disabled", !0);
currentPage++;
const moreData = await related(movieId, currentPage, reviewCount);
this.displayRelated(moreData, $relatedContainer);
if (moreData.length < reviewCount) {
$("#loadMoreRelated").remove();
$("#reviewsEnd").show();
} else $("#loadMoreRelated").text("加载更多清单").prop("disabled", !1);
}));
} else dataList.length > 0 && $relatedFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>');
} catch (e) {
console.error(e);
$("#relate-load").remove();
$relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取失败</div>');
}
} else {
textSpan.text("展开");
iconSpan.text("▼");
$relatedContainer.hide();
$relatedFooter.hide();
}
}));
$magnets.append('<div id="relatedContainer"></div>');
$magnets.append('<div id="relatedFooter"></div>');
const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter");
}
displayRelated(dataList, $container) {
dataList.length && dataList.forEach((item => {
let commentHtml = `\n <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;position:relative;">\n <span style="position:absolute;top:5px;right:10px;color:#999;font-size:12px;">#${this.floorIndex++}</span>\n <span style="position:absolute;bottom:5px;right:10px;color:#999;font-size:12px;">创建时间: ${item.createTime}</span>\n <p><a href="/lists/${item.relatedId}" target="_blank" style="color:#2e8abb">${item.name}</a></p>\n <p style="margin-top: 5px;">收藏次数: ${item.collectionCount} 被查看次数: ${item.viewCount}</p>\n </div>\n `;
$container.append(commentHtml);
}));
}
}
class WantAndWatchedVideosPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "type", null);
}
async handle() {
if (window.location.href.includes("/want_watch_videos")) {
$("h3").append('<a class="a-primary" id="wantWatchBtn" style="padding:10px;">导入至 JSH</a>');
$("#wantWatchBtn").on("click", (event2 => {
this.type = Status_FAVORITE;
this.importWantWatchVideos(event2, "是否将 想看的影片 导入到 JSH-收藏?");
}));
}
if (window.location.href.includes("/watched_videos")) {
$("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JSH</a>');
$("#wantWatchBtn").on("click", (event2 => {
this.type = Status_HAS_DOWN;
this.importWantWatchVideos(event2, "是否将 看过的影片 导入到 JSH-已下载?");
}));
}
}
importWantWatchVideos(event2, title) {
utils.q(null, `${title} <br/> <span style='color: #f40'>执行此功能前请记得备份数据</span>`, (async () => {
let loadObj = loading();
try {
await this.parseMovieList();
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}));
}
async parseMovieList($dom) {
let movieList, nextPageLink;
if ($dom) {
movieList = $dom.find(this.getSelector().itemSelector);
nextPageLink = $dom.find(".pagination-next").attr("href");
} else {
movieList = $(this.getSelector().itemSelector);
nextPageLink = $(".pagination-next").attr("href");
}
for (const element of movieList) {
const item = $(element), href = item.find("a").attr("href"), carNum = item.find(".video-title strong").text().trim();
if (href && carNum) try {
if (await storageManager.getCar(carNum)) {
show.info(`${carNum} 已存在, 跳过`);
continue;
}
await storageManager.saveCar(carNum, href, "", this.type);
} catch (error) {
console.error(`保存失败 [${carNum}]:`, error);
}
}
if (nextPageLink) {
show.info("发现下一页,正在解析:", nextPageLink);
await new Promise((resolve => setTimeout(resolve, 1e3)));
$.ajax({
url: nextPageLink,
method: "GET",
success: html => {
const parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html"));
this.parseMovieList(next$dom);
},
error: function(err) {
console.error(err);
show.error("加载下一页失败:" + err.message);
}
});
} else {
show.ok("导入结束!");
window.refresh();
}
}
}
class CopyTitleOrDownImgPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "titleSvg", '<svg t="1747553289744" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7507" width="200" height="200"><path d="M959.8 150.8c0-2.3-1.9-4.2-4.2-4.2H253.3c-2.3 0-4.2 1.9-4.2 4.2v115.9c0 2.3 1.9 4.2 4.2 4.2h702.3c2.3 0 4.2-1.9 4.2-4.2V150.8z" fill="" p-id="7508"></path><path d="M126.4 208.8m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7509"></path><path d="M851.5 453.7c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.7-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V453.7z" fill="" p-id="7510"></path><path d="M126.4 512m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7511"></path><path d="M851.5 756.9c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.8-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V756.9z" fill="" p-id="7512"></path><path d="M126.4 815.2m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7513"></path></svg>');
__publicField(this, "carNumSvg", '<svg t="1747552574854" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3539" width="200" height="200"><path d="M920.337035 447.804932c-6.067182-6.067182-10.918677-11.643178-16.985859-17.71036l48.536436-30.334889-42.469254-109.207238-121.340579 12.134365c-6.067182-6.067182-6.067182-12.134365-12.134365-18.201547-12.134365-12.134365-18.201547-24.267706-24.267706-30.334889-24.26873-36.402071-30.334889-42.469254-54.603619-42.469254H339.116511c-18.201547 0-24.267706 6.067182-54.603619 42.469254-6.067182 6.067182-12.134365 18.201547-24.267706 30.334889 0 0-6.067182 6.067182-12.134365 18.201547l-115.27442-12.134365-48.536436 109.207238 51.090608 24.378223c-6.067182 6.067182-30.334889 34.660404-30.334889 34.660405l-15.542998 22.280446-12.282744 17.018605c-6.067182 12.134365-5.064342 10.868535-5.064342 29.070082v224.480635c0 36.402071 18.201547 60.670801 54.603618 60.670801h115.273397c36.402071 0 54.603619-24.267706 54.603619-54.603619v-18.201547h424.693562v18.201547c0 30.334889 18.201547 54.603619 54.603618 54.603619h115.273397c36.402071 0 60.670801-24.267706 60.670801-60.670801V539.300786c0-42.469254 0.685615-46.662763-11.44875-64.863287-4.731768-6.744611-11.94403-16.196891-20.101827-26.632567z m-35.186383-78.381161l-30.334889 18.201547-12.134365-12.134365c-6.067182-8.899694-12.134365-12.134365-12.134365-18.201547l42.469254-6.067183 12.134365 18.201548z m-533.899776-97.072873h339.755054l78.871325 103.140055H272.378527l78.872349-103.140055zM175.305655 357.290429h36.402071c-6.067182 6.067182-6.067182 12.134365-12.134365 18.201547l-18.201547 6.067183-18.201547-12.134365 12.135388-12.134365z m667.375743 394.35765h-54.603619V678.843936H242.043638v72.804143H132.837424V527.167444c0-12.134365-0.041956-20.662599 1.216711-23.556508 1.258667-2.89391 9.955746-16.924461 21.193695-29.173437l35.722596-38.276768h639.576607l21.917172 20.938891c6.067182 6.067182 21.847587 21.366633 25.712615 28.732392 7.621585 9.996678 6.973832 10.999518 13.041014 23.133883v242.682182h-48.536436zM242.043638 533.234627h133.474944v60.670801H242.043638v-60.670801z m412.559197 0h133.474944v60.670801H654.602835v-60.670801z" p-id="3540"></path></svg>');
__publicField(this, "downSvg", '<svg t="1747552626242" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4551" width="200" height="200"><path d="M641.6 660l-8.64-64 32-4.32a211.2 211.2 0 0 0-26.72-420.32 215.36 215.36 0 0 0-213.12 192 94.56 94.56 0 0 0 0 11.52v41.28h-64V384v-7.04a153.12 153.12 0 0 1 0-19.52A279.84 279.84 0 0 1 636.16 108H640A275.2 275.2 0 0 1 673.28 656z" fill="#333333" p-id="4552"></path><path d="M490.4 446.24l-7.52-39.84a182.4 182.4 0 0 1 107.52-162.88l29.12-13.28L646.08 288l-29.12 13.28a117.92 117.92 0 0 0-70.08 101.28l6.24 30.4zM392.96 652.32h-78.72A202.24 202.24 0 0 1 256 256l30.72-9.12 18.24 61.28-30.72 9.12a138.24 138.24 0 0 0 39.68 270.72h78.72zM479.2 512h64v320h-64z" fill="#333333" p-id="4553"></path><path d="M510.4 908l-156.32-147.68 43.84-46.4 112.48 106.08 112.8-106.08 43.84 46.56-156.64 147.52z" fill="#333333" p-id="4554"></path></svg>');
}
async initCss() {
return `\n .box .tags {\n justify-content: space-between;\n }\n .tool-box span{\n opacity:.3\n }\n \n .tool-box span:hover{\n opacity:1\n }\n ${isJavBus ? ".tool-box .icon{ height: 2rem; width: 2rem; }" : ""}\n `;
}
handle() {
if (window.isListPage) {
this.addCopy();
this.bindClick();
}
}
addCopy() {
$(this.getSelector().itemSelector).toArray().forEach((ele => {
let $box = $(ele);
isJavDb && $box.find(".tags").append(`\n <div class="tool-box">\n <span class="titleSvg" title="复制标题" style="margin-right: 15px; color:#c5a45d;">${this.titleSvg}</span>\n <span class="carNumSvg" title="复制番号" style="margin-right: 15px; color:#9f2727;">${this.carNumSvg}</span>\n <span class="downSvg" title="下载封面" style="margin-right: 15px; color:#2ca5c0;">${this.downSvg}</span>\n </div>\n `);
isJavBus && $box.find(".photo-info").append(`\n <div class="tool-box">\n <span class="titleSvg" title="复制标题" style="margin-right: 15px; color:#c5a45d;">${this.titleSvg}</span>\n <span class="carNumSvg" title="复制番号" style="margin-right: 15px; color:#9f2727;">${this.carNumSvg}</span>\n <span class="downSvg" title="下载封面" style="margin-right: 15px; color:#2ca5c0;">${this.downSvg}</span>\n </div> \n `);
}));
}
bindClick() {
const listPagePlugin = this.getBean("ListPagePlugin");
$(this.getSelector().boxSelector).on("click", ".titleSvg", (event2 => {
event2.preventDefault();
event2.stopPropagation();
const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
navigator.clipboard.writeText(title).then((() => {
show.info("标题已复制到剪切板, " + title);
})).catch((err => {
console.error("复制失败: ", err);
}));
})).on("click", ".carNumSvg", (event2 => {
event2.preventDefault();
event2.stopPropagation();
const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
navigator.clipboard.writeText(carNum).then((() => {
show.info("番号已复制到剪切板, " + carNum);
})).catch((err => {
console.error("复制失败: ", err);
}));
})).on("click", ".downSvg", (event2 => {
event2.preventDefault();
event2.stopPropagation();
const $box = $(event2.target).closest(".item"), {carNum: carNum, aHref: aHref, title: title} = listPagePlugin.findCarNumAndHref($box);
let $img = $box.find(".cover img");
isJavBus && ($img = $box.find(".photo-frame img"));
const url = $img.attr("src");
console.log(url);
GM_download({
url: url,
name: title + ".jpg"
});
}));
}
}
utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/layer.min.css");
utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css");
window.onload = async function() {
window.isDetailPage = function() {
let href = window.location.href;
return href.includes("javdb") ? href.includes("/v/") : !!href.includes("javbus") && $("#magnet-table").length > 0;
}();
window.isListPage = function() {
let href = window.location.href;
return href.includes("javdb") ? $(".movie-list").length > 0 : !!href.includes("javbus") && $(".masonry > div .item").length > 0;
}();
!function() {
const pluginManager = new PluginManager;
let hostname = window.location.hostname;
if (hostname.includes("javdb")) {
pluginManager.register(ListPagePlugin);
pluginManager.register(AutoPagePlugin);
pluginManager.register(Fc2Plugin);
pluginManager.register(FoldCategoryPlugin);
pluginManager.register(ListPageButtonPlugin);
pluginManager.register(HistoryPlugin);
pluginManager.register(SettingPlugin);
pluginManager.register(NavBarPlugin);
pluginManager.register(HitShowPlugin);
pluginManager.register(TOP250Plugin);
pluginManager.register(SyncDataPlugin);
pluginManager.register(SearchByImagePlugin);
pluginManager.register(CopyTitleOrDownImgPlugin);
pluginManager.register(DetailPagePlugin);
pluginManager.register(ReviewPlugin);
pluginManager.register(RelatedPlugin);
pluginManager.register(DetailPageButtonPlugin);
pluginManager.register(HighlightMagnetPlugin);
pluginManager.register(PreviewVideoPlugin);
pluginManager.register(FilterTitleKeywordPlugin);
pluginManager.register(ActressInfoPlugin);
pluginManager.register(OtherSitePlugin);
pluginManager.register(WantAndWatchedVideosPlugin);
}
if (hostname.includes("javbus")) {
pluginManager.register(ListPagePlugin);
pluginManager.register(ListPageButtonPlugin);
pluginManager.register(SettingPlugin);
pluginManager.register(HistoryPlugin);
pluginManager.register(SyncDataPlugin);
pluginManager.register(AutoPagePlugin);
pluginManager.register(SearchByImagePlugin);
pluginManager.register(BusNavBarPlugin);
pluginManager.register(CopyTitleOrDownImgPlugin);
pluginManager.register(BusDetailPagePlugin);
pluginManager.register(DetailPageButtonPlugin);
pluginManager.register(ReviewPlugin);
pluginManager.register(FilterTitleKeywordPlugin);
pluginManager.register(HighlightMagnetPlugin);
pluginManager.register(BusPreviewVideoPlugin);
}
hostname.includes("javtrailers") && pluginManager.register(JavTrailersPlugin);
hostname.includes("subtitlecat") && pluginManager.register(SubTitleCatPlugin);
hostname.includes("aliyundrive") && pluginManager.register(AliyunPanPlugin);
pluginManager.process().then();
}();
};
}();