// ==UserScript==
// @name JAV-JSH
// @namespace https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version 1.6.8
// @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 = e => {
throw TypeError(e);
}, __defNormalProp = (e, t, n) => t in e ? __defProp(e, t, {
enumerable: !0,
configurable: !0,
writable: !0,
value: n
}) : e[t] = n, __publicField = (e, t, n) => __defNormalProp(e, "symbol" != typeof t ? t + "" : t, n), __accessCheck = (e, t, n) => t.has(e) || __typeError("Cannot " + n), __privateAdd = (e, t, n) => t.has(e) ? __typeError("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, n), __privateMethod = (e, t, n) => (__accessCheck(e, t, "access private method"),
n);
!function() {
"use strict";
var e, t, n, a, i;
const r = {
boxSelector: ".masonry",
itemSelector: ".masonry .item",
coverImgSelector: ".item .photo-frame img",
requestDomItemSelector: "#waterfall .item"
}, o = {
boxSelector: ".movie-list",
itemSelector: ".movie-list .item",
coverImgSelector: ".cover img",
requestDomItemSelector: ".movie-list .item"
}, s = window.location.href.includes("javdb"), l = window.location.href.includes("javbus"), c = "favorite", d = "filter", p = "hasDown", g = 2592e6;
function h(e) {
if (e) if (e.includes("<style>")) document.head.insertAdjacentHTML("beforeend", e); else {
const t = document.createElement("style");
t.textContent = e;
document.head.appendChild(t);
}
}
l && h("\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");
s && h('\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');
h("\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");
h("\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");
e = new WeakSet;
t = async function() {
if (!window.location.hostname.includes("javdb")) return;
(await this.forage.keys()).forEach((e => e.startsWith("SCORE_") && this.forage.removeItem(e)));
const e = Date.now();
try {
const t = await this.forage.getItem("lastCleanupTime");
if (t && e - t < 864e5) return;
const n = await this.forage.keys();
for (const e of n) {
if (this.interceptedKeys.includes(e)) continue;
const t = await this.forage.getItem(e);
if ("object" == typeof t && "expires" in t && "expiresStr" in t && Date.now() > t.expires) {
console.log("清理过期数据:", e);
await this.forage.removeItem(e);
}
}
await this.forage.setItem("lastCleanupTime", e);
} catch (t) {
console.error("[自动清理失败]", t);
await this.forage.setItem("lastCleanupTime", e);
}
};
n = async function(e, t, n) {
let a;
if (Array.isArray(e)) a = [ ...e ]; else {
a = await this.forage.getItem(t) || [];
if (a.includes(e)) {
const t = `${e} ${n}已存在`;
show.error(t);
throw new Error(t);
}
a.push(e);
}
await this.forage.setItem(t, a);
return a;
};
let u = class _StorageManager {
constructor() {
__privateAdd(this, e);
__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, e, t).call(this).then();
}
async saveFilterActor(t) {
return __privateMethod(this, e, n).call(this, t, this.filter_actor_key, "演员");
}
async saveReviewFilterKeyword(t) {
return __privateMethod(this, e, n).call(this, t, this.review_filter_keyword_key, "评论关键词");
}
async saveTitleFilterKeyword(t) {
return __privateMethod(this, e, n).call(this, t, 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(e = null, t) {
const n = await this.forage.getItem(this.setting_key) || {};
if (null === e) return n;
const a = n[e];
return a ? "true" === a || "false" === a ? "true" === a.toLowerCase() : "string" != typeof a || isNaN(Number(a)) ? a : Number(a) : t;
}
async saveSetting(e) {
await this.forage.setItem(this.setting_key, e);
}
async getReviewFilterKeywordList() {
return await this.forage.getItem(this.review_filter_keyword_key) || [];
}
async saveCar(e, t, n, a) {
if (!e) {
show.error("番号为空!");
throw new Error("番号为空!");
}
if (!t) {
show.error("url为空!");
throw new Error("url为空!");
}
t.includes("http") || (t = window.location.origin + t);
const i = await this.forage.getItem(this.car_list_key) || [];
let r = i.find((t => t.carNum === e));
if (r) r.createDate = utils.getNowStr(); else {
r = {
carNum: e,
url: t,
actress: n,
status: "",
createDate: utils.getNowStr()
};
i.push(r);
}
switch (a) {
case d:
if (r.status === d) {
const t = `${e} 已在屏蔽列表中`;
show.error(t);
throw new Error(t);
}
r.status = d;
break;
case c:
if (r.status === c) {
const t = `${e} 已在收藏列表中`;
show.error(t);
throw new Error(t);
}
r.status = c;
break;
case p:
r.status = p;
break;
default:
const t = "actionType错误";
show.error(t);
throw new Error(t);
}
await this.forage.setItem(this.car_list_key, i);
}
async getCarList() {
return (await this.forage.getItem(this.car_list_key) || []).sort(((e, t) => {
if (!e || !t) return 0;
const n = e.createDate ? new Date(e.createDate).getTime() : 0;
return (t.createDate ? new Date(t.createDate).getTime() : 0) - n;
}));
}
async getCar(e) {
return (await this.getCarList()).find((t => t.carNum === e));
}
async removeCar(e) {
const t = await this.getCarList(), n = t.length, a = t.filter((t => t.carNum !== e));
if (a.length === n) {
show.error(`${e} 不存在`);
return !1;
}
await this.forage.setItem(this.car_list_key, a);
return !0;
}
async overrideCarList(e) {
if (!Array.isArray(e)) throw new TypeError("必须传入数组类型数据");
const t = e.filter((e => !e || "object" != typeof e || !e.carNum));
if (t.length > 0) throw new Error(`缺少必要字段 carNum 的数据项: ${t.length} 条`);
const n = new Set, a = e.filter((e => {
if (n.has(e.carNum)) return !0;
n.add(e.carNum);
return !1;
}));
if (a.length > 0) throw new Error(`发现重复: ${a.slice(0, 3).map((e => e.carNum)).join(", ")}${a.length > 3 ? "..." : ""}`);
await this.forage.setItem(this.car_list_key, e);
}
async getItem(e) {
if (this.interceptedKeys.includes(e)) {
let t = `危险操作, 改key已有方法实现获取, 请用内部方法调用! key: ${e}`;
show.error(t);
throw new Error(t);
}
const t = await this.forage.getItem(e);
if (null == t) return null;
if ("object" == typeof t && "expires" in t && "expiresStr" in t) {
if (Date.now() > t.expires) {
await this.forage.removeItem(e);
return null;
}
return t.value;
}
return t;
}
async setItem(e, t, n = null) {
if (this.interceptedKeys.includes(e)) {
let t = `危险操作, 改key已有方法实现获取, 请用内部方法调用! key: ${e}`;
show.error(t);
throw new Error(t);
}
let a = t;
if (null !== n) {
const e = Date.now() + n;
a = {
value: t,
expires: e,
expiresStr: utils.formatDate(new Date(e))
};
}
return await this.forage.setItem(e, a);
}
async removeItem(e) {
if (this.interceptedKeys.includes(e)) {
let t = `危险操作, 改key不可删除! key: ${e}`;
show.error(t);
throw new Error(t);
}
return await this.forage.removeItem(e);
}
async importData(e) {
let t = e.filterKeywordList;
Array.isArray(t) && await this.forage.setItem(this.title_filter_keyword_key, t);
t = e.filterActorList;
Array.isArray(t) && await this.forage.setItem(this.filter_actor_key, t);
t = e.reviewKeywordList;
Array.isArray(t) && await this.forage.setItem(this.review_filter_keyword_key, t);
e.dataList && await this.overrideCarList(e.dataList);
t = e[this.title_filter_keyword_key];
Array.isArray(t) && await this.forage.setItem(this.title_filter_keyword_key, t);
t = e[this.filter_actor_key];
Array.isArray(t) && await this.forage.setItem(this.filter_actor_key, t);
t = e[this.review_filter_keyword_key];
Array.isArray(t) && await this.forage.setItem(this.review_filter_keyword_key, t);
e[this.car_list_key] && await this.overrideCarList(e[this.car_list_key]);
e.setting && await this.saveSetting(e.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", (e => {
if (e) {
-1 === e.indexOf("<style>") && (e = "<style>" + e + "</style>");
$("head").append(e);
}
}));
Utils.instance || (Utils.instance = this);
return Utils.instance;
}
importResource(e) {
let t;
if (e.indexOf("css") >= 0) {
t = document.createElement("link");
t.setAttribute("rel", "stylesheet");
t.href = e;
} else {
t = document.createElement("script");
t.setAttribute("type", "text/javascript");
t.src = e;
}
document.documentElement.appendChild(t);
}
openPage(e, t, n, a) {
n || (n = !0);
if (a && (a.ctrlKey || a.metaKey)) window.open(e); else {
e.includes("?") ? e += "&hideNav=1" : e += "?hideNav=1";
layer.open({
type: 2,
title: t,
content: e,
scrollbar: !1,
shadeClose: n,
area: [ "80%", "90%" ],
isOutAnim: !1,
anim: -1
});
}
}
closePage() {
parent.document.documentElement.style.overflow = "auto";
[ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(e) {
parent.document.querySelectorAll(e).forEach((function(e) {
e.parentNode.removeChild(e);
}));
}));
window.close();
}
loopDetector(e, t, n = 20, a = 1e4, i = !0) {
let r = !1;
const o = Math.random(), s = (new Date).getTime();
this.intervalContainer[o] = setInterval((() => {
if ((new Date).getTime() - s > a) {
console.warn("loopDetector timeout!", e, t);
r = i;
}
if (e() || r) {
clearInterval(this.intervalContainer[o]);
t && t();
delete this.intervalContainer[o];
}
}), n);
}
rightClick(e, t) {
if (e) {
e.jquery ? e = e.toArray() : e instanceof HTMLElement ? e = [ e ] : Array.isArray(e) || (e = [ e ]);
e && 0 !== e.length ? e.forEach((e => {
e && e.addEventListener("contextmenu", (e => {
t(e);
}));
})) : console.error("rightClick(), 找不到元素");
}
}
q(e, t, n, a) {
let i, r;
if (e) {
i = e.clientX - 130;
r = e.clientY - 120;
} else {
i = window.innerWidth / 2 - 120;
r = window.innerHeight / 2 - 120;
}
let o = layer.confirm(t, {
offset: [ r, i ],
title: "提示",
btn: [ "确定", "取消" ],
zIndex: 999999991
}, (function() {
n();
layer.close(o);
}), (function() {
a && a();
}));
}
getNowStr(e = "-", t = ":", n = null) {
let a;
a = n ? new Date(n) : new Date;
const i = a.getFullYear(), r = String(a.getMonth() + 1).padStart(2, "0"), o = String(a.getDate()).padStart(2, "0"), s = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0");
return `${[ i, r, o ].join(e)} ${[ s, l, c ].join(t)}`;
}
formatDate(e, t = "-", n = ":") {
let a;
if (e instanceof Date) a = e; else {
if ("string" != typeof e) throw new Error("Invalid date input: must be Date object or date string");
a = new Date(e);
if (isNaN(a.getTime())) throw new Error("Invalid date string");
}
const i = a.getFullYear(), r = String(a.getMonth() + 1).padStart(2, "0"), o = String(a.getDate()).padStart(2, "0"), s = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0");
return `${[ i, r, o ].join(t)} ${[ s, l, c ].join(n)}`;
}
download(e, t) {
const n = new Blob([ e ], {
type: "application/json"
}), a = URL.createObjectURL(n), i = document.createElement("a");
i.href = a;
i.download = t;
document.body.appendChild(i);
i.click();
setTimeout((() => {
document.body.removeChild(i);
URL.revokeObjectURL(a);
}), 100);
}
smoothScrollToTop(e = 500) {
return new Promise((t => {
const n = performance.now(), a = window.pageYOffset;
window.requestAnimationFrame((function i(r) {
const o = r - n, s = Math.min(o / e, 1), l = s < .5 ? 4 * s * s * s : 1 - Math.pow(-2 * s + 2, 3) / 2;
window.scrollTo(0, a * (1 - l));
s < 1 ? window.requestAnimationFrame(i) : t();
}));
}));
}
simpleId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
}
log(...e) {
console.groupCollapsed("📌", ...e);
const t = (new Error).stack.split("\n").slice(2).map((e => e.trim())).filter((e => e.trim()));
console.log(t.join("\n"));
console.groupEnd();
}
isUrl(e) {
try {
new URL(e);
return !0;
} catch (t) {
return !1;
}
}
}
window.utils = new Utils;
window.http = new class {
get(e, t = {}, n = {}) {
return this.jqueryRequest("GET", e, null, t, n);
}
post(e, t = {}, n = {}) {
return this.jqueryRequest("POST", e, t, null, n);
}
put(e, t = {}, n = {}) {
return this.jqueryRequest("PUT", e, t, null, n);
}
del(e, t = {}, n = {}) {
return this.jqueryRequest("DELETE", e, null, t, n);
}
jqueryRequest(e, t, n = {}, a = {}, i = {}) {
"POST" === e && (i = {
"Content-Type": "application/json",
...i
});
return new Promise(((r, o) => {
$.ajax({
method: e,
url: t,
data: "GET" === e || "DELETE" === e ? a : JSON.stringify(n),
headers: i,
success: (e, t, n) => {
var a;
if (null == (a = n.getResponseHeader("Content-Type")) ? void 0 : a.includes("application/json")) try {
r("object" == typeof e ? e : JSON.parse(e));
} catch (i) {
r(e);
} else r(e);
},
error: (e, t, n) => {
let a = n;
if (e.responseText) try {
const t = JSON.parse(e.responseText);
a = t.message || t.msg || e.responseText;
} catch {
a = e.responseText;
}
o(new Error(a));
}
});
}));
}
};
window.gmHttp = new class {
get(e, t = {}, n = {}) {
return this.gmRequest("GET", e, null, t, n);
}
post(e, t = {}, n = {}) {
return this.gmRequest("POST", e, t, null, n);
}
put(e, t = {}, n = {}) {
return this.gmRequest("PUT", e, t, null, n);
}
del(e, t = {}, n = {}) {
return this.gmRequest("DELETE", e, null, t, n);
}
gmRequest(e, t, n = {}, a = {}, i = {}) {
if (("GET" === e || "DELETE" === e) && a && Object.keys(a).length) {
const e = new URLSearchParams(a).toString();
t += (t.includes("?") ? "&" : "?") + e;
}
"POST" !== e && "PUT" !== e || (i = {
"Content-Type": "application/json",
...i
});
return new Promise(((a, r) => {
GM_xmlhttpRequest({
method: e,
url: t,
headers: i,
data: "POST" === e || "PUT" === e ? JSON.stringify(n) : void 0,
onload: e => {
var t;
try {
if (e.status >= 200 && e.status < 300) if (e.responseText && (null == (t = e.responseHeaders) ? void 0 : t.toLowerCase().includes("application/json"))) try {
a(JSON.parse(e.responseText));
} catch (n) {
a(e.responseText);
} else a(e.responseText || e); else if (e.responseText) try {
const t = JSON.parse(e.responseText);
r(t);
} catch {
r(new Error(e.responseText || `HTTP Error ${e.status}`));
} else r(new Error(`HTTP Error ${e.status}`));
} catch (n) {
r(n);
}
},
onerror: e => {
r(new Error(e.error || "Network Error"));
},
ontimeout: () => {
r(new Error("Request Timeout"));
}
});
}));
}
};
window.storageManager = new u;
const m = new BroadcastChannel("channel-refresh");
window.refresh = function() {
m.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 e = document.createElement("div");
e.className = "loading-container";
const t = document.createElement("div");
t.className = "loading-animation";
e.appendChild(t);
document.body.appendChild(e);
return {
close: () => {
e && e.parentNode && e.parentNode.removeChild(e);
}
};
};
}();
!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(e) {
this.defaults = {
tableClass: "data-table",
showBorder: !1,
buttons: []
};
this.config = {
...this.defaults,
...e
};
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 e = document.createElement("thead"), t = document.createElement("tr");
this.config.columns.forEach((e => {
const n = document.createElement("th");
n.textContent = e.title || e.key;
e.width && (n.style.width = e.width);
e.headerClass && (n.className = e.headerClass);
t.appendChild(n);
}));
if (this.config.buttons && this.config.buttons.length > 0) {
const e = document.createElement("th");
e.textContent = "操作";
this.config.buttonColumnWidth && (e.style.width = this.config.buttonColumnWidth);
t.appendChild(e);
}
e.appendChild(t);
this.table.appendChild(e);
}
createBody() {
const e = document.createElement("tbody");
0 === this.config.data.length ? this.renderEmptyData(e) : this.renderDataRows(e);
this.table.appendChild(e);
}
renderEmptyData(e) {
const t = document.createElement("tr"), n = document.createElement("td");
n.colSpan = this.config.columns.length + (this.config.buttons.length > 0 ? 1 : 0);
n.textContent = "暂无数据";
n.style.textAlign = "center";
t.appendChild(n);
e.appendChild(t);
}
renderDataRows(e) {
this.config.data.forEach(((t, n) => {
const a = document.createElement("tr");
this.renderDataCells(a, t, n);
this.config.buttons && this.config.buttons.length > 0 && this.renderButtonCells(a, t, n);
e.appendChild(a);
}));
}
renderDataCells(e, t, n) {
this.config.columns.forEach((a => {
const i = document.createElement("td");
a.render ? i.innerHTML = a.render(t, n) : i.textContent = t[a.key] || "";
a.cellClass && (i.className = a.cellClass);
e.appendChild(i);
}));
}
renderButtonCells(e, t, n) {
const a = document.createElement("td");
this.config.buttons.forEach((e => {
const i = document.createElement("a");
i.textContent = e.text;
i.className = e.class || "a-primary";
i.addEventListener("click", (a => {
if (e.onClick) {
const i = e.onClick.length;
3 === i ? e.onClick(a, t, n) : 2 === i ? e.onClick(a, t) : e.onClick(t);
}
}));
a.appendChild(i);
}));
e.appendChild(a);
}
update(e) {
this.config.data = e;
this.init();
}
getTableElement() {
return this.table;
}
};
}();
!function() {
const e = (e, t, n, a, i) => {
let r;
if ("object" == typeof n) r = n; else {
r = "object" == typeof a ? a : i || {};
r.gravity = n || "top";
r.position = "string" == typeof a ? a : "center";
}
r.gravity && "center" !== r.gravity || (r.offset = {
y: "calc(50vh - 150px)"
});
const o = "#60A5FA", s = "#93C5FD", l = "#10B981", c = "#6EE7B7", d = "#EF4444", p = "#FCA5A5", g = {
borderRadius: "12px",
color: "white",
padding: "12px 16px",
boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
minWidth: "150px",
textAlign: "center",
zIndex: 999999999
}, h = {
text: e,
duration: 2e3,
close: !1,
gravity: "top",
position: "center",
style: {
info: {
...g,
background: `linear-gradient(to right, ${o}, ${s})`
},
success: {
...g,
background: `linear-gradient(to right, ${l}, ${c})`
},
error: {
...g,
background: `linear-gradient(to right, ${d}, ${p})`
}
}[t],
stopOnFocus: !0,
oldestFirst: !1,
...r
};
Toastify(h).showToast();
};
window.show = {
ok: (t, n = "center", a, i) => {
e(t, "success", n, a, i);
},
error: (t, n = "center", a, i) => {
e(t, "error", n, a, i);
},
info: (t, n = "center", a, i) => {
e(t, "info", n, a, i);
}
};
}();
class PluginManager {
constructor() {
this.plugins = new Map;
}
register(e) {
if ("function" != typeof e) throw new Error("插件必须是一个类");
const t = e.name;
if (!t) throw new Error("类必须要有名称");
const n = t.toLowerCase();
if (this.plugins.has(n)) throw new Error(`插件"${t}"已注册`);
const a = new e;
a.pluginManager = this;
this.plugins.set(n, a);
}
getBean(e) {
return this.plugins.get(e.toLowerCase());
}
_getDependencies(e) {
const t = e.toString();
return t.slice(t.indexOf("(") + 1, t.indexOf(")")).split(",").map((e => e.trim())).filter((e => e));
}
async process() {
const e = (await Promise.allSettled(Array.from(this.plugins).map((async ([e, t]) => {
try {
if ("function" == typeof t.handle) {
const n = await t.initCss();
utils.insertStyle(n);
await t.handle();
return {
name: e,
status: "fulfilled"
};
}
console.log("加载插件", e);
} catch (n) {
console.error(`插件 ${e} 执行失败`, n);
return {
name: e,
status: "rejected",
error: n
};
}
})))).filter((e => "rejected" === e.status));
e.length && console.error("以下插件执行失败:", e.map((e => e.name)));
document.body.classList.add("script-ready");
}
}
class BasePlugin {
constructor() {
__publicField(this, "pluginManager", null);
}
getBean(e) {
let t = this.pluginManager.getBean(e);
if (!t) {
let t = "容器中不存在: " + e;
show.error(t);
throw new Error(t);
}
return t;
}
async initCss() {
return "";
}
async handle() {}
getPageInfo() {
let e, t, n, a, i, r = window.location.href;
if (s) {
e = $('a[title="複製番號"]').attr("data-clipboard-text");
t = r.split("?")[0].split("#")[0];
n = $(".female").prev().map(((e, t) => $(t).text())).get().join(" ");
a = $(".male").prev().map(((e, t) => $(t).text())).get().join(" ");
const o = window.location.href.split("?")[0].split("/");
i = o[o.length - 1].split("#")[0];
}
if (l) {
t = r.split("?")[0];
e = t.split("/").filter(Boolean).pop();
n = $('span[onmouseover*="star_"] a').map(((e, t) => $(t).text())).get().join(" ");
a = "";
}
return {
carNum: e,
url: t,
actress: n,
actors: a,
movieId: i
};
}
getSelector() {
return s ? o : l ? r : 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 e = await storageManager.getFilterActorList();
let t = this.getPageInfo().actors;
e.forEach((e => {
if (t.indexOf(e) > -1) {
const e = this.getBean("detailPageButtonPlugin");
e.answerCount++;
utils.q(null, "存在xxx演员, 是否屏蔽?", (() => {
e.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 e = $(".preview-video-container");
e.on("click", (e => {
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") && e[0].click();
}
async handleVideo() {
const e = $("#preview-video"), t = e.find("source"), n = e.parent();
if (!e.length || !t.length) return;
const a = e[0];
a.muted = !1;
n.css("position", "relative");
const i = t.attr("src"), r = [ "hhb", "hmb", "mhb", "mmb" ], o = r.find((e => i.includes(e))) || "mhb", s = [ {
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 l = `videoQualities_${this.getPageInfo().carNum}`;
let c = JSON.parse(sessionStorage.getItem(l));
if (!c) {
c = (await Promise.all(s.map((async e => {
const t = i.replace(new RegExp(r.join("|"), "g"), e.quality);
try {
return (await fetch(t, {
method: "HEAD"
})).ok ? e : null;
} catch {
return null;
}
})))).filter(Boolean);
sessionStorage.setItem(l, JSON.stringify(c));
}
if (c.length <= 1) return;
const d = c.map(((e, t) => `\n <button class="video-control-btn${e.quality === o ? " active" : ""}" \n id="${e.id}" \n data-quality="${e.quality}"\n style="bottom: ${50 * t}px; right: -105px;">\n ${e.text}\n </button>\n `)).join("");
n.append(d);
const p = n.find(".video-control-btn");
n.on("click", ".video-control-btn", (async e => {
const n = $(e.currentTarget), o = n.data("quality");
if (!n.hasClass("active")) try {
const e = i.replace(new RegExp(r.join("|"), "g"), o);
t.attr("src", e);
a.load();
a.muted = !1;
await a.play();
p.removeClass("active");
n.addClass("active");
} catch (s) {
console.error("切换画质失败:", s);
}
}));
p.last().trigger("click");
}
}
const f = class _HotkeyManager {
constructor() {
if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(e, t, n = null) {
if (Array.isArray(e)) {
let a = [];
e.forEach((e => {
if (!this.isHotkeyFormat(e)) throw new Error("快捷键格式错误");
let i = this.recordHotkey(e, t, n);
a.push(i);
}));
return a;
}
if (!this.isHotkeyFormat(e)) throw new Error("快捷键格式错误");
return this.recordHotkey(e, t, n);
}
static recordHotkey(e, t, n) {
let a = Math.random().toString(36).substr(2);
this.registerHotKeyMap.set(a, {
hotkeyString: e,
callback: t,
keyupCallback: n
});
return a;
}
static unregisterHotkey(e) {
this.registerHotKeyMap.has(e) && this.registerHotKeyMap.delete(e);
}
static isHotkeyFormat(e) {
return e.toLowerCase().split("+").map((e => e.trim())).every((e => [ "ctrl", "shift", "alt" ].includes(e) || 1 === e.length));
}
static judgeHotkey(e, t) {
const n = e.toLowerCase().split("+").map((e => e.trim())), a = n.includes("ctrl"), i = n.includes("shift"), r = n.includes("alt"), o = n.find((e => "ctrl" !== e && "shift" !== e && "alt" !== e));
return (this.isMac ? t.metaKey : t.ctrlKey) === a && t.shiftKey === i && t.altKey === r && t.key.toLowerCase() === o;
}
};
__publicField(f, "isMac", 0 === navigator.platform.indexOf("Mac"));
__publicField(f, "registerHotKeyMap", new Map);
__publicField(f, "handleKeydown", (e => {
for (const [t, n] of f.registerHotKeyMap) {
let t = n.hotkeyString, a = n.callback;
f.judgeHotkey(t, e) && a(e);
}
}));
__publicField(f, "handleKeyup", (e => {
for (const [t, n] of f.registerHotKeyMap) {
let t = n.hotkeyString, a = n.keyupCallback;
a && (f.judgeHotkey(t, e) && a(e));
}
}));
let w = f;
document.addEventListener("keydown", (e => {
w.handleKeydown(e);
}));
document.addEventListener("keyup", (e => {
w.handleKeyup(e);
}));
class JavTrailersPlugin extends BasePlugin {
constructor() {
super();
this.hasBand = !1;
}
handle() {
let e = window.location.href;
if (!e.includes("handle=1")) return;
if ($("h1:contains('Page not found')").length) {
let t = e.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
window.location.href = "https://javtrailers.com/search/" + t + "?handle=1";
return;
}
let t = $(".videos-list .video-link").toArray();
if (t.length) {
const n = e.split("?")[0].split("search/")[1].toLowerCase(), a = t.find((e => $(e).find(".vid-title").text().toLowerCase().includes(n)));
if (a) {
window.location.href = $(a).attr("href") + "?handle=1";
return;
}
}
this.handlePlayJavTrailers();
$("#videoPlayerContainer").on("click", (() => {
this.handlePlayJavTrailers();
}));
window.addEventListener("message", (e => {
let t = document.getElementById("vjs_video_3_html5_api");
t && (t.currentTime += 5);
}));
w.registerHotkey("z", (() => {
const e = document.getElementById("vjs_video_3_html5_api");
e && (e.currentTime += 5);
}));
w.registerHotkey("a", (() => window.parent.postMessage("a", "*")));
w.registerHotkey("s", (() => window.parent.postMessage("s", "*")));
}
handlePlayJavTrailers() {
this.hasBand || utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
setTimeout((() => {
this.hasBand = !0;
let e = document.getElementById("vjs_video_3_html5_api");
e.play();
e.currentTime = 5;
e.addEventListener("timeupdate", (function() {
e.currentTime >= 14 && e.currentTime < 16 && (e.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 e = window.location.href.split("=")[1].toLowerCase();
$(".sub-table tr td a").toArray().forEach((t => {
let n = $(t);
n.text().toLowerCase().includes(e) || n.parent().parent().hide();
}));
}
}
const b = "https://jdforrepam.com/api";
async function v() {
const e = Math.floor(Date.now() / 1e3);
if (e - (await storageManager.getItem(storageManager.review_ts_key) || 0) <= 20) return await storageManager.getItem(storageManager.review_sign_key);
const t = `${e}.lpw6vgqzsp.${md5(`${e}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
await storageManager.setItem(storageManager.review_ts_key, e);
await storageManager.setItem(storageManager.review_sign_key, t);
return t;
}
const y = async (e, t = 1, n = 20) => {
let a = `${b}/v1/movies/${e}/reviews`, i = {
jdSignature: await v()
};
return (await http.get(a, {
page: t,
sort_by: "hotly",
limit: n
}, i)).data.reviews;
}, x = async e => {
let t = `${b}/v4/movies/${e}`, n = {
jdSignature: await v()
};
const a = await http.get(t, null, n);
if (!a.data) {
show.error("获取视频详情失败: " + a.message);
throw new Error(a.message);
}
const i = a.data.movie, r = i.preview_images, o = [];
r.forEach((e => {
o.push(e.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
}));
return {
movieId: i.id,
actors: i.actors,
title: i.origin_title,
carNum: i.number,
score: i.score,
releaseDate: i.release_date,
watchedCount: i.watched_count,
imgList: o
};
}, k = async (e, t = 1, n = 20) => {
let a = `${b}/v1/lists/related?movie_id=${e}&page=${t}&limit=${n}`, i = {
jdSignature: await v()
};
const r = await gmHttp.get(a, null, i), o = [];
r.data.lists.forEach((e => {
o.push({
relatedId: e.id,
name: e.name,
movieCount: e.movies_count,
collectionCount: e.collections_count,
viewCount: e.views_count,
createTime: utils.formatDate(e.created_at)
});
}));
return o;
};
class Fc2Plugin extends BasePlugin {
handle() {
let e = "/advanced_search?type=3&score_min=3&d=1";
$('.navbar-item:contains("FC2")').attr("href", e);
$('.tabs a:contains("FC2")').attr("href", e);
if (window.location.href.includes("collection_codes?movieId")) {
const e = new URLSearchParams(window.location.search);
let t = e.get("movieId"), n = e.get("carNum"), a = e.get("url");
t && n && a && this.openFc2Page(t, n, a);
}
}
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(e, t, n) {
layer.open({
type: 1,
title: "影片详情",
content: '\n <div class="movie-detail-container">\n <div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>\n <div class="right-box">\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n <div 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: (a, i) => {
this.loadData(e, t);
$("#favoriteBtn").on("click", (async e => {
const a = $("#data-actress").text();
await storageManager.saveCar(t, n, a, c);
window.refresh();
layer.closeAll();
}));
$("#filterBtn").on("click", (e => {
utils.q(e, `是否屏蔽${t}?`, (async () => {
const e = $("#data-actress").text();
await storageManager.saveCar(t, n, e, d);
window.refresh();
layer.closeAll();
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
}));
$("#hasDownBtn").on("click", (async e => {
const a = $("#data-actress").text();
await storageManager.saveCar(t, n, a, p);
window.refresh();
layer.closeAll();
}));
},
end() {
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}
});
}
loadData(e, t) {
this.handleVideo(t.replace("FC2-", ""));
this.handleMovieDetail(e);
this.handleMagnets(e);
this.getBean("reviewPlugin").showReview(e, $("#reviews-content")).then();
}
handleMovieDetail(e) {
x(e).then((e => {
const t = e.actors || [], n = e.imgList || [];
let a = "";
if (t.length > 0) {
let e = "";
for (let n = 0; n < t.length; n++) {
let i = t[n];
a += `<span class="actor-tag"><a href="/actors/${i.id}" target="_blank">${i.name}</a></span>`;
0 === i.gender && (e += i.name);
}
$("#data-actress").text(e);
} else a = '<span class="no-data">暂无演员信息</span>';
let i = "";
i = Array.isArray(n) && n.length > 0 ? n.map(((e, t) => `\n <a href="${e}" data-fancybox="movie-gallery" data-caption="剧照 ${t + 1}">\n <img src="${e}" class="movie-image-thumb" alt=""/>\n </a>\n `)).join("") : '<div class="no-data">暂无剧照</div>';
$(".movie-info-container").html(`\n <h3 class="movie-title">${e.title || "无标题"}</h3>\n <div class="movie-meta">\n <span>番号: ${e.carNum || "未知"}</span>\n <span>年份: ${e.releaseDate || "未知"}</span>\n <span>评分: ${e.score || "无"}</span>\n </div>\n <div class="movie-actors">\n <div class="actor-list">主演: ${a}</div>\n </div>\n <div class="movie-gallery" style="margin-top:10px">\n <h4>剧照: </h4>\n <div class="image-list">${i}</div>\n </div>\n `);
})).catch((e => {
console.error(e);
$(".movie-info-container").html(`\n <div class="movie-error">加载失败: ${e.message}</div>\n `);
}));
}
handleMagnets(e) {
(async e => {
let t = `${b}/v1/movies/${e}/magnets`, n = {
jdSignature: await v()
};
return (await http.get(t, null, n)).data.magnets;
})(e).then((e => {
let t = "";
if (e.length > 0) for (let n = 0; n < e.length; n++) {
let a = e[n], i = "";
n % 2 == 0 && (i = "odd");
t += `\n <div class="item columns is-desktop ${i}">\n <div class="magnet-name column is-four-fifths">\n <a href="magnet:?xt=urn:btih:${a.hash}" title="右鍵點擊並選擇「複製鏈接地址」">\n <span class="name">${a.name}</span>\n <br>\n <span class="meta">\n ${(a.size / 1024).toFixed(2)}GB, ${a.files_count}個文件 \n </span>\n <br>\n <div class="tags">\n ${a.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""}\n ${a.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:${a.hash}" type="button"> 複製 </button>\n </div>\n <div class="date column"><span class="time">${a.created_at}</span></div>\n </div>\n `;
} else t = '<span class="no-data">暂无磁力信息</span>';
$("#magnets-content").html(t);
})).catch((e => {
console.error(e);
$("#magnets-content").html(`\n <div class="movie-error">加载失败: ${e.message}</div>\n `);
}));
}
handleVideo(e) {
(async e => {
let t = `https://hohoj.tv/search?text=${e}`, n = await gmHttp.get(t), a = null;
if (n.includes("找不到任何影片")) return a;
const i = (new DOMParser).parseFromString(n, "text/html");
$(i).find(".video-item a").toArray().forEach((t => {
if ($(t).find(".video-item-title").text().includes(e)) {
let e = $(t).attr("href").split("id=")[1];
a = "https://hohoj.tv/embed?id=" + e;
}
}));
return a;
})(e).then((t => {
const n = document.querySelector(".movie-poster-container"), a = document.querySelector(".movie-trailer");
if (t) $(a).attr("src", t); else {
n.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-${e}" target="_blank">missav</a></p>\n </div>\n `;
a.style.display = "none";
}
}));
}
}
class FoldCategoryPlugin extends BasePlugin {
async handle() {
if (!window.isListPage) return;
let e, t = $(".tabs ul");
if (t.length > 0) {
e = $("#tags");
let n = $("#tags dl div.tag.is-info").map((function() {
return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
})).get().join(" ");
if (!n) return;
t.append('\n <li class="is-active" id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span></span>\n <i style="margin-left: 10px"></i>\n </a>\n </li>\n ');
$(".tabs").append(`<div style="padding-top:10px"><span>已选分类: ${n}</span></div>`);
}
let n = $("h2.section-title");
if (n.length > 0) {
n.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 ');
e = $("section > div > div.box");
}
if (!e) return;
let a = $("#foldCategoryBtn"), i = "yes" === await storageManager.getItem(storageManager.fold_category_key), [r, o] = i ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
a.find("span").text(r).end().find("i").attr("class", o);
window.location.href.includes("noFold=1") || e[i ? "hide" : "show"]();
a.on("click", (async t => {
t.preventDefault();
i = !i;
await storageManager.setItem(storageManager.fold_category_key, i ? "yes" : "no");
const [n, r] = i ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
a.find("span").text(n).end().find("i").attr("class", r);
e[i ? "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 e = $(".female").prev().map(((e, t) => $(t).text().trim())).get();
if (!e.length) return;
let t = null, n = "";
for (let i = 0; i < e.length; i++) {
let r = e[i];
t = await storageManager.getItem(storageManager.actress_prefix_key + r);
if (!t) try {
t = await this.searchInfo(r);
t && await storageManager.setItem(storageManager.actress_prefix_key + r, t, g);
} catch (a) {
console.error("该名称查询失败,尝试其它名称");
}
let o = "";
o = t ? `\n <div class="panel-block">\n <strong>${r}:</strong>\n <a href="${t.url}" style="margin-left: 5px" target="_blank">\n <span class="info-tag">${t.birthday} ${t.age}</span>\n <span class="info-tag">${t.height} ${t.weight}</span>\n <span class="info-tag">${t.threeSizeText} ${t.braSize}</span>\n </a>\n </div>\n ` : `<div class="panel-block"><a href="${this.apiUrl + r}" target="_blank"><strong>${r}:</strong></a></div> `;
n += o;
}
$('strong:contains("演員")').parent().after(n);
}
async handleStarPage() {
let e = [], t = $(".actor-section-name");
t.length && t.text().trim().split(",").forEach((t => {
e.push(t.trim());
}));
let n = $(".section-meta:not(:contains('影片'))");
n.length && n.text().trim().split(",").forEach((t => {
e.push(t.trim());
}));
if (!e.length) return;
let a = null;
for (let o = 0; o < e.length; o++) {
let t = e[o];
a = await storageManager.getItem(storageManager.actress_prefix_key + t);
if (a) break;
try {
a = await this.searchInfo(t);
} catch (r) {
console.error("该名称查询失败,尝试其它名称");
}
if (a) break;
}
a && e.forEach((e => {
storageManager.setItem(storageManager.actress_prefix_key + e, a, g);
}));
let i = '<div style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>';
a && (i = `\n <a href="${a.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;">出生日期: ${a.birthday}</span>\n <span style="width: 200px;">年龄: ${a.age}</span>\n <span style="width: 200px;">身高: ${a.height}</span>\n </div>\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">体重: ${a.weight}</span>\n <span style="width: 200px;">三围: ${a.threeSizeText}</span>\n <span style="width: 200px;">罩杯: ${a.braSize}</span>\n </div>\n </div>\n </a>\n `);
t.parent().append(i);
}
async searchInfo(e) {
"三上悠亞" === e && (e = "三上悠亜");
let t = this.apiUrl + e;
const n = await gmHttp.get(t), a = new DOMParser, i = $(a.parseFromString(n, "text/html"));
let r = i.find('tr:has(a[title="誕生日"]) td').text().trim(), o = i.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt(i.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", s = i.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", l = i.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim();
"― kg" === l && (l = "");
return {
birthday: r,
age: o,
height: s,
weight: l,
threeSizeText: i.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(),
braSize: i.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(),
url: t
};
}
}
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", (e => {
let t = localStorage.getItem("token");
if (!t) {
alert("请先登录!");
return;
}
let n = JSON.parse(t).refresh_token;
navigator.clipboard.writeText(n).then((() => {
alert("已复制到剪切板 如失败, 请手动复制: " + n);
})).catch((e => {
console.error("Failed to copy refresh token: ", e);
}));
}));
}
}
class HitShowPlugin extends BasePlugin {
constructor() {
super();
}
handle() {
$('a[href*="rankings/playback"]').on("click", (e => {
e.preventDefault();
e.stopPropagation();
window.location.href = "/?handlePlayback=1&period=daily";
}));
this.handlePlayback().then();
}
async handlePlayback() {
if (!window.location.href.includes("handlePlayback=1")) return;
let e = new URLSearchParams(window.location.search).get("period");
this.toolBar(e);
let t = $(".movie-list");
t.html("");
let n = loading();
try {
const n = await (async (e = "daily", t = "high_score") => {
let n = `${b}/v1/rankings/playback?period=${e}&filter_by=${t}`, a = {
jdSignature: await v()
};
return (await http.get(n, null, a)).data.movies;
})(e);
let a = this.markDataListHtml(n);
t.html(a);
window.refresh();
this.loadScore(n);
} finally {
n.close();
}
}
toolBar(e) {
$(".pagination").remove();
$(".main-tabs ul li").removeClass("is-active");
$(".main-tabs ul li:first").addClass("is-active");
let t = `\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" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=daily">日榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=weekly">周榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === e ? "is-info" : ""}" href="/?handlePlayback=1&period=monthly">月榜</a>\n </div>\n </div>\n `;
$(".toolbar").html(t);
}
getStarRating(e) {
let t = "";
const n = Math.floor(e);
for (let a = 0; a < n; a++) t += '<i class="icon-star"></i>';
for (let a = 0; a < 5 - n; a++) t += '<i class="icon-star gray"></i>';
return t;
}
loadScore(e) {
if (0 === e.length) return;
(async () => {
const t = [];
for (const a of e) try {
const e = a.id;
if ($(`#${e}`).is(":hidden")) continue;
const t = await storageManager.getItem(storageManager.score_prefix_key + e);
if (t) {
this.appendScoreHtml(e, t);
continue;
}
for (;!document.hasFocus(); ) await new Promise((e => setTimeout(e, 500)));
const n = await x(e);
let i = n.score, r = n.watchedCount, o = `\n <span class="value">\n <span class="score-stars">${this.getStarRating(i)}</span> \n ${i}分,由${r}人評價\n </span>\n `;
this.appendScoreHtml(e, o);
await storageManager.setItem(storageManager.score_prefix_key + e, o, 6048e5);
await new Promise((e => setTimeout(e, 1e3)));
} catch (n) {
t.push({
carNum: a.number,
error: n.message,
stack: n.stack
});
console.error(`🚨 解析评分数据失败 | 编号: ${a.number}\n`, `错误详情: ${n.message}\n`, n.stack ? `调用栈:\n${n.stack}` : "");
}
if (t.length > 0) {
show.error("解析评分数据失败, 个数:", t.length);
console.table(t);
}
})();
}
appendScoreHtml(e, t) {
let n = $(`#score_${e}`);
"" === n.html().trim() && n.slideUp(0, (function() {
$(this).html(t).slideDown(500);
}));
}
markDataListHtml(e) {
let t = "";
e.forEach((e => {
t += `\n <div class="item" id="${e.id}">\n <a href="/v/${e.id}" class="box" title="${e.origin_title}">\n <div class="cover ">\n <img loading="lazy" src="${e.cover_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt="">\n </div>\n <div class="video-title"><strong>${e.number}</strong> ${e.origin_title}</div>\n <div class="score" id="score_${e.id}">\n </div>\n <div class="meta">\n ${e.release_date}\n </div>\n <div class="tags has-addons">\n ${e.has_cnsub ? '<span class="tag is-warning">含中字磁鏈</span>' : e.magnets_count > 0 ? '<span class="tag is-success">含磁鏈</span>' : '<span class="tag is-info">无磁鏈</span>'}\n ${e.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""}\n </div>\n </a>\n </div>\n `;
}));
return t;
}
}
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", (e => {
e.preventDefault();
e.stopPropagation();
const t = $(e.target), n = (t.is("a") ? t : t.closest("a")).attr("href");
let a = n.includes("?") ? n.split("?")[1] : n;
const i = new URLSearchParams(a);
this.checkLogin(e, i);
}));
this.handleTop().then();
}
async handleTop() {
if (!window.location.href.includes("handleTop=1")) return;
const e = new URLSearchParams(window.location.search);
let t = e.get("type") || "all", n = e.get("type_value") || "";
this.has_cnsub = e.get("has_cnsub") || "";
let a = e.get("page") || 1;
this.toolBar(t, n, a);
let i = $(".movie-list");
i.html("");
let r = loading();
try {
const e = await (async (e = "all", t = "", n = 1, a = 40) => {
let i = `${b}/v1/movies/top?start_rank=1&type=${e}&type_value=${t}&ignore_watched=false&page=${n}&limit=${a}`, r = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
authorization: "Bearer " + await storageManager.getItem("appAuthorization"),
jdsignature: await v()
};
return await gmHttp.get(i, null, r);
})(t, n, a, 50);
let r = e.success, o = e.message, s = e.action;
if (1 === r) {
let t = e.data.movies;
if (0 === t.length) {
show.error("无数据");
return;
}
this.movies = t;
const n = this.getBean("hitShowPlugin");
let a = n.markDataListHtml(t);
i.html(a);
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();
}
n.loadScore(t);
} else {
console.error(e);
i.html(`<h3>${o}</h3>`);
show.error(o);
}
if ("JWTVerificationError" === s) {
await storageManager.removeItem("appAuthorization");
await this.checkLogin(null, new URLSearchParams(window.location.search));
}
} catch (o) {
console.error("获取Top数据失败:", o);
show.error(`获取Top数据失败: ${o ? o.message : o}`);
} finally {
r.close();
}
}
toolBar(e, t, n) {
$(".main-tabs ul li").removeClass("is-active");
$(".main-tabs ul li:eq(1)").addClass("is-active");
if ("5" === n.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 a = "";
for (let r = (new Date).getFullYear(); r >= 2008; r--) a += `\n <a style="padding:18px 18px !important;" \n class="button is-small ${t === r.toString() ? "is-info" : ""}" \n href="/?handleTop=1&type=year&type_value=${r}&has_cnsub=${this.has_cnsub}">\n ${r}\n </a>\n `;
let i = `\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" === e ? "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" === t ? "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" === t ? "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" === t ? "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" === t ? "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 ${a}\n </div>\n </div>\n `;
$(".toolbar").html(i);
$("a[data-cnsub-value]").on("click", (e => {
const t = $(e.currentTarget).data("cnsub-value");
this.has_cnsub = t.toString();
$("a[data-cnsub-value]").removeClass("is-info");
$(e.currentTarget).addClass("is-info");
$(".toolbar a.button").not("[data-cnsub-value]").each(((e, n) => {
const a = $(n), i = new URL(a.attr("href"), window.location.origin);
i.searchParams.set("has_cnsub", t);
a.attr("href", i.toString());
}));
const n = new URL(window.location.href);
n.searchParams.set("has_cnsub", t);
history.pushState({}, "", n.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(e, t) {
if (!(await storageManager.getItem("appAuthorization"))) {
show.error("未登录手机端接口, 无法查看");
this.openLoginDialog();
return;
}
let n = "all", a = "", i = t.get("t") || "";
if (/^y\d+$/.test(i)) {
n = "year";
a = i.substring(1);
} else if ("" !== i) {
n = "video_type";
a = i;
}
let r = `/?handleTop=1&type=${n}&type_value=${a}`;
e && (e.ctrlKey || e.metaKey) ? window.open(r, "_blank") : window.location.href = r;
}
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: (e, t) => {
$("#loginBtn").click((function() {
const e = $("#username").val(), n = $("#password").val();
if (!e || !n) {
show.error("请输入用户名和密码");
return;
}
let a = loading();
(async (e, t) => {
let n = `${b}//v1/sessions?username=${e}&password=${t}&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`, a = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
"content-type": "multipart/form-data; boundary=--dio-boundary-2210433284",
jdsignature: await v()
};
return await gmHttp.post(n, null, a);
})(e, n).then((async e => {
let n = e.success;
if (0 === n) show.error(e.message); else {
if (1 !== n) {
console.error("登录失败", e);
throw new Error(e.message);
}
{
let n = e.data.token;
await storageManager.setItem("appAuthorization", n);
await storageManager.setItem("appUser", e.data);
show.ok("登录成功");
layer.close(t);
window.location.href = "/?handleTop=1&period=daily";
}
}
})).catch((e => {
console.error("登录异常:", e);
show.error(e.message);
})).finally((() => {
a.close();
}));
}));
}
});
}
}
class NavBarPlugin extends BasePlugin {
handle() {
this.margeNav();
this.hookSearch();
if (window.location.href.includes("/search?q")) {
let e = new URLSearchParams(window.location.search).get("q");
$("#search-keyword").val(e);
}
}
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", (e => {
setTimeout((() => {
$("#search-btn").click();
}), 0);
})).on("keypress", (e => {
"Enter" === e.key && setTimeout((() => {
$("#search-btn").click();
}), 0);
}));
$("#search-btn").on("click", (e => {
let t = $("#search-keyword").val(), n = $("#search-type option:selected").val();
"" !== t && (window.location.href.includes("/search?q") ? window.location.href = "/search?q=" + t + "&f=" + n : window.open("/search?q=" + t + "&f=" + n));
}));
$("#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 e = this.getPageInfo().carNum, t = `\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/${e}/" 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/${e}" 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=${e}" 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(t);
$("#javTrailersBtn").on("click", (t => utils.openPage(`https://javtrailers.com/video/${e.toLowerCase().replace("-", "00")}?handle=1`, e, !1, t)));
}
}
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 e = $(".avatar-box");
if (e.length > 0) {
let t = e.parent();
t.css("position", "initial");
t.insertBefore(t.parent());
}
}
}
}
class DetailPageButtonPlugin extends BasePlugin {
constructor() {
super();
this.answerCount = 1;
}
handle() {
this.bindHotkey();
window.isDetailPage && this.createMenuBtn();
}
createMenuBtn() {
const e = this.getPageInfo(), t = e.carNum, n = '\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 ';
s && $(".tabs").after(n);
l && $("#mag-submit-show").before(n);
$("#favoriteBtn").on("click", (() => this.favoriteOne()));
$("#filterBtn").on("click", (e => this.filterOne(e)));
$("#hasDownBtn").on("click", (async () => {
await storageManager.saveCar(e.carNum, e.url, e.actress, p);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}));
$("#enable-magnets-filter").on("click", (e => {
let t = $("#magnets-span");
const n = this.getBean("HighlightMagnetPlugin");
if ("关闭磁力过滤" === t.text()) {
n.showAll();
t.text("开启磁力过滤");
} else {
n.handle();
t.text("关闭磁力过滤");
}
}));
$("#search-subtitle-btn").on("click", (e => utils.openPage(`https://subtitlecat.com/index.php?search=${t}`, t, !1, e)));
$("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(t)));
this.showStatus(t).then();
}
async showStatus(e) {
let t = await storageManager.getCar(e);
if (t) switch (t.status) {
case d:
$("#filterBtn").text("已屏蔽(a)");
break;
case c:
$("#favoriteBtn").text("已收藏(s)");
break;
case p:
$("#hasDownBtn").text("已加入已下载");
}
}
async favoriteOne() {
let e = this.getPageInfo();
await storageManager.saveCar(e.carNum, e.url, e.actress, c);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}
searchXunLeiSubtitle(e) {
let t = loading();
gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${e}`).then((t => {
let n = t.data;
n && 0 !== n.length ? layer.open({
type: 1,
title: "迅雷字幕",
content: '<div id="table-container"></div>',
area: [ "50%", "70%" ],
success: t => {
new TableGenerator({
containerId: "table-container",
columns: [ {
key: "name",
title: "文件名"
}, {
key: "ext",
title: "类型"
}, {
key: "extra_name",
title: "来源"
} ],
data: n,
buttons: [ {
text: "下载",
class: "a-primary",
onClick: t => {
gmHttp.get(t.url).then((n => {
utils.download(n, e + "." + t.ext);
}));
}
} ]
});
}
}) : show.error("迅雷中找不到相关字幕!");
})).finally((() => {
t.close();
}));
}
async filterOne(e, t) {
e && e.preventDefault();
let n = this.getPageInfo();
if (t) {
await storageManager.saveCar(n.carNum, n.url, n.actress, d);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
} else utils.q(e, `是否屏蔽${n.carNum}?`, (async () => {
await storageManager.saveCar(n.carNum, n.url, n.actress, d);
window.refresh();
show.ok("操作成功", {
duration: 100,
callback: () => {
utils.closePage();
}
});
}));
}
speedVideo() {
if ($("#preview-video").is(":visible")) {
const e = document.getElementById("preview-video");
if (e) {
e.muted = !1;
e.currentTime += 5;
}
return;
}
const e = $('iframe[id^="layui-layer-iframe"]');
if (e.length > 0) {
e[0].contentWindow.postMessage("speedVideo", "*");
return;
}
let t = $(".preview-video-container");
if (t.length > 0) {
t[0].click();
const e = document.getElementById("preview-video");
if (e) {
e.currentTime += 5;
e.muted = !1;
}
} else $("#javTrailersBtn").click();
}
bindHotkey() {
const e = {
a: () => {
this.answerCount >= 2 ? this.filterOne(null, !0) : this.filterOne(null);
this.answerCount++;
},
s: () => this.favoriteOne(null),
z: () => this.speedVideo()
}, t = (e, t) => {
w.registerHotkey(e, (() => {
window.isDetailPage ? t() : (e => {
const t = $(".layui-layer-content iframe");
if (0 === t.length) return !1;
t[0].contentWindow.postMessage(e, "*");
})(e);
}));
};
window.isDetailPage && window.addEventListener("message", (t => {
e[t.data] && e[t.data]();
}));
Object.entries(e).forEach((([e, n]) => {
t(e, n);
}));
}
}
class HistoryPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "dataType", "all");
__publicField(this, "tableObj", null);
}
handle() {
s && $(".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>');
l && $("#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", (e => 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 e => {
const t = await this.getDataList();
this.loadTableData(t);
$(".layui-layer-content").on("click", ".history-btn", (async e => {
this.dataType = $(e.target).data("action");
this.reloadTable();
}));
},
end: async () => window.refresh()
});
}
async handleClickDetail(e, t) {
if (s) if (t.carNum.includes("FC2-")) {
const e = t.url.split("/"), n = e[e.length - 1].split("#")[0];
this.getBean("fc2Plugin").openFc2Page(n, t.carNum, t.url);
} else utils.openPage(t.url, t.carNum, !1, e);
if (l) {
let n = t.url;
if (n.includes("javdb")) if (t.carNum.includes("FC2-")) {
const e = n.split("/"), a = e[e.length - 1].split("#")[0];
let i = `${await storageManager.getSetting("javDbUrl", "https://javdb.com")}/users/collection_codes?movieId=${a}&carNum=${t.carNum}&url=${n}`;
window.open(i, "_blank");
} else window.open(n, "_blank"); else utils.openPage(t.url, t.carNum, !1, e);
}
}
async reloadTable() {
const e = await this.getDataList();
this.tableObj.update(e);
}
handleDelete(e, t) {
utils.q(e, `是否移除${t.carNum}?`, (async () => {
await storageManager.removeCar(t.carNum);
this.getBean("listPagePlugin").showCarNumBox(t.carNum);
this.reloadTable().then();
}));
}
async getDataList() {
let e = await storageManager.getCarList();
this.allCount = e.length;
this.filterCount = 0;
this.favoriteCount = 0;
this.hasDownCount = 0;
e.forEach((e => {
switch (e.status) {
case d:
this.filterCount++;
break;
case c:
this.favoriteCount++;
break;
case p:
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 ? e : e.filter((e => e.status === this.dataType));
}
loadTableData(e) {
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: e => {
let t = e.url;
return t.includes("javdb") ? '<span style="color:#d34f9e">Javdb</span>' : t.includes("javbus") ? '<span style="color:#eaa813">JavBus</span>' : `<span style="color:#050505">${t}</span>`;
}
}, {
key: "status",
title: "状态",
width: "250px",
render: e => {
let t, n = "";
switch (e.status) {
case "filter":
t = "#ec4949";
n = "已屏蔽";
break;
case "favorite":
t = "#50adb9";
n = "已收藏";
break;
case "hasDown":
t = "#8ebd6e";
n = "已下载";
}
return `<span style="color:${t}">${n}</span>`;
}
} ],
data: e,
buttons: [ {
text: "移除",
class: "a-danger",
onClick: e => {
this.handleDelete(event, e);
}
}, {
text: "详情页",
class: "a-info",
onClick: e => {
this.handleClickDetail(event, e);
}
} ]
});
}
}
class ReviewPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "floorIndex", 1);
}
async handle() {
if (window.isDetailPage) {
if (s) {
const e = window.location.href.split("?")[0].split("/"), t = e[e.length - 1].split("#")[0];
await this.showReview(t);
await this.getBean("RelatedPlugin").showRelated();
}
if (l) {
let e = this.getPageInfo().carNum;
const t = await (async e => {
let t = `${b}/v2/search`, n = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
jdsignature: await v()
}, a = {
q: e,
page: 1,
type: "movie",
limit: 1,
movie_type: "all",
from_recent: "false",
movie_filter_by: "all",
movie_sort_by: "relevance"
};
return (await gmHttp.get(t, a, n)).data.movies;
})(e);
let n = null;
for (let a = 0; a < t.length; a++) {
let i = t[a];
if (i.number.toLowerCase() === e.toLowerCase()) {
n = i.id;
break;
}
}
if (!n) {
show.error("解析视频ID失败, 该视频可能在JavDb中不存在, 无法获取评论数据");
return;
}
this.showReview(n, $("#sample-waterfall")).then();
}
}
}
async showReview(e, t) {
let n = $("#magnets-content");
t && (n = t);
n.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
let a = await storageManager.getSetting("reviewCount", 20), i = null;
try {
i = await y(e, 1, a);
} catch (l) {
console.error(l);
}
$("#reviewsLoading").remove();
n.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 e = $("#reviewsFold .toggle-text"), t = $("#reviewsFold .toggle-icon");
if ("展开" === e.text()) {
e.text("折叠");
t.text("▲");
r.show();
o.show();
} else {
e.text("展开");
t.text("▼");
r.hide();
o.hide();
}
}));
n.append('<div id="reviewsContainer"></div>');
n.append('<div id="reviewsFooter"></div>');
const r = $("#reviewsContainer"), o = $("#reviewsFooter");
if (!i) {
r.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论失败</div>');
return;
}
0 === i.length && r.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>');
const s = await storageManager.getReviewFilterKeywordList();
this.displayReviews(i, r, s);
if (i.length === a) {
o.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 t = 1;
$("#loadMoreReviews").click((async () => {
$("#loadMoreReviews").text("加载中...").prop("disabled", !0);
t++;
const n = await y(e, t, a);
this.displayReviews(n, r, s);
if (n.length < a) {
$("#loadMoreReviews").remove();
$("#reviewsEnd").show();
} else $("#loadMoreReviews").text("加载更多评论").prop("disabled", !1);
}));
} else i.length > 0 && o.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>');
}
displayReviews(e, t, n) {
if (e.length) {
e.forEach((e => {
let a = !1;
for (let t = 0; t < n.length; t++) if (e.content.indexOf(n[t]) > -1) {
a = !0;
break;
}
if (a) return;
let i = "";
for (let t = 0; t < e.score; t++) i += '<i class="icon-star"></i>';
let r = e.content.replace(/(https?:\/\/[^\s]+|magnet:\?[^\s"'\u4e00-\u9fa5,。?!()【】]+)/gi, (e => `<a href="${e}" class="a-primary" \n style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${e}</a>\n\x3c!-- <a class="a-success review-magnet" style="padding:0;margin-left:0">预览</a>--\x3e`)), o = `\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 ${e.username} <span class="score-stars">${i}</span> \n <span class="time">${utils.formatDate(e.created_at)}</span> \n 点赞:${e.likes_count}\n <p class="review-content" style="margin-top: 5px;"> ${r} </p>\n </div>\n `;
t.append(o);
}));
utils.rightClick($(".review-content"), (e => {
const t = window.getSelection().toString();
if (t) {
e.preventDefault();
utils.q(e, `是否将 '${t}' 加入评论区关键词?`, (async () => {
await storageManager.saveReviewFilterKeyword(t);
show.ok("操作成功, 刷新页面后生效");
}));
}
}));
}
}
}
class FilterTitleKeywordPlugin extends BasePlugin {
handle() {
if (!window.isDetailPage) return;
let e, t;
if (s) {
e = $("h2");
t = $(".male").prev();
}
l && (e = $("h3"));
utils.rightClick(e, (e => {
const t = window.getSelection().toString();
if (t) {
e.preventDefault();
let n = {
clientX: e.clientX,
clientY: e.clientY + 80
};
utils.q(n, `是否屏蔽标题关键词 ${t}?`, (async () => {
await storageManager.saveTitleFilterKeyword(t);
window.refresh();
utils.closePage();
}));
}
}));
t && t.length > 0 && utils.rightClick(t, (e => {
e.preventDefault();
let t = $(e.target).text().trim();
utils.q(e, `是否屏蔽演员${t}?`, (async () => {
await storageManager.saveFilterActor(t);
window.refresh();
const e = this.getBean("detailPageButtonPlugin");
await e.filterOne(null, !0);
}));
}));
}
}
class ListPageButtonPlugin extends BasePlugin {
handle() {
window.isListPage && this.createMenuBtn();
}
createMenuBtn() {
if (s) {
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 (l) {
const e = '\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(e);
}
$("#waitCheckBtn").on("click", (e => {
this.openWaitCheck(e).then();
}));
$("#waitDownBtn").on("click", (e => {
this.openFavorite(e).then();
}));
$("#sort-toggle-btn").on("click", (e => {
const t = localStorage.getItem("sortMethod");
let n;
n = t && "default" !== t ? "rateCount" === t ? "date" : "default" : "rateCount";
const a = {
default: "默认",
rateCount: "评价人数",
date: "时间"
}[n];
$(e.target).text(`当前排序方式: ${a}`);
localStorage.setItem("sortMethod", n);
this.sortItems();
}));
}
sortItems() {
const e = localStorage.getItem("sortMethod");
if (!e) return;
$(".movie-list .item").each((function(e) {
$(this).attr("data-original-index") || $(this).attr("data-original-index", e);
}));
const t = $(".movie-list"), n = $(".item", t);
if ("default" === e) n.sort((function(e, t) {
return $(e).data("original-index") - $(t).data("original-index");
})).appendTo(t); else {
const a = n.get();
a.sort((function(t, n) {
if ("rateCount" === e) {
const e = e => {
const t = $(e).find(".score .value").text().match(/由(\d+)人/);
return t ? parseFloat(t[1]) : 0;
};
return e(n) - e(t);
}
{
const e = e => {
const t = $(e).find(".meta").text().trim();
return new Date(t);
};
return e(n) - e(t);
}
}));
t.empty().append(a);
}
}
async openWaitCheck() {
let e;
s && (e = o);
l && (e = r);
const t = await storageManager.getSetting("waitCheckCount", 5), n = [ "已收藏", "已屏蔽", "已下载" ];
let a = 0;
$(`${e.itemSelector}:visible`).each(((e, i) => {
if (a >= t) return !1;
const r = $(i);
if (!n.some((e => r.find(`span:contains('${e}')`).length > 0))) {
const e = r.find("a");
if (e.length) {
let t = e.attr("href");
if (t) {
t += t.includes("?") ? "&autoPlay=1" : "?autoPlay=1";
window.open(t);
a++;
}
}
}
}));
0 === a && show.info("没有需鉴定的视频");
}
async openFavorite() {
let e = await storageManager.getSetting("waitCheckCount", 5);
const t = (await storageManager.getCarList()).filter((e => e.status === c));
for (let n = 0; n < e; n++) {
if (n >= t.length) return;
let e = t[n], a = e.carNum, i = e.url;
if (a.includes("FC2-")) {
const t = e.url.split("/"), n = t[t.length - 1].split("#")[0];
let r = await storageManager.getSetting("javDbUrl", "https://javdb.com");
window.open(`${r}/users/collection_codes?movieId=${n}&carNum=${a}&url=${i}`);
} else window.open(i);
}
}
}
class ListPagePlugin extends BasePlugin {
async handle() {
this.cleanRepeatId();
this.replaceHdImg();
await this.doFilter();
this.bindClick().then();
new BroadcastChannel("channel-refresh").addEventListener("message", (async e => {
"refresh" === e.data.type && await this.doFilter();
}));
this.checkDom();
}
checkDom() {
if (!window.isListPage) return;
const e = this.getSelector(), t = document.querySelector(e.boxSelector), n = new MutationObserver((e => {
utils.log("检查");
n.disconnect();
try {
this.replaceHdImg();
this.doFilter().then();
this.getBean("ListPageButtonPlugin").sortItems();
} finally {
n.observe(t, a);
}
})), a = {
childList: !0,
subtree: !1
};
n.observe(t, a);
}
cleanRepeatId() {
if (!l) return;
$("#waterfall_h").removeAttr("id").attr("id", "no-page");
const e = $('[id="waterfall"]');
0 !== e.length && e.each((function() {
const e = $(this);
if (!e.hasClass("masonry")) {
e.children().insertAfter(e);
e.remove();
}
}));
}
async doFilter() {
if (!window.isListPage) return;
let e = $(this.getSelector().itemSelector).toArray();
await this.filterMovieList(e);
await this.getBean("autoPagePlugin").handlePaging();
}
async filterMovieList(e) {
const t = await storageManager.getCarList(), n = await storageManager.getTitleFilterKeyword(), a = t.filter((e => e.status === d)).map((e => e.carNum)), i = t.filter((e => e.status === c)).map((e => e.carNum)), r = t.filter((e => e.status === p)).map((e => e.carNum));
let o = await storageManager.getSetting("hideFilterItem", "yes"), g = window.location.href;
(g.includes("search?q") || g.includes("/search/") || g.includes("/users/")) && (o = "no");
e.forEach((e => {
let t = $(e);
const {carNum: c, aHref: d, title: p} = this.findCarNumAndHref(t), g = `${c}-hide`, h = `${c}-keywordHide`, u = `${c}-tag`;
if ("no" === o && t.attr("data-hide") === g) {
t.show();
t.removeAttr("data-hide");
}
if (n.some((e => p.includes(e) || c.includes(e))) && t.attr("data-keyword-hide") !== h) {
t.hide();
t.attr("data-keyword-hide", h);
return;
}
if (a.includes(c) && "yes" === o && t.attr("data-hide") !== g) {
t.hide();
t.attr("data-hide", g);
return;
}
if (r.includes(c) && "yes" === o && t.attr("data-hide") !== g) {
t.hide();
t.attr("data-hide", g);
return;
}
let m = "", f = "";
if (a.includes(c)) {
m = "已屏蔽";
f = "#d95427";
} else if (i.includes(c)) {
m = "已收藏";
f = "#2caac0";
} else if (r.includes(c)) {
m = "已下载";
f = "#58c433";
}
if (m && t.attr("data-tag") !== u) {
s && t.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: ${f} !important;">\n ${m}\n </span>`);
if (l) {
let e = `<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: ${f} !important;"><span>${m}</span></a>`;
t.find(".item-tag").append(e);
}
t.attr("data-tag", u);
}
}));
$("#waitDownBtn span").text(`打开已收藏 (${i.length})`);
}
async bindClick() {
let e = this.getSelector(), t = await storageManager.getSetting("dialogOpenDetail", "yes");
$(e.boxSelector).on("click", ".item img", (e => {
e.preventDefault();
if ($(e.target).closest("div.meta-buttons").length) return;
const n = $(e.target).closest(".item"), {carNum: a, aHref: i} = this.findCarNumAndHref(n);
if (a.includes("FC2-")) {
let e = i.split("/").filter(Boolean).pop();
this.getBean("fc2Plugin").openFc2Page(e, a, i);
} else "yes" === t ? utils.openPage(i, a, !1, e) : window.open(i);
}));
$(e.boxSelector).on("contextmenu", ".item img", (e => {
e.preventDefault();
const t = $(e.target).closest(".item"), {carNum: n, aHref: a} = this.findCarNumAndHref(t);
utils.q(e, `是否屏蔽番号 ${n}?`, (async () => {
await storageManager.saveCar(n, a, "", d);
window.refresh();
show.ok("操作成功");
}));
}));
}
findCarNumAndHref(e) {
let t, n, a = e.find("a").attr("href");
if (s) {
t = e.find(".video-title").find("strong").text();
n = e.find(".video-title").text();
}
if (l) {
t = a.split("/").filter(Boolean).pop();
n = e.find("img").attr("title");
}
return {
carNum: t,
aHref: a,
title: n
};
}
showCarNumBox(e) {
const t = $(".movie-list .item").toArray().find((t => $(t).find(".video-title strong").text() === e));
if (t) {
const n = $(t);
if (n.attr("data-hide") === `${e}-hide`) {
n.show();
n.removeAttr("data-hide");
}
}
}
replaceHdImg(e) {
e || (e = document.querySelectorAll(this.getSelector().coverImgSelector));
s && e.forEach((e => {
e.src = e.src.replace("thumbs", "covers");
}));
if (l) {
const t = /\/(imgs|pics)\/(thumb|thumbs)\//, n = /(\.jpg|\.jpeg|\.png)$/i, a = e => {
if (e.src && t.test(e.src) && "true" !== e.dataset.hdReplaced) {
e.src = e.src.replace(t, "/$1/cover/").replace(n, "_b$1");
e.dataset.hdReplaced = "true";
e.loading = "lazy";
}
};
e.forEach((e => {
a(e);
}));
}
}
}
class AutoPagePlugin extends BasePlugin {
constructor() {
super();
this.paging = !1;
this.selector = null;
s && (this.selector = o);
l && (this.selector = r);
}
async handle() {
window.isListPage && !this.shouldDisablePaging() && this.bindPageClick().then();
}
async bindPageClick() {
$(".pagination-link, .pagination-next, .pagination-previous, .pagination li a").on("click", (e => {
e.preventDefault();
e.stopPropagation();
let t = $(e.target).attr("href");
this.parsePage(t).then();
}));
0 === $("#auto-page").length && await this.insertPageBtn();
$("#auto-page").on("click", (async e => {
e.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 e = "yes" === await storageManager.getItem(storageManager.auto_page_key) ? "关闭自动翻页" : "开启自动翻页";
s && $(".pagination").prepend(`<a style="background-color:#fff; order: 2;padding: calc(.5em - 1px) .75em;" id='auto-page'>${e}</a>`);
l && $(".pagination").append(`<li><a style="margin-left: 20px;cursor: pointer;" id='auto-page'>${e}</a></li>`);
}
async parsePage(e) {
let t = loading();
try {
const t = await http.get(e), n = (new DOMParser).parseFromString(t, "text/html");
let a = n.querySelectorAll(this.selector.requestDomItemSelector), i = n.querySelectorAll(".pagination");
const r = this.getBean("listPagePlugin");
await r.filterMovieList(a);
let o = n.querySelectorAll(this.selector.coverImgSelector);
r.replaceHdImg(o);
let s = $(this.selector.boxSelector);
s.fadeOut(300, (() => {
s.html(a).fadeIn(300, (async () => {}));
}));
await this.insertPageBtn();
$(".pagination").replaceWith(i);
window.history.pushState({}, "", e);
if (l) {
const t = this.getPageNumberFromUrl(e);
document.title = document.title.replace(/第\d+頁/, "第" + t + "頁");
}
await utils.smoothScrollToTop();
await this.bindPageClick();
await this.handlePaging();
} finally {
t.close();
}
}
getPageNumberFromUrl(e) {
const t = e.match(/\/page\/(\d+)/);
return t ? parseInt(t[1], 10) : null;
}
shouldDisablePaging() {
return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos" ].some((e => window.location.href.includes(e)));
}
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 e = !0;
$(`${this.selector.itemSelector}:visible`).each(((t, n) => {
0 === $(n).find("span:contains('已收藏')").length && 0 === $(n).find("span:contains('已屏蔽')").length && 0 === $(n).find("span:contains('已下载')").length && (e = !1);
}));
if (!e) return;
if ("yes" !== await storageManager.getItem(storageManager.auto_page_key)) return;
let t = null;
s && (t = $(".pagination-next"));
l && (t = $("#next"));
if (t && 0 !== t.length) {
this.paging = !0;
show.info("下一页....", {
duration: 500,
callback: () => {
t[0].click();
this.paging = !1;
}
});
}
}
}
class HighlightMagnetPlugin extends BasePlugin {
handle() {
this.handleDb();
this.handleBus();
}
handleDb() {
if (!s || !isDetailPage) return;
let e = $("#magnets-content .name").toArray(), t = !1;
e.forEach((e => {
let n = $(e), a = n.text().toLowerCase();
a.indexOf("4k") > -1 && n.css("color", "#f40");
(a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1) && (t = !0);
}));
t ? e.forEach((e => {
let t = $(e), n = t.text().toLowerCase();
n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.indexOf("4k") > -1 || t.parent().parent().parent().hide();
})) : $("#enable-magnets-filter").addClass("do-hide");
}
handleBus() {
l && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
let e = $("#magnet-table tr td:first-child a:first-child").toArray(), t = !1;
e.forEach((e => {
let n = $(e), a = n.text().toLowerCase();
a.indexOf("4k") > -1 && n.css("color", "#f40");
(a.indexOf("-c") > -1 || a.indexOf("-uc") > -1 || a.indexOf("4k") > -1) && (t = !0);
}));
t ? e.forEach((e => {
let t = $(e), n = t.text().toLowerCase();
n.indexOf("-c") > -1 || n.indexOf("-uc") > -1 || n.indexOf("4k") > -1 || t.parent().parent().hide();
})) : $("#enable-magnets-filter").addClass("do-hide");
}));
}
showAll() {
if (s) {
$("#magnets-content .item").toArray().forEach((e => $(e).show()));
}
l && $("#magnet-table tr").toArray().forEach((e => $(e).show()));
}
}
class AliyunApi {
constructor(e) {
this.baseApiUrl = "https://api.aliyundrive.com";
this.refresh_token = e;
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 e = this.baseApiUrl + "/v2/account/token", t = {
refresh_token: this.refresh_token,
grant_type: "refresh_token"
};
try {
return "Bearer " + (await http.post(e, t)).access_token;
} catch (n) {
throw n.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : n;
}
}
async getUserInfo() {
const e = await this.getHeaders();
let t = this.baseApiUrl + "/v2/user/get";
return await http.post(t, {}, e);
}
async deleteFile(e, t = null) {
if (!e) throw new Error("未传入file_id");
t || (t = await this.getDefaultDriveId());
let n = {
file_id: e,
drive_id: t
}, a = this.baseApiUrl + "/v2/recyclebin/trash";
const i = await this.getHeaders();
await gmHttp.post(a, n, i);
return {};
}
async createFolder(e, t = null, n = "root") {
t || (t = await this.getDefaultDriveId());
let a = this.baseApiUrl + "/adrive/v2/file/createWithFolders", i = {
name: e,
type: "folder",
parent_file_id: n,
check_name_mode: "auto_rename",
content_hash_name: "sha1",
drive_id: t
};
const r = await this.getHeaders(), o = await gmHttp.post(a, i, r);
return JSON.parse(o);
}
async getFileList(e = "root", t = null) {
t || (t = await this.getDefaultDriveId());
let n = this.baseApiUrl + "/adrive/v3/file/list";
const a = {
drive_id: t,
parent_file_id: e,
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"
}, i = await this.getHeaders();
return (await gmHttp.post(n, a, i)).items;
}
async uploadFile(e, t, n, a = null) {
let i = this.baseApiUrl + "/adrive/v2/file/createWithFolders";
a || (a = await this.getDefaultDriveId());
let r = {
drive_id: a,
part_info_list: [ {
part_number: 1
} ],
parent_file_id: e,
name: t,
type: "file",
check_name_mode: "auto_rename"
};
const o = await this.getHeaders(), s = await gmHttp.post(i, r, o), l = s.upload_id, c = s.file_id, d = s.part_info_list[0].upload_url;
console.log("创建完成: ", s);
await this._doUpload(d, n);
const p = await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", r = {
drive_id: "745851",
file_id: c,
upload_id: l
}, o);
console.log("标记完成:", p);
}
_doUpload(e, t) {
return new Promise(((n, a) => {
$.ajax({
type: "PUT",
url: e,
data: t,
contentType: " ",
processData: !1,
success: (e, t, i) => {
if (200 === i.status) {
console.log("上传成功:", e);
n({});
} else a(i);
},
error: e => {
console.error("上传失败", e.responseText);
a(e);
}
});
}));
}
async getDownloadUrl(e, t = null) {
t || (t = await this.getDefaultDriveId());
let n = this.baseApiUrl + "/v2/file/get_download_url";
const a = await this.getHeaders();
let i = {
file_id: e,
drive_id: t
};
return (await gmHttp.post(n, i, a)).url;
}
async _createBackupFolder(e) {
const t = await this.getFileList();
let n = null;
for (let a = 0; a < t.length; a++) {
let i = t[a];
if (i.name === e) {
n = i;
break;
}
}
if (!n) {
console.log("不存在目录, 进行创建");
n = await this.createFolder(e);
}
this.backupFolderId = n.file_id;
}
async backup(e, t, n) {
if (this.backupFolderId) await this.uploadFile(this.backupFolderId, t, n); else {
await this._createBackupFolder(e);
await this.uploadFile(this.backupFolderId, t, n);
}
}
async getBackupList(e) {
let t = null;
if (this.backupFolderId) t = await this.getFileList(this.backupFolderId); else {
await this._createBackupFolder(e);
t = await this.getFileList(this.backupFolderId);
}
const n = [];
t.forEach((e => {
n.push({
name: e.name,
fileId: e.file_id,
createTime: e.created_at,
size: e.size
});
}));
return n;
}
}
class WebDavApi {
constructor(e, t, n) {
this.davUrl = e.endsWith("/") ? e : e + "/";
this.username = t;
this.password = n;
this.folderName = null;
}
_getAuthHeaders() {
return {
Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`,
Depth: "1"
};
}
_sendRequest(e, t, n = {}, a) {
return new Promise(((i, r) => {
const o = this.davUrl + t, s = {
...this._getAuthHeaders(),
...n
};
GM_xmlhttpRequest({
method: e,
url: o,
headers: s,
data: a,
onload: e => {
e.status >= 200 && e.status < 300 ? i(e) : r(new Error(`Request failed with status ${e.status}: ${e.statusText}`));
},
onerror: e => {
console.error("请求WebDav发生错误:", e);
r(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确"));
}
});
}));
}
async backup(e, t, n) {
await this._sendRequest("MKCOL", e);
const a = e + "/" + t;
await this._sendRequest("PUT", a, {
"Content-Type": "text/plain"
}, n);
}
async getFileList(e) {
var t, n;
const a = (await this._sendRequest("PROPFIND", e, {
"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, i = (new DOMParser).parseFromString(a, "text/xml").getElementsByTagNameNS("DAV:", "response"), r = [];
for (let o = 0; o < i.length; o++) {
if (0 === o) continue;
if ("1" === i[o].getElementsByTagNameNS("DAV:", "iscollection")[0].textContent) continue;
const e = i[o].getElementsByTagNameNS("DAV:", "displayname")[0].textContent, a = (null == (t = i[o].getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : t.textContent) || "0", s = (null == (n = i[o].getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : n.textContent) || "";
r.push({
fileId: e,
name: e,
size: a,
createTime: s
});
}
r.reverse();
return r;
}
async deleteFile(e) {
let t = this.folderName + "/" + encodeURI(e);
await this._sendRequest("DELETE", t, {
"Cache-Control": "no-cache"
});
}
async getBackupList(e) {
this.folderName = e;
await this._sendRequest("MKCOL", e);
return this.getFileList(e);
}
async getFileContent(e) {
let t = this.folderName + "/" + e;
return (await this._sendRequest("GET", t, {
Accept: "application/octet-stream"
})).responseText;
}
}
class SettingPlugin extends BasePlugin {
constructor() {
super(...arguments);
__privateAdd(this, a);
__publicField(this, "folderName", "JSH-数据备份");
}
async initCss() {
let e = await storageManager.getSetting("containerWidth", "100"), t = await storageManager.getSetting("containerColumns", 5), n = `\n section .container{\n max-width: 1000px !important;\n min-width: ${e}%;\n }\n .movie-list{\n grid-template-columns: repeat(${t}, minmax(0, 1fr));\n }\n `;
l && (n = `\n .container-fluid .row{\n max-width: 1000px !important;\n min-width: ${e}%;\n margin: auto auto;\n }\n \n .masonry {\n grid-template-columns: repeat(${t}, minmax(0, 1fr));\n }\n `);
return `\n <style>\n ${n}\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() {
s && $(".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>');
l ? $("#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: (e, t) => {
$(e).find(".layui-layer-content").css("position", "relative");
this.loadForm();
this.bindClick();
}
});
}));
}
async loadForm() {
let e = await storageManager.getSetting();
$("#hideFilterItem").val(e.hideFilterItem || "yes");
$("#dialogOpenDetail").val(e.dialogOpenDetail || "yes");
$("#reviewCount").val(e.reviewCount || 20);
$("#waitCheckCount").val(e.waitCheckCount || 5);
$("#containerWidth").val((e.containerWidth || 100) - 70);
$("#showContainerWidth").text(e.containerWidth + "%");
$("#containerColumns").val(e.containerColumns || 4);
$("#showContainerColumns").text(e.containerColumns || 4);
$("#refresh_token").val(e.refresh_token || "");
$("#javDbUrl").val(e.javDbUrl || "https://javdb.com");
$("#webDavUrl").val(e.webDavUrl || "");
$("#webDavUsername").val(e.webDavUsername || "");
$("#webDavPassword").val(e.webDavPassword || "");
let t = await storageManager.getReviewFilterKeywordList(), n = await storageManager.getTitleFilterKeyword(), a = await storageManager.getFilterActorList();
t && t.forEach((e => {
this.addLabelTag("#reviewKeywordContainer", e);
}));
n && n.forEach((e => {
this.addLabelTag("#filterKeywordContainer", e);
}));
a && a.forEach((e => {
this.addLabelTag("#filterActorContainer", e);
}));
[ "#reviewKeywordContainer", "#filterKeywordContainer", "#filterActorContainer" ].forEach((e => {
$(`${e} .add-tag-btn`).on("click", (t => this.addKeyword(t, e)));
$(`${e} .keyword-input`).on("keypress", (t => {
"Enter" === t.key && this.addKeyword(t, e);
}));
}));
}
bindClick() {
$("#importBtn").on("click", (e => this.importData(e)));
$("#exportBtn").on("click", (e => this.exportData(e)));
$("#syncDataBtn").on("click", (e => this.syncData(e)));
$("#backupBtn").on("click", (e => this.backupData(e)));
$("#backupListBtn").on("click", (e => this.backupListBtn(e)));
$("#webdavBackupBtn").on("click", (e => this.backupDataByWebDav(e)));
$("#webdavBackupListBtn").on("click", (e => this.backupListBtnByWebDav(e)));
$("#getRefreshTokenBtn").on("click", (e => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", {
yes: function(e, t, n) {
window.open("https://www.aliyundrive.com/drive/home");
layer.close(e);
}
})));
$("#containerWidth").on("input", (e => {
const t = parseInt($(e.target).val()) + 70 + "%";
$("#showContainerWidth").text(t);
if (s) {
document.querySelector("section .container").style.minWidth = t;
}
if (l) {
document.querySelector(".container-fluid .row").style.minWidth = t;
}
}));
$("#containerColumns").on("input", (e => {
let t = $("#containerColumns").val();
$("#showContainerColumns").text(t);
if (s) {
document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${t}, minmax(0, 1fr))`;
}
if (l) {
document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${t}, minmax(0, 1fr))`;
}
}));
$("#saveBtn").on("click", (() => this.saveForm()));
}
async saveForm() {
const e = $("#hideFilterItem").val(), t = $("#reviewCount").val(), n = $("#waitCheckCount").val(), a = parseInt($("#containerWidth").val()), i = parseInt($("#containerColumns").val()), r = $("#refresh_token").val();
let o = await storageManager.getSetting();
o.hideFilterItem = e;
o.reviewCount = t;
o.waitCheckCount = n;
o.containerWidth = a + 70;
o.containerColumns = i;
o.refresh_token = r;
o.webDavUrl = $("#webDavUrl").val();
o.webDavUsername = $("#webDavUsername").val();
o.webDavPassword = $("#webDavPassword").val();
o.dialogOpenDetail = $("#dialogOpenDetail").val();
o.javDbUrl = new URL($("#javDbUrl").val()).origin;
await storageManager.saveSetting(o);
let s = [];
$("#reviewKeywordContainer .keyword-label").toArray().forEach((e => {
let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
s.push(t);
}));
await storageManager.saveReviewFilterKeyword(s);
let l = [];
$("#filterKeywordContainer .keyword-label").toArray().forEach((e => {
let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
l.push(t);
}));
await storageManager.saveTitleFilterKeyword(l);
let c = [];
$("#filterActorContainer .keyword-label").toArray().forEach((e => {
let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
c.push(t);
}));
await storageManager.saveFilterActor(c);
show.ok("保存成功");
window.refresh();
}
addLabelTag(e, t) {
const n = $(`${e} .tag-box`), a = $(`\n <div class="keyword-label" style="background-color: #c5b9a0">\n ${t}\n <span class="keyword-remove">×</span>\n </div>\n `);
a.find(".keyword-remove").click((e => {
$(e.target).parent().remove();
}));
n.append(a);
}
addKeyword(e, t) {
let n = $(`${t} .keyword-input`);
const a = n.val().trim();
if (a) {
this.addLabelTag(t, a);
n.val("");
}
}
importData() {
try {
const e = document.createElement("input");
e.type = "file";
e.accept = ".json";
e.onchange = e => {
const t = e.target.files[0];
if (!t) return;
const n = new FileReader;
n.onload = e => {
try {
const t = e.target.result.toString(), n = JSON.parse(t);
layer.confirm("确定是否要覆盖导入?", {
icon: 3,
title: "确认覆盖",
btn: [ "确定", "取消" ]
}, (async function(e) {
await storageManager.importData(n);
show.ok("数据导入成功");
layer.close(e);
location.reload();
}));
} catch (t) {
console.error(t);
show.error("导入失败:文件内容不是有效的JSON格式 " + t);
}
};
n.onerror = () => {
show.error("读取文件时出错");
};
n.readAsText(t);
};
document.body.appendChild(e);
e.click();
setTimeout((() => document.body.removeChild(e)), 1e3);
} catch (e) {
console.error(e);
show.error("导入数据时出错: " + e.message);
}
}
async backupData(e) {
const t = await storageManager.getSetting("refresh_token");
if (!t) {
show.error("请填写refresh_token并保存后, 再试此功能");
return;
}
let n = utils.getNowStr("_", "_") + ".json", a = JSON.stringify(await storageManager.exportData());
a = C(a);
let i = loading();
try {
const e = new AliyunApi(t);
await e.backup(this.folderName, n, a);
show.ok("备份完成");
} catch (r) {
console.error(r);
show.error(r.toString());
} finally {
i.close();
}
}
async backupListBtn(e) {
const t = await storageManager.getSetting("refresh_token");
if (!t) {
show.error("请填写refresh_token并保存后, 再试此功能");
return;
}
let n = loading();
try {
const e = new AliyunApi(t), n = await e.getBackupList(this.folderName);
this.openFileListDialog(n, e, "阿里云盘");
} catch (a) {
console.error(a);
show.error(`发生错误: ${a ? a.message : a}`);
} finally {
n.close();
}
}
async backupDataByWebDav(e) {
const t = await storageManager.getSetting(), n = t.webDavUrl;
if (!n) {
show.error("请填写webDav服务地址并保存后, 再试此功能");
return;
}
const a = t.webDavUsername;
if (!a) {
show.error("请填写webDav用户名并保存后, 再试此功能");
return;
}
const i = t.webDavPassword;
if (!i) {
show.error("请填写webDav密码并保存后, 再试此功能");
return;
}
let r = utils.getNowStr("_", "_") + ".json", o = JSON.stringify(await storageManager.exportData());
o = C(o);
let s = loading();
try {
const e = new WebDavApi(n, a, i);
await e.backup(this.folderName, r, o);
show.ok("备份完成");
} catch (l) {
console.error(l);
show.error(l.toString());
} finally {
s.close();
}
}
async backupListBtnByWebDav(e) {
const t = await storageManager.getSetting(), n = t.webDavUrl;
if (!n) {
show.error("请填写webDav服务地址并保存后, 再试此功能");
return;
}
const a = t.webDavUsername;
if (!a) {
show.error("请填写webDav用户名并保存后, 再试此功能");
return;
}
const i = t.webDavPassword;
if (!i) {
show.error("请填写webDav密码并保存后, 再试此功能");
return;
}
let r = loading();
try {
const e = new WebDavApi(n, a, i), t = await e.getBackupList(this.folderName);
this.openFileListDialog(t, e, "WebDav");
} catch (o) {
console.error(o);
show.error(`发生错误: ${o ? o.message : o}`);
} finally {
r.close();
}
}
openFileListDialog(e, t, n) {
layer.open({
type: 1,
title: n + "备份文件",
content: '<div id="table-container"></div>',
area: [ "40%", "70%" ],
success: a => {
const i = new TableGenerator({
containerId: "table-container",
columns: [ {
key: "name",
title: "文件名"
}, {
key: "createTime",
title: "备份日期",
render: e => `${utils.getNowStr("-", ":", e.createTime)}`
}, {
key: "size",
title: "文件大小",
render: e => {
const t = [ "B", "KB", "MB", "GB", "TB", "PB" ];
let n = 0, a = e.size;
for (;a >= 1024 && n < t.length - 1; ) {
a /= 1024;
n++;
}
return `${a % 1 == 0 ? a.toFixed(0) : a.toFixed(2)} ${t[n]}`;
}
} ],
data: e,
buttons: [ {
text: "删除",
class: "a-danger",
onClick: async (e, a) => {
layer.confirm(`是否删除 ${a.name} ?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async e => {
layer.close(e);
let r = loading();
try {
await t.deleteFile(a.fileId);
let e = await t.getBackupList(this.folderName);
i.update(e);
"阿里云盘" === n ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功");
} catch (o) {
console.error(o);
show.error(`发生错误: ${o ? o.message : o}`);
} finally {
r.close();
}
}));
}
}, {
text: "下载",
class: "a-primary",
onClick: e => {
let a = loading();
try {
"阿里云盘" === n ? t.getDownloadUrl(e.fileId).then((t => {
gmHttp.get(t, null, {
Referer: "https://www.aliyundrive.com/"
}).then((t => {
t = P(t);
utils.download(t, e.name);
}));
})).catch((e => {
console.error(e);
show.error("下载失败: " + e);
})) : t.getFileContent(e.fileId).then((t => {
t = P(t);
utils.download(t, e.name);
}));
} catch (i) {
console.error(i);
show.error("下载失败: " + i);
} finally {
a.close();
}
}
}, {
text: "导入",
class: "a-success",
onClick: e => {
layer.confirm(`是否将该云备份数据 ${e.name} 导入?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async a => {
layer.close(a);
let i = loading();
try {
let a;
if ("阿里云盘" === n) {
const n = await t.getDownloadUrl(e.fileId);
a = await gmHttp.get(n, null, {
Referer: "https://www.aliyundrive.com/"
});
} else a = await t.getFileContent(e.fileId);
a = P(a);
const i = JSON.parse(a);
await storageManager.importData(i);
show.ok("导入成功!");
window.location.reload();
} catch (r) {
console.error(r);
show.error(r);
} finally {
i.close();
}
}));
}
} ]
});
}
});
}
async exportData(e) {
try {
const e = JSON.stringify(await storageManager.exportData()), t = `${utils.getNowStr("_", "_")}.json`;
utils.download(e, t);
show.ok("数据导出成功");
} catch (t) {
console.error(t);
show.error("导出数据时出错: " + t.message);
}
}
async syncData(e) {
let t = null, n = null;
if (s) {
t = "是否将JavBus的数据及配置同步到本站中? ";
n = "https://www.javbus.com/temp?syncData=1";
}
if (l) {
t = "是否将JavDB的数据及配置同步到本站中? ";
n = await storageManager.getSetting("javDbUrl", "https://javdb.com") + "/feedbacks/new?syncData=1";
}
utils.q(e, t, (() => {
const e = window.open(n);
let t = new URL(n).origin;
console.log("开始连接接受方:", t);
let r, o = 0;
if (!this.hasListenMsg) {
window.addEventListener("message", (n => {
if (n.origin === t) if ("ok" === n.data) {
clearInterval(r);
console.log("连接确认,开始同步数据");
e.postMessage("syncData", t);
} else {
const e = n.data;
console.log("收到数据", e);
__privateMethod(this, a, i).call(this, e);
}
}));
this.hasListenMsg = !0;
}
const s = () => {
if (o >= 8) {
clearInterval(r);
console.log("超过最大重试次数,停止尝试");
show.error("同步失败, 目标网站已中断, 请检查是否登录后再试!", {
close: !0,
duration: -1
});
} else {
console.log(`第 ${o + 1} 次ping...`);
e.postMessage("ping", t);
o++;
}
};
r = setInterval(s, 1e3);
s();
}));
}
}
a = new WeakSet;
i = async function(e) {
try {
const t = e.carList || [], n = e.filterActor || [], a = e.titleFilterKeyword || [], i = e.reviewFilterKeyword || [], r = e.setting || {}, o = await storageManager.getCarList() || [], s = await storageManager.getFilterActorList() || [], l = await storageManager.getTitleFilterKeyword() || [], c = await storageManager.getReviewFilterKeywordList() || [], d = await storageManager.getSetting() || {}, p = [ ...o ];
t.forEach((e => {
o.some((t => t.carNum === e.carNum)) || p.push(e);
}));
const g = [ ...new Set([ ...s, ...n ]) ], h = [ ...new Set([ ...l, ...a ]) ], u = [ ...new Set([ ...c, ...i ]) ], m = {
...d
};
Object.keys(r).forEach((e => {
e in m && m[e] || (m[e] = r[e]);
}));
await storageManager.overrideCarList(p);
await storageManager.saveFilterActor(g);
await storageManager.saveTitleFilterKeyword(h);
await storageManager.saveReviewFilterKeyword(u);
await storageManager.saveSetting(m);
show.ok("同步完成, 关闭提示后, 将重载数据", {
close: !0,
duration: -1,
callback: () => {
window.location.reload();
}
});
} catch (t) {
console.error(t);
show.error("同步数据时出错:", t);
}
};
const _ = "x7k9p3";
function C(e) {
return (_ + e + _).split("").map((e => {
const t = e.codePointAt(0);
return String.fromCodePoint(t + 5);
})).join("");
}
function P(e) {
return e.split("").map((e => {
const t = e.codePointAt(0);
return String.fromCodePoint(t - 5);
})).join("").slice(_.length, -_.length);
}
class SyncDataPlugin extends BasePlugin {
async handle() {
if (!window.location.href.includes("syncData=1")) return;
l && $("h4").html("临时页面, 用于同步数据");
let e = null;
s && (e = "https://www.javbus.com");
l && (e = await storageManager.getSetting("javDbUrl", "https://javdb.com"));
console.log("等待发送方:", e);
window.addEventListener("message", (async t => {
if (t.origin === e) if ("ping" === t.data) {
console.log("收到 ping,发送确认");
t.source.postMessage("ok", t.origin);
} else if ("syncData" === t.data) {
console.log("开始发送数据...");
const e = await storageManager.getCarList(), n = await storageManager.getFilterActorList(), a = await storageManager.getTitleFilterKeyword(), i = await storageManager.getReviewFilterKeywordList(), r = await storageManager.getSetting();
t.source.postMessage({
carList: e,
filterActor: n,
titleFilterKeyword: a,
reviewFilterKeyword: i,
setting: r
}, t.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 e = $("#sample-waterfall a:first").attr("href"), t = $(`\n <a class="preview-video-container sample-box" style="cursor: pointer">\n <div class="photo-frame" style="position:relative;">\n <img src="${e}" 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(t);
let n = $(".preview-video-container");
n.on("click", (async t => {
t.preventDefault();
t.stopPropagation();
if (!$("#preview-video").length) {
let t = await this.parseVideo(e);
console.log("解析播放地址:", t);
$("#magneturlpost").next().after(`<div><video id="preview-video" controls style="width: 100%;margin-top: 5px;"><source src="${t}" /></video></div>`);
this.handleVideo().then((() => {
const e = document.getElementById("preview-video");
if (e) {
const t = e.getBoundingClientRect();
window.scrollTo({
top: window.scrollY + t.top - 100,
behavior: "smooth"
});
}
}));
}
}));
window.location.href.includes("autoPlay=1") && n[0].click();
}
async handleVideo() {
const e = $("#preview-video"), t = e.find("source"), n = e.parent();
if (!e.length || !t.length) return;
const a = e[0];
a.muted = !1;
a.play();
n.css("position", "relative");
const i = t.attr("src"), r = [ "hhb", "hmb", "mhb", "mmb" ], o = r.find((e => i.includes(e))) || "mhb", s = [ {
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 l = `videoQualities_${this.getPageInfo().carNum}`;
let c = JSON.parse(sessionStorage.getItem(l));
if (!c) {
c = (await Promise.all(s.map((async e => {
const t = i.replace(new RegExp(r.join("|"), "g"), e.quality);
try {
return (await fetch(t, {
method: "HEAD"
})).ok ? e : null;
} catch {
return null;
}
})))).filter(Boolean);
c.length && sessionStorage.setItem(l, JSON.stringify(c));
}
if (c.length <= 1) return;
const d = c.map(((e, t) => `\n <button class="video-control-btn${e.quality === o ? " active" : ""}" \n id="${e.id}" \n data-quality="${e.quality}"\n style="bottom: ${50 * t}px; right: -105px;">\n ${e.text}\n </button>\n `)).join("");
n.append(d);
const p = n.find(".video-control-btn");
n.on("click", ".video-control-btn", (async e => {
const n = $(e.currentTarget), o = n.data("quality");
if (!n.hasClass("active")) try {
const e = i.replace(new RegExp(r.join("|"), "g"), o);
t.attr("src", e);
a.load();
a.muted = !1;
await a.play();
p.removeClass("active");
n.addClass("active");
} catch (s) {
console.error("切换画质失败:", s);
}
}));
p.last().trigger("click");
}
async parseVideo(e) {
const t = `ok_url_${this.getPageInfo().carNum}`;
let n = sessionStorage.getItem(t);
if (n) return n;
const a = e.match(/\/digital\/video\/([^\/]+)\//);
if (!a || a.length < 2) {
show.error("解析id错误" + e + ", 该视频没有对应的dmm视频");
console.error("解析dmm视频id错误", e);
setTimeout((() => {
$("#preview-video").remove();
}), 1e3);
return null;
}
const i = a[1], r = i.charAt(0).toLowerCase();
let o = i.substring(0, 3);
const s = async e => {
try {
console.log("测试视频地址", e);
return (await fetch(e, {
method: "HEAD"
})).ok ? e : null;
} catch {
return null;
}
};
let l = i.replace("00", ""), c = [ `https://cc3001.dmm.co.jp/litevideo/freepv/${r}/${o}/${i}/${i}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${r}/${o}/${l}/${l}hhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${r}/${o}/${i}/${i}mhb.mp4`, `https://cc3001.dmm.co.jp/litevideo/freepv/${r}/${o}/${l}/${l}mhb.mp4` ], d = null;
for (let p = 0; p < c.length; p++) {
let e = await s(c[p]);
if (e) {
console.log("测试成功,", e);
d = e;
break;
}
}
if (!d) {
show.error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
throw new Error("解析dmm预览视频失败, 请联系作者, 提供番号信息");
}
sessionStorage.setItem(t, d);
return d;
}
}
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 e => {
this.initEventListeners();
}
});
}
initEventListeners() {
const e = $("#upload-area"), t = $("#image-file"), n = $("#select-image-btn"), a = $("#preview-area"), i = $("#preview-image"), r = $("#action-btns"), o = $("#handle-btn"), s = $("#cancel-btn"), l = $("#url-input-container"), c = $("#image-url"), d = $("#search-results"), p = $("#site-btns-container");
e.on("dragover", (t => {
t.preventDefault();
e.addClass("highlight");
})).on("dragleave", (() => {
e.removeClass("highlight");
})).on("drop", (t => {
t.preventDefault();
e.removeClass("highlight");
if (t.originalEvent.dataTransfer.files && t.originalEvent.dataTransfer.files[0]) {
this.handleImageFile(t.originalEvent.dataTransfer.files[0]);
this.resetSearchUI();
}
}));
n.on("click", (() => {
t.trigger("click");
}));
t.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 t = e.originalEvent.clipboardData.items;
for (let a = 0; a < t.length; a++) if (-1 !== t[a].type.indexOf("image")) {
const e = t[a].getAsFile();
this.handleImageFile(e);
this.resetSearchUI();
return;
}
const n = e.originalEvent.clipboardData.getData("text");
if (n && utils.isUrl(n)) {
l.show();
c.val(n);
i.attr("src", n);
a.show();
this.resetSearchUI();
}
}));
o.on("click", (() => {
const e = i.attr("src");
e ? this.searchByImage(e).then((e => {
r.hide();
d.show();
p.empty();
this.siteList.forEach((t => {
const n = t.url.replace("{占位符}", encodeURIComponent(e));
p.append(`\n <a href="${n}" class="site-btn" target="_blank" title="${t.name}">\n <img src="${t.ico}" alt="${t.name}">\n <span>${t.name}</span>\n </a>\n `);
}));
p.show();
})) : show.info("请粘贴或上传图片");
}));
s.on("click", (() => {
a.hide();
l.hide();
t.val("");
c.val("");
}));
c.on("change", (() => {
if (utils.isUrl(c.val())) {
i.attr("src", c.val());
a.show();
}
}));
$("#openAll").on("click", (() => {
$(".site-btn").toArray().forEach((e => {
window.open($(e).attr("href"));
}));
}));
}
resetSearchUI() {
$("#action-btns").show();
$("#search-results").hide();
$("#site-btns-container").hide().empty();
}
handleImageFile(e) {
const t = document.getElementById("preview-image"), n = document.getElementById("preview-area"), a = document.getElementById("url-input-container");
if (!e.type.match("image.*")) {
show.info("请选择图片文件");
return;
}
const i = new FileReader;
i.onload = e => {
t.src = e.target.result;
n.style.display = "block";
a.style.display = "none";
};
i.readAsDataURL(e);
}
async searchByImage(e) {
let t = loading();
try {
let t = e;
if (e.startsWith("data:")) {
show.info("开始上传图片...");
const n = await async function(e) {
var t;
const n = e.match(/^data:(.+);base64,(.+)$/);
if (!n || n.length < 3) throw new Error("无效的Base64图片数据");
const a = n[1], i = n[2], r = atob(i), o = new Array(r.length);
for (let g = 0; g < r.length; g++) o[g] = r.charCodeAt(g);
const s = new Uint8Array(o), l = new Blob([ s ], {
type: a
}), c = new FormData;
c.append("image", l);
const d = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: {
Authorization: "Client-ID d70305e7c3ac5c6"
},
body: c
}), p = await d.json();
if (p.success && p.data && p.data.link) return p.data.link;
throw new Error((null == (t = p.data) ? void 0 : t.error) || "上传到Imgur失败");
}(e);
if (!n) {
show.error("上传到失败");
return;
}
t = n;
}
return t;
} catch (n) {
show.error(`搜索失败: ${n.message}`);
console.error("搜索失败:", n);
} finally {
t.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 e = this.getPageInfo().movieId, t = $("#magnets-content");
t.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 n = null;
$("#relatedFold").on("click", (async () => {
const t = $("#relatedFold .toggle-text"), r = $("#relatedFold .toggle-icon");
if ("展开" === t.text()) {
t.text("折叠");
r.text("▲");
a.show();
i.show();
if (n) return;
try {
a.append('<div id="relate-load" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">正在查询中...</div>');
let t = 1, r = 20;
n = await k(e, t, r);
$("#relate-load").remove();
if (!n) {
a.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单失败</div>');
return;
}
0 === n.length && a.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>');
this.displayRelated(n, a);
if (n.length === r) {
i.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 t = 1;
$("#loadMoreRelated").click((async () => {
$("#loadMoreRelated").text("加载中...").prop("disabled", !0);
t++;
const n = await k(e, t, r);
this.displayRelated(n, a);
if (n.length < r) {
$("#loadMoreRelated").remove();
$("#reviewsEnd").show();
} else $("#loadMoreRelated").text("加载更多清单").prop("disabled", !1);
}));
} else n.length > 0 && i.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>');
} catch (o) {
console.error(o);
$("#relate-load").remove();
a.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取失败</div>');
}
} else {
t.text("展开");
r.text("▼");
a.hide();
i.hide();
}
}));
t.append('<div id="relatedContainer"></div>');
t.append('<div id="relatedFooter"></div>');
const a = $("#relatedContainer"), i = $("#relatedFooter");
}
displayRelated(e, t) {
e.length && e.forEach((e => {
let n = `\n <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;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;">创建时间: ${e.createTime}</span>\n <p><a href="/lists/${e.relatedId}" target="_blank" style="color:#2e8abb">${e.name}</a></p>\n <p style="margin-top: 5px;">收藏次数: ${e.collectionCount} 被查看次数: ${e.viewCount}</p>\n </div>\n `;
t.append(n);
}));
}
}
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", (e => {
this.type = c;
this.importWantWatchVideos(e, "是否将 想看的影片 导入到 JSH-收藏?");
}));
}
if (window.location.href.includes("/watched_videos")) {
$("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JSH</a>');
$("#wantWatchBtn").on("click", (e => {
this.type = p;
this.importWantWatchVideos(e, "是否将 看过的影片 导入到 JSH-已下载?");
}));
}
}
importWantWatchVideos(e, t) {
utils.q(null, `${t} <br/> <span style='color: #f40'>执行此功能前请记得备份数据</span>`, (async () => {
let e = loading();
try {
await this.parseMovieList();
} catch (t) {
console.error(t);
} finally {
e.close();
}
}));
}
async parseMovieList(e) {
let t, n;
if (e) {
t = e.find(this.getSelector().itemSelector);
n = e.find(".pagination-next").attr("href");
} else {
t = $(this.getSelector().itemSelector);
n = $(".pagination-next").attr("href");
}
for (const i of t) {
const e = $(i), t = e.find("a").attr("href"), n = e.find(".video-title strong").text().trim();
if (t && n) try {
if (await storageManager.getCar(n)) {
show.info(`${n} 已存在, 跳过`);
continue;
}
await storageManager.saveCar(n, t, "", this.type);
} catch (a) {
console.error(`保存失败 [${n}]:`, a);
}
}
if (n) {
show.info("发现下一页,正在解析:", n);
await new Promise((e => setTimeout(e, 1e3)));
$.ajax({
url: n,
method: "GET",
success: e => {
const t = new DOMParser, n = $(t.parseFromString(e, "text/html"));
this.parseMovieList(n);
},
error: function(e) {
console.error(e);
show.error("加载下一页失败:" + e.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 ${l ? ".tool-box .icon{ height: 2rem; width: 2rem; }" : ""}\n `;
}
handle() {
if (window.isListPage) {
this.addCopy();
this.bindClick();
}
}
addCopy() {
$(this.getSelector().itemSelector).toArray().forEach((e => {
let t = $(e);
s && t.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 `);
l && t.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 e = this.getBean("ListPagePlugin");
$(this.getSelector().boxSelector).on("click", ".titleSvg", (t => {
t.preventDefault();
t.stopPropagation();
const n = $(t.target).closest(".item"), {carNum: a, aHref: i, title: r} = e.findCarNumAndHref(n);
navigator.clipboard.writeText(r).then((() => {
show.info("标题已复制到剪切板, " + r);
})).catch((e => {
console.error("复制失败: ", e);
}));
})).on("click", ".carNumSvg", (t => {
t.preventDefault();
t.stopPropagation();
const n = $(t.target).closest(".item"), {carNum: a, aHref: i, title: r} = e.findCarNumAndHref(n);
navigator.clipboard.writeText(a).then((() => {
show.info("番号已复制到剪切板, " + a);
})).catch((e => {
console.error("复制失败: ", e);
}));
})).on("click", ".downSvg", (t => {
t.preventDefault();
t.stopPropagation();
const n = $(t.target).closest(".item"), {carNum: a, aHref: i, title: r} = e.findCarNumAndHref(n);
let o = n.find(".cover img");
l && (o = n.find(".photo-frame img"));
const s = o.attr("src");
console.log(s);
GM_download({
url: s,
name: r + ".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 e = window.location.href;
return e.includes("javdb") ? e.includes("/v/") : !!e.includes("javbus") && $("#magnet-table").length > 0;
}();
window.isListPage = function() {
let e = window.location.href;
return e.includes("javdb") ? $(".movie-list").length > 0 : !!e.includes("javbus") && $(".masonry > div .item").length > 0;
}();
!function() {
const e = new PluginManager;
let t = window.location.hostname;
if (t.includes("javdb")) {
e.register(ListPagePlugin);
e.register(AutoPagePlugin);
e.register(Fc2Plugin);
e.register(FoldCategoryPlugin);
e.register(ListPageButtonPlugin);
e.register(HistoryPlugin);
e.register(SettingPlugin);
e.register(NavBarPlugin);
e.register(HitShowPlugin);
e.register(TOP250Plugin);
e.register(SyncDataPlugin);
e.register(SearchByImagePlugin);
e.register(CopyTitleOrDownImgPlugin);
e.register(DetailPagePlugin);
e.register(ReviewPlugin);
e.register(RelatedPlugin);
e.register(DetailPageButtonPlugin);
e.register(HighlightMagnetPlugin);
e.register(PreviewVideoPlugin);
e.register(FilterTitleKeywordPlugin);
e.register(ActressInfoPlugin);
e.register(OtherSitePlugin);
e.register(WantAndWatchedVideosPlugin);
}
if (t.includes("javbus")) {
e.register(ListPagePlugin);
e.register(ListPageButtonPlugin);
e.register(SettingPlugin);
e.register(HistoryPlugin);
e.register(SyncDataPlugin);
e.register(AutoPagePlugin);
e.register(SearchByImagePlugin);
e.register(BusNavBarPlugin);
e.register(CopyTitleOrDownImgPlugin);
e.register(BusDetailPagePlugin);
e.register(DetailPageButtonPlugin);
e.register(ReviewPlugin);
e.register(FilterTitleKeywordPlugin);
e.register(HighlightMagnetPlugin);
e.register(BusPreviewVideoPlugin);
}
t.includes("javtrailers") && e.register(JavTrailersPlugin);
t.includes("subtitlecat") && e.register(SubTitleCatPlugin);
t.includes("aliyundrive") && e.register(AliyunPanPlugin);
e.process().then();
}();
};
}();