// ==UserScript==
// @name JAV-JHS
// @namespace https://sleazyfork.org/zh-CN/scripts/533695-jav-jhs
// @version 3.2.6
// @author xie bro
// @description Jav-鉴黄师 收藏、屏蔽、标记已下载; 免VIP查看热榜、Top250排行榜、Fc2ppv等数据; 可查看所有评论信息; 支持云盘备份; 以图识图; 字幕搜索; JavBus|JavDb
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com
// @match https://javdb.com/*
// @match https://www.javbus.com/*
// @include https://javdb*.com/*
// @include https://*javbus*/*
// @include https://*javsee*/*
// @include https://*seejav*/*
// @include https://115.com/*
// @include https://javtrailers.com/*
// @include https://subtitlecat.com/*
// @include https://www.aliyundrive.com/*
// @include https://www.alipan.com/*
// @include https://5masterzzz.site/*
// @exclude https://*javbus*/forum/*
// @exclude https://*javbus*/*actresses
// @exclude https://*javsee*/forum/*
// @exclude https://*javsee*/*actresses
// @exclude https://*seejav*/forum/*
// @exclude https://*seejav*/*actresses
// @require https://update.greasyfork.org/scripts/540597/1613170/parallel_GM_xmlhttpRequest.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js/tabulator.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
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/viewer.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js
// @connect xunlei.com
// @connect geilijiasu.com
// @connect aliyundrive.com
// @connect aliyundrive.net
// @connect ja.wikipedia.org
// @connect beta.magnet.pics
// @connect jdforrepam.com
// @connect cc3001.dmm.co.jp
// @connect cc3001.dmm.com
// @connect www.dmm.co.jp
// @connect www.dmm.com
// @connect api.dmm.com
// @connect special.dmm.co.jp
// @connect adult.contents.fc2.com
// @connect fc2ppvdb.com
// @connect 123av.com
// @connect u3c3.com
// @connect u9a9.com
// @connect btsow.lol
// @connect sukebei.nyaa.si
// @connect javstore.net
// @connect 3xplanet.com
// @connect javbest.net
// @connect missav.live
// @connect jable.tv
// @connect www.av.gl
// @connect jav.rs
// @connect javtrailers.com
// @connect javdb.com
// @connect javbus.com
// @connect supjav.com
// @connect 115.com
// @connect 127.0.0.1
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==
var e, t, n = Object.defineProperty, a = e => {
throw TypeError(e);
}, i = (e, t, a) => ((e, t, a) => t in e ? n(e, t, {
enumerable: !0,
configurable: !0,
writable: !0,
value: a
}) : e[t] = a)(e, "symbol" != typeof t ? t + "" : t, a), s = (e, t, n) => (((e, t, n) => {
t.has(e) || a("Cannot " + n);
})(e, t, "access private method"), n);
const o = window.location.href, r = o.includes("javdb"), l = o.includes("javbus") || o.includes("seejav") || o.includes("bus") || o.includes("javsee"), c = o.includes("/search?q") || o.includes("/search/") || o.includes("/users/"), d = "filter", h = "favorite", g = "hasDown", p = "hasWatch", u = "🚫 屏蔽", m = "🚫 已屏蔽", f = "#de3333", v = "⭐ 收藏", b = "⭐ 已收藏", w = "#25b1dc", y = "📥️ 已下载", x = "#7bc73b", k = "🔍 已观看", S = "#d7a80c", C = "no", _ = "yes", T = "javdb", I = "javbus", B = "actor", P = "actress", D = "censored", L = "uncensored", A = [ {
id: "video-mhb",
quality: "dmb_w",
text: "旧视频源-中画质宽版 (404p)",
canSelect: !1
}, {
id: "video-mhb",
quality: "sm_s",
text: "旧视频源-低画质 (240p)",
canSelect: !1
}, {
id: "video-mhb",
quality: "dm_s",
text: "旧视频源-中画质 (360p)",
canSelect: !1
}, {
id: "video-mhb",
quality: "dmb_s",
text: "旧视频源-中画质 (480p)",
canSelect: !1
}, {
id: "video-mhb",
quality: "mhb_w",
text: "旧视频源-高画质宽版 (404p)",
canSelect: !1
}, {
id: "video-mmb",
quality: "mmb",
text: "中画质 (432p)",
canSelect: !0
}, {
id: "video-mhb",
quality: "mhb",
text: "高画质 (576p)",
canSelect: !0
}, {
id: "video-hmb",
quality: "hmb",
text: "HD (720p)",
canSelect: !0
}, {
id: "video-hhb",
quality: "hhb",
text: "FullHD (1080p)",
canSelect: !0
}, {
id: "video-hhbs",
quality: "hhbs",
text: "FullHD (1080p60fps)",
canSelect: !0
}, {
id: "video-4k",
quality: "4k",
text: "4K (2160p)",
canSelect: !0
}, {
id: "video-4ks",
quality: "4ks",
text: "4K (2160p60fps)",
canSelect: !0
} ];
let M = "";
window.location.href.includes("hideNav=1") && (M = "\n .navbar-default {\n display: none !important;\n }\n body {\n padding-top:0px!important;\n }\n ");
const N = `\n<style>\n .top-bar {\n z-index: 12345679 !important;\n }\n \n ${M}\n\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 align-items: start;\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 overflow: inherit !important;\n }\n .masonry .movie-box .photo-frame {\n /*height: 70% !important;*/\n height:auto !important;\n margin: 0 !important;\n position:relative; /* 方便预览视频定位*/\n }\n .masonry .movie-box img {\n max-height: 500px;\n height: 100% !important;\n object-fit: contain;\n object-position: top;\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 \n footer{\n display: none!important;\n }\n \n \n .video-title {\n white-space: normal !important;\n height: 75px; /* 固定高度 容器就不会出现高低不一*/\n \n display: -webkit-box !important; /* 必须设置,使接下来的属性生效 */\n -webkit-box-orient: vertical; /* 垂直方向堆叠行 */\n -webkit-line-clamp: 3; /* 设置文本最多显示的行数*/\n }\n\n \n</style>\n`;
let j = "";
window.location.href.includes("hideNav=1") && (j = "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n ");
const E = `\n<style>\n ${j}\n \n .navbar {\n z-index: 12345679 !important;\n padding: 0 0;\n }\n \n .navbar-link:not(.is-arrowless) {\n padding-right: 33px;\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-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 .video-title {\n white-space: normal !important;\n height: 80px; /* 固定高度 容器就不会出现高低不一*/\n \n display: -webkit-box; /* 必须设置,使接下来的属性生效 */\n -webkit-box-orient: vertical; /* 垂直方向堆叠行 */\n -webkit-line-clamp: 3; /* 设置文本最多显示的行数*/\n }\n \n /* 列表页顶部分类自适应 */\n .main-tabs, .tabs {\n overflow-x:hidden;\n flex-wrap: wrap;\n justify-content: flex-start;\n }\n \n .main-tabs ul, .tabs ul {\n flex-wrap: wrap;\n flex-grow: 0;\n }\n \n \n /* 二级工具栏 大小封面,可播放,含磁链...*/\n .toolbar {\n display: flex;\n }\n\n</style>\n`;
const F = `\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;\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 \n .main-tab-btn {\n border-bottom:none !important; \n border-radius:3px !important; \n height: 30px; \n margin-left: 5px !important; \n }\n\n .jhs-icon {\n width: 16px;\n height: 16px;\n }\n \n .tool-box .jhs-icon {\n width: 1.5rem;\n height: 1.5rem; \n }\n \n \n /*表格内按钮溢出,防止被隐藏*/\n .tabulator .tabulator-row .action-cell-dropdown {\n overflow: visible !important;\n }\n /* 去除行内鼠标小手*/\n .tabulator .tabulator-row.tabulator-selectable:hover {\n cursor: default !important;\n }\n \n /* 排序小箭头颜色 */\n .tabulator .tabulator-col.tabulator-sortable[aria-sort="ascending"] .tabulator-arrow {\n border-bottom-color: #337ab7 !important;\n }\n .tabulator .tabulator-col.tabulator-sortable[aria-sort="descending"] .tabulator-arrow {\n border-top-color: #337ab7 !important;\n }\n \n /* 针对折叠行的容器或内容进行样式修改 */\n .tabulator-responsive-collapse {\n border-top: none !important;\n }\n \n .tabulator-responsive-collapse table{\n margin-left: 50px !important;\n }\n \n .tabulator-cell {\n height:auto !important;\n }\n \n /* 列允许换行,去除省略号 */\n .tabulator .tabulator-cell {\n white-space: normal !important; \n text-overflow: clip !important; \n }\n \n .tabulator-tableholder {\n overflow-x: hidden !important;\n }\n\n ${function() {
const e = [ ".jhs-scrollbar", ".content-panel", ".tabulator-tableholder", ".has-navbar-fixed-top", ".layui-layer-content" ], t = (e, t) => e.map((e => `${e}${t}`)).join(","), n = "::-webkit-scrollbar-track", a = "::-webkit-scrollbar-thumb", i = "::-webkit-scrollbar-thumb:hover";
return `\n ${t(e, "::-webkit-scrollbar")}{width:6px;height:6px;}\n ${t(e, n)}{background:#f1f1f1;border-radius:10px;}\n ${t(e, a)}{background:#888;border-radius:10px;}\n ${t(e, i)}{background:#555;}\n `.trim().replace(/\n/g, "");
}()}\n</style>\n`;
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), r && H(E), H("\n<style>\n .a-normal, /* 白色 */\n .a-primary, /* 浅蓝色 */\n .a-success, /* 浅绿色 */\n .a-danger, /* 浅粉色 */\n .a-warning, /* 浅橙色 */\n .a-info /* 灰色 */\n {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px 14px;\n margin-right: 10px;\n border-radius: 6px;\n text-decoration: none;\n font-size: 13px;\n font-weight: 500;\n transition: all 0.2s ease;\n 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: #e2e8f0;\n color: #334155;\n border-color: #cbd5e1;\n }\n \n .a-info:hover {\n background: #cbd5e1;\n }\n \n .a-normal {\n background: transparent;\n color: #64748b;\n border-color: #cbd5e1;\n }\n \n .a-normal:hover {\n background: #f8fafc;\n }\n</style>\n"),
H(F);
e = new WeakSet, t = async function(e, t, n) {
let a;
if (Array.isArray(e)) a = [ ...e ]; else {
if (a = await this.forage.getItem(t) || [], a.includes(e)) {
const t = `${e} ${n}已存在`;
throw show.error(t), new Error(t);
}
a.push(e);
}
return await this.forage.setItem(t, a), a;
};
let z = class n {
constructor() {
var t, s, o;
if (t = this, (s = e).has(t) ? a("Cannot add the same private member more than once") : s instanceof WeakSet ? s.add(t) : s.set(t, o),
i(this, "car_list_key", "car_list"), i(this, "filter_keyword_title_key", "filter_keyword_title"),
i(this, "filter_keyword_review_key", "filter_keyword_review"), i(this, "setting_key", "setting"),
i(this, "blacklist_key", "blacklist"), i(this, "blacklist_car_list_key", "blacklist_car_list"),
i(this, "favorite_actresses_key", "favorite_actresses"), i(this, "highlighted_tags_key", "highlighted_tags"),
i(this, "forage", localforage.createInstance({
driver: localforage.INDEXEDDB,
name: "JAV-JHS",
version: 1,
storeName: "appData"
})), i(this, "cache_filter_actor_actress_car_list", null), i(this, "cacheSettingObj", null),
n.instance) throw new Error("LocalStorageManager已被实例化过了!");
n.instance = this;
}
async getCarList() {
return await this.forage.getItem(this.car_list_key) || [];
}
async getCar(e) {
return (await this.getCarList()).find((t => t.carNum === e));
}
_saveSingleCar(e, t) {
let {carNum: n, url: a, names: i, actionType: s, publishTime: o, starId: r} = e;
if (!n) throw show.error("番号为空!"), new Error("番号为空!");
if (!a) throw show.error("url为空!"), new Error("url为空!");
a.includes("http") || (a = window.location.origin + a), i && (i = i.trim());
let l = t.find((e => e.carNum === n));
if (l) l.url = a, i && (l.names = i), o && (l.publishTime = o), l.updateDate = utils.getNowStr(); else {
let e = utils.getNowStr();
l = {
carNum: n,
url: a,
names: i,
status: "",
createDate: e,
updateDate: e,
publishTime: o
}, r && (l.starId = r), t.push(l);
}
switch (s) {
case d:
if (l.status === d) {
const e = `${n} 已在屏蔽列表中`;
throw show.error(e), new Error(e);
}
l.status = d;
break;
case h:
if (l.status === h) {
const e = `${n} 已在收藏列表中`;
throw show.error(e), new Error(e);
}
l.status = h;
break;
case g:
l.status = g;
break;
case p:
l.status = p;
break;
default:
const e = "actionType错误, 请联系作者更正";
throw show.error(e), new Error(e);
}
}
async saveCar(e) {
const t = await this.forage.getItem(this.car_list_key) || [];
this._saveSingleCar(e, t), await this.forage.setItem(this.car_list_key, t), await this.removeNewVideoList([ e.carNum ]);
}
async saveCarList(e) {
if (!e || !Array.isArray(e) || 0 === e.length) throw show.error("记录列表为空!"), new Error("记录列表为空!");
const t = await this.forage.getItem(this.car_list_key) || [];
for (const a of e) try {
this._saveSingleCar(a, t);
} catch (n) {
throw n;
}
await this.forage.setItem(this.car_list_key, t);
}
async removeNewVideoList(e) {
const t = await this.getFavoriteActressList();
let n = !1;
const a = t.map((t => {
if (!t.newVideoList || !Array.isArray(t.newVideoList)) return t;
const a = t.newVideoList.filter((a => {
const i = e.includes(a);
return i && (clog.log("移除关联女优新作品", t.name, a), n = !0), !i;
}));
return {
...t,
newVideoList: a
};
}));
n && await this.forage.setItem(this.favorite_actresses_key, a);
}
async removeCar(e) {
const t = await this.getCarList(), n = t.length, a = t.filter((t => t.carNum !== e));
return a.length === n ? (show.error(`${e} 不存在`), !1) : (await this.forage.setItem(this.car_list_key, a),
!0);
}
async batchRemoveCars(e) {
const t = await this.getCarList(), n = t.length, a = new Set(e), i = t.filter((e => !a.has(e.carNum))), s = n - i.length;
return 0 !== s && (await this.forage.setItem(this.car_list_key, i), s);
}
async getBlacklist() {
return await this.forage.getItem(this.blacklist_key) || [];
}
async addBlacklistItem(e) {
let {starId: t, name: n, allName: a, role: i, movieType: s, url: o} = e;
if (!t) throw new Error("缺失starId");
if (!n) throw new Error("缺失name");
if (!i) throw new Error("缺失role");
const r = await this.getBlacklist(), l = r.find((e => e.starId === t));
if (l) l.createTime = utils.getNowStr(), l.url = o, l.role = i, l.movieType = s,
clog.log("更新黑名单演员信息", l); else {
const e = {
starId: t,
name: n,
allName: a || [ n ],
createTime: utils.getNowStr(),
role: i,
movieType: s,
url: o
};
r.push(e), clog.log("增加黑名单演员信息", e);
}
await this.forage.setItem(this.blacklist_key, r);
}
async updateBlacklistItem(e) {
if (!e || !e.starId) throw new Error("参数不全");
const t = await this.getBlacklist(), n = t.find((t => t.starId === e.starId));
if (!n) throw new Error(`未找到黑名单演员信息:${e.name} ${e.starId}`);
e.checkTime && (n.checkTime = e.checkTime), e.lastPublishTime && (n.lastPublishTime = e.lastPublishTime),
await this.forage.setItem(this.blacklist_key, t);
}
async deleteBlacklistItem(e) {
const t = await this.getBlacklist(), n = t.filter((t => t.starId !== e));
t.length !== n.length && await this.forage.setItem(this.blacklist_key, n);
}
async getBlacklistCarList() {
return this.cache_filter_actor_actress_car_list && this.cache_filter_actor_actress_car_list.length > 0 || (this.cache_filter_actor_actress_car_list = await this.forage.getItem(this.blacklist_car_list_key) || []),
this.cache_filter_actor_actress_car_list;
}
async batchSaveBlacklistCarList(e) {
const t = await this.getBlacklistCarList(), n = JSON.parse(JSON.stringify(t));
let a = !1, i = [];
for (const s of e) {
n.find((e => e.carNum === s.carNum)) || (this._saveSingleCar(s, n), clog.log(`屏蔽演员番号: <span style="color: #f40">${s.names} ${s.carNum}</span>`),
a = !0, i.push(s.carNum));
}
a && (await this.forage.setItem(this.blacklist_car_list_key, n), await this.removeNewVideoList(i),
window.cleanCache_filter_actor_actress_car_list());
}
async removeBlacklistCarList(e) {
const t = await this.getBlacklistCarList(), n = t.filter((t => t.starId !== e));
n.length !== t.length && (await this.forage.setItem(this.blacklist_car_list_key, n),
window.cleanCache_filter_actor_actress_car_list());
}
async getFavoriteActressList() {
return await this.forage.getItem(this.favorite_actresses_key) || [];
}
async addFavoriteActressList(e) {
const t = await this.getFavoriteActressList();
let n = 0;
for (const a of e) {
let {starId: e, name: i, allName: s, avatar: o, lastCheckTime: r, lastPublishTime: l, actressType: c} = a;
if (!e) throw new Error("缺失starId");
if (!i) throw new Error("缺失name");
s || (s = [ i ]);
const d = "(無碼)";
if (!c) {
c = i.includes(d) || s.some((e => e.includes(d))) ? L : D;
}
i = i.replace(d, ""), s = s.map((e => e.replace(d, "")));
let h = t.find((t => t.starId === e));
if (h) {
h.avatar && h.avatar.includes("https") || o && (clog.log(o), h.avatar = o, clog.log(`<span style="color: #f40">补全女优头像: ${i}</span>`),
n++), !h.actressType && c && (h.actressType = c, clog.log(`<span style="color: #f40">补全女优类别: ${i} ${c}</span>`),
n++), h.name.includes(d) && (h.name = i, h.allName = s, clog.log(`<span style="color: #f40">更正女优名字: ${i} ${s}</span>`),
n++);
continue;
}
const g = utils.getNowStr();
t.push({
starId: e,
name: i,
allName: s,
avatar: o,
lastCheckTime: r,
lastPublishTime: l,
createDate: g,
updateDate: g,
actressType: c
}), clog.log(`<span style="color: #f40">同步JavDB已收藏的演员: ${i}</span>`), n++;
}
return n > 0 ? await this.forage.setItem(this.favorite_actresses_key, t) : clog.log("信息已记录, 无需要进行同步收藏的演员"),
n;
}
async removeFavoriteActress(e) {
const t = await this.getFavoriteActressList(), n = t.length, a = t.filter((t => t.starId !== e));
return a.length === n ? (clog.error(`移除演员失败, ${e} 不存在`), !1) : (await this.forage.setItem(this.favorite_actresses_key, a),
!0);
}
async updateFavoriteActress(e) {
const t = await this.getFavoriteActressList(), {starId: n, name: a, allName: i, avatar: s, lastCheckTime: o, newVideoList: r, lastPublishTime: l, actressType: c} = e;
if (!n) throw new Error("缺失starId");
let d = t.find((e => e.starId === n));
if (!d) return clog.error("未找到演员信息", n, a), !1;
a && (d.name = a), i && (d.allName = i), s && (d.avatar = s), null != c && (d.actressType = c),
o && (d.lastCheckTime = o), r && (d.newVideoList = r), l && (d.lastPublishTime = l),
d.updateDate = utils.getNowStr(), await this.forage.setItem(this.favorite_actresses_key, t);
}
async getHighlightedTags() {
return await this.forage.getItem(this.highlighted_tags_key) || [];
}
async setHighlightedTags(e) {
return await this.forage.setItem(this.highlighted_tags_key, e);
}
async saveTitleFilterKeyword(n) {
if (await s(this, e, t).call(this, n, this.filter_keyword_title_key, "标题关键词"), Array.isArray(n)) return null;
const a = await this.getFavoriteActressList();
let i = !1;
const o = a.map((e => {
if (!e.newVideoList || !Array.isArray(e.newVideoList)) return e;
const t = e.newVideoList.filter((t => {
const a = t.startsWith(n);
return a && (clog.log("移除关联女优新作品", e.name, t), i = !0), !a;
}));
return {
...e,
newVideoList: t
};
}));
i && await this.forage.setItem(this.favorite_actresses_key, o);
}
async getTitleFilterKeyword() {
return await this.forage.getItem(this.filter_keyword_title_key) || [];
}
async getReviewFilterKeywordList() {
return await this.forage.getItem(this.filter_keyword_review_key) || [];
}
async saveReviewFilterKeyword(n) {
return s(this, e, t).call(this, n, this.filter_keyword_review_key, "评论关键词");
}
async getSetting(e = null, t) {
this.cacheSettingObj || (this.cacheSettingObj = await this.forage.getItem(this.setting_key) || {});
let n = this.cacheSettingObj;
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) {
e ? (await this.forage.setItem(this.setting_key, e), window.clean_cacheSettingObj()) : show.error("设置对象为空");
}
async saveSettingItem(e, t) {
if (!e) return void show.error("key 不能为空");
let n = await this.getSetting();
n[e] = t, await this.saveSetting(n), window.clean_cacheSettingObj();
}
async importData(e) {
await this.forage.clear();
const t = [];
for (const n in e) {
const a = e[n], i = this.forage.setItem(n, a);
t.push(i);
}
await Promise.all(t);
}
async exportData() {
const e = {};
if (await this.forage.iterate(((t, n) => {
e[n] = t;
})), 0 === Object.keys(e).length) throw new Error("没有可导出的数据");
return e;
}
async merge_table_name() {
let e = "filter_actor_actress_info_list", t = await this.forage.getItem(e) || [];
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.blacklist_key, t)),
await this.forage.removeItem(e), e = "favorite_actresses_info_list", t = await this.forage.getItem(e) || [],
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.favorite_actresses_key, t)),
await this.forage.removeItem(e), e = "car_list_filter_actor_actress", t = await this.forage.getItem(e) || [],
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.blacklist_car_list_key, t)),
await this.forage.removeItem(e), e = "title_filter_keyword", t = await this.forage.getItem(e) || [],
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.filter_keyword_title_key, t)),
await this.forage.removeItem(e), e = "review_filter_keyword", t = await this.forage.getItem(e) || [],
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.filter_keyword_review_key, t)),
await this.forage.removeItem(e), e = "highlightedTags", t = await this.forage.getItem(e) || [],
t && t.length > 0 && (console.log("更正", e), await this.forage.setItem(this.highlighted_tags_key, t)),
await this.forage.removeItem(e);
}
async clean_no_url_blacklist() {
const [e, t] = await Promise.all([ this.getBlacklistCarList(), this.getBlacklist() ]);
if (e.length && !e[0].actress) return;
const n = new Set(t.map((e => e.name))), a = e.filter((e => !e.actress || n.has(e.actress)));
e.length !== a.length && (clog.debug("清理 blacklistCarList 前", e.length), clog.debug("清理 blacklistCarList 后", a.length),
await this.forage.setItem(this.blacklist_car_list_key, a), this.cache_filter_actor_actress_car_list = null);
const i = new Set(a.map((e => e.actress)));
let s = t.filter((e => i.has(e.name)));
s = s.map((e => {
const {key: t, recordTime: n, ...a} = e, i = a;
return void 0 !== n && (i.createTime = n), i;
})), (t.length !== s.length || t.some((e => "key" in e || "recordTime" in e))) && (clog.debug("清理 Blacklist 前", t.length),
clog.debug("清理 Blacklist 后", s.length), await this.forage.setItem(this.blacklist_key, s));
}
async async_merge_other() {
const e = await this.getSetting();
let t = !1;
const n = {
enableCheckFilterActorActress: "enableCheckBlacklist",
checkIntervalTime_filterActorActress: "checkBlacklist_intervalTime",
checkIntervalTime_ruleTime: "checkNewVideo_ruleTime",
checkIntervalTime_newVideo: "checkNewVideo_intervalTime",
checkIntervalTime_favoriteActress: "checkFavoriteActress_IntervalTime"
};
for (const a in n) {
const i = n[a];
Object.prototype.hasOwnProperty.call(e, a) && (e[i] = e[a], delete e[a], t = !0);
}
e.checkFilterTime && (delete e.checkFilterTime, t = !0), e.checkFilterConcurrencyCount && (delete e.checkFilterConcurrencyCount,
t = !0), e.checkFilterSleep && (delete e.checkFilterSleep, t = !0), e.downPath115 && (delete e.downPath115,
t = !0), t && (await this.saveSetting(e), clog.debug("配置数据已更正"));
}
async merge_blacklist() {
const e = await this.getBlacklist();
if (!e || 0 === e.length) return;
let t = !1;
const n = e.map((e => {
let n = !1;
if (Object.prototype.hasOwnProperty.call(e, "isActor") && !e.role && (e.role = e.isActor ? B : P,
delete e.isActor, n = !0), !e.starId && e.url) try {
const t = new URL(e.url).pathname, a = t.split("/").filter((e => "" !== e.trim())).pop();
e.starId !== a && (e.starId = a, n = !0);
} catch (a) {
clog.error("提取url-starId发生错误", e.url, a);
}
if (e.allName || (e.allName = e.name ? [ e.name ] : [], n = !0), e.movieType || (e.movieType = D,
n = !0), !e.url && e.url.includes("sort_type")) {
const t = new URL(e.url);
t.searchParams.delete("sort_type"), e.url = t.toString(), clog.debug("去除黑名单地址sort_type参数");
}
return n && (t = !0), e;
}));
t && (clog.debug("更正 Blacklist 数据结构"), await this.forage.setItem(this.blacklist_key, n));
const a = await this.getBlacklistCarList();
t = !1;
const i = a.map((n => {
if (!n.starId) {
let a = e.find((e => e.name === n.actress));
a && (n.starId = a.starId), t = !0;
}
return n.type && (delete n.type, t = !0), n;
}));
t && (clog.debug("更正 blacklistCarList 数据结构"), await this.forage.setItem(this.blacklist_car_list_key, i));
}
async merge_favoriteActress() {
const e = await this.getFavoriteActressList();
if (!e || 0 === e.length) return;
let t = !1;
const n = e.map((e => {
let n = !1;
return e.dbId && (e.starId = e.dbId, delete e.dbId, n = !0), n && (t = !0), e;
}));
t && (clog.debug("更正 favoriteActressesInfoList 数据结构"), await this.forage.setItem(this.favorite_actresses_key, n));
}
async merge_tow_car_list_table() {
const e = await this.getBlacklistCarList(), t = await this.getCarList();
let n = !1;
const a = e.map((e => {
let t = !1;
return void 0 !== e.actress && (e.names = e.actress, delete e.actress, t = !0),
t && (n = !0), e;
}));
n && (clog.debug("更正 blacklistCarList 数据结构 actress->names"), await this.forage.setItem(this.blacklist_car_list_key, a)),
n = !1;
const i = t.map((e => {
let t = !1;
return void 0 !== e.actress && (e.names = e.actress, delete e.actress, t = !0),
t && (n = !0), e;
}));
n && (clog.debug("更正 carList 数据结构 actress->names"), await this.forage.setItem(this.car_list_key, i));
}
};
class U {
constructor() {
return i(this, "intervalContainer", {}), i(this, "mimeTypes", {
txt: "text/plain",
html: "text/html",
css: "text/css",
csv: "text/csv",
json: "application/json",
xml: "application/xml",
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
pdf: "application/pdf",
doc: "application/msword",
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
xls: "application/vnd.ms-excel",
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
ppt: "application/vnd.ms-powerpoint",
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
zip: "application/zip",
rar: "application/x-rar-compressed",
"7z": "application/x-7z-compressed",
mp3: "audio/mpeg",
wav: "audio/wav",
mp4: "video/mp4",
webm: "video/webm",
ogg: "audio/ogg"
}), i(this, "timers", new Map), i(this, "insertStyle", (e => {
e && (-1 === e.indexOf("<style>") && (e = "<style>" + e + "</style>"), $("head").append(e));
})), i(this, "layerIndexStack", []), U.instance || (U.instance = this), U.instance;
}
importResource(e) {
let t;
e.indexOf("css") >= 0 ? (t = document.createElement("link"), t.setAttribute("rel", "stylesheet"),
t.href = e) : (t = document.createElement("script"), t.setAttribute("type", "text/javascript"),
t.src = e), document.documentElement.appendChild(t);
}
openPage(e, t, n, a) {
if (n = n ?? !0, a && (a.ctrlKey || a.metaKey)) return void GM_openInTab(e.includes("http") ? e : window.location.origin + e, {
insert: 0
});
let i = e;
e.includes("/actors/") || e.includes("/star/") || (i = e.includes("?") ? `${e}&hideNav=1` : `${e}?hideNav=1`),
layer.open({
type: 2,
title: t,
content: i,
scrollbar: !1,
shadeClose: n,
area: this.getResponsiveArea([ "85%", "90%" ]),
isOutAnim: !1,
anim: -1,
success: (e, t) => {
this.setupEscClose(t);
}
});
}
_handleGlobalEscKey(e) {
if ("Escape" !== e.key && 27 !== e.keyCode) return;
if (0 === this.layerIndexStack.length) return;
const t = this.layerIndexStack[this.layerIndexStack.length - 1], n = $(`#layui-layer${t}`);
let a = !1;
if (n.find(".viewer-container").length > 0) a = !0; else {
const e = n.find(`#layui-layer-iframe${t}`)[0];
if (e && e.contentDocument) try {
$(e.contentDocument).find(".viewer-container").length > 0 && (a = !0);
} catch (i) {
clog.warn("无法检查跨域 iframe 内的 .viewer-container");
}
}
a || (this.layerIndexStack.pop(), layer.close(t));
}
setupEscClose(e) {
this._boundHandler || (this._boundHandler = this._handleGlobalEscKey.bind(this),
$(document).off("keydown.globalLayerEsc"), $(document).on("keydown.globalLayerEsc", this._boundHandler)),
-1 === this.layerIndexStack.indexOf(e) && this.layerIndexStack.push(e);
try {
const t = $(`#layui-layer-iframe${e}`)[0];
(null == t ? void 0 : t.contentDocument) && $(t.contentDocument).on(`keydown.layerEsc${e}`, this._boundHandler);
} catch (t) {
clog.error("iframe监听失败 (跨域或未加载完毕):", t);
}
}
closePage() {
storageManager.getSetting("needClosePage", "yes").then((e => {
if ("yes" !== e) return;
parent.document.documentElement.style.overflow = "auto";
[ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(e) {
const t = parent.document.querySelectorAll(e);
if (t.length > 0) {
const e = t.length > 1 ? t[t.length - 1] : t[0];
e.parentNode.removeChild(e);
}
})), window.close();
}));
}
loopDetector(e, t, n = 20, a = 1e4, i = !0) {
const s = Math.random(), o = (new Date).getTime(), r = e => {
clearInterval(this.intervalContainer[s]), e && t && t(), delete this.intervalContainer[s];
};
this.intervalContainer[s] = setInterval((() => {
const n = (new Date).getTime() - o;
e() ? r(!0) : n >= a && (console.warn("loopDetector timeout!", e, t), r(i));
}), n);
}
rightClick(e, t, n) {
let a;
"string" == typeof e ? a = document.querySelector(e) : e instanceof HTMLElement && (a = e),
a || (console.warn("rightClick(), 容器无效或未提供,将使用 document.body 进行全局委托。"), a = document.body),
"string" == typeof t && "" !== t.trim() ? a.addEventListener("contextmenu", (e => {
const a = e.target.closest(t);
a && n(e, a);
})) : console.error("rightClick(), 必须提供有效的 targetSelector。");
}
q(e, t, n, a) {
let i, s;
e ? (i = e.clientX - 130, s = e.clientY - 120) : (i = window.innerWidth / 2 - 120,
s = window.innerHeight / 2 - 120);
let o = layer.confirm(t, {
offset: [ s, i ],
title: "提示",
btn: [ "确定", "取消" ],
shade: 0,
zIndex: 999999991
}, (function() {
n && 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(), s = String(a.getMonth() + 1).padStart(2, "0"), o = String(a.getDate()).padStart(2, "0"), r = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0");
return `${[ i, s, o ].join(e)} ${[ r, 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");
if (a = new Date(e), isNaN(a.getTime())) throw new Error("Invalid date string");
}
const i = a.getFullYear(), s = String(a.getMonth() + 1).padStart(2, "0"), o = String(a.getDate()).padStart(2, "0"), r = String(a.getHours()).padStart(2, "0"), l = String(a.getMinutes()).padStart(2, "0"), c = String(a.getSeconds()).padStart(2, "0");
return `${[ i, s, o ].join(t)} ${[ r, l, c ].join(n)}`;
}
getHourDifference(e, t) {
const n = e.getTime(), a = t.getTime(), i = Math.abs(a - n) / 36e5;
return Math.floor(i);
}
download(e, t) {
show.info("开始请求下载...");
const n = t.split(".").pop().toLowerCase();
let a, i = this.mimeTypes[n] || "application/octet-stream";
if (e instanceof Blob) a = e; else if (e instanceof ArrayBuffer || ArrayBuffer.isView(e)) a = new Blob([ e ], {
type: i
}); else if ("string" == typeof e && e.startsWith("data:")) {
const t = atob(e.split(",")[1]), n = new ArrayBuffer(t.length), s = new Uint8Array(n);
for (let e = 0; e < t.length; e++) s[e] = t.charCodeAt(e);
a = new Blob([ s ], {
type: i
});
} else a = new Blob([ e ], {
type: i
});
const s = URL.createObjectURL(a), o = document.createElement("a");
o.href = s, o.download = t, document.body.appendChild(o), o.click(), setTimeout((() => {
document.body.removeChild(o), URL.revokeObjectURL(s);
}), 100);
}
smoothScrollToTop(e = 500) {
return new Promise((t => {
const n = performance.now(), a = window.pageYOffset;
window.requestAnimationFrame((function i(s) {
const o = s - n, r = Math.min(o / e, 1), l = r < .5 ? 4 * r * r * r : 1 - Math.pow(-2 * r + 2, 3) / 2;
window.scrollTo(0, a * (1 - l)), r < 1 ? window.requestAnimationFrame(i) : t();
}));
}));
}
simpleId() {
return crypto.randomUUID().replace("-", "");
}
isUrl(e) {
try {
return new URL(e), !0;
} catch (t) {
return !1;
}
}
setHrefParam(e, t) {
const n = new URL(window.location.href);
n.searchParams.set(e, t), window.history.pushState({}, "", n.toString());
}
getResponsiveArea(e) {
const t = window.innerWidth;
return t >= 1200 ? e || this.getDefaultArea() : t >= 768 ? [ "70%", "90%" ] : [ "95%", "95%" ];
}
getDefaultArea() {
return [ "85%", "90%" ];
}
isMobile() {
const e = navigator.userAgent.toLowerCase();
return [ "iphone", "ipod", "ipad", "android", "blackberry", "windows phone", "nokia", "webos", "opera mini", "mobile", "mobi", "tablet" ].some((t => e.includes(t)));
}
copyToClipboard(e, t) {
navigator.clipboard.writeText(t).then((() => show.info(`${e}已复制到剪切板, ${t}`))).catch((e => console.error("复制失败: ", e)));
}
htmlTo$dom(e) {
const t = new DOMParser;
return $(t.parseFromString(e, "text/html"));
}
addCookie(e, t = {}) {
const {maxAge: n = 604800, path: a = "/", domain: i = "", secure: s = !1, sameSite: o = "Lax"} = t;
e.split(";").forEach((e => {
const t = e.trim();
if (t) {
const e = t.split("=");
if (e.length >= 2 && e[0].trim()) {
let t = [ `${e[0].trim()}=${e.slice(1).join("=")}` ];
n > 0 && t.push(`max-age=${n}`), t.push(`path=${a}`), i && t.push(`domain=${i}`),
s && t.push("Secure"), o && t.push(`SameSite=${o}`), console.log("document.cookie = '" + t.join("; ") + "'"),
document.cookie = t.join("; ");
}
}
}));
}
isHidden(e) {
const t = e.jquery ? e[0] : e;
return !t || (t.offsetWidth <= 0 && t.offsetHeight <= 0 || "none" === window.getComputedStyle(t).display);
}
time(e = "default", t = "s", n = 2) {
if (this.timers.has(e)) {
const t = this.timers.get(e), n = performance.now() - t.startTime;
let a, i;
return "s" === t.unit ? (a = (n / 1e3).toFixed(t.precision), i = "秒") : (a = n.toFixed(t.precision),
i = "毫秒"), this.timers.delete(e), `${e}: ${a}${i}`;
}
this.timers.set(e, {
startTime: performance.now(),
unit: t,
precision: n
});
}
sleep(e = 1e3) {
return new Promise((t => setTimeout(t, e)));
}
genericSort(e, t, n = !0) {
if (!Array.isArray(e) || 0 === e.length) return [];
if (!Array.isArray(t) || 0 === t.length) return [ ...e ];
const a = [ ...e ], i = e => {
if (e instanceof Date) return e;
if ("string" == typeof e) {
const t = new Date(e);
if (!isNaN(t.getTime())) return t;
}
return e;
};
return a.sort(((e, a) => {
for (const s of t) {
const {key: t, order: o = "asc"} = s;
let r = e, l = a;
null != t && ("function" == typeof t ? (r = t(e), l = t(a)) : (r = e && "object" == typeof e ? e[t] : void 0,
l = a && "object" == typeof a ? a[t] : void 0));
const c = i(r), d = i(l);
let h = 0;
const g = null == r, p = null == l;
if (g && p) return 0;
if (g) return n ? 1 : -1;
if (p) return n ? 1 : -1;
if (h = c instanceof Date && d instanceof Date ? c.getTime() - d.getTime() : "number" == typeof r && "number" == typeof l ? r - l : "string" == typeof r && "string" == typeof l ? r.localeCompare(l) : String(r).localeCompare(String(l)),
"desc" === o && (h *= -1), 0 !== h) return h;
}
return 0;
}));
}
}
window.utils = new U, window.http = new class {
get(e, t = {}, n = {}, a) {
return this.jqueryRequest("GET", e, null, t, n, a);
}
post(e, t = {}, n = {}, a) {
return this.jqueryRequest("POST", e, t, null, n, a);
}
put(e, t = {}, n = {}, a) {
return this.jqueryRequest("PUT", e, t, null, n, a);
}
del(e, t = {}, n = {}, a) {
return this.jqueryRequest("DELETE", e, null, t, n, a);
}
jqueryRequest(e, t, n = {}, a = {}, i = {}, s) {
return "POST" === e && (i = {
"Content-Type": "application/json",
...i
}), new Promise(((o, r) => {
$.ajax({
method: e,
url: t,
timeout: s || 7e3,
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 {
o("object" == typeof e ? e : JSON.parse(e));
} catch (i) {
o(e);
} else o(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;
}
r(new Error(a));
}
});
}));
}
}, window.gmHttp = new class {
get(e, t = {}, n = {}, a, i) {
return this.gmRequest("GET", e, null, t, n, a, i);
}
post(e, t = {}, n = {}, a) {
n = {
"Content-Type": "application/json",
...n
};
let i = JSON.stringify(t);
return this.gmRequest("POST", e, i, null, n, a);
}
postForm(e, t = {}, n = {}, a) {
n || (n = {}), n["Content-Type"] || (n["Content-Type"] = "application/x-www-form-urlencoded");
let i = "";
return t && Object.keys(t).length > 0 && (i = Object.entries(t).map((([e, t]) => `${e}=${t}`)).join("&")),
this.gmRequest("POST", e, i, null, n, a);
}
postFormData(e, t = {}, n = {}, a) {
n || (n = {});
const i = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
n["Content-Type"] = `multipart/form-data; boundary=${i}`;
let s = "";
return t && Object.keys(t).length > 0 && (s = Object.entries(t).map((([e, t]) => `--${i}\r\nContent-Disposition: form-data; name="${e}"\r\n\r\n${t}\r\n`)).join("")),
s += `--${i}--`, this.gmRequest("POST", e, s, null, n, a);
}
checkUrlStatus(e, t = {}, n) {
return new Promise(((a, i) => {
GM_xmlhttpRequest({
method: "HEAD",
url: e,
headers: t,
timeout: n || 7e3,
onload: e => {
a(e.status);
},
onerror: e => {
i(new Error(`请求失败: ${e}`));
},
ontimeout: () => {
i(new Error(`请求超时(${n}ms)`));
}
});
}));
}
gmRequest(e, t, n = {}, a = {}, i = {}, s, o = !1) {
if (a && Object.keys(a).length) {
const e = new URLSearchParams(a).toString();
t += (t.includes("?") ? "&" : "?") + e;
}
return new Promise(((a, r) => {
GM_xmlhttpRequest({
method: e,
url: t,
headers: i,
timeout: s || 7e3,
data: n,
onload: e => {
try {
if (o && e.finalUrl !== t && r("请求被重定向了,URL是:" + e.finalUrl), e.status >= 200 && e.status < 300) if (e.responseText) try {
a(JSON.parse(e.responseText));
} catch (n) {
a(e.responseText);
} else a(e.responseText || e); else if (clog.error("请求失败,状态码:", e.status, t), 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 => {
clog.error("网络错误:", t), r(new Error(e.error || "Network Error"));
},
ontimeout: () => {
r(new Error("Request Timeout"));
}
});
}));
}
}, window.storageManager = new z;
const O = new BroadcastChannel("channel-refresh");
window.refresh = function() {
O.postMessage({
type: "refresh"
});
}, window.cleanCache_filter_actor_actress_car_list = function() {
O.postMessage({
type: "cleanCache_filter_actor_actress_car_list"
});
}, window.clean_cacheSettingObj = function() {
O.postMessage({
type: "clean_cacheSettingObj"
});
}, 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");
return t.className = "loading-animation", e.appendChild(t), document.body.appendChild(e),
{
close: () => {
e && e.parentNode && e.parentNode.removeChild(e);
}
};
}, function() {
const e = (e, t, n, a, i) => {
let s;
"object" == typeof n ? s = n : (s = "object" == typeof a ? a : i || {}, s.gravity = n || "top",
s.position = "string" == typeof a ? a : "center"), s.gravity && "center" !== s.gravity || (s.offset = {
y: "calc(50vh - 150px)"
});
const o = "#60A5FA", r = "#93C5FD", l = "#10B981", c = "#6EE7B7", d = "#EF4444", h = "#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
}, p = {
text: e,
duration: 1e3,
close: !1,
gravity: "top",
position: "center",
style: {
info: {
...g,
background: `linear-gradient(to right, ${o}, ${r})`
},
success: {
...g,
background: `linear-gradient(to right, ${l}, ${c})`
},
error: {
...g,
background: `linear-gradient(to right, ${d}, ${h})`
}
}[t],
stopOnFocus: !0,
oldestFirst: !1,
...s
};
-1 === p.duration && (p.close = !0);
const u = Toastify(p);
return u.showToast(), u.closeShow = () => {
u.toastElement.remove();
}, u;
};
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)
};
}(), function() {
function e(e = 10) {
setTimeout((() => {
const e = document.querySelectorAll(".layui-layer-shade").length;
document.documentElement.style.overflow = e > 0 ? "hidden" : "";
}), e);
}
document.head.insertAdjacentHTML("beforeend", "\n <style>\n .viewer-canvas {\n overflow: auto !important;\n }\n \n .viewer-close {\n background: rgba(255,0,0,0.6) !important;\n }\n .viewer-close:hover {\n background: rgba(255,0,0,0.8) !important;\n }\n </style>\n "),
window.showImageViewer = function(t, n = "") {
let a = null, i = !1;
"string" == typeof t || t instanceof String ? (a = $('<div class="temporary-container" style="display:none;">').append(`<img src="${t}" alt="${n}">`).appendTo("body"),
i = !0) : a = $(t);
const s = {
zIndex: 999999990,
navbar: !1,
zoomOnWheel: !1,
zoomRatio: .1,
toggleOnDblclick: !1,
toolbar: {
zoomIn: 1,
zoomOut: 1,
reset: 1,
rotateLeft: 0,
rotateRight: 0,
flipHorizontal: 0,
flipVertical: 0
},
title: !1,
keyboard: !1,
viewed() {
o.zoomTo(1.4);
let e = (o.viewerData.width - o.imageData.width) / 2;
o.moveTo(e, 0);
},
shown() {
i && a.remove(), document.documentElement.style.overflow = "hidden", document.body.style.overflow = "hidden",
o.handleKeydown = function(t) {
"Escape" !== t.key && " " !== t.key || (t.preventDefault(), t.stopPropagation(),
o.destroy(), document.removeEventListener("keydown", o.handleKeydown), document.documentElement.style.overflow = "",
document.body.style.overflow = "", e());
}, document.addEventListener("keydown", o.handleKeydown);
},
hidden() {
o && o.handleKeydown && document.removeEventListener("keydown", o.handleKeydown),
o.destroy(), document.documentElement.style.overflow = "", document.body.style.overflow = "",
e();
}
}, o = new Viewer(a[0], s);
o.show();
};
}(), window.ImageHoverPreview = class {
constructor(e = {}) {
this.config = {
selector: ".hover-preview",
dataAttribute: "data-full",
maxWidth: 1e3,
maxHeight: 1e3,
offsetX: 20,
offsetY: 20,
zIndex: 9999999999,
transition: .2,
autoAdjustPosition: !0,
...e
}, this.preview = null, this.currentTarget = null, this.timer = null, this.imgElement = null,
this.boundElements = new WeakSet, this.init();
}
init() {
this.injectStyles(), this.createPreviewElement(), this.bindEvents();
}
injectStyles() {
const e = `\n <style>\n .image-hover-preview {\n position: fixed;\n display: none;\n z-index: ${this.config.zIndex};\n border-radius: 4px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n pointer-events: none;\n opacity: 0;\n transition: opacity ${this.config.transition}s ease;\n background-color: #fff;\n }\n \n .image-hover-preview.active {\n opacity: 1;\n }\n \n .image-hover-preview img {\n max-width: ${this.config.maxWidth}px;\n max-height: ${this.config.maxHeight}px;\n display: block;\n object-fit: contain;\n }\n \n .image-hover-preview::after {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.03);\n pointer-events: none;\n }\n \n .image-hover-preview.loading::before {\n content: '加载中...';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #666;\n font-size: 14px;\n }\n </style>\n `;
document.head.insertAdjacentHTML("beforeend", e);
}
createPreviewElement() {
this.preview = document.createElement("div"), this.preview.className = "image-hover-preview",
document.body.appendChild(this.preview);
}
bindEvents() {
document.querySelectorAll(this.config.selector).forEach((e => {
this.boundElements.has(e) || (e.addEventListener("mouseenter", (e => this.handleMouseEnter(e))),
e.addEventListener("mouseleave", (e => this.handleMouseLeave(e))), e.addEventListener("mousemove", (e => this.handleMouseMove(e))),
this.boundElements.add(e));
}));
}
handleMouseEnter(e) {
clearTimeout(this.timer), this.currentTarget = e.currentTarget;
const t = this.currentTarget.getAttribute(this.config.dataAttribute) || this.currentTarget.src;
if (!t) return;
this.preview.innerHTML = "", this.preview.classList.add("loading"), this.preview.style.display = "block",
this.preview.classList.remove("active");
const n = new Image;
n.onload = () => {
this.preview.classList.remove("loading"), this.preview.innerHTML = `<img src="${t}" alt="预览图">`,
this.imgElement = this.preview.querySelector("img");
const {width: a, height: i} = this.calculateImageSize(n);
this.preview.style.width = `${a}px`, this.preview.style.height = `${i}px`, this.preview.offsetHeight,
this.preview.classList.add("active"), this.handleMouseMove(e);
}, n.onerror = () => {
this.preview.classList.remove("loading"), this.preview.innerHTML = '<div style="padding:10px;color:#f00;">图片加载失败</div>';
}, n.src = t;
}
calculateImageSize(e) {
let t = e.naturalWidth, n = e.naturalHeight;
if (t > this.config.maxWidth || n > this.config.maxHeight) {
const e = Math.min(this.config.maxWidth / t, this.config.maxHeight / n);
t *= e, n *= e;
}
return {
width: t,
height: n
};
}
handleMouseMove(e) {
if (!this.currentTarget || !this.preview.classList.contains("active")) return;
let {offsetX: t, offsetY: n} = this.config, a = e.clientX + t, i = e.clientY + n;
if (this.config.autoAdjustPosition) {
const s = this.preview.offsetWidth, o = this.preview.offsetHeight;
a + s > window.innerWidth && (a = e.clientX - s - t), i + o > window.innerHeight && (i = e.clientY - o - n),
a = Math.max(0, a), i = Math.max(0, i);
}
this.preview.style.left = `${a}px`, this.preview.style.top = `${i}px`;
}
handleMouseLeave() {
this.preview.classList.remove("active"), this.preview.style.display = "none", this.currentTarget = null,
this.imgElement = null;
}
destroy() {
document.querySelectorAll(this.config.selector).forEach((e => {
this.boundElements.has(e) && (e.removeEventListener("mouseenter", this.handleMouseEnter),
e.removeEventListener("mouseleave", this.handleMouseLeave), e.removeEventListener("mousemove", this.handleMouseMove),
this.boundElements.delete(e));
})), this.preview && this.preview.parentNode && this.preview.parentNode.removeChild(this.preview);
}
}, function() {
document.head.insertAdjacentHTML("beforeend", "\n <style>\n .console-logger-container {\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 99999999;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n display: flex;\n flex-direction: column; \n align-items: flex-end;\n width: fit-content;\n }\n\n .console-logger-toggle {\n width: 40px;\n height: 30px;\n background: #2c3e50;\n border-radius: 120px 10px 0 0;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n color: white;\n font-size: 16px;\n }\n\n .console-logger-toggle:hover {\n background: #34495e;\n }\n\n .console-logger-toggle::after {\n content: '▼';\n transition: transform 0.3s ease;\n }\n\n .console-logger-toggle.collapsed::after {\n content: '▲';\n }\n\n .console-logger-window {\n width: 400px;\n height: 400px;\n background: white;\n border-radius: 10px 0 10px 10px;\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n transform: translateY(0);\n opacity: 1;\n /* 简化过渡属性 */\n transition: width 0.3s ease, height 0.3s ease, opacity 0.3s ease, transform 0.3s ease;\n }\n\n .console-logger-window.maximized {\n width: 600px !important;\n height: 85vh !important;\n border-radius: 10px 0 0 10px; /* 调整圆角以匹配右下角 */\n }\n\n .console-logger-window.collapsed {\n height: 0 !important;\n min-height: 0 !important; \n opacity: 0;\n }\n\n .console-logger-header {\n background: #2c3e50;\n color: white;\n padding: 12px 15px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-shrink: 0;\n }\n\n .console-logger-title {\n font-weight: 600;\n font-size: 16px;\n }\n\n .console-logger-controls {\n display: flex;\n gap: 10px;\n }\n\n .console-logger-controls button {\n background: transparent;\n border: 1px solid rgba(255, 255, 255, 0.3);\n padding: 5px 10px;\n font-size: 12px;\n color: white;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.3s;\n }\n\n .console-logger-controls button:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n /* 新增的按钮样式 */\n .console-logger-maximize-toggle {\n line-height: 1;\n font-size: 14px !important; /* 使箭头看起来更大 */\n padding: 5px 8px !important;\n }\n .console-logger-maximize-toggle::before {\n content: '⇱'; /* Unicode symbol for maximized */\n }\n .console-logger-maximize-toggle.active::before {\n content: '⇲'; /* Unicode symbol for minimized */\n }\n\n\n .console-logger-filters {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 10px;\n background: #f8f9fa;\n border-bottom: 1px solid #e9ecef;\n flex-shrink: 0;\n overflow-x: hidden; \n }\n\n /* 新增: 过滤器按钮组的容器,负责滚动 */\n .console-logger-filter-group {\n display: flex;\n gap: 5px;\n overflow-x: auto; /* 允许过滤器按钮滚动 */\n flex-grow: 1; /* 占据剩余空间 */\n padding-right: 10px; /* 避免滚动条影响按钮 */\n }\n\n .console-logger-filter {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 15px;\n background: #ecf0f1;\n color: #7f8c8d;\n border: 1px solid #ddd;\n cursor: pointer;\n transition: all 0.3s;\n white-space: nowrap;\n flex-shrink: 0; /* 确保不被压缩 */\n }\n\n .console-logger-filter.active {\n background: #3498db;\n color: white;\n border-color: #3498db;\n }\n\n /* 新增: 滚动到底部按钮的样式 (位于 filtersContainer 内部右侧) */\n .console-logger-scroll-to-bottom {\n background: #3498db;\n border: none;\n padding: 5px 10px;\n font-size: 12px;\n color: white;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.3s;\n line-height: 1;\n height: fit-content;\n white-space: nowrap;\n margin-left: auto; /* 将按钮推到最右侧 */\n flex-shrink: 0; /* 确保不被压缩 */\n }\n\n .console-logger-scroll-to-bottom:hover {\n background: #2980b9;\n }\n\n\n .console-logger-content {\n flex: 1;\n overflow-y: auto;\n padding: 10px;\n background: #ffffff;\n word-wrap: break-word;\n text-align: left;\n }\n\n .console-logger-entry {\n padding: 8px 10px;\n margin-bottom: 3px;\n border-radius: 4px;\n font-size: 12px;\n line-height: 1.4;\n /*animation: consoleFadeIn 0.3s ease;*/\n border-left: 3px solid transparent;\n }\n\n @keyframes consoleFadeIn {\n from { opacity: 0; transform: translateY(5px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .console-logger-timestamp {\n color: #7f8c8d;\n font-size: 11px;\n margin-right: 2px;\n }\n\n @media (max-width: 768px) {\n .console-logger-container {\n right: 10px;\n bottom: 10px;\n }\n\n .console-logger-window {\n width: calc(100vw - 20px);\n height: 300px;\n }\n }\n \n .console-logger-message[data-type=\"json\"] {\n white-space: pre-wrap; \n }\n </style>\n ");
const e = {
base: {
label: "信息",
background: "#e8f4fd",
borderLeftColor: "#3498db"
},
warn: {
label: "警告",
background: "#fef9e7",
borderLeftColor: "#f39c12"
},
error: {
label: "错误",
background: "#fdedec",
borderLeftColor: "#e74c3c"
},
debug: {
label: "调试",
background: "#f4f6f6",
borderLeftColor: "#95a5a6"
}
}, t = {
base: [ "base", "warn", "error" ],
warn: [ "warn" ],
error: [ "error" ],
debug: [ "base", "warn", "error", "debug" ]
}, n = "jhs_clog_maximize", a = "jhs_clog_expand", i = "jhs_clog_filter";
class s {
constructor() {
const t = localStorage.getItem(i);
this.currentFilter = t && e[t] ? t : "base", this.logs = [], this.isInitialized = !1,
this.userScrolledUp = !1;
}
tryInitialize() {
return "loading" !== document.readyState && (this.isInitialized || (this.init(),
this.isInitialized = !0), !0);
}
init() {
this.createContainer(), this.bindEvents(), this.checkInitialMaximizeState(), this.checkInitialCollapseState();
}
createContainer() {
this.container = document.createElement("div"), this.container.className = "console-logger-container",
this.container.style.display = "none", this.toggleBtn = document.createElement("div"),
this.toggleBtn.className = "console-logger-toggle collapsed", this.container.appendChild(this.toggleBtn),
this.window = document.createElement("div"), this.window.className = "console-logger-window collapsed";
const t = document.createElement("div");
t.className = "console-logger-header";
const n = document.createElement("div");
n.className = "console-logger-title", n.textContent = "JHS V3.2.6";
const a = document.createElement("div");
a.className = "console-logger-controls", this.maximizeBtn = document.createElement("button"),
this.maximizeBtn.textContent = "", this.maximizeBtn.classList.add("console-logger-maximize-toggle"),
a.appendChild(this.maximizeBtn);
const i = document.createElement("button");
i.textContent = "清空", i.addEventListener("click", (() => this.clear())), a.appendChild(i),
t.appendChild(n), t.appendChild(a), this.filtersContainer = document.createElement("div"),
this.filtersContainer.className = "console-logger-filters", this.filterButtonGroup = document.createElement("div"),
this.filterButtonGroup.className = "console-logger-filter-group", this.filtersContainer.appendChild(this.filterButtonGroup),
this.scrollToBottomBtn = document.createElement("button"), this.scrollToBottomBtn.className = "console-logger-scroll-to-bottom",
this.scrollToBottomBtn.textContent = "到底部", this.filtersContainer.appendChild(this.scrollToBottomBtn),
this.content = document.createElement("div"), this.content.className = "console-logger-content jhs-scrollbar",
this.window.appendChild(t), this.window.appendChild(this.filtersContainer), this.window.appendChild(this.content),
this.container.appendChild(this.window), document.body.appendChild(this.container),
Object.keys(e).forEach((t => {
const n = document.createElement("div");
n.className = "console-logger-filter", t === this.currentFilter && n.classList.add("active"),
n.textContent = e[t].label, n.dataset.type = t, n.addEventListener("click", (() => this.setFilter(t))),
this.filterButtonGroup.appendChild(n);
}));
}
bindEvents() {
this.toggleBtn.addEventListener("click", (() => {
this.toggleExpandCollapsed();
})), this.maximizeBtn.addEventListener("click", (() => this.toggleMaximize())),
this.scrollToBottomBtn.addEventListener("click", (() => {
this.content.scrollTop = this.content.scrollHeight, this.userScrolledUp = !1;
})), this.content.addEventListener("scroll", (() => {
const e = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 5;
this.userScrolledUp = !e;
})), this.content.addEventListener("wheel", (e => {
const t = 0 === this.content.scrollTop, n = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 1;
(t && e.deltaY < 0 || n && e.deltaY > 0) && (e.preventDefault(), e.stopPropagation());
}), {
passive: !1
});
}
toggleExpandCollapsed() {
const e = this.window.classList.toggle("collapsed");
this.toggleBtn.classList.toggle("collapsed"), e ? localStorage.setItem(a, "no") : (localStorage.setItem(a, "yes"),
this.reRenderAllLogs());
}
checkInitialCollapseState() {
const e = localStorage.getItem(a);
e && "no" !== e ? (this.window.classList.toggle("collapsed"), this.toggleBtn.classList.toggle("collapsed"),
setTimeout((() => {
this.content.scrollTop = this.content.scrollHeight;
}), 0)) : (this.window.classList.add("collapsed"), this.toggleBtn.classList.add("collapsed"));
}
checkInitialMaximizeState() {
"maximized" === localStorage.getItem(n) && (this.window.classList.add("maximized"),
this.maximizeBtn.classList.add("active"));
}
toggleMaximize() {
const e = this.window.classList.toggle("maximized");
this.maximizeBtn.classList.toggle("active", e), e ? localStorage.setItem(n, "maximized") : localStorage.setItem(n, "minimized"),
this.window.classList.contains("collapsed") || (this.content.scrollTop = this.content.scrollHeight);
}
addLog(t, n = "base", ...a) {
const i = this.tryInitialize();
let s, o = [];
e[n] ? (s = n, o = a) : (s = "base", o = [ n, ...a ]), s = e[s] ? s : "base";
const r = [ t, ...o ];
let l = "msg";
const c = [];
r.forEach((e => {
if ("[object Error]" === Object.prototype.toString.call(e)) c.push(String(e)); else if ("object" == typeof e && null !== e) try {
c.push("<br/>" + JSON.stringify(e, null, 2)), l = "json";
} catch (t) {
c.push(String(e)), l = "msg";
} else c.push(String(e));
}));
let d = c.join(" ");
d = d.replace(/(?:(?:https?|ftp):\/\/|www\.|(?:\/\/))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]/gi, (e => {
const t = e.startsWith("http") || e.startsWith("ftp"), n = e.startsWith("//"), a = e.startsWith("www.");
let i = e;
return n ? i = `http:${e}` : !t && a && (i = `http://${e}`), `<a href="${i}" target="_blank">${e}</a>`;
}));
const h = {
message: d,
messageType: l,
type: s,
timestamp: new Date,
id: Date.now() + Math.random()
};
if (this.logs.push(h), this.logs.length > 3e3 && (this.logs.shift(), i)) {
const e = this.content.querySelector(".console-logger-entry");
e && this.content.removeChild(e);
}
i && this.renderLog(h);
}
log(...e) {
const [t, ...n] = e;
setTimeout((() => {
this.addLog(t, "base", ...n);
}), 0);
}
error(...e) {
const [t, ...n] = e;
console.error(...e), setTimeout((() => {
this.addLog(t, "error", ...n);
}), 0);
}
warn(...e) {
const [t, ...n] = e;
setTimeout((() => {
this.addLog(t, "warn", ...n);
}), 0);
}
debug(...e) {
const [t, ...n] = e;
setTimeout((() => {
this.addLog(t, "debug", ...n);
}), 0);
}
renderLog(e) {
if ("none" === this.container.style.display) return;
if (this.window.classList.contains("collapsed")) return;
if (!(t[this.currentFilter] || []).includes(e.type)) return;
const n = this._createLogElement(e);
this.content.appendChild(n), this.window.classList.contains("collapsed") || this.userScrolledUp || (this.content.scrollTop = this.content.scrollHeight);
}
reRenderAllLogs() {
"none" !== this.container.style.display && (this.window.classList.contains("collapsed") || setTimeout((() => {
if (this.content.innerHTML = "", 0 === this.logs.length) return;
const e = t[this.currentFilter] || [], n = document.createDocumentFragment();
this.logs.forEach((t => {
if (e.includes(t.type)) {
const e = this._createLogElement(t);
n.appendChild(e);
}
})), this.content.appendChild(n), this.content.scrollTop = this.content.scrollHeight;
}), 0));
}
_createLogElement(t) {
const n = document.createElement("div");
n.className = "console-logger-entry", n.dataset.type = t.type, n.dataset.id = t.id;
const a = e[t.type] || e.base;
n.style.borderLeft = "3px solid " + a.borderLeftColor, n.style.background = a.background;
const i = (t.timestamp instanceof Date ? t.timestamp : new Date(t.timestamp)).toTimeString().split(" ")[0];
return n.innerHTML = `\n <span class="console-logger-timestamp">[${i}]</span>\n <span class="console-logger-message" data-type="${t.messageType}">${t.message}</span>\n `,
n;
}
setFilter(e) {
if (this.currentFilter === e) return;
this.currentFilter = e, localStorage.setItem(i, e);
this.filterButtonGroup.querySelectorAll(".console-logger-filter").forEach((t => {
t.dataset.type === e ? t.classList.add("active") : t.classList.remove("active");
})), this.reRenderAllLogs();
}
clear() {
this.logs = [], this.content.innerHTML = "";
}
show() {
(this.isInitialized && this.container || this.tryInitialize() && this.container) && (this.container.style.display = "",
this.reRenderAllLogs());
}
hide() {
this.isInitialized && this.container && (this.container.style.display = "none");
}
lowZIndex() {
this.isInitialized && this.container && (this.container.style.zIndex = "12345678");
}
highZIndex() {
this.isInitialized && this.container && (this.container.style.zIndex = "99999999");
}
}
try {
unsafeWindow.parent.clog && "function" == typeof unsafeWindow.parent.clog.log ? window.clog = unsafeWindow.clog = unsafeWindow.parent.clog : window.clog = unsafeWindow.clog = new s;
} catch (o) {
console.error("创建日志控制台出现异常", o), window.clog = unsafeWindow.clog = new s;
}
!function() {
const e = window.clog || console;
window.addEventListener("error", (function(t) {
const n = t.filename, a = t.message;
n.includes("javdb") || n.includes("javbus") || e.error(`[全局 Error 异常捕获] ${a} 来源: ${n}`);
})), window.addEventListener("unhandledrejection", (function(t) {
const n = t.reason, a = (null == n ? void 0 : n.message) ?? "";
if (a.includes("play()")) return;
if (a.includes("The element has no supported sources")) return show.error("播放失败, 请检查是否已对节点分流?"),
void e.error("播放失败, 请检查是否已对节点分流?");
if (a.includes("<span>1005</span>") && a.includes("fc2ppvdb")) return;
const i = `[全局 Promise 异常捕获] ${n.message || n}`;
e.error(i, n), t.preventDefault();
}));
}(), document.addEventListener("mousedown", (e => {
const t = window.clog;
if (!t.isInitialized || !t.container) return;
const n = e.target, a = t.container.contains(n), i = n.classList.contains("layui-layer-shade");
a || i ? t.highZIndex() : t.lowZIndex();
}));
}(), function() {
function e(e, t, n) {
const a = function(e) {
const t = document.createElement("div");
t.classList.add("js-tooltip");
const n = document.createElement("div");
return n.innerHTML = e, t.appendChild(n), document.body.appendChild(t), t;
}(t);
a.style.display = "block";
const i = e.getBoundingClientRect(), s = a.getBoundingClientRect();
a.style.display = "none";
const o = window.innerWidth, r = window.innerHeight;
let l, c, d = n;
const h = e => e >= 8 && e + s.height <= r - 8, g = e => e >= 8 && e + s.width <= o - 8, p = i.left + i.width / 2 - s.width / 2, u = i.top + i.height / 2 - s.height / 2;
switch (n) {
case "top":
c = i.top - s.height - 0, c < 8 && h(i.bottom + 0) && (c = i.bottom + 0, d = "bottom");
break;
case "bottom":
c = i.bottom + 0, c + s.height > r - 8 && h(i.top - s.height - 0) && (c = i.top - s.height - 0,
d = "top");
break;
case "left":
l = i.left - s.width - 0, l < 8 && g(i.right + 0) && (l = i.right + 0, d = "right");
break;
case "right":
l = i.right + 0, l + s.width > o - 8 && g(i.left - s.width - 0) && (l = i.left - s.width - 0,
d = "left");
}
const m = "left" === d || "right" === d;
"top" === d || "bottom" === d ? (l = p, l < 8 ? l = 8 : l + s.width > o - 8 && (l = o - s.width - 8)) : m && (c = u,
c < 8 ? c = 8 : c + s.height > r - 8 && (c = r - s.height - 8)), a.style.left = `${l}px`,
a.style.top = `${c}px`, a.classList.add("is-active"), e.tooltipElement = a;
}
document.head.insertAdjacentHTML("beforeend", "\n <style>\n .js-tooltip {\n /* 通用样式 */\n position: fixed;\n padding: 8px 12px; \n border-radius: 6px; \n white-space: normal;\n max-width: 600px; \n \n pointer-events: none;\n font-size: 14px;\n line-height: 1.5;\n z-index: 9999999999;\n \n background: #F0FDF4; \n color: #166534; \n border: none; \n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); \n \n display: none; \n }\n .js-tooltip.is-active {\n display: block !important;\n }\n\n </style>\n ");
const t = "[data-tip-top], [data-tip-bottom], [data-tip-left], [data-tip-right], [data-tip]";
document.addEventListener("mouseover", (n => {
const a = n.target.closest(t);
if (a && !a.tooltipElement) {
let t, n = "top";
if (a.hasAttribute("data-tip-bottom") ? (t = a.getAttribute("data-tip-bottom"),
n = "bottom") : a.hasAttribute("data-tip-left") ? (t = a.getAttribute("data-tip-left"),
n = "left") : a.hasAttribute("data-tip-right") ? (t = a.getAttribute("data-tip-right"),
n = "right") : a.hasAttribute("data-tip-top") ? (t = a.getAttribute("data-tip-top"),
n = "top") : a.hasAttribute("data-tip") && (t = a.getAttribute("data-tip"), n = "top"),
!t) return;
a.hoverTimeout = setTimeout((() => {
a.matches(":hover") && !a.tooltipElement && e(a, t, n);
}), 50);
}
})), document.addEventListener("mouseout", (e => {
const n = e.target.closest(t);
var a;
n && (n.hoverTimeout && (clearTimeout(n.hoverTimeout), n.hoverTimeout = null), n.contains(e.relatedTarget) || n.tooltipElement && ((a = n.tooltipElement) && a.parentNode && a.remove(),
n.tooltipElement = null));
}));
}();
class V {
constructor() {
this.plugins = new Map;
}
register(e) {
if ("function" != typeof e) throw new Error("插件必须是一个类");
const t = new e;
t.pluginManager = this;
const n = t.getName();
if (this.plugins.has(n)) throw new Error(`插件"${name}"已注册`);
this.plugins.set(n, t);
}
getBean(e) {
return this.plugins.get(e);
}
async processCss() {
const e = (await Promise.allSettled(Array.from(this.plugins).map((async ([e, t]) => {
try {
if ("function" == typeof t.initCss) {
const n = await t.initCss();
return n && utils.insertStyle(n), {
name: e,
status: "fulfilled"
};
}
return {
name: e,
status: "skipped"
};
} catch (n) {
return console.error(`插件 ${e} 加载 CSS 失败`, n), {
name: e,
status: "rejected",
error: n
};
}
})))).filter((e => "rejected" === e.status));
e.length && console.error("以下插件的 CSS 加载失败:", e.map((e => e.value.name)));
}
async processPlugins() {
const e = (await Promise.allSettled(Array.from(this.plugins).map((async ([e, t]) => {
try {
if ("function" == typeof t.handle) return await t.handle(), {
name: e,
status: "fulfilled"
};
} catch (n) {
return clog.error(`插件 ${e} 执行失败`, n), {
name: e,
status: "rejected",
error: n
};
}
})))).filter((e => "rejected" === e.status));
e.length && console.error("以下插件执行失败:", e.map((e => e.value.name)));
}
}
class R {
constructor() {
i(this, "pluginManager", null), i(this, "settingSvg", '<svg t="1760926954860" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4947" width="200" height="200"><path d="M511.099222 365.825763c-80.7786 0-146.26579 65.482515-146.26579 146.259556 0 80.7786 65.48719 146.259556 146.26579 146.259556 80.777041 0 146.259556-65.480957 146.259556-146.259556C657.358779 431.308278 591.876263 365.825763 511.099222 365.825763L511.099222 365.825763zM511.099222 585.215097c-40.391637 0-73.136012-32.742816-73.136012-73.129778 0-40.391637 32.742816-73.129778 73.136012-73.129778 40.386962 0 73.129778 32.738141 73.129778 73.129778C584.229 552.472281 551.486184 585.215097 511.099222 585.215097L511.099222 585.215097zM511.099222 585.215097M900.893017 568.24369l-26.451395-15.268032c3.065451-27.021784 3.138697-54.472139 0.077922-81.822754l26.373473-15.225955c69.953678-40.391637 93.920921-129.844512 53.533959-199.799749-40.390079-69.95212-129.839837-93.925596-199.799749-53.533959l-26.373473 15.225955c-22.153219-16.330888-45.963059-29.99217-70.896534-40.843585l0-30.545416c0-80.777041-65.48719-146.259556-146.26579-146.259556-80.7786 0-146.259556 65.482515-146.259556 146.259556l0 30.515806c-12.377127 5.421811-24.587501 11.55583-36.562551 18.473743-11.97505 6.917913-23.396854 14.420242-34.277879 22.432179l-26.431136-15.258682c-69.958353-40.391637-159.406553-16.424395-199.79819 53.533959C27.378272 326.082437 51.343956 415.535311 121.299193 455.922273l26.449837 15.275825c-3.063892 27.020226-3.137139 54.465905-0.077922 81.822754l-26.373473 15.224397c-69.953678 40.391637-93.920921 129.841395-53.533959 199.799749 40.391637 69.95212 129.839837 93.920921 199.79819 53.533959l26.375032-15.224397c22.153219 16.32933 45.963059 29.984378 70.896534 40.843585l0 30.537624c0 80.7786 65.48719 146.26579 146.26579 146.26579 80.777041 0 146.259556-65.48719 146.259556-146.26579l0-30.515806c12.377127-5.415577 24.587501-11.55583 36.567226-18.467509 11.97505-6.917913 23.398412-14.420242 34.277879-22.432179l26.423343 15.258682c69.959912 40.391637 159.408111 16.418162 199.799749-53.533959C994.813938 698.085085 970.848254 608.635327 900.893017 568.24369L900.893017 568.24369zM891.096666 731.474653c-20.198936 34.982294-64.923035 46.962019-99.900654 26.770875l-63.331869-36.567226 0 0 0 0-7.988562-4.611422c-18.134004 18.450366-39.024886 34.787489-62.516805 48.353705-23.49971 13.559983-48.091888 23.482568-73.129778 29.964118l0 9.222846 0 0 0 65.828489 0 7.301289c0 40.391637-32.742816 73.136012-73.136012 73.136012-40.386962 0-73.129778-32.742816-73.129778-73.136012l0-7.402588 0-65.72719 0 0 0-9.300768c-50.682014-13.090892-97.855981-39.682547-135.652816-78.232109l-7.983886 4.606747 0 0-63.331869 36.567226c-34.977618 20.191144-79.706394 8.206743-99.900654-26.770875-20.192702-34.977618-8.206743-79.701718 26.770875-99.899095l6.341291-3.657657 0 0 64.972905-37.516316c-14.487254-52.005129-13.929333-106.151555 0.073247-156.593569l-8.057133-4.650384 0 0-63.331869-36.567226c-34.982294-20.192702-46.963578-64.923035-26.770875-99.900654 20.192702-34.97606 64.923035-46.962019 99.900654-26.763083l6.324148 3.649866 0 0 64.996282 37.528784c18.132445-18.450366 39.024886-34.790606 62.516805-48.353705 23.493477-13.559983 48.085654-23.485685 73.129778-29.964118l0-9.229079L437.960093 153.739276l0-7.309082c0-40.385404 32.742816-73.129778 73.129778-73.129778 40.391637 0 73.129778 32.744375 73.129778 73.129778l0 7.404147 0 65.72719 0 9.307001c50.686689 13.086217 97.862215 39.684106 135.657491 78.232109l48.487732-27.997368 22.828023-13.176607c34.977618-20.192702 79.701718-8.212977 99.89442 26.763083 20.198936 34.982294 8.212977 79.706394-26.764641 99.900654l-30.822819 17.79738-32.50905 18.769847 0 0 0 0-7.983886 4.605189c14.488813 52.009805 13.929333 106.159347-0.077922 156.599803l64.979139 37.511641 0 0 6.414537 3.701294C899.303409 651.772936 911.289368 696.498594 891.096666 731.474653L891.096666 731.474653zM891.096666 731.474653M197.330785 324.240361c-1.932465 3.232203-3.824411 6.497135-5.649343 9.785442L197.330785 324.240361 197.330785 324.240361zM197.330785 324.240361M830.515443 690.133926l-5.655577 9.804144C826.793889 696.699632 828.685835 693.433143 830.515443 690.133926L830.515443 690.133926zM830.515443 690.133926M505.297151 146.430195l11.304921 0C512.835324 146.369416 509.067017 146.374091 505.297151 146.430195L505.297151 146.430195zM505.297151 146.430195M516.898176 877.740444l-11.31583 0C509.350653 877.796547 513.125193 877.796547 516.898176 877.740444L516.898176 877.740444zM516.898176 877.740444" fill="#272636" p-id="4948"></path></svg>'),
i(this, "editSvg", '<svg t="1760920692801" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3545" width="200" height="200"><path d="M1013.929675 128.26571a143.759824 143.759824 0 0 1 10.44409 53.858738 84.576649 84.576649 0 0 1-5.836403 30.308339 92.870485 92.870485 0 0 1-18.635533 29.284408 1314.726599 1314.726599 0 0 1-24.983901 24.574329c-7.372299 7.06512-13.82306 13.311095-19.249891 18.737926-6.143582 6.143582-12.082378 11.672806-17.406817 16.382886L720.266444 82.598415c9.317766-8.601015 20.478607-18.942712 33.277737-31.02509s23.448006-21.604931 31.946628-28.67005a102.085858 102.085858 0 0 1 68.193763-22.731255c11.263234 0.307179 22.116896 2.047861 32.560985 5.222045 10.546483 3.071791 19.659463 6.655547 27.441334 10.546483 16.280493 8.601015 34.301667 23.550399 54.063524 45.052936 19.864249 21.502538 35.120812 43.82422 46.076867 67.272226z m-907.20231 570.943576l32.560986-33.38013c17.099637-17.509209 38.397389-39.216533 64.098041-64.917186l84.986221-85.395793 94.303987-94.815953 250.350976-251.477299L850.817567 389.163169 600.46659 640.640468l-93.177663 94.815953c-31.02509 30.410732-58.978389 58.364031-83.859898 83.655111-24.779115 25.29108-45.360116 46.17926-61.743001 62.562146a504.797674 504.797674 0 0 1-55.804206 50.274981c-10.239304 7.884264-20.581 14.130239-31.537055 18.737926a507.152714 507.152714 0 0 1-47.715156 19.86425 1609.311367 1609.311367 0 0 1-131.063087 42.185931c-20.478607 5.426831-35.837563 8.908194-45.974474 10.546483-20.88818 2.35504-34.813633-0.819144-41.981145-9.42016-6.860333-8.601015-8.805801-22.93604-5.73401-43.312254a396.261054 396.261054 0 0 1 11.058448-47.305584c5.836403-20.683394 12.082378-42.185931 18.635532-64.40522 6.553154-22.219289 13.003916-42.697897 19.249891-61.435822 6.143582-18.635533 11.263234-31.537055 15.15417-38.602176 4.607687-10.853662 9.829732-20.785787 15.666135-29.796373a192.49891 192.49891 0 0 1 25.086294-29.796374z" fill="#FF9500" p-id="3546"></path></svg>'),
i(this, "deleteSvg", '<svg t="1760921450746" class="jhs-icon icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4530" width="200" height="200"><path d="M761.086847 36.028779s309.754321-147.538628 424.952209 231.50509c2.047962 6.570546 71.337359 253.862013-220.838618 415.139055-12.970429 7.167869-267.515096 145.746661-370.339877 341.327076 0 0-90.963666-205.649563-393.379455-351.566888-6.399883-3.071944-304.549083-156.583796-163.751664-487.2444 3.669266-8.533177 163.666333-336.20717 466.423449-99.411511l24.575549 27.391498L387.931021 324.279495l237.648977 159.570408-109.139333 145.746661L625.579998 849.069874l-30.719437-205.820227 166.226286-169.81022-216.486698-168.103585L761.086847 36.028779z" fill="#F4382E" p-id="4531"></path></svg>'),
i(this, "checkSvg", '<svg t="1760921633527" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5603" width="200" height="200"><path d="M924.928 544A413.76 413.76 0 0 1 544 924.736v3.264h-64v-3.2A413.696 413.696 0 0 1 99.072 544H96v-64h3.072A413.696 413.696 0 0 1 480 99.2V96h64v3.2a413.76 413.76 0 0 1 380.928 380.8h3.072v64h-3.072z m-64-64A350.016 350.016 0 0 0 544 163.2V288h-64V163.2A350.016 350.016 0 0 0 163.072 480H288v64H163.072A350.016 350.016 0 0 0 480 860.8V736h64v124.8a350.016 350.016 0 0 0 316.928-316.8H736v-64h124.928zM512 544a32 32 0 1 1 32-32 32 32 0 0 1-32 32z" fill="#333333" p-id="5604"></path></svg>'),
i(this, "actressSvg", '<svg t="1760926744637" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1948" width="200" height="200"><path d="M265.950168 668.467036V209.809493A209.809493 209.809493 0 0 1 475.759661 0h40.949536A209.809493 209.809493 0 0 1 726.564189 209.809493v440.435" p-id="1949"></path><path d="M916.558657 825.861124a193.463804 193.463804 0 0 0-137.442564-155.83573l-186.001889-45.795231-10.487631-124.293214H424.106373L412.231008 624.025416l-170.623063 44.44162a193.452429 193.452429 0 0 0-133.666108 154.698244L76.410695 1023.192384h871.189985z" fill="#FFE7D9" p-id="1950"></path><path d="M668.472724 265.682859c68.431223-29.187919 96.140409 100.349111 5.20969 151.774902z" fill="#FFCFB5" p-id="1951"></path><path d="M676.378259 334.421203c1.137487-99.814492-38.674561-172.158671-38.674561-172.15867l-59.740822 11.920865a493.805894 493.805894 0 0 1-80.761583 9.099896 493.669396 493.669396 0 0 1-80.761583-9.099896l-59.683948-11.88674s-39.812048 72.344179-38.776934 172.15867l-1.080613 92.05683c5.209691 56.271486 92.4777 121.381247 195.022161 119.163147 61.196805 0.034125 165.59537-51.573665 165.59537-119.197272z" fill="#FFE7D9" p-id="1952"></path><path d="M322.198905 274.703131c-68.419848-29.187919-96.140409 100.349111-5.209691 151.774902z" fill="#FFCFB5" p-id="1953"></path><path d="M297.390311 812.461526H742.034014a38.458438 38.458438 0 0 1 38.458438 38.458439V1020.325917H258.931873V850.90859a38.458438 38.458438 0 0 1 38.458438-38.447064z" fill="#FFD527" p-id="1954"></path><path d="M690.539973 92.284327c-20.645391 84.287793-275.613121 235.323328-424.589805 117.525166l104.955934-95.548915 139.399042-64.529643z" p-id="1955"></path><path d="M285.321573 383.708519h33.624119v177.118114h-33.624119zM675.855015 383.708519h33.624118v177.118114h-33.624118z" fill="#FFD527" p-id="1956"></path></svg>'),
i(this, "newSvg", '<svg t="1760926857487" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3954" width="200" height="200"><path d="M508.330667 733.994667c-11.008-7.338667-13.44-17.109333-7.338667-29.333334 28.117333-37.888 41.557333-98.986667 40.341333-183.317333v-165.013333c0-14.656 7.338667-23.210667 21.994667-25.664 37.888-1.216 82.496-5.504 133.845333-12.842667 13.44-2.432 21.376 3.072 23.829334 16.512 1.216 12.224-4.266667 19.562667-16.512 21.994667a1787.093333 1787.093333 0 0 1-113.664 11.008c-6.101333 0-9.173333 3.669333-9.173334 10.986666v84.330667h135.68c12.224 1.237333 18.944 7.957333 20.16 20.181333-1.216 10.986667-7.936 17.109333-20.16 18.346667h-36.672v223.658667c-1.216 12.202667-7.936 18.944-20.16 20.16-11.008-1.216-17.109333-7.957333-18.346666-20.16V501.162667h-60.48v18.346666c1.216 92.885333-13.44 161.92-44.010667 207.146667-6.101333 12.224-15.893333 14.677333-29.333333 7.338667z m-131.989334-282.325334c-1.237333 0-2.453333 0.618667-3.669333 1.834667h45.824a522.666667 522.666667 0 0 0 16.512-31.168c7.317333-12.224 12.224-20.778667 14.656-25.664 6.122667-11.008 15.274667-14.677333 27.52-11.008 9.770667 6.122667 12.202667 14.058667 7.317333 23.829333-4.906667 9.792-13.44 24.448-25.664 44.010667h49.493334c9.770667 1.216 15.274667 6.72 16.512 16.490667-1.237333 11.008-6.741333 17.109333-16.512 18.346666h-82.496a12.437333 12.437333 0 0 1 3.669333 9.173334v38.485333h69.653333c9.792 1.216 15.296 6.72 16.512 16.490667-1.216 11.008-6.72 17.130667-16.512 18.346666h-69.653333v108.16c0 34.218667-15.274667 51.946667-45.845333 53.162667h-16.490667a195.157333 195.157333 0 0 1-20.16 1.834667c-12.224 0-19.562667-6.72-22.016-20.16 1.237333-12.224 7.338667-18.944 18.346667-20.16 2.432 0 6.101333 0.597333 10.986666 1.834666h11.008c15.893333 0 23.829333-8.554667 23.829334-25.685333v-98.986667H314.026667c-11.008-1.216-17.109333-7.338667-18.346667-18.346666 1.237333-9.770667 7.338667-15.274667 18.346667-16.490667h75.157333V497.493333c0-3.669333 1.216-6.72 3.669333-9.173333h-89.813333c-11.029333-1.216-17.130667-7.317333-18.346667-18.325333 1.216-9.770667 7.317333-15.274667 18.346667-16.490667h56.810667c-3.669333-1.216-6.72-4.266667-9.173334-9.173333-1.216-1.216-3.050667-4.266667-5.482666-9.173334a758.336 758.336 0 0 0-14.677334-23.829333c-4.885333-9.770667-3.050667-17.706667 5.504-23.829333 11.008-3.669333 19.562667-1.216 25.664 7.338666 2.453333 2.432 6.122667 7.338667 11.008 14.656 6.101333 8.554667 9.770667 14.08 10.986667 16.512 4.906667 9.770667 2.453333 18.346667-7.317333 25.664z m-60.501333-71.509333c-9.792-1.216-15.274667-7.317333-16.512-18.346667 1.237333-9.749333 6.72-15.253333 16.512-16.490666h75.157333c-3.669333-12.202667-7.338667-21.973333-10.986666-29.333334-1.237333-12.202667 3.648-19.541333 14.656-21.973333 12.224-2.453333 21.397333 1.216 27.52 10.986667 0 1.216 0.597333 3.669333 1.813333 7.338666 4.906667 15.872 9.173333 26.88 12.842667 32.981334h60.48c11.008 1.237333 17.130667 6.741333 18.346666 16.512-1.216 11.008-7.338667 17.109333-18.346666 18.346666h-181.482667z m-14.677333 311.68c-8.533333-6.122667-10.986667-14.08-7.338667-23.829333a1659.648 1659.648 0 0 0 33.002667-66.005334c4.906667-9.792 12.224-12.842667 22.016-9.173333 9.770667 4.906667 13.44 12.224 10.986666 21.994667-3.669333 6.122667-9.173333 17.728-16.490666 34.837333-8.554667 15.893333-14.677333 27.52-18.346667 34.837333-4.885333 8.554667-12.821333 11.008-23.829333 7.338667z m201.664-25.664c-9.770667 4.885333-18.346667 2.432-25.664-7.338667a1138.56 1138.56 0 0 1-27.498667-44.010666c-4.885333-8.533333-3.050667-16.490667 5.504-23.829334 9.770667-3.669333 18.346667-1.216 25.664 7.338667l14.677333 21.994667c6.101333 9.770667 10.389333 17.109333 12.821334 21.994666 4.906667 8.554667 3.050667 16.512-5.504 23.850667z" fill="#333333" p-id="3955"></path><path d="M675.328 117.717333A425.429333 425.429333 0 0 0 512 85.333333C276.352 85.333333 85.333333 276.352 85.333333 512s191.018667 426.666667 426.666667 426.666667 426.666667-191.018667 426.666667-426.666667c0-56.746667-11.093333-112-32.384-163.328a21.333333 21.333333 0 0 0-39.402667 16.341333A382.762667 382.762667 0 0 1 896 512c0 212.074667-171.925333 384-384 384S128 724.074667 128 512 299.925333 128 512 128c51.114667 0 100.8 9.984 146.986667 29.12a21.333333 21.333333 0 0 0 16.341333-39.402667z" fill="#333333" p-id="3956"></path></svg>'),
i(this, "refreshSvg", '<svg t="1760926993643" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5942" width="200" height="200"><path d="M511.966722 0a511.966722 511.966722 0 1 0 179.828311 32.445891l-22.46254 59.964102A447.970882 447.970882 0 1 1 511.966722 63.99584a31.99792 31.99792 0 0 0 0-63.99584z" fill="#333333" p-id="5943"></path><path d="M649.2378 9.151405A30.909991 30.909991 0 0 1 671.316364 0h193.267438a31.99792 31.99792 0 0 1 31.357962 31.99792c0 17.662852-13.759106 31.99792-31.357962 31.99792H703.954243v160.629559a31.99792 31.99792 0 0 1-31.99792 31.357962 31.485953 31.485953 0 0 1-31.99792-31.357962V31.357962c0-8.511447 3.647763-16.318939 9.343392-21.950573z" fill="#333333" p-id="5944"></path></svg>'),
i(this, "blacklistSvg", '<svg t="1761386375897" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1936" width="200" height="200"><path d="M513.199827 65.667605c-246.537999 0-446.399933 199.861934-446.399933 446.399933 0 246.553349 199.861934 446.399933 446.399933 446.399933 246.553349 0 446.399933-199.846584 446.399933-446.399933C959.599759 265.529539 759.753175 65.667605 513.199827 65.667605zM513.199827 894.697075c-211.320916 0-382.629537-171.322947-382.629537-382.628514 0-94.183056 34.029024-180.417069 90.461291-247.080352l165.389818 165.389818c4.320399 39.651069 26.816762 73.840752 58.981323 94.068446-72.189136 27.369348-123.517151 97.156784-123.517151 178.936345l337.541643 0 100.846826 100.846826C693.608709 860.664981 607.375719 894.697075 513.199827 894.697075zM805.362956 759.14175 697.264982 651.0448c-16.556071-58.332547-60.10082-105.306394-116.275213-126.601396 35.888372-22.570042 59.752896-62.511729 59.752896-108.032482 0-70.436212-57.108672-127.542838-127.542838-127.542838-48.218188 0-90.184999 26.765597-111.865787 66.245773L266.120498 219.900316c66.663282-56.432267 152.897296-90.461291 247.079328-90.461291 211.304544 0 382.628514 171.308621 382.628514 382.629537C895.82834 606.244454 861.796246 692.476421 805.362956 759.14175z" fill="#272636" p-id="1937"></path></svg>'),
i(this, "copySvg", '<svg t="1749017229420" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9184" width="200" height="200"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667z m0 810.666666c-204.8 0-373.333333-168.533333-373.333333-373.333333S307.2 138.666667 512 138.666667 885.333333 307.2 885.333333 512 716.8 885.333333 512 885.333333z" fill="#666666" p-id="9185"></path><path d="M512 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 1 0-85.333334 0Z" fill="#666666" p-id="9186"></path><path d="M341.333333 512m-42.666666 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9187"></path><path d="M682.666667 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9188"></path></svg>'),
i(this, "titleSvg", '<svg t="1747553289744" class="jhs-icon 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>'),
i(this, "carNumSvg", '<svg t="1747552574854" class="jhs-icon 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>'),
i(this, "downSvg", '<svg t="1747552626242" class="jhs-icon 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>'),
i(this, "handleSvg", '<svg t="1749106236917" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2628" width="200" height="200"><path d="M838 989.48a32 32 0 0 1-22.5-9.22L519.3 687.6 207.48 980.8a32 32 0 0 1-54-23.32V136.52A98.54 98.54 0 0 1 252 38.1h519.6A98.52 98.52 0 0 1 870 136.52v820.96a32 32 0 0 1-32 32zM252 102.1a34.46 34.46 0 0 0-34.42 34.42v746.96L498 619.84a32 32 0 0 1 44.42 0.56L806 880.88V136.52a34.46 34.46 0 0 0-34.4-34.42z" p-id="2629"></path><path d="M648 604.92a28 28 0 0 1-16.46-5.34l-112.84-82-112.84 82a28 28 0 0 1-43.08-31.32l43.1-132.64-112.84-82a28 28 0 0 1 16.46-50.66h139.48L492 170.34a28 28 0 0 1 53.26 0l43.1 132.64h139.48a28 28 0 0 1 16.46 50.66l-112.84 82 43.1 132.64A28 28 0 0 1 648 604.92z m-129.3-150a27.86 27.86 0 0 1 16.46 5.36l59.58 43.28-22.76-70a28 28 0 0 1 10.02-31.28l59.58-43.3H568a28 28 0 0 1-26.64-19.34l-22.76-70-22.76 70a28 28 0 0 1-26.62 19.34h-73.64l59.58 43.3a28 28 0 0 1 10.16 31.3l-22.76 70 59.58-43.28a28 28 0 0 1 16.46-5.32z" p-id="2630"></path></svg>'),
i(this, "siteSvg", '<svg t="1749107903569" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12439" width="200" height="200"><path d="M882.758621 133.674884C882.758621 59.84828 822.91034 0 749.083736 0 675.25715 0 615.40887 59.84828 615.40887 133.674884 615.40887 163.358402 625.152318 191.656395 642.813352 214.773283L670.872117 193.336726 648.314739 166.170836 253.911693 493.666092 276.469054 520.831982 302.371681 496.834595C277.256669 469.725608 241.995388 453.990153 204.295574 453.990153 130.46897 453.990153 70.62069 513.838433 70.62069 587.66502 70.62069 661.491624 130.46897 721.339904 204.295574 721.339904 255.555319 721.339904 301.619094 692.208675 324.036714 647.136344L276.646223 663.002394 706.082022 877.440106 721.856794 845.849335 690.37312 829.861888C680.932829 848.452414 675.940882 869.068818 675.940882 890.325116 675.940882 964.15172 735.789162 1024 809.615766 1024 883.442353 1024 943.290633 964.15172 943.290633 890.325116 943.290633 874.050807 940.36533 858.125365 934.723584 843.16446L868.645076 868.0826C871.294817 875.109252 872.669943 882.595452 872.669943 890.325116 872.669943 925.14899 844.439623 953.37931 809.615766 953.37931 774.791892 953.37931 746.561571 925.14899 746.561571 890.325116 746.561571 880.245089 748.902894 870.575616 753.340487 861.836782L769.436089 830.140063 737.631567 814.258564 308.195769 599.820853 276.554929 584.02108 260.805279 615.686903C250.212352 636.984797 228.494795 650.719214 204.295574 650.719214 169.4717 650.719214 141.241379 622.488894 141.241379 587.66502 141.241379 552.841163 169.4717 524.610842 204.295574 524.610842 222.12269 524.610842 238.680594 531.99985 250.566444 544.829369L273.29589 569.363385 299.026432 547.997855 693.429478 220.502616 719.514606 198.84265 698.930882 171.900169C690.596687 160.991373 686.029559 147.727007 686.029559 133.674884 686.029559 98.85101 714.25988 70.62069 749.083736 70.62069 783.90761 70.62069 812.137931 98.85101 812.137931 133.674884 812.137931 148.208022 807.249885 161.899255 798.379608 172.996785L853.543883 217.089695C872.331935 193.584128 882.758621 164.379366 882.758621 133.674884ZM749.083736 196.729062C729.149334 196.729062 710.818745 187.460449 698.930882 171.900169L642.813352 214.773283C667.922573 247.639305 706.904064 267.349751 749.083736 267.349751 790.225902 267.349751 828.357809 248.599782 853.543883 217.089695L798.379608 172.996785C786.455411 187.915034 768.530291 196.729062 749.083736 196.729062ZM337.970441 587.66502C337.970441 553.551854 325.093782 521.360666 302.371681 496.834595L250.566444 544.829369C261.309069 556.424898 267.349751 571.526356 267.349751 587.66502 267.349751 597.565263 265.091478 607.069184 260.805279 615.686903L324.036714 647.136344C333.156105 628.801148 337.970441 608.540036 337.970441 587.66502ZM809.615766 756.650249C758.753986 756.650249 712.986006 785.330865 690.37312 829.861888L753.340487 861.836782C764.027215 840.791658 785.603302 827.270938 809.615766 827.270938 836.08553 827.270938 859.461862 843.730308 868.645076 868.0826L934.723584 843.16446C915.252259 791.529949 865.714547 756.650249 809.615766 756.650249Z" fill="#389BFF" p-id="12440"></path></svg>'),
i(this, "videoSvg", '<svg t="1749003664455" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1952" width="200" height="200"><path d="M825.6 153.6H198.4C124.5 153.6 64 214.1 64 288v448c0 73.9 60.5 134.4 134.4 134.4h627.2c73.9 0 134.4-60.5 134.4-134.4V288c0-73.9-60.5-134.4-134.4-134.4z m-138.2 44.8l112 112H706l-112-112h93.4z m-156.8 0l112 112H526.7l-112-112h115.9z m-179.2 0l112 112H347.5l-112-112h115.9zM108.8 288c0-41.4 28.4-76.1 66.7-86.3l108.7 108.7H108.8V288z m806.4 448c0 49.4-40.2 89.6-89.6 89.6H198.4c-49.4 0-89.6-40.2-89.6-89.6V355.2h806.4V736z m0-425.6h-52.5l-112-112h74.9c49.4 0 89.6 40.2 89.6 89.6v22.4z" p-id="1953"></path><path d="M454 687.2l149.3-77.6c27.5-13.8 27.5-53 0-66.8L468 472.2c-31.2-15.6-68 7.1-68 42v139.6c0 27.8 29.2 45.8 54 33.4zM444.8 512l134.4 67.2-134.4 67.2V512z" p-id="1954"></path></svg>'),
i(this, "screenSvg", '<svg t="1750691468062" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2693" width="200" height="200"><path d="M288 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64v-576a64 64 0 0 0-64-64h-448m0-64h448a128 128 0 0 1 128 128v576a128 128 0 0 1-128 128h-448a128 128 0 0 1-128-128v-576a128 128 0 0 1 128-128z" fill="#4078FD" p-id="2694"></path><path d="M416 352m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" fill="#FE9C23" p-id="2695"></path><path d="M352 732.448a32 32 0 0 1-32-32v-160a32 32 0 0 1 44.224-29.568l130.112 53.632 153.952-169.984a32 32 0 0 1 55.712 21.472v284.448a32 32 0 0 1-32 32z m0-32h320z" fill="#4078FD" opacity=".2" p-id="2696"></path><path d="M672 416l-169.088 186.656-150.912-62.208v160h320V416m0-32a32 32 0 0 1 32 32v284.448a32 32 0 0 1-32 32h-320a32 32 0 0 1-32-32v-160a32 32 0 0 1 44.192-29.6l130.112 53.632 153.984-169.984a32 32 0 0 1 23.712-10.496z" fill="#4078FD" p-id="2697"></path></svg>'),
i(this, "recoveryVideoSvg", '<svg t="1749003779161" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8204" width="200" height="200"><path d="M938.666667 553.92V768c0 64.8-52.533333 117.333333-117.333334 117.333333H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V256c0-64.8 52.533333-117.333333 117.333334-117.333333h618.666666c64.8 0 117.333333 52.533333 117.333334 117.333333v297.92z m-64-74.624V256a53.333333 53.333333 0 0 0-53.333334-53.333333H202.666667a53.333333 53.333333 0 0 0-53.333334 53.333333v344.48A290.090667 290.090667 0 0 1 192 597.333333a286.88 286.88 0 0 1 183.296 65.845334C427.029333 528.384 556.906667 437.333333 704 437.333333c65.706667 0 126.997333 16.778667 170.666667 41.962667z m0 82.24c-5.333333-8.32-21.130667-21.653333-43.648-32.917333C796.768 511.488 753.045333 501.333333 704 501.333333c-121.770667 0-229.130667 76.266667-270.432 188.693334-2.730667 7.445333-7.402667 20.32-13.994667 38.581333-7.68 21.301333-34.453333 28.106667-51.370666 13.056-16.437333-14.634667-28.554667-25.066667-36.138667-31.146667A222.890667 222.890667 0 0 0 192 661.333333c-14.464 0-28.725333 1.365333-42.666667 4.053334V768a53.333333 53.333333 0 0 0 53.333334 53.333333h618.666666a53.333333 53.333333 0 0 0 53.333334-53.333333V561.525333zM320 480a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0-64a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" fill="#000000" p-id="8205"></path></svg>');
}
getName() {
throw new Error(`${this.constructor.name} 未显示getName()`);
}
getBean(e) {
return this.pluginManager.getBean(e);
}
async initCss() {
return "";
}
async handle() {}
getPageInfo() {
let e, t, n, a, i, s = window.location.href;
return r && (e = $('a[title="複製番號"]').attr("data-clipboard-text"), t = s.split("?")[0].split("#")[0],
n = $(".female").prev().map(((e, t) => $(t).text())).get().join(" "), a = $(".male").prev().map(((e, t) => $(t).text())).get().join(" "),
i = $('strong:contains("日期:")').parent(".panel-block").find(".value").text().trim()),
l && (t = s.split("?")[0], e = t.split("/").filter(Boolean).pop().replace(/_\d{4}-\d{2}-\d{2}$/, ""),
n = $('span[onmouseover*="star_"] a').map(((e, t) => $(t).text())).get().join(" "),
a = "", i = $('span.header:contains("發行日期:")').parent("p").text().trim().replace("發行日期:", "").trim()),
{
carNum: e,
url: t,
actress: n,
actors: a,
publishTime: i
};
}
getActressId() {
const e = o.match(/\/actors\/([^/?]+)/);
return e && e.length > 1 ? e[1] : null;
}
getActressPageInfo() {
let e = window.location.href;
if (!e.includes("/actors/") && !e.includes("/star/")) throw new Error("接口调用错误, 非演员详情页");
let t = [], n = r ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
n.length && n.text().trim().split(",").forEach((e => {
t.push(e.trim());
}));
let a = $(".section-meta:not(:contains('影片'))");
a.length && a.text().trim().split(",").forEach((e => {
t.push(e.trim());
}));
let i = $(".section-meta:contains('男優')").length > 0 ? B : P, s = D;
t.some((e => e.includes("無碼"))) && (s = L), e.includes("uncensored") && (s = L);
let o = null, c = null;
const d = new URL(e);
if (r) {
c = d.pathname.split("/").filter((e => "" !== e.trim())).pop();
const e = d.searchParams;
e.delete("sort_type"), e.delete("page"), o = d.toString();
} else if (l) {
const t = "/star/", n = e.split(t);
if (n.length < 2) throw new Error("提取演员url失败");
const a = n[0];
c = n[1].split("/")[0], o = a + t + c;
}
return {
starId: c,
name: t[0],
allName: t,
role: i,
movieType: s,
blacklistUrl: o
};
}
getSelector(e) {
const t = e || (r ? T : l ? I : null), n = {
javdb: {
boxSelector: ".movie-list",
itemSelector: ".movie-list .item",
coverImgSelector: ".cover img",
requestDomItemSelector: ".movie-list .item",
nextPageSelector: ".pagination-next"
},
javbus: {
boxSelector: ".masonry",
itemSelector: ".masonry .item",
coverImgSelector: ".masonry .movie-box .photo-frame img",
requestDomItemSelector: "#waterfall .item",
nextPageSelector: "#next"
}
};
if (!t || !n[t]) throw new Error("类型错误: 无法确定选择器类型 (JavDb 或 JavBus)");
return n[t];
}
parseMovieId(e) {
return e.split("/").pop().split(/[?#]/)[0];
}
}
class K extends R {
getName() {
return "DetailPagePlugin";
}
constructor() {
super();
}
handle() {
window.isDetailPage && ($(".video-meta-panel a").each((function() {
const e = $(this).attr("href");
e && (e.startsWith("http://") || e.startsWith("https://") || e.startsWith("/")) && $(this).attr("target", "_blank");
})), this.handleFancyBox());
}
handleFancyBox() {
if (document.addEventListener("click", (function(e) {
if (e.target.closest(".fancybox-button--thumbs")) {
const e = !$(".fancybox-thumbs").is(":hidden");
localStorage.setItem("jhs_fancyboxThumbs", e.toString()), unsafeWindow.$.fancybox.defaults.thumbs.autoStart = e;
}
})), void 0 !== unsafeWindow.$.fancybox) {
const e = localStorage.getItem("jhs_fancyboxThumbs");
unsafeWindow.$.fancybox.defaults.thumbs.autoStart = "true" === e;
}
}
}
const W = (e, t) => {
if (!e || 0 === e.length) return null;
const n = new Set(e);
if (n.has(t)) return t;
const a = A.map((e => e.quality)).reverse();
for (const i of a) if (n.has(i)) return i;
return e[0];
}, q = "jhs_dmm_video";
class J {
constructor(e, t = !0) {
this.carNum = e, this.showErrorMessages = t;
}
_checkCache() {
const e = localStorage.getItem(q) ? JSON.parse(localStorage.getItem(q)) : {};
return e[this.carNum] ? (clog.debug("缓存中存在预览视频信息", e[this.carNum]), e[this.carNum]) : null;
}
_updateCache(e) {
const t = localStorage.getItem(q) ? JSON.parse(localStorage.getItem(q)) : {};
t[this.carNum] = e, clog.debug("成功解析出预览视频并已缓存:", e), localStorage.setItem(q, JSON.stringify(t));
}
async _searchContentIds() {
const e = this.carNum, t = e.replace(/-/g, ""), n = [ {
keyword: e.replace("-", "00"),
name: "00-替换关键词"
}, {
keyword: e,
name: "原始番号关键词"
}, {
keyword: t,
name: "无连字符关键词"
} ], a = e.toLowerCase();
for (const o of n) {
const {keyword: e, name: n} = o, i = e.toLowerCase();
clog.debug(`--- 尝试使用 ${n} (${e}) 进行 API 搜索 ---`);
const r = `https://api.dmm.com/affiliate/v3/ItemList?${new URLSearchParams({
api_id: "UrwskPfkqQ0DuVry2gYL",
affiliate_id: "10278-996",
output: "json",
site: "FANZA",
sort: "match",
keyword: e
}).toString()}`;
let l;
try {
l = await gmHttp.get(r);
} catch (s) {
clog.error(`API 请求失败,跳过 ${n}:`, s);
continue;
}
if (!l || !l.result || !l.result.result_count) {
clog.debug("API 返回无结果,尝试下一个关键词。");
continue;
}
const c = [];
for (const s of l.result.items) {
if (c.length >= 2) break;
const e = s.content_id || "", o = s.maker_product || "";
(e.includes(i.replace("-", "")) || a === o.toLowerCase() || e.includes(t.toLowerCase())) && (c.push({
serviceCode: s.service_code,
floorCode: s.floor_code,
contentId: e,
pageUrl: s.URL
}), clog.debug(`[${n}] cid|makerProduct 匹配成功:`, e, o));
}
if (c.length > 0) {
clog.debug(`--- 成功通过 ${n} 找到 Content IDs ---`);
const t = $("#fanzaBtn");
let a = `https://www.dmm.co.jp/search/=/searchstr=${e}`, i = "single";
c.length > 1 ? (t.attr("href", a), t.append('<span class="site-tag" style="top:-15px">多结果</span>'),
t.css("backgroundColor", "#7bc73b"), i = "multiple") : (a = c[0].pageUrl, t.attr("href", a),
t.css("backgroundColor", "#7bc73b"));
const s = "jhs_other_site_dmm", o = localStorage.getItem(s) ? JSON.parse(localStorage.getItem(s)) : {};
return o[this.carNum] = {
type: i,
url: a
}, localStorage.setItem(s, JSON.stringify(o)), c;
}
clog.debug(`[${n}] API 返回结果数 ${l.result.result_count},但无精确匹配的 Content ID。`);
}
clog.warn("所有关键词尝试均未找到匹配的 Content ID。");
const i = $("#fanzaBtn");
return i.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`),
i.attr("title", "未查询到, 点击前往搜索页"), i.css("backgroundColor", "#de3333"), null;
}
async _extractTrailerLinks({contentId: e, serviceCode: t, floorCode: n}) {
const a = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${e}/mtype=AhRVShI_/service=${t}/floor=${n}/mode=/`, i = await gmHttp.get(a, null, {
"accept-language": "ja-JP,ja;q=0.9",
Cookie: "age_check_done=1"
});
if ("string" == typeof i && i.includes("このサービスはお住まいの地域からは")) throw new Error("节点不可用,请将DMM域名分流到日本ip");
const s = i.match(/const\s+args\s+=\s+(.*);/);
if (!s) throw new Error("未在脚本中找到 const args = ... 变量");
let o;
try {
({bitrates: o} = JSON.parse(s[1]));
} catch (d) {
throw new Error(`解析播放器脚本 JSON 失败: ${d.message}`);
}
const r = {}, l = A.map((e => e.quality)).join("|"), c = new RegExp(`(${l})\\.mp4$`);
if (!Array.isArray(o)) throw clog.error("解析画质链接失败: bitrates 字段不是一个数组或不存在"), new Error("解析画质链接失败: bitrates 字段不是一个数组或不存在");
clog.debug("原始数据返回:", o);
for (const h of o) {
const e = null == h ? void 0 : h.src;
if (!e || "string" != typeof e || !e.endsWith(".mp4")) continue;
const t = e.match(c);
let n = "";
t && t[1] && (n = t[1]), n && !r[n] && (r[n] = e);
}
if (0 === Object.keys(r).length) throw new Error("未找到匹配要求的预览画质视频");
return r;
}
async fetchVideo() {
const e = this._checkCache();
if (e) return e;
let t;
try {
const e = this.carNum.toLowerCase();
if (e.startsWith("heyzo") || /^(n\d+|\d+(-\d+)*)$/.test(e) || /^n\d+$/.test(e)) throw new Error("无码番号类型, 取消dmm解析");
t = await this._searchContentIds();
} catch (n) {
clog.error("DMM API 搜索失败:", n);
const e = $("#fanzaBtn");
return e.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`),
e.attr("title", "未查询到, 点击前往搜索页"), e.css("backgroundColor", "#de3333"), null;
}
if (!t || 0 === t.length) return null;
try {
const e = await Promise.any(t.map((e => this._extractTrailerLinks(e))));
return this._updateCache(e), e;
} catch (a) {
const e = a.errors || [ a ];
if (e.some((e => e.message.includes("节点不可用")))) this.showErrorMessages && show.error("节点不可用,请将DMM域名分流到日本ip"); else {
const t = e[0].message || e[0];
clog.error(`解析失败: ${t}`, e), this.showErrorMessages && show.error(`解析失败: ${t}`);
}
const t = $("#fanzaBtn");
return t.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`),
t.attr("title", "未查询到, 点击前往搜索页"), t.css("backgroundColor", "#de3333"), null;
}
}
}
const G = async (e, t = !0) => new J(e, t).fetchVideo();
class Y extends R {
getName() {
return "PreviewVideoPlugin";
}
async initCss() {
return "\n .video-control-btn {\n min-width:120px;\n padding: 7px 12px;\n font-size: 12px;\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 ";
}
async handle() {
if (!isDetailPage) return;
let e = await storageManager.getSetting();
this.filterHotKey = e.filterHotKey, this.favoriteHotKey = e.favoriteHotKey, this.speedVideoHotKey = e.speedVideoHotKey;
let t = $(".preview-video-container");
t.on("click", (e => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
this.handleVideo().then();
}));
}));
if (await storageManager.getSetting("enableLoadPreviewVideo", _) === _ && !o.includes("autoPlay=1")) {
let e = await storageManager.getSetting("videoQuality");
clog.debug("解析其它画质预览视频", "设置-期望画质", e), G(this.getPageInfo().carNum, !1).then((n => {
if (n) {
e = W(Object.keys(n), e);
let a = n[e];
clog.log("切换其它画质预览视频: ", a);
let i = $("#preview-video");
if (t.length) i.attr("src", a); else {
clog.debug("JavDB没有视频播放元素, 开始创建...");
const e = $(".column-video-cover img").attr("src");
$(".preview-images").prepend(`<a class="preview-video-container" data-fancybox="gallery" href="#preview-video">\n <span>預告片</span>\n <img src="${e}" class="video-cover" style="width: 150px; height: auto;">\n </a>\n `),
t = $(".preview-video-container"), t.on("click", (e => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
this.handleVideo().then();
}));
}));
}
!utils.isHidden(i) && i.length && i[0].play();
}
}));
}
let n = window.location.href;
(n.includes("gallery-1") || n.includes("gallery-2")) && utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
$(".fancybox-content #preview-video").length > 0 && this.handleVideo().then();
})), n.includes("autoPlay=1") && t.length > 0 && t[0].click();
}
async handleVideo() {
if (await storageManager.getSetting("enableLoadPreviewVideo", _) === C) return;
const e = $("#preview-video");
if (!e.length) return;
const t = e.parent();
t.css("position", "relative");
const n = e[0], a = localStorage.getItem("jhs_videoMuted");
a && (n.muted = "yes" === a), n.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", n.muted ? "yes" : "no");
})), n.play();
let i = this.getPageInfo().carNum;
const s = await G(i);
let o = $("<div></div>").attr("id", "video-bottom-toolbar").css({
display: "flex",
gap: "5px",
"align-items": "center",
"flex-wrap": "wrap"
}), r = $("<div></div>").css({
display: "flex",
gap: "5px",
"align-items": "center"
}), l = null;
if (s) {
let t = await storageManager.getSetting("videoQuality");
l = W(Object.keys(s), t);
let a = s[l];
e.attr("src") !== a && (e.attr("src", a), n.load(), n.play()), A.forEach((e => {
let t = s[e.quality];
if (t) {
const n = l === e.quality;
let a = $(`\n <button class="video-control-btn${n ? " active" : ""}" \n id="${e.id}" \n data-quality="${e.quality}"\n data-video-src="${t}"\n style="min-width: 40px; border: 1px solid #ccc; background-color: ${n ? "#007bff" : "#fff"}; color: ${n ? "white" : "black"};">\n ${e.text}\n </button>\n `);
r.append(a);
}
}));
}
o.append(r);
let c = $("<div></div>").css({
display: "flex",
gap: "5px",
"align-items": "center",
"margin-left": "auto"
}), d = $(`<button class="menu-btn" id="video-filterBtn" style="min-width: 120px; background-color:#de3333;">屏蔽 ${this.filterHotKey ? "(" + this.filterHotKey + ")" : ""}</button>`);
c.append(d);
let h = $(`<button class="menu-btn" id="video-favoriteBtn" style="min-width: 120px; background-color:#25b1dc;">收藏 ${this.favoriteHotKey ? "(" + this.favoriteHotKey + ")" : ""}</button>`);
c.append(h);
let g = $(`<button class="menu-btn" id="speed-btn" style="min-width: 120px; background-color:#76b45d;">快进 ${this.speedVideoHotKey ? "(" + this.speedVideoHotKey + ")" : ""}</button>`);
c.append(g), o.append(c), t.append(o), o.on("click", ".video-control-btn", (async t => {
const a = $(t.currentTarget), i = a.data("video-src");
if (!a.hasClass("active")) try {
const t = n.currentTime;
e.attr("src", i), n.load(), n.currentTime = t, await n.play(), o.find(".video-control-btn").removeClass("active").css({
"background-color": "#fff",
color: "black"
}), a.addClass("active").css({
"background-color": "#007bff",
color: "white"
});
} catch (s) {
console.error("切换画质失败:", s);
}
})), $("#speed-btn").on("click", (() => {
this.getBean("DetailPageButtonPlugin").speedVideo();
})), utils.rightClick(document.body, "#speed-btn", (e => {
this.getBean("DetailPageButtonPlugin").filterOne(e);
})), $("#video-filterBtn").on("click", (e => {
this.getBean("DetailPageButtonPlugin").filterOne(e);
})), $("#video-favoriteBtn").on("click", (e => {
this.getBean("DetailPageButtonPlugin").favoriteOne(e);
}));
}
}
const X = class e {
constructor() {
if (new.target === e) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(e, t, n = null) {
if (Array.isArray(e)) {
let a = [];
return e.forEach((e => {
if (!this.isHotkeyFormat(e)) throw new Error("快捷键格式错误");
let i = this.recordHotkey(e, t, n);
a.push(i);
})), 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);
return this.registerHotKeyMap.set(a, {
hotkeyString: e,
callback: t,
keyupCallback: n
}), 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"), s = 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 === s && t.key.toLowerCase() === o;
}
};
i(X, "isMac", 0 === navigator.platform.indexOf("Mac")), i(X, "registerHotKeyMap", new Map),
i(X, "handleKeydown", (e => {
for (const [t, n] of X.registerHotKeyMap) {
let t = n.hotkeyString, a = n.callback;
X.judgeHotkey(t, e) && a(e);
}
})), i(X, "handleKeyup", (e => {
for (const [t, n] of X.registerHotKeyMap) {
let t = n.hotkeyString, a = n.keyupCallback;
a && (X.judgeHotkey(t, e) && a(e));
}
}));
let Q = X;
document.addEventListener("keydown", (e => {
Q.handleKeydown(e);
})), document.addEventListener("keyup", (e => {
Q.handleKeyup(e);
}));
class Z extends R {
getName() {
return "JavTrailersPlugin";
}
constructor() {
super(), this.hasBand = !1;
}
handle() {
let e = window.location.href;
if (!e.includes("handle=1")) return;
if ($("h1:contains('Page not found')").length) {
console.log("番号无法匹配, 跳搜索");
let t = e.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
return void (window.location.href = "/search/" + encodeURIComponent(t) + window.location.search);
}
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) return void (window.location.href = $(a).attr("href") + window.location.search);
}
this.handlePlayJavTrailers(), $("#videoPlayerContainer").on("click", (() => {
this.handlePlayJavTrailers();
})), window.addEventListener("message", (e => {
let t = document.getElementById("vjs_video_3_html5_api");
t && (t.currentTime += 5);
}));
const n = new URLSearchParams(window.location.search), a = n.get("filterHotKey"), i = n.get("favoriteHotKey"), s = n.get("speedVideoHotKey");
a && Q.registerHotkey(a, (() => window.parent.postMessage(a, "*"))), i && Q.registerHotkey(i, (() => window.parent.postMessage(i, "*"))),
s && Q.registerHotkey(s, (() => {
const e = document.getElementById("vjs_video_3_html5_api");
e && (e.currentTime += 5);
}));
}
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");
console.log(e), 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"
});
}), 100);
})), utils.loopDetector((() => $("#vjs_video_3 canvas").length > 0), (() => {
0 !== $("#vjs_video_3 canvas").length && $("#vjs_video_3 canvas").css({
position: "fixed",
width: "100vw",
height: "100vh",
objectFit: "cover",
top: "0",
right: "0",
zIndex: "999999998"
});
})));
}
}
class ee extends R {
getName() {
return "SubTitleCatPlugin";
}
handle() {
$(".t-banner-inner").hide(), $("#navbar").hide();
let e = new URLSearchParams(window.location.search).get("search").toLowerCase(), t = $(".sub-table tr td a").toArray(), n = 0;
t.forEach((t => {
let a = $(t);
a.text().toLowerCase().includes(e) ? n++ : a.parent().parent().hide();
})), 0 === n && show.error("该番号无字幕!");
const a = $(".sec-title"), i = a.html().replace(/^\d+/, n);
a.html(i);
}
}
const te = "https://jdforrepam.com/api";
async function ne() {
const e = "jhs_review_ts", t = "jhs_review_sign", n = Math.floor(Date.now() / 1e3);
if (n - (localStorage.getItem(e) || 0) <= 20) return localStorage.getItem(t);
const a = `${n}.lpw6vgqzsp.${md5(`${n}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
return localStorage.setItem(e, n), localStorage.setItem(t, a), a;
}
const ae = async (e, t = 1, n = 20) => {
let a = `${te}/v1/movies/${e}/reviews`, i = {
jdSignature: await ne()
};
return (await gmHttp.get(a, {
page: t,
sort_by: "hotly",
limit: n
}, i)).data.reviews;
}, ie = async e => {
let t = `${te}/v4/movies/${e}`, n = {
jdSignature: await ne()
};
const a = await gmHttp.get(t, null, n);
if (!a.data) throw show.error("获取视频详情失败: " + a.message), new Error(a.message);
const i = a.data.movie, s = i.preview_images, o = [];
return s.forEach((e => {
o.push(e.large_url.replace("https://tp-iu.cmastd.com/rhe951l4q", "https://c0.jdbstatic.com"));
})), {
movieId: i.id,
actors: i.actors,
duration: i.duration,
title: i.origin_title,
carNum: i.number,
score: i.score,
releaseDate: i.release_date,
watchedCount: i.watched_count,
imgList: o
};
}, se = async (e, t = 1, n = 20) => {
let a = `${te}/v1/lists/related?movie_id=${e}&page=${t}&limit=${n}`, i = {
jdSignature: await ne()
};
const s = await gmHttp.get(a, null, i, 3e3), o = [];
return s.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)
});
})), o;
}, oe = async (e = "daily", t = "high_score") => {
let n = `${te}/v1/rankings/playback?period=${e}&filter_by=${t}`, a = {
jdSignature: await ne()
};
return (await gmHttp.get(n, null, a)).data.movies;
}, re = async (e = "all", t = "", n = 1, a = 40) => {
let i = `${te}/v1/movies/top?start_rank=1&type=${e}&type_value=${t}&ignore_watched=false&page=${n}&limit=${a}`, s = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
authorization: "Bearer " + localStorage.getItem("jhs_appAuthorization"),
jdsignature: await ne()
};
return await gmHttp.get(i, null, s);
};
class le extends R {
getName() {
return "Fc2Plugin";
}
async initCss() {
return "\n <style>\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 margin: 40px;\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 .fc2-movie-panel-info .panel-block {\n padding: 0 !important;\n }\n </style>\n ";
}
handle() {
let e = "/advanced_search?type=3&score_min=0&d=1";
if ($('.navbar-item:contains("FC2")').attr("href", e), $('.tabs a:contains("FC2")').attr("href", e),
o.includes("advanced_search?type=3")) {
$("h2.section-title").contents().first().replaceWith("Fc2PPV"), $(".section .container > .box").remove();
}
if (o.includes("collection_codes?movieId")) {
$("section").html("");
const e = new URLSearchParams(window.location.search);
let t = e.get("movieId"), n = e.get("carNum"), a = e.get("url");
t && n && a && this.openFc2Dialog(t, n, a);
}
}
openFc2Dialog(e, t, n) {
let a = t.replace("FC2-", "");
if (n.includes("123av")) return void this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(t, n);
let i = `\n <div class="movie-detail-container">\n \x3c!--<div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>--\x3e\n \x3c!-- <div class="right-box">--\x3e\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n \n <div class="movie-panel-info fc2-movie-panel-info" style="margin-top:20px"><strong>第三方资源: </strong></div>\n \n <div style="margin: 30px 0">\n <a id="filterBtn" class="menu-btn" style="background-color:${f}"><span>${u}</span></a>\n <a id="favoriteBtn" class="menu-btn" style="background-color:${w}"><span>${v}</span></a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:${x}"><span>${y}</span></a>\n <a id="hasWatchBtn" class="menu-btn" style="background-color:${S};"><span>${k}</span></a>\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 <a id="magnetSearchBtn" class="menu-btn fr-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </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 <div id="related-content">\n </div>\n <span id="data-actress" style="display: none"></span>\n \x3c!--</div>--\x3e\n </div>\n `;
layer.open({
type: 1,
title: t,
content: i,
area: utils.getResponsiveArea([ "70%", "90%" ]),
skin: "movie-detail-layer",
scrollbar: !1,
success: (i, s) => {
this.loadData(e, t), $("#favoriteBtn").on("click", (async e => {
const a = $("#data-actress").text(), i = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: t,
url: n,
names: a,
actionType: h,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#filterBtn").on("click", (e => {
utils.q(e, `是否屏蔽${t}?`, (async () => {
const e = $("#data-actress").text(), a = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: t,
url: n,
names: e,
actionType: d,
publishTime: a
}), window.refresh(), layer.closeAll(), window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
})), $("#hasDownBtn").on("click", (async e => {
const a = $("#data-actress").text(), i = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: t,
url: n,
names: a,
actionType: g,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#hasWatchBtn").on("click", (async e => {
const a = $("#data-actress").text(), i = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: t,
url: n,
names: a,
actionType: p,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#search-subtitle-btn").on("click", (e => utils.openPage(`https://subtitlecat.com/index.php?search=${t}`, t, !1, e))),
$("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(t))),
$("#magnetSearchBtn").on("click", (() => {
let e = this.getBean("MagnetHubPlugin").createMagnetHub(t);
layer.open({
type: 1,
title: "磁力搜索",
content: '<div id="magnetHubBox"></div>',
area: utils.getResponsiveArea([ "60%", "80%" ]),
scrollbar: !1,
success: () => {
$("#magnetHubBox").append(e);
}
});
})), this.getBean("OtherSitePlugin").loadOtherSite(a, t).then(), utils.setupEscClose(s);
},
end() {
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}
});
}
loadData(e, t) {
let n = t.replace("FC2-", "");
this.handleMovieDetail(e), this.handleLongImg(n), this.handleMagnets(e);
this.getBean("ReviewPlugin").showReview(e, $("#reviews-content")).then(), this.getBean("RelatedPlugin").showRelated($("#related-content"), e).then();
}
handleMovieDetail(e) {
ie(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"><strong class="current-title">${e.title || "无标题"}</strong></h3>\n <div class="movie-meta">\n <span><strong>番号: </strong>${e.carNum || "未知"}</span>\n <span><strong>年份: </strong>${e.releaseDate || "未知"}</span>\n <span><strong>评分: </strong>${e.score || "无"}</span>\n <span><strong>时长: </strong>${e.duration + " m" || "无"}</span>\n </div>\n <div class="movie-meta">\n <span>\n <strong>站点: </strong>\n <a href="https://fc2ppvdb.com/articles/${e.carNum.replace("FC2-", "")}" target="_blank">fc2ppvdb</a>\n <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${e.carNum.replace("FC2-", "")}/" target="_blank">fc2电子市场</a>\n </span>\n </div>\n <div class="movie-actors">\n <div class="actor-list"><strong>主演: </strong>${a}</div>\n </div>\n <div class="movie-gallery" style="margin-top:10px">\n <strong>剧照: </strong>\n <div class="image-list">${i}</div>\n </div>\n <div id="data-releaseDate" style="display: none">${e.releaseDate || ""}</div>\n `),
this.getBean("TranslatePlugin").translate(e.carNum, !1).then();
})).catch((e => {
console.error(e), $(".movie-info-container").html(`\n <div class="movie-error">加载失败: ${e.message}</div>\n `);
}));
}
handleLongImg(e) {
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
$(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
const t = this.getBean("ScreenShotPlugin"), n = await t.getScreenshot(e);
n && await t.addImg("缩略图", n);
}));
}
handleMagnets(e) {
(async e => {
let t = `${te}/v1/movies/${e}/magnets`, n = {
jdSignature: await ne()
};
return (await gmHttp.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), $(".buttons button[data-clipboard-text*='magnet:']").each(((e, t) => {
$(t).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async e => {
e.stopPropagation(), e.preventDefault();
let n = loading();
try {
await this.getBean("WangPan115TaskPlugin").handleAddTask($(t).attr("data-clipboard-text"));
} catch (a) {
show.error("发生错误:" + a), console.error(a);
} finally {
n.close();
}
})));
}));
})).catch((e => {
console.error(e), $("#magnets-content").html(`\n <div class="movie-error">加载失败: ${e.message}</div>\n `);
}));
}
async openFc2Page(e, t, n) {
const a = this.getBean("OtherSitePlugin");
let i = await a.getJavDbUrl();
window.open(`${i}/users/collection_codes?movieId=${e}&carNum=${t}&url=${n}`);
}
}
class ce extends R {
getName() {
return "HighlightMagnetPlugin";
}
doFilterMagnet() {
this.handleDb(), this.handleBus();
}
handleDb() {
if (!r) return;
let e = $("#magnets-content .name");
if (0 === e.length) return;
const t = [ "4k", "-c", "-u", "-uc" ];
let n = !1;
e.each(((e, a) => {
const i = $(a), s = i.text().toLowerCase(), o = t.some((e => s.includes(e)));
i.parent().parent().parent().addClass("magnet-row"), s.includes("4k") && i.css("color", "#f40"),
o && (n = !0, i.parent().parent().parent().addClass("high-quality"));
})), n ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-hide");
}
handleBus() {
l && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
const e = $("#magnet-table tr"), t = [ "4k", "-c", "-u", "-uc" ];
let n = !1;
e.each(((e, a) => {
const i = $(a), s = i.find("td:first-child"), o = s.find("a:first-child"), r = s.find("a:nth-child(2)"), l = o.text().toLowerCase();
l.includes("4k") && o.css("color", "#f40");
(t.some((e => l.includes(e))) || r.length && r.text().includes("字幕")) && (n = !0,
i.addClass("high-quality"));
})), n ? e.each(((e, t) => {
const n = $(t);
n.hasClass("high-quality") || n.hide();
})) : $("#enable-magnets-filter").addClass("do-hide");
}));
}
showAll() {
if (r) {
$("#magnets-content .item").toArray().forEach((e => $(e).show()));
}
l && $("#magnet-table tr").toArray().forEach((e => $(e).show()));
}
}
class de extends R {
getName() {
return "FoldCategoryPlugin";
}
async initCss() {
const e = await storageManager.getSetting();
return `\n <style>\n #tags a.tag, .tags a.tag {\n position:relative;\n }\n .highlight-btn {\n position: absolute;\n top: -10px;\n right: -10px;\n background-color: #4CAF50;\n color: white;\n border: none;\n border-radius: 50%;\n width: 24px;\n height: 24px;\n font-size: 14px;\n line-height: 24px;\n text-align: center;\n cursor: pointer;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n display: none;\n z-index: 999;\n }\n /* 当父元素被高亮时,按钮变为其他颜色 */\n .highlighted .highlight-btn {\n background-color: #FF5722;\n }\n /* 高亮状态下的标签样式 */\n .highlighted {\n /* 浅黄色 */\n border: ${e.highlightedTagNumber || 1}px solid ${e.highlightedTagColor || "#ce2222"};\n }\n </style>\n `;
}
async handle() {
window.isListPage && (o.includes("advanced_search") || (this.highlightTag(), utils.loopDetector((() => $("#waitCheckBtn").length), (() => {
this.createFoldBtn();
}), 1, 1e4, !0)));
}
highlightTag() {
(async () => {
const e = await storageManager.getHighlightedTags();
e && e.forEach((e => {
$(`#tags a.tag:contains(${e})`).addClass("highlighted"), $(`.tags a.tag:contains(${e})`).addClass("highlighted");
}));
})().then(), $("#tags a.tag, .tags a.tag").hover((function() {
const e = $(this), t = $('<button class="highlight-btn" title="高亮显示">★</button>');
e.append(t), t.fadeIn(0);
}), (function() {
$(this).find(".highlight-btn").fadeOut(0, (function() {
$(this).remove();
}));
})), $(document).on("click", ".highlight-btn", (async function(e) {
e.stopPropagation(), e.preventDefault();
const t = $(this).closest("a.tag"), n = t.clone();
n.find(".highlight-btn").remove();
const a = n.text().trim().replace(/\s*\(\d+\)$/, "");
let i = await storageManager.getHighlightedTags();
i.includes(a) ? (i = i.filter((e => e !== a)), t.removeClass("highlighted")) : (i.push(a),
t.addClass("highlighted")), await storageManager.setHighlightedTags(i);
}));
}
async createFoldBtn() {
let e = $("#tags"), t = $("#tags dl div.tag.is-info").map((function() {
return $(this).text().replaceAll("\n", "").replaceAll(" ", "");
})).get().join(" ");
if (!t) return;
$(".tabs").append(`\n <div style="display: flex;align-items: center;flex-grow:1;justify-content: flex-end;">\n <a class="menu-btn main-tab-btn" id="foldCategoryBtn" style="background-color:#d23e60 !important;">\n <span></span>\n <i style="margin-left: 10px"></i>\n </a>\n <div style="margin-left:10px"><span>已选分类: ${t}</span></div>\n </div>\n `);
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")), !e) return;
let a = $("#foldCategoryBtn"), i = localStorage.getItem("jhs_foldCategory") === _, [s, o] = i ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
a.find("span").text(s).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, localStorage.setItem("jhs_foldCategory", i ? _ : C);
const [n, s] = i ? [ "展开", "icon-angle-double-down" ] : [ "折叠", "icon-angle-double-up" ];
a.find("span").text(n).end().find("i").attr("class", s), e[i ? "hide" : "show"]();
}));
}
}
class he extends R {
constructor() {
super(...arguments), i(this, "apiUrl", "https://ja.wikipedia.org/wiki/");
}
getName() {
return "ActressInfoPlugin";
}
async handle() {
"yes" === await storageManager.getSetting("enableLoadActressInfo", "yes") && this.loadActressInfo();
}
loadActressInfo() {
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() {
if ($(".actress-info").length > 0) return;
let e = $(".female").prev().map(((e, t) => $(t).text().trim())).get();
if (!e.length) return;
const t = "jhs_actress_info", n = localStorage.getItem(t) ? JSON.parse(localStorage.getItem(t)) : {};
let a = null, i = "";
for (let o = 0; o < e.length; o++) {
let t = e[o];
if (a = n[t], !a) try {
a = await this.searchInfo(t), a && (n[t] = a);
} catch (s) {
console.error("该名称查询失败,尝试其它名称");
}
let r = "";
r = a ? `\n <div class="panel-block actress-info">\n <strong>${t}:</strong>\n <a href="${a.url}" style="margin-left: 5px" target="_blank">\n <span class="info-tag">${a.birthday} ${a.age}</span>\n <span class="info-tag">${a.height} ${a.weight}</span>\n <span class="info-tag">${a.threeSizeText} ${a.braSize}</span>\n </a>\n </div>\n ` : `<div class="panel-block actress-info"><a href="${this.apiUrl + t}" target="_blank"><strong>${t}:</strong></a></div> `,
i += r;
}
$('strong:contains("演員")').parent().after(i), localStorage.setItem(t, JSON.stringify(n));
}
async handleStarPage() {
if ($(".actress-info").length > 0) return;
let e = [], t = $(".actor-section-name");
t.length && t.text().trim().split(",").forEach((t => {
e.push(t.trim());
}));
let n = $(".section-meta:not(:contains('影片'))");
if (n.length && n.text().trim().split(",").forEach((t => {
e.push(t.trim());
})), !e.length) return;
const a = "jhs_actress_info", i = localStorage.getItem(a) ? JSON.parse(localStorage.getItem(a)) : {};
let s = null;
for (let l = 0; l < e.length; l++) {
let t = e[l];
if (s = i[t], s) break;
try {
s = await this.searchInfo(t);
} catch (r) {
console.error("该名称查询失败,尝试其它名称");
}
if (s) break;
}
s && e.forEach((e => {
i[e] = s;
}));
let o = '<div class="actress-info" style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>';
s && (o = `\n <a class="actress-info" href="${s.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;">出生日期: ${s.birthday}</span>\n <span style="width: 200px;">年龄: ${s.age}</span>\n <span style="width: 200px;">身高: ${s.height}</span>\n </div>\n <div style="display: flex; margin-bottom: 10px;">\n <span style="width: 300px;">体重: ${s.weight}</span>\n <span style="width: 200px;">三围: ${s.threeSizeText}</span>\n <span style="width: 200px;">罩杯: ${s.braSize}</span>\n </div>\n </div>\n </a>\n `),
t.parent().append(o), localStorage.setItem(a, JSON.stringify(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 s = i.find('a[title="誕生日"]').parent().parent().find("td").text().trim(), o = i.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt(i.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", r = 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();
return "― kg" === l && (l = ""), {
birthday: s,
age: o,
height: r,
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 ge extends R {
getName() {
return "AliyunPanPlugin";
}
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) return void alert("请先登录!");
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 pe extends R {
constructor() {
super(), i(this, "$contentBox", $(".section .container"));
}
getName() {
return "HitShowPlugin";
}
handle() {
$('a[href*="rankings/playback"]').on("click", (e => {
e.preventDefault(), e.stopPropagation(), window.location.href = "/advanced_search?handlePlayback=1&period=daily";
})), this.handlePlayback().then();
}
hookPage() {
let e = $("h2.section-title");
e.contents().first().replaceWith("热播"), e.css("marginBottom", "0"), $(".empty-message").remove(),
$(".section .container .box").remove(), $("#sort-toggle-btn").remove(), this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'),
this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>');
}
async handlePlayback() {
if (!window.location.href.includes("handlePlayback=1")) return;
let e = new URLSearchParams(window.location.search).get("period");
this.toolBar(e), this.hookPage();
let t = $(".movie-list");
t.html("");
let n = loading();
let a = !1;
for (let s = 1; s <= 3 && !a; s++) try {
const n = await oe(e);
let i = this.markDataListHtml(n);
t.html(i), this.loadScore(n), a = !0;
} catch (i) {
s < 3 ? (clog.error(`获取热播数据失败 (第 ${s} 次重试)`, i), await new Promise((e => setTimeout(e, 1e3)))) : clog.error("所有重试尝试均失败,无法获取数据。", i);
} finally {
(a || 3 === s) && n.close();
}
}
toolBar(e) {
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="/advanced_search?handlePlayback=1&period=daily">日榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === e ? "is-info" : ""}" href="/advanced_search?handlePlayback=1&period=weekly">周榜</a>\n <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === e ? "is-info" : ""}" href="/advanced_search?handlePlayback=1&period=monthly">月榜</a>\n </div>\n </div>\n `;
this.$contentBox.append(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 () => {
let t = "jhs_score_info";
for (const a of e) try {
const e = a.id;
if (!$(`#score_${e}`).length) return;
if ($(`#${e}`).is(":hidden")) continue;
const n = localStorage.getItem(t) ? JSON.parse(localStorage.getItem(t)) : {}, i = n[e];
if (i) {
this.appendScoreHtml(e, i);
continue;
}
for (;!document.hasFocus(); ) await new Promise((e => setTimeout(e, 500)));
const s = await ie(e);
let o = s.score, r = s.watchedCount, l = `\n <span class="value">\n <span class="score-stars">${this.getStarRating(o)}</span> \n ${o}分,由${r}人評價\n </span>\n `;
this.appendScoreHtml(e, l), n[e] = l, localStorage.setItem(t, JSON.stringify(n)),
await new Promise((e => setTimeout(e, 500)));
} catch (n) {
clog.error(`🚨 解析评分数据失败 | 编号: ${a.number}\n`, `错误详情: ${n.message}\n`, n.stack ? `调用栈:\n${n.stack}` : "");
}
})();
}
appendScoreHtml(e, t) {
let n = $(`#score_${e}`);
n.length && "" === n.html().trim() && n.slideUp(0, (function() {
$(this).html(t).slideDown(500);
}));
}
markDataListHtml(e) {
let t = "";
return 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 `;
})), t;
}
}
class ue extends R {
constructor() {
super(), i(this, "has_cnsub", ""), i(this, "$contentBox", $(".section .container")),
i(this, "movies", []);
}
getName() {
return "TOP250Plugin";
}
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();
}
hookPage() {
$("h2.section-title").contents().first().replaceWith("Top250"), $(".empty-message").remove(),
$(".section .container .box").remove(), $("#sort-toggle-btn").remove(), this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'),
this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>'),
this.renderPagination();
}
renderPagination() {
const e = new URLSearchParams(window.location.search);
let t = parseInt(e.get("page")) || 1;
this.$contentBox.append((e => {
const t = e >= 5;
let n = "";
for (let a = 1; a <= 5; a++) {
n += `<li><a class="pagination-link ${e === a ? "is-current" : ""}" data-page="${a}">${a}</a></li>`;
}
return `\n <nav class="pagination">\n <a class="pagination-previous ${e <= 1 ? "do-hide" : ""}" data-page="${e - 1}">上一頁</a>\n <a class="pagination-next ${t ? "do-hide" : ""}" data-page="${e + 1}">下一頁</a>\n \n <ul class="pagination-list">\n ${n}\n </ul>\n </nav>\n `;
})(t)), this.$contentBox.on("click", ".pagination-link, .pagination-previous, .pagination-next", (t => {
t.preventDefault();
const n = parseInt($(t.currentTarget).data("page"));
!isNaN(n) && n > 0 && (t => {
e.set("page", t), window.history.pushState({}, "", "?" + e.toString()), window.location.reload();
})(n);
}));
}
async handleTop() {
if (!window.location.href.includes("handleTop=1")) return;
const e = new URLSearchParams(window.location.search);
let t = e.get("handleType") || "all", n = e.get("type_value") || "";
this.has_cnsub = e.get("has_cnsub") || "";
let a = e.get("page") || 1;
this.toolBar(t, n, a), this.hookPage();
let i = $(".movie-list");
i.html("");
let s = loading();
let o = !1;
for (let l = 1; l <= 3 && !o; l++) try {
const e = await re(t, n, a, 50);
let r = e.success, l = e.message, c = e.action;
if (1 === r) {
let t = e.data.movies;
if (0 === t.length) return show.error("无数据"), void s.close();
this.movies = t;
const n = t.filter((e => "1" === this.has_cnsub ? e.has_cnsub : "0" !== this.has_cnsub || !e.has_cnsub)), a = this.getBean("HitShowPlugin");
let r = a.markDataListHtml(n);
i.html(r), a.loadScore(n), o = !0;
} else console.error(e), i.html(`<h3>${l}</h3>`), show.error(l), "JWTVerificationError" === c && (await localStorage.removeItem("jhs_appAuthorization"),
await this.checkLogin(null, new URLSearchParams(window.location.search))), o = !0;
} catch (r) {
l < 3 ? (clog.error(`获取Top数据失败 (第 ${l} 次重试):`, r), await new Promise((e => setTimeout(e, 1e3)))) : (clog.error("所有重试尝试均失败,无法获取Top数据。", r),
i.html("<h3>无法加载数据,请稍后再试。</h3>"));
} finally {
(o || 3 === l) && s.close();
}
}
toolBar(e, t, n) {
"5" === n.toString() && $(".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 s = (new Date).getFullYear(); s >= 2008; s--) a += `\n <a style="padding:18px 18px !important;" \n class="button is-small ${t === s.toString() ? "is-info" : ""}" \n href="/advanced_search?handleTop=1&handleType=year&type_value=${s}&has_cnsub=${this.has_cnsub}">\n ${s}\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="/advanced_search?handleTop=1&handleType=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="/advanced_search?handleTop=1&handleType=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="/advanced_search?handleTop=1&handleType=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="/advanced_search?handleTop=1&handleType=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="/advanced_search?handleTop=1&handleType=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 `;
this.$contentBox.append(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 = this.movies.filter((e => "1" === this.has_cnsub ? e.has_cnsub : "0" !== this.has_cnsub || !e.has_cnsub)), a = this.getBean("HitShowPlugin");
let i = a.markDataListHtml(n);
$(".movie-list").html(i), a.loadScore(n);
}));
}
async checkLogin(e, t) {
if (!localStorage.getItem("jhs_appAuthorization")) return show.error("该类别依赖移动端接口,请先完成登录"),
void this.openLoginDialog();
let n = "all", a = "", i = t.get("t") || "";
/^y\d+$/.test(i) ? (n = "year", a = i.substring(1)) : "" !== i && (n = "video_type",
a = i);
let s = `/advanced_search?handleTop=1&handleType=${n}&type_value=${a}`;
e && (e.ctrlKey || e.metaKey) ? GM_openInTab(window.location.origin + s, {
insert: 0
}) : window.location.href = s;
}
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) return void show.error("请输入用户名和密码");
let a = loading();
(async (e, t) => {
let n = `${te}//v1/sessions?username=${encodeURIComponent(e)}&password=${encodeURIComponent(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 ne()
};
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) throw console.error("登录失败", e), new Error(e.message);
{
let n = e.data.token;
await localStorage.setItem("jhs_appAuthorization", n), show.ok("登录成功"), layer.close(t),
window.location.href = "/advanced_search?handleTop=1&period=daily";
}
}
})).catch((e => {
console.error("登录异常:", e), show.error(e.message);
})).finally((() => {
a.close();
}));
}));
}
});
}
}
class me extends R {
getName() {
return "NavBarPlugin";
}
async initCss() {
return "\n .highlight-red {\n /* 核心要求:高亮红色文本 */\n color: red !important; \n \n /* 建议:增加字体加粗,效果更明显 */\n font-weight: bold;\n \n /* 建议:增加背景色,效果更突出 */\n /* background-color: yellow; */ \n}\n ";
}
handle() {
if (this.margeNav(), this.hookSearch(), this.hookOldSearch(), this.toggleOtherNavItem(),
$(window).resize(this.toggleOtherNavItem), window.location.href.includes("/search")) {
const e = new URLSearchParams(window.location.search);
let t = e.get("q"), n = e.get("f");
$("#search-keyword").val(t), n && $("#search-type").val(n), t && this.highlightKeyword(t);
}
}
highlightKeyword(e) {
const t = e.trim();
if (!t) return;
const n = t.toLowerCase();
$(".video-title strong, .actor-box strong").each((function() {
const e = $(this);
e.text().toLowerCase().includes(n) && e.addClass("highlight-red");
}));
}
hookSearch() {
$("#navbar-menu-hero").after('\n <div class="navbar-menu" id="search-box">\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 => {
const t = e.originalEvent.clipboardData.items;
for (let n = 0; n < t.length; n++) if (-1 !== t[n].type.indexOf("image")) {
const e = t[n].getAsFile(), a = this.getBean("SearchByImagePlugin");
return void a.open((() => {
a.handleImageFile(e), a.resetSearchUI();
}));
}
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") ? window.location.href = "/search?q=" + t + "&f=" + n : window.open("/search?q=" + t + "&f=" + n));
})), $("#search-img-btn").on("click", (() => {
this.getBean("SearchByImagePlugin").open();
}));
}
hookOldSearch() {
const e = document.querySelector(".search-image");
if (!e) return;
const t = e.cloneNode(!0);
e.parentNode.replaceChild(t, e), $("#button-search-image").attr("data-tooltip", "以图识图"),
$(".search-image").on("click", (e => {
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 ');
}
toggleOtherNavItem() {
let e = $("#search-box"), t = $("#search-bar-container");
$(window).width() < 1600 && $(window).width() > 1023 && (e.hide(), t.show()), $(window).width() > 1600 && (e.show(),
t.hide());
}
}
class fe {
constructor() {
this.queue = Promise.resolve();
}
addTask(e) {
this.queue = this.queue.then((() => e())).catch((e => {
clog.error("执行异步队列任务失败:", e);
}));
}
async waitAllFinished() {
return this.queue;
}
}
class ve extends R {
constructor() {
super(...arguments), i(this, "okBackgroundColor", "#7bc73b"), i(this, "errorBackgroundColor", "#de3333"),
i(this, "warnBackgroundColor", "#d7a80c"), i(this, "domainErrorBackgroundColor", "#d7780c"),
i(this, "timeout", "3000"), i(this, "retry", 3), i(this, "siteConfigs", [ {
id: "javTrailersBtn",
getBaseUrl: async () => await this.getJavTrailersUrl(),
itemSelector: ".videos-list .video-link",
searchPath: (e, t) => `${e}/search/${t}`,
getDetailPageHref: e => e.attr("href"),
findCarNumOrTitle: e => e.find("p.card-text").text()
}, {
id: "123AvBtn",
getBaseUrl: async () => await this.getAv123Url() + "/ja",
itemSelector: ".box-item",
searchPath: (e, t) => `${e}/search?keyword=${t}`,
getDetailPageHref: e => e.find(".detail a").attr("href"),
findCarNumOrTitle: e => e.find("img").attr("title")
}, {
id: "jableBtn",
getBaseUrl: async () => await this.getjableUrl(),
itemSelector: "#list_videos_videos_list_search_result .detail .title a",
searchPath: (e, t) => `${e}/search/${t}/`,
getDetailPageHref: e => e.attr("href"),
findCarNumOrTitle: e => e.text()
}, {
id: "avgleBtn",
getBaseUrl: async () => await this.getAvgleUrl(),
itemSelector: ".text-secondary",
searchPath: (e, t) => `${e}/vod/search.html?wd=${t}`,
getDetailPageHref: e => e.attr("href"),
findCarNumOrTitle: e => e.text()
}, {
id: "missAvBtn",
getBaseUrl: async () => await this.getMissAvUrl(),
itemSelector: ".text-secondary",
searchPath: (e, t) => `${e}/search/${t}`,
getDetailPageHref: e => e.attr("href"),
findCarNumOrTitle: e => e.text()
}, {
id: "supJavBtn",
getBaseUrl: async () => await this.getSupJavUrl(),
itemSelector: ".posts post",
searchPath: (e, t) => `${e}/?s=${t}`,
getDetailPageHref: (e, t, n) => e.attr("href"),
findCarNumOrTitle: e => e.attr("title")
}, {
id: "javDbBtn",
getBaseUrl: async () => await this.getJavDbUrl(),
itemSelector: ".movie-list .item",
searchPath: (e, t) => `${e}/search?q=${t}`,
getDetailPageHref: e => e.find("a").attr("href"),
findCarNumOrTitle: e => e.find(".video-title").text(),
condition: e => l
}, {
id: "javBusBtn",
getBaseUrl: async () => await this.getJavBusUrl(),
itemSelector: ".container h3",
searchPath: (e, t) => `${e}/${t}`,
getDetailPageHref: (e, t, n) => `${t}/${n}`,
findCarNumOrTitle: e => e.text(),
condition: e => r && e && !e.includes("FC2")
}, {
id: "fanzaBtn",
noHandle: !0,
condition: e => e && !e.includes("FC2")
} ]), i(this, "settingCache", null), i(this, "lastFetchTime", 0), i(this, "CACHE_DURATION", 1e4);
}
getName() {
return "OtherSitePlugin";
}
async initCss() {
return "\n <style>\n .site-btn {\n position: relative !important;\n min-width: 80px;\n display: inline-block;\n padding: 5px 10px;\n color: white !important;\n background-color:#938585;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n margin-bottom: 5px;\n }\n .site-btn:hover {\n color: white;\n transform: translateY(-2px);\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n .site-tag {\n position: absolute; \n top: -15px; \n right: 0; \n background-color: #ffc107; \n color: #333; \n font-size: 12px; \n padding: 2px 6px; \n border-radius: 4px;\n }\n </style>\n ";
}
async handle() {
isDetailPage && this.loadOtherSite().then();
}
async loadOtherSite(e, t) {
if ("yes" !== await storageManager.getSetting("enableLoadOtherSite", "yes")) return;
e || (e = this.getPageInfo().carNum);
const n = this.getEnabledSites(), a = `\n <div id="otherSiteBox" class="panel-block" style="${r ? "margin-top:8px;font-size:13px" : "margin-top:10px;font-size:13px"}; user-select: none; ">\n <div style="display: flex;gap: 5px;flex-wrap: wrap">\n ${this.siteConfigs.map((e => {
if (e.sourceCarNum = t, e.condition && !1 === e.condition(e.sourceCarNum)) return "";
return `<a target="_blank" class="site-btn" style="${n.includes(e.id) ? "" : "display:none"}" id="${e.id}"><span>${e.id.replace("Btn", "")}</span></a>`;
})).join("")}\n <a id="settingSiteBtn" class="site-btn"><span>设置</span></a>\n </div>\n </div>\n \n <div id="settingsArea" class="panel-block" style="display: none; margin-top:10px; margin-bottom: 10px; user-select: none; ">\n <div id="siteCheckboxes" style="display: flex;gap: 5px;flex-wrap: wrap">\n </div>\n </div>\n `;
$(".movie-panel-info").append(a), $(".container .info").append(a), $("#javTrailersBtn").on("click", (async t => {
t.preventDefault();
let n = await storageManager.getSetting();
const a = n.filterHotKey, i = n.favoriteHotKey, s = n.speedVideoHotKey;
let o = $("#javTrailersBtn").attr("href"), r = o + `?handle=1&filterHotKey=${a}&favoriteHotKey=${i}&speedVideoHotKey=${s}`;
t && (t.ctrlKey || t.metaKey) && (r = o), utils.openPage(r, e, !1, t);
})), await Promise.all(this.siteConfigs.map((async t => {
t.condition && !1 === t.condition(t.sourceCarNum) || await this.handleSite(e, t);
}))), this.renderSettingsArea(), this.setupEventListeners();
}
async handleSite(e, t) {
const n = $(`#${t.id}`);
if (t.noHandle && !0 === t.noHandle) {
const t = "jhs_other_site_dmm", a = (localStorage.getItem(t) ? JSON.parse(localStorage.getItem(t)) : {})[e];
a && ("single" === a.type ? (n.attr("href", a.url), n.css("backgroundColor", this.okBackgroundColor)) : "multiple" === a.type && (n.attr("href", a.url),
n.append('<span class="site-tag" style="top:-15px">多结果</span>'), n.css("backgroundColor", this.okBackgroundColor)));
} else try {
if (n.attr("href")) return;
if (utils.isHidden(n)) return;
const a = "jhs_other_site", i = localStorage.getItem(a) ? JSON.parse(localStorage.getItem(a)) : {}, s = e + "_" + t.id.replace("Btn", ""), o = i[s];
if (o) return void ("single" === o.type ? (n.attr("href", o.url), n.css("backgroundColor", this.okBackgroundColor)) : "multiple" === o.type && (n.attr("href", o.url),
n.append('<span class="site-tag" style="top:-15px">多结果</span>'), n.css("backgroundColor", this.okBackgroundColor)));
const r = await t.getBaseUrl(), l = t.searchPath(r, e);
n.attr("href", l);
const c = await this.retryWithTimeout((() => gmHttp.get(l, null, t.headers, this.timeout, !0)), this.retry, s, l), d = utils.htmlTo$dom(c), h = [];
d.find(t.itemSelector).each(((n, a) => {
const i = $(a);
if (!t.findCarNumOrTitle(i).toLowerCase().includes(e.toLowerCase())) return;
let s = t.getDetailPageHref(i, r, e);
if (!s) throw new Error("解析href失败");
s.includes("http") || (s = r + (s.startsWith("/") ? s : "/" + s)), h.push(s);
}));
let g = "", p = null;
if (1 === h.length) {
let e = h[0];
n.attr("href", e), n.css("backgroundColor", this.okBackgroundColor), p = {
type: "single",
url: e
};
} else h.length > 1 ? (n.attr("href", l), g += '<span class="site-tag" style="top:-15px">多结果</span>',
n.css("backgroundColor", this.okBackgroundColor), p = {
type: "multiple",
url: l
}) : (n.attr("href", l), n.attr("title", "未查询到, 点击前往搜索页"), n.css("backgroundColor", this.errorBackgroundColor));
p && (new fe).addTask((() => {
const e = localStorage.getItem(a) ? JSON.parse(localStorage.getItem(a)) : {};
e[s] = p, localStorage.setItem(a, JSON.stringify(e));
})), g && n.append(g);
} catch (a) {
const e = String(a), i = t.id.replace("Btn", "");
e.includes("Just a moment") ? (n.attr("title", "请求失败:Cloudflare 安全检查。"), n.css("backgroundColor", this.warnBackgroundColor),
clog.warn(`检测第三方资源失败, ${i} 需Cloudflare安全检查`)) : e.includes("重定向") ? (n.attr("title", "域名失效"),
n.css("backgroundColor", this.domainErrorBackgroundColor), clog.warn(`检测第三方资源失败, ${i} 域名被重定向`)) : e.includes("404 Page Not Found") ? (n.attr("title", "未查询到, 点击前往搜索页"),
n.css("backgroundColor", this.errorBackgroundColor)) : (console.error(a), n.attr("title", "请求失败。"),
n.css("backgroundColor", this.errorBackgroundColor), clog.warn(`检测第三方资源失败, ${i}`));
}
}
async getSettingCache() {
const e = Date.now();
return (!this.settingCache || e - this.lastFetchTime > this.CACHE_DURATION) && (this.settingCache = await storageManager.getSetting(),
this.lastFetchTime = e), this.settingCache;
}
async getMissAvUrl() {
return (await this.getSettingCache()).missAvUrl || "https://missav.live";
}
async getjableUrl() {
return (await this.getSettingCache()).jableUrl || "https://jable.tv";
}
async getAvgleUrl() {
return (await this.getSettingCache()).avgleUrl || "https://jav.rs";
}
async getJavTrailersUrl() {
return (await this.getSettingCache()).javTrailersUrl || "https://javtrailers.com";
}
async getAv123Url() {
return (await this.getSettingCache()).av123Url || "https://123av.com";
}
async getJavDbUrl() {
return (await this.getSettingCache()).javDbUrl || "https://javdb.com";
}
async getJavBusUrl() {
return (await this.getSettingCache()).javBusUrl || "https://www.javbus.com";
}
async getSupJavUrl() {
return (await this.getSettingCache()).supJavUrl || "https://supjav.com";
}
async retryWithTimeout(e, t, n, a) {
let i = 0;
for (;i < t; ) try {
return await Promise.race([ e() ]);
} catch (s) {
const e = String(s);
if (e.includes("Just a moment") || e.includes("重定向")) throw s;
if (i++, i === t) throw s;
}
}
getEnabledSites() {
const e = localStorage.getItem("jhs_enabled_sites");
return e ? JSON.parse(e) : this.siteConfigs.map((e => e.id));
}
saveEnabledSites(e) {
localStorage.setItem("jhs_enabled_sites", JSON.stringify(e));
}
renderSettingsArea() {
const e = this.getEnabledSites(), t = document.getElementById("siteCheckboxes");
t && (t.innerHTML = this.siteConfigs.map((t => {
const n = e.includes(t.id);
return `\n <div style="margin-right: 15px; display: flex; align-items: ${r ? "center" : "flex-start"};">\n <input type="checkbox" id="checkbox-${t.id}" data-site-id="${t.id}" ${n ? "checked" : ""} style="margin-right: 8px; cursor: pointer;">\n <label for="checkbox-${t.id}" style="color: #333; font-weight: 500; cursor: pointer;">${t.id.replace("Btn", "")}</label>\n </div>\n `;
})).join(""));
}
setupEventListeners() {
const e = document.getElementById("settingsArea");
document.addEventListener("click", (t => {
if ("settingSiteBtn" === t.target.id || t.target.closest("#settingSiteBtn")) {
const t = "none" === e.style.display || "" === e.style.display;
e.style.display = t ? "block" : "none";
}
})), e.addEventListener("change", (t => {
if ("checkbox" === t.target.type) {
const n = t.target.getAttribute("data-site-id");
if (t.target.checked) {
$(`#${n}`).show();
const e = this.getPageInfo().carNum, t = this.siteConfigs.find((e => e.id === n));
this.handleSite(e, t).then();
} else $(`#${n}`).hide();
const a = Array.from(e.querySelectorAll('input[type="checkbox"]:checked')).map((e => e.getAttribute("data-site-id")));
this.saveEnabledSites(a);
}
}));
}
}
class be extends R {
getName() {
return "BusDetailPagePlugin";
}
async initCss() {
if (!window.isDetailPage) return "";
$("h4:contains('推薦')").hide();
}
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());
}
}
$(".genre a").each((function() {
const e = $(this).attr("href");
e && (e.startsWith("http://") || e.startsWith("https://") || e.startsWith("/")) && $(this).attr("target", "_blank");
}));
}
}
class we extends R {
getName() {
return "DetailPageButtonPlugin";
}
constructor() {
super(), this.answerCount = 1;
}
async handle() {
let e = await storageManager.getSetting();
this.filterHotKey = e.filterHotKey, this.favoriteHotKey = e.favoriteHotKey, this.hasDownHotKey = e.hasDownHotKey,
this.hasWatchHotKey = e.hasWatchHotKey, this.speedVideoHotKey = e.speedVideoHotKey,
this.bindHotkey().then(), this.hideVideoControls(), window.isDetailPage && this.createMenuBtn();
}
async createMenuBtn() {
const e = this.getPageInfo(), t = e.carNum, n = `\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:${f}; color: white; text-align: center; padding: 8px 0;">\n <span>${u}</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:${w}; color: white; text-align: center; padding: 8px 0;">\n <span>${v}</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:${x}; color: white; text-align: center; padding: 8px 0;">\n <span>${y}</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:${S}; color: white; text-align: center; padding: 8px 0;">\n <span>${k}</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">关闭磁力过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n </div>\n </div>\n `;
r && $(".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 () => this.hasDownOne())),
$("#hasWatchBtn").on("click", (async () => this.hasWatchOne())), $("#magnetSearchBtn").on("click", (() => {
let t = this.getBean("MagnetHubPlugin").createMagnetHub(e.carNum);
layer.open({
type: 1,
title: "磁力搜索 " + e.carNum,
content: '<div id="magnetHubBox"></div>',
area: utils.getResponsiveArea([ "60%", "80%" ]),
scrollbar: !1,
success: () => {
$("#magnetHubBox").append(t);
}
});
}));
const a = this.getBean("HighlightMagnetPlugin"), i = await storageManager.getSetting("enableMagnetsFilter", _);
$("#magnets-span").text(i === _ ? "关闭磁力过滤" : "开启磁力过滤"), i === _ && a.doFilterMagnet(),
$("#enable-magnets-filter").on("click", (e => {
let t = $("#magnets-span");
"关闭磁力过滤" === t.text() ? (a.showAll(), t.text("开启磁力过滤"), storageManager.saveSettingItem("enableMagnetsFilter", C)) : (a.doFilterMagnet(),
t.text("关闭磁力过滤"), storageManager.saveSettingItem("enableMagnetsFilter", _));
})), $("#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) {
const t = $("#filterBtn span"), n = $("#favoriteBtn span"), a = $("#hasDownBtn span"), i = $("#hasWatchBtn span"), s = e => e ? `(${e})` : "";
t.text(`${u} ${s(this.filterHotKey)}`), n.text(`${v} ${s(this.favoriteHotKey)}`),
a.text(`${y} ${s(this.hasDownHotKey)}`), i.text(`${k} ${s(this.hasWatchHotKey)}`);
const o = await storageManager.getCar(e);
if (o) switch (o.status) {
case d:
t.text(`${m} ${s(this.filterHotKey)}`);
break;
case h:
n.text(`${b} ${s(this.favoriteHotKey)}`);
break;
case g:
a.text(`📥️ 已标记下载 ${s(this.hasDownHotKey)}`);
break;
case p:
i.text(`🔍 已标记观看 ${s(this.hasWatchHotKey)}`);
}
}
async favoriteOne() {
let e = this.getPageInfo();
await storageManager.saveCar({
carNum: e.carNum,
url: e.url,
names: e.actress,
actionType: h,
publishTime: e.publishTime
}), this.showStatus(e.carNum).then(), window.refresh(), utils.closePage();
}
async hasDownOne() {
let e = this.getPageInfo();
await storageManager.saveCar({
carNum: e.carNum,
url: e.url,
names: e.actress,
actionType: g,
publishTime: e.publishTime
}), this.showStatus(e.carNum).then(), window.refresh(), utils.closePage();
}
async hasWatchOne() {
let e = this.getPageInfo();
await storageManager.saveCar({
carNum: e.carNum,
url: e.url,
names: e.actress,
actionType: p,
publishTime: e.publishTime
}), this.showStatus(e.carNum).then(), window.refresh(), 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: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="xunlei-table-container" style="height: 100%;padding-bottom: 20px"></div>\n </div>\n ',
scrollbar: !1,
area: utils.getResponsiveArea([ "60%", "70%" ]),
anim: -1,
success: (t, a) => {
new Tabulator("#xunlei-table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: n,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
columns: [ {
title: "文件名",
field: "name",
headerSort: !1,
responsive: 0
}, {
title: "类型",
field: "ext",
headerSort: !1,
responsive: 0
}, {
title: "操作",
responsive: 0,
headerSort: !1,
formatter: (t, n, a) => {
const i = t.getData();
return a((() => {
const n = t.getElement().querySelector(".a-primary"), a = t.getElement().querySelector(".a-success");
n && n.addEventListener("click", (async t => {
let n = i.url, a = e + "." + i.ext;
this.previewSubtitle(n, a);
})), a && a.addEventListener("click", (async t => {
let n = i.url, a = e + "." + i.ext, s = await gmHttp.get(n);
utils.download(s, a);
}));
})), '\n <a class="a-primary">预览</a>\n <a class="a-success">下载</a>\n ';
}
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
}), utils.setupEscClose(a);
}
}) : show.error("迅雷中找不到相关字幕!");
})).catch((e => {
console.error(e), show.error(e);
})).finally((() => {
t.close();
}));
}
async filterOne(e, t) {
e && e.preventDefault();
let n = this.getPageInfo();
t ? (await storageManager.saveCar({
carNum: n.carNum,
url: n.url,
names: n.actress,
actionType: d,
publishTime: n.publishTime
}), this.showStatus(n.carNum).then(), window.refresh(), utils.closePage(), layer.closeAll(),
this.answerCount = 1) : utils.q(e, `是否屏蔽${n.carNum}?`, (async () => {
await storageManager.saveCar({
carNum: n.carNum,
url: n.url,
names: n.actress,
actionType: d,
publishTime: n.publishTime
}), this.showStatus(n.carNum).then(), window.refresh(), utils.closePage();
}), (() => {
this.answerCount = 1;
}));
}
speedVideo() {
if ($("#preview-video").is(":visible")) {
const e = document.getElementById("preview-video");
return void (e && (e.muted = !1, e.controls = !1, e.currentTime + 5 < e.duration ? e.currentTime += 5 : (show.info("预览视频结束, 已回到开头"),
e.currentTime = 1)));
}
const e = $('iframe[id^="layui-layer-iframe"]');
if (e.length > 0) return void e[0].contentWindow.postMessage("speedVideo", "*");
let t = $(".preview-video-container");
if (t.length > 0) {
t[0].click();
const e = document.getElementById("preview-video");
e && (e.currentTime += 5, e.muted = !1);
} else $("#javTrailersBtn").click();
}
hideVideoControls() {
$(document).on("mouseenter", "#preview-video", (function() {
$(this).prop("controls", !0);
}));
}
async bindHotkey() {
const e = {};
this.filterHotKey && (e[this.filterHotKey] = () => {
this.answerCount >= 2 ? this.filterOne(null, !0) : this.filterOne(null), this.answerCount++;
}), this.favoriteHotKey && (e[this.favoriteHotKey] = () => this.favoriteOne(null)),
this.hasDownHotKey && (e[this.hasDownHotKey] = () => this.hasDownOne()), this.hasWatchHotKey && (e[this.hasWatchHotKey] = () => this.hasWatchOne()),
this.speedVideoHotKey && (e[this.speedVideoHotKey] = () => this.speedVideo());
const t = (e, t) => {
Q.registerHotkey(e, (n => {
const a = document.activeElement;
"INPUT" === a.tagName || "TEXTAREA" === a.tagName || a.isContentEditable || (window.isDetailPage ? t() : (e => {
const t = $(".layui-layer-content iframe");
0 !== t.length && 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);
}));
}
previewSubtitle(e, t) {
if (!e) return void console.error("未提供文件URL");
const n = e.split(".").pop().toLowerCase();
"ass" === n || "srt" === n ? gmHttp.get(e).then((e => {
let a = e, i = "字幕预览";
if ("ass" === n) {
i = "ASS字幕预览 - " + t;
const n = e.match(/\[Events][\s\S]*?(?=\[|$)/i);
n && (a = n[0]);
} else "srt" === n && (i = "SRT字幕预览 - " + t);
layer.open({
type: 1,
title: i,
area: [ "80%", "80%" ],
scrollbar: !1,
content: `<div style="padding:15px;background:#1E1E1E;color:#FFF;font-family:Consolas,Monaco,monospace;white-space:pre-wrap;overflow:auto;height:100%;">${a}</div>`,
btn: [ "下载", "关闭" ],
btn1: function(n, a, i) {
return utils.download(e, t), !1;
}
});
})).catch((e => {
show.error(`预览失败: ${e.message}`), console.error("预览字幕文件出错:", e);
})) : alert("仅支持预览ASS和SRT字幕文件");
}
}
class ye extends R {
constructor() {
super(...arguments), i(this, "tableObj", null);
}
getName() {
return "HistoryPlugin";
}
async initCss() {
return "\n <style>\n /* 下拉菜单容器(相对定位) */\n .sub-btns {\n position: relative;\n display: inline-block;\n }\n \n /* 下拉菜单内容(默认隐藏) */\n .sub-btns-menu {\n display: none;\n position: absolute;\n right: 80px;\n top:-10px;\n background: white;\n padding:10px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 100;\n border-radius: 4px;\n overflow: hidden;\n }\n \n \n /* 点击后显示菜单(JS 控制) */\n .sub-btns-menu.show {\n display: flex !important;\n flex-direction: column;\n }\n \n .table-link-param {\n cursor: pointer;\n }\n </style\n ";
}
handleResize() {
$(".navbar-search").is(":hidden") ? ($(".historyBtnBox").show(), $(".miniHistoryBtnBox").hide()) : ($(".historyBtnBox").hide(),
$(".miniHistoryBtnBox").show());
}
handle() {
r && ($(".navbar-end").prepend('<div class="navbar-item has-sub-btns is-hoverable historyBtnBox">\n <a id="historyBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n 鉴定记录\n </a>\n </div>'),
$(".navbar-search").css("margin-left", "0").before('\n <div class="navbar-item miniHistoryBtnBox">\n <a id="miniHistoryBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-left:0 !important;padding-right:0 !important;">\n 鉴定记录\n </a>\n </div>\n '),
this.handleResize(), $(window).resize((() => {
this.handleResize();
})), $("#historyBtn,#miniHistoryBtn").on("click", (e => this.openHistory()))), l && utils.loopDetector((() => $("#setting-btn").length), (() => {
$("#top-right-box").append('\n <a id="historyBtn" class="menu-btn main-tab-btn" style="background-color:#b68625 !important;">\n 鉴定记录\n </a>\n '),
$("#historyBtn,#miniHistoryBtn").on("click", (e => this.openHistory()));
}), 1, 1e4, !1), this.bindClick();
}
openHistory() {
let e = `\n <div style="padding: 10px 20px; height: 100%;overflow:hidden;"> \n <div id="filterBox" style="display: flex;gap: 5px;">\n <select id="dataType" style="text-align: center;min-width: 150px;">\n <option value="all" selected>所有</option>\n <option value="filter">${m}</option>\n <option value="favorite">${b}</option>\n <option value="hasDown">${y}</option>\n <option value="hasWatch">${k}</option>\n </select>\n <input id="searchCarNum" type="text" placeholder="搜索番号|演员" style="padding: 4px 5px;">\n <a id="clearSearchbtn" class="a-info" style="margin-left: 0">重置</a>\n </div>\n <div id="allSelectBox" style="margin-top: 8px;display: none">\n <a class="menu-btn multiple-histroy-deleteBtn" style="background-color:#8c8080; color:white; margin-bottom: 5px;"> <span>✂️ 移除</span> </a>\n <a class="menu-btn multiple-histroy-hasWatchBtn" style="background-color:${S};margin-bottom: 5px">${k}</a>\n <a class="menu-btn multiple-histroy-hasDownBtn" style="background-color:${x};margin-bottom: 5px">${y}</a>\n <a class="menu-btn multiple-histroy-favoriteBtn" style="background-color:${w};margin-bottom: 5px">${v}</a>\n <a class="menu-btn multiple-histroy-filterBtn" style="background-color:${f};margin-bottom: 5px">${u}</a>\n </div>\n <div id="table-container" style="height: calc(100% - 50px); overflow-x:hidden;"></div>\n </div>\n `;
layer.open({
type: 1,
title: "鉴定记录",
content: e,
scrollbar: !1,
shadeClose: !0,
area: utils.getResponsiveArea([ "70%", "90%" ]),
anim: -1,
success: async e => {
await this.loadTableData(), $(".layui-layer-content").on("click", "#clearSearchbtn", (async e => {
$("#searchCarNum").val(""), $("#dataType").val("all"), await this.reloadTable(),
$("#allSelectBox").hide();
})).on("focusout keydown", "#searchCarNum", (async e => {
if ("focusout" === e.type || "Enter" === e.key) {
if ("Enter" === e.key && e.preventDefault(), "keydown" === e.type && "Enter" !== e.key) return;
await this.reloadTable();
}
})).on("click", ".table-link-param", (async e => {
let t = $(e.currentTarget);
$("#searchCarNum").val(t.text()), await this.reloadTable();
})).on("change", "#dataType", (async () => {
await this.reloadTable();
}));
},
end: () => {
this.tableObj && (this.tableObj.destroy(), this.tableObj = null), window.refresh();
}
});
}
async reloadTable() {
this.tableObj.deselectRow(), this.tableObj.setPage(1);
}
bindClick() {
document.addEventListener("click", (function(e) {
if (e.target.closest(".sub-btns-toggle")) {
const t = e.target.closest(".sub-btns").querySelector(".sub-btns-menu");
document.querySelectorAll(".sub-btns-menu.show").forEach((e => {
e !== t && e.classList.remove("show");
})), t.classList.toggle("show");
} else document.querySelectorAll(".sub-btns-menu.show").forEach((e => {
e.classList.remove("show");
}));
})), $(document).on("click", ".histroy-deleteBtn, .histroy-filterBtn, .histroy-favoriteBtn, .histroy-hasDownBtn, .histroy-hasWatchBtn, .histroy-detailBtn", (e => {
e.preventDefault(), e.stopPropagation();
const t = $(e.currentTarget), n = t.closest(".action-btns"), a = n.attr("data-car-num"), i = n.attr("data-href"), s = async e => {
await storageManager.saveCar({
carNum: a,
url: i,
names: null,
actionType: e
}), window.refresh(), await this.reloadTable();
};
t.hasClass("histroy-filterBtn") ? utils.q(e, `是否屏蔽${a}?`, (() => s(d))) : t.hasClass("histroy-favoriteBtn") ? s(h).then() : t.hasClass("histroy-hasDownBtn") ? s(g).then() : t.hasClass("histroy-hasWatchBtn") ? s(p).then() : t.hasClass("histroy-deleteBtn") ? this.handleDelete(e, a) : t.hasClass("histroy-detailBtn") && this.handleClickDetail(e, {
carNum: a,
url: i
}).then();
})), $(document).on("click", ".multiple-histroy-deleteBtn, .multiple-histroy-filterBtn, .multiple-histroy-favoriteBtn, .multiple-histroy-hasDownBtn, .multiple-histroy-hasWatchBtn", (e => {
e.preventDefault(), e.stopPropagation();
const t = $(e.currentTarget);
let n = this.tableObj.getSelectedData(), a = "", i = "";
t.hasClass("multiple-histroy-filterBtn") ? (a = "屏蔽", i = d) : t.hasClass("multiple-histroy-favoriteBtn") ? (a = "收藏",
i = h) : t.hasClass("multiple-histroy-hasDownBtn") ? (a = "已下载", i = g) : t.hasClass("multiple-histroy-hasWatchBtn") ? (a = "已观看",
i = p) : t.hasClass("multiple-histroy-deleteBtn") && (a = "移除", i = "delete"), utils.q(e, `当前已勾选${n.length}条数据, 是否全标记为 ${a}?`, (async () => {
let e = loading();
try {
if ("delete" === i) {
const e = n.map((e => e.carNum)), t = await storageManager.batchRemoveCars(e);
t > 0 ? show.ok(`已成功删除 ${t} 个番号`) : !1 === t && show.error("提供的番号中没有一个存在于列表中。");
} else {
const e = JSON.parse(JSON.stringify(n));
e.forEach((e => {
e.actionType = i;
})), await storageManager.saveCarList(e), show.ok("操作成功");
}
this.tableObj.deselectRow(), this.reloadTable().then();
} catch (t) {
console.error(t);
} finally {
e.close();
}
}));
}));
}
async getDataList(e, t, n) {
let a = await storageManager.getCarList();
this.allCount = a.length, this.filterCount = 0, this.favoriteCount = 0, this.hasDownCount = 0,
this.hasWatchCount = 0, a.forEach((e => {
switch (e.status) {
case d:
this.filterCount++;
break;
case h:
this.favoriteCount++;
break;
case g:
this.hasDownCount++;
break;
case p:
this.hasWatchCount++;
}
})), $('#dataType option[value="all"]').text(`所有 (${this.allCount})`), $('#dataType option[value="filter"]').text(`${m} (${this.filterCount})`),
$('#dataType option[value="favorite"]').text(`${b} (${this.favoriteCount})`), $('#dataType option[value="hasDown"]').text(`${y} (${this.hasDownCount})`),
$('#dataType option[value="hasWatch"]').text(`${k} (${this.hasWatchCount})`);
const i = $("#dataType").val();
let s = "all" === i ? a : a.filter((e => e.status === i));
const o = $("#searchCarNum").val().trim();
if (o) {
let e = o.toLowerCase().replace("-c", "").replace("-uc", "").replace("-4k", "");
s = s.filter((t => {
const n = t.carNum.toLowerCase().includes(e);
const a = (t.names ? t.names : "").toLowerCase().includes(e);
return n || a;
}));
}
if (n && n.length > 0) {
const e = n[0], t = e.field, a = e.dir;
s.sort(((e, n) => {
const i = e[t], s = n[t], o = null == i || "" === i, r = null == s || "" === s;
return o && !r ? 1 : !o && r ? -1 : o && r ? 0 : i < s ? "asc" === a ? -1 : 1 : i > s ? "asc" === a ? 1 : -1 : 0;
}));
}
const r = s.length, l = Math.ceil(r / t), c = (e - 1) * t, u = c + t;
return s = s.slice(c, u), {
maxPage: l,
dataList: s,
totalCount: r
};
}
async loadTableData() {
this.tableObj = new Tabulator("#table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
pagination: !0,
paginationMode: "remote",
sortMode: "remote",
ajaxURL: "queryRealm",
dataLoader: !1,
ajaxRequestFunc: async (e, t, n) => {
const a = n.page, i = n.size, s = n.sort;
return await this.getDataList(a, i, s);
},
dataReceiveParams: {
last_page: "maxPage",
last_row: "totalCount",
data: "dataList"
},
paginationSize: 20,
paginationSizeSelector: [ 20, 50, 100, 1e3, 99999 ],
paginationCounter: (e, t, n, a, i) => `共 ${a} 条记录`,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
persistence: {
headerFilter: !0
},
selectableRowsPersistence: !1,
index: "carNum",
columns: [ {
formatter: "rowSelection",
titleFormatter: "rowSelection",
hozAlign: "center",
headerSort: !1,
responsive: 0,
width: 40,
titleFormatterParams: {
rowRange: "active"
},
cellClick: (e, t) => {
t.getRow().toggleSelect();
}
}, {
title: "番号",
field: "carNum",
width: 120,
sorter: "string",
responsive: 0,
formatter: (e, t, n) => {
const a = e.getData().carNum, i = a.indexOf("-");
if (-1 === i) return a;
return `<a class="table-link-param">${a.substring(0, i + 1)}</a>${a.substring(i + 1)}`;
}
}, {
title: "演员",
field: "names",
minWidth: 200,
sorter: "string",
responsive: 5,
headerSort: !1,
formatter: (e, t, n) => (e.getData().names || "").split(" ").filter((e => "" !== e.trim())).map((e => `<a class="table-link-param">${e}</a>`)).join(" ")
}, {
title: "创建时间",
field: "createDate",
width: 170,
sorter: "string",
responsive: 4
}, {
title: "修改时间",
field: "updateDate",
width: 170,
sorter: "string",
responsive: 4
}, {
title: "发行时间",
field: "publishTime",
width: 170,
sorter: "string",
responsive: 4
}, {
title: "来源",
field: "url",
width: 80,
sorter: "string",
responsive: 5,
hozAlign: "left",
formatter: (e, t, n) => {
let a = e.getData().url;
return a ? a.includes("javdb") ? '<span style="color:#d34f9e">Javdb</span>' : a.includes("javbus") ? '<span style="color:#eaa813">JavBus</span>' : a.includes("123av") ? '<span style="color:#eaa813">123Av</span>' : `<span style="color:#050505">${a}</span>` : "";
}
}, {
title: "状态",
field: "status",
width: 100,
sorter: "string",
responsive: 1,
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData().status;
let i = "", s = "";
switch (a) {
case "filter":
i = f, s = u;
break;
case "favorite":
i = w, s = v;
break;
case "hasDown":
i = x, s = y;
break;
case "hasWatch":
i = S, s = k;
break;
default:
s = a;
}
return `<span style="color:${i}">${s}</span>`;
}
}, {
title: "操作",
sorter: "string",
minWidth: 150,
cssClass: "action-cell-dropdown",
responsive: 0,
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData();
return `\n <div class="action-btns" style="display: flex; gap: 5px;justify-content:center" data-car-num="${a.carNum}" data-href="${a.url ? a.url : ""}">\n <div class="sub-btns">\n <a class="menu-btn sub-btns-toggle" style="background-color:#c59d36; color:white; margin-bottom: 5px;">\n <span>✏️ 变更</span>\n </a>\n <div class="sub-btns-menu">\n <a class="menu-btn histroy-deleteBtn" style="background-color:#8c8080; color:white; margin-bottom: 5px;"> <span>✂️ 移除</span> </a>\n <a class="menu-btn histroy-hasWatchBtn" style="background-color:${S};margin-bottom: 5px">${k}</a>\n <a class="menu-btn histroy-hasDownBtn" style="background-color:${x};margin-bottom: 5px">${y}</a>\n <a class="menu-btn histroy-favoriteBtn" style="background-color:${w};margin-bottom: 5px">${v}</a>\n <a class="menu-btn histroy-filterBtn" style="background-color:${f};margin-bottom: 5px">${u}</a>\n </div>\n </div>\n \n <a class="menu-btn histroy-detailBtn" style="background-color:#3397de; color:white; margin-bottom: 5px;"> <span>📄 详情页</span> </a>\n \n </div>\n `;
}
} ],
initialSort: [ {
column: "updateDate",
dir: "desc"
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
}), this.tableObj.on("rowSelectionChanged", ((e, t, n, a) => {
const i = $("#allSelectBox"), s = $("#filterBox");
e && e.length > 0 ? (s.hide(), i.show()) : (s.show(), i.hide());
})), this.tableObj.on("rowDblClick", (function(e, t) {
t.toggleSelect();
})), this.tableObj.on("tableBuilt", (async () => {}));
}
handleDelete(e, t) {
utils.q(e, `是否移除${t}?`, (async () => {
await storageManager.removeCar(t), this.getBean("ListPagePlugin").showCarNumBox(t),
this.reloadTable(null).then();
}));
}
async handleClickDetail(e, t) {
if (r) if (t.carNum.includes("FC2-")) {
const e = this.parseMovieId(t.url);
this.getBean("Fc2Plugin").openFc2Dialog(e, t.carNum, t.url);
} else {
if (!t.url) return void window.open("/search?q=" + t.carNum, "_blank");
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 = this.parseMovieId(n);
await this.getBean("Fc2Plugin").openFc2Page(e, t.carNum, n);
} else window.open(n, "_blank"); else utils.openPage(t.url, t.carNum, !1, e);
}
}
}
class xe extends R {
constructor() {
super(...arguments), i(this, "floorIndex", 1), i(this, "isInit", !1);
}
getName() {
return "ReviewPlugin";
}
async handle() {
if ($(document).on("click", ".down-115", (async e => {
const t = $(e.currentTarget).data("magnet");
let n = loading();
try {
await this.getBean("WangPan115TaskPlugin").handleAddTask(t);
} catch (a) {
show.error("发生错误:" + a), console.error(a);
} finally {
n.close();
}
})), window.isDetailPage) {
if (r) {
const e = this.parseMovieId(window.location.href);
await this.showReview(e), await this.getBean("RelatedPlugin").showRelated($("#magnets-content"), e);
}
if (l) {
let e = this.getPageInfo().carNum;
const t = await (async e => {
let t = `${te}/v2/search`, n = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
jdsignature: await ne()
}, 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) return;
this.showReview(n, $("#sample-waterfall")).then();
}
}
}
async showReview(e, t) {
const n = await storageManager.getSetting("enableLoadReview", _), a = t || $("#magnets-content");
a.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;" data-tip="想要发表评论? 滑上去, 点击上面的按钮-看过">❓ 评论区</span>\n <a id="reviewsFold" style="margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center;">\n <span class="toggle-text">${n === _ ? "折叠" : "展开"}</span>\n <span class="toggle-icon" style="margin-left: 4px;">${n === _ ? "▲" : "▼"}</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", (t => {
t.preventDefault(), t.stopPropagation();
const n = $("#reviewsFold .toggle-text"), a = $("#reviewsFold .toggle-icon"), i = "展开" === n.text();
n.text(i ? "折叠" : "展开"), a.text(i ? "▲" : "▼"), i ? ($("#reviewsContainer").show(),
$("#reviewsFooter").show(), this.isInit || (this.fetchAndDisplayReviews(e), this.isInit = !0),
storageManager.saveSettingItem("enableLoadReview", _)) : ($("#reviewsContainer").hide(),
$("#reviewsFooter").hide(), storageManager.saveSettingItem("enableLoadReview", C));
})), a.append('<div id="reviewsContainer"></div>'), a.append('<div id="reviewsFooter"></div>'),
n === _ && await this.fetchAndDisplayReviews(e);
}
async fetchAndDisplayReviews(e) {
const t = $("#reviewsContainer"), n = $("#reviewsFooter");
t.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>');
const a = await storageManager.getSetting("reviewCount", 20);
let i = null;
try {
i = await ae(e, 1, a);
} catch (o) {
o.toString().includes("簽名已過期") && show.error("生成签名失败, 请检查系统时间及时区是否正确!"), clog.error("获取评论失败:", o),
console.error("获取评论失败:", o);
} finally {
$("#reviewsLoading").remove();
}
if (!i) return t.append('\n <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n 获取评论失败\n <a id="retryFetchReviews" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n </div>\n '),
void $("#retryFetchReviews").on("click", (async () => {
$("#retryFetchReviews").parent().remove(), await this.fetchAndDisplayReviews(e);
}));
if (0 === i.length) return void t.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>');
const s = await storageManager.getReviewFilterKeywordList();
if (this.displayReviews(i, t, s), i.length === a) {
n.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 i = 1, r = $("#loadMoreReviews");
r.on("click", (async () => {
let n;
r.text("加载中...").prop("disabled", !0), i++;
try {
n = await ae(e, i, a);
} catch (o) {
console.error("加载更多评论失败:", o);
} finally {
r.text("加载失败, 请点击重试").prop("disabled", !1);
}
n && (this.displayReviews(n, t, s), n.length < a ? (r.remove(), $("#reviewsEnd").show()) : r.text("加载更多评论").prop("disabled", !1));
}));
} else n.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>');
}
displayReviews(e, t, n) {
e.length && (e.forEach((e => {
if (n.some((t => e.content.includes(t)))) return;
const a = Array(e.score).fill('<i class="icon-star"></i>').join(""), i = e.content.replace(/ed2k:\/\/\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/|magnet:\?[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+|https?:\/\/[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+/g, (e => e.startsWith("ed2k://") ? `\n <span style="word-break: break-all;background: #e0f2fe;color: #0369a1;">${e}</span>\n <button class="button is-info down-115" data-magnet="${e}" style="font-size: 11px">115离线下载</button>\n ` : e.startsWith("magnet:") ? `\n <a href="${e}" class="a-primary" style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${e}</a>\n <button class="button is-info down-115" data-magnet="${e}" style="font-size: 11px">115离线下载</button>\n ` : e.startsWith("http://") || e.startsWith("https://") ? `\n <a href="${e}" class="a-primary" style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${e}</a>\n ` : e)), s = `\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">${a}</span> \n <span class="time">${utils.formatDate(e.created_at)}</span> \n 点赞:${e.likes_count}\n <p class="review-content" style="margin-top: 5px;"> ${i} </p>\n </div>\n `;
t.append(s);
})), this.rightClickFilter());
}
async rightClickFilter() {
await storageManager.getSetting("enableTitleSelectFilter", _) === _ && utils.rightClick(document.body, ".review-content", (async e => {
const t = window.getSelection().toString();
t && (e.preventDefault(), await utils.q(e, `是否将 '${t}' 加入评论区关键词?`, (async () => {
await storageManager.saveReviewFilterKeyword(t), show.ok("操作成功, 刷新页面后生效");
})));
}));
}
}
class $e extends R {
getName() {
return "FilterTitleKeywordPlugin";
}
async handle() {
if (!isDetailPage && !isFc2Page) return;
if (await storageManager.getSetting("enableTitleSelectFilter", _) !== _) return;
let e;
r ? e = ".title strong, .current-title" : l && (e = "h3"), utils.rightClick(document.body, 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();
}));
}
}));
}
}
class ke extends R {
getName() {
return "BlacklistPlugin";
}
async addBlacklist(e) {
let t = {
clientX: e.clientX,
clientY: e.clientY + 80
};
const {starId: n, name: a, allName: i, role: s, movieType: r, blacklistUrl: c} = this.getActressPageInfo(), d = $("#addBlacklistBtn span").text().includes("已加入");
let h = `是否将该演员 ${a} 加入到黑名单中?`;
d && (h = `演员 ${a} 已在黑名单中, 是否清空该数据并重新加入?`), utils.q(t, h, (async () => {
this.loadObj = loading();
try {
if (o.includes("page") && !o.includes("page=1")) return void show.error("当前页面非第一页, 请前往第一页再执行此操作, 以免屏蔽缺漏数据");
if (l) {
const e = o.split("/star/"), t = e[1].split("/");
if (t.length > 1) {
if (parseInt(t[1]) > 1) return void show.error("当前页面非第一页, 请前往第一页再执行此操作, 以免屏蔽缺漏数据");
}
}
await storageManager.removeBlacklistCarList(n), await storageManager.addBlacklistItem({
starId: n,
name: a,
allName: i,
role: s,
movieType: r,
url: c
}), await this.filterActorVideo(a, n);
} catch (e) {
show.error(e), clog.error(e);
} finally {
this.loadObj.close();
}
}));
}
async resetBtnTip() {
const e = this.getBean("TaskPlugin"), t = localStorage.getItem(e.lastCheckBlacklistTimeKey) || "无", n = await storageManager.getSetting("checkBlacklist_intervalTime", 12);
this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime", 8760),
$("#checkBlacklistBtn").attr("data-tip", `上次检测时间: ${t}; 检测间隔时间: ${n}小时`);
}
async openBlacklistDialog() {
const e = this.getBean("TaskPlugin"), t = await storageManager.getSetting();
let n = `\n <div style="padding: 10px 20px; height: 100%;overflow:hidden;"> \n <div style="display: flex;justify-content: space-between;">\n <div style="display: flex; gap:5px">\n <a id="checkBlacklistBtn" class="a-danger" data-tip="上次检测时间: ${localStorage.getItem(e.lastCheckBlacklistTimeKey) || "无"}; 检测间隔时间: ${t.checkBlacklist_intervalTime}小时">${this.blacklistSvg} 手动检测黑名单</a>\n <a class="a-info" id="toSetting">${this.settingSvg} 配置</a>\n </div>\n <div style="display: flex; gap:5px">\n <select id="dataType" style="text-align: center;min-width: 150px;">\n <option value="" selected>所有</option>\n <option value="actor">男演员</option>\n <option value="actress">女演员</option>\n </select>\n <select id="statusType" style="text-align: center;min-width: 150px;">\n <option value="" selected>--检测状态--</option>\n <option value="normal">正常检测</option>\n <option value="stop">停止检测</option>\n </select>\n <select id="urlType" data-tip="在演员页屏蔽时,是否选择了分类" style="text-align: center;min-width: 150px; ${r ? "" : "display: none;"}">\n <option value="" selected>--屏蔽类型--</option>\n <option value="hasT">按所选分类屏蔽</option>\n <option value="noT">未筛选分类</option>\n </select>\n <input id="searchValue" type="text" placeholder="搜索演员" style="padding: 4px 5px;">\n <a id="cleanQueryBtn" class="a-info" style="margin-left: 0">重置</a>\n </div>\n\n </div>\n <div id="table-container" style="height: calc(100% - 50px);"></div>\n </div>\n `;
layer.open({
type: 1,
title: "演员黑名单",
content: n,
scrollbar: !1,
area: utils.getResponsiveArea([ "70%", "90%" ]),
anim: -1,
success: async t => {
await this.loadTableData(), $(".layui-layer-content").on("click", "#cleanQueryBtn", (async e => {
$("#searchValue").val(""), $("#dataType").val(""), $("#statusType").val(""), await this.reloadTable();
})).on("focusout keydown", "#searchValue", (async e => {
if ("focusout" === e.type || "Enter" === e.key) {
if ("Enter" === e.key && e.preventDefault(), "keydown" === e.type && "Enter" !== e.key) return;
$("#dataType").val(""), await this.reloadTable();
}
})).on("change", "#dataType", (async () => {
$("#searchValue").val(""), await this.reloadTable();
})).on("change", "#statusType", (async () => {
await this.reloadTable();
})).on("change", "#urlType", (async () => {
await this.reloadTable();
})).on("click", "#toSetting", (() => {
this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => {
$("#setting-blacklist").css({
border: "1px solid #f40"
});
}));
})).on("click", ".open-url", (e => {
e.preventDefault();
const t = $(e.currentTarget), n = t.attr("data-url"), a = t.attr("data-name");
utils.openPage(n, a, !0, e);
})).on("click", ".delete-btn", (async e => {
const t = $(e.currentTarget), n = t.attr("data-name"), a = t.attr("data-starId");
n ? a ? utils.q(e, `是否移除对 ${n} 的屏蔽?`, (async () => {
try {
await storageManager.removeBlacklistCarList(a), await storageManager.deleteBlacklistItem(a),
show.info("操作成功"), this.reloadTable().then();
} catch (e) {
clog.error("操作失败:", e), show.error("操作失败:" + e);
}
})) : show.error("获取starId失败") : show.error("获取名称失败");
})).on("click", "#checkBlacklistBtn", (t => {
utils.q({
clientX: t.clientX,
clientY: t.clientY + 20
}, "是否手动检测黑名单?", (() => {
navigator.locks.request(e.singleTaskKey, {
ifAvailable: !0
}, (async t => {
t ? (await e.loadConfig(), await e.checkBlacklist(!0)) : show.error("当前有定时任务在后台执行中, 无法发起手动任务");
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
}));
}));
}));
},
end: () => {
this.tableObj && (this.tableObj.destroy(), this.tableObj = null), window.refresh();
}
});
}
async reloadTable() {
if (!this.tableObj) return;
const e = await this.getTableData();
this.tableObj.setData(e);
}
async getTableData() {
const e = this.getBean("TaskPlugin"), t = await storageManager.getBlacklist(), n = await storageManager.getBlacklistCarList(), a = $("#searchValue").val(), i = $("#statusType").val(), s = $("#dataType"), o = s.val(), r = $("#urlType").val(), l = t.length;
let c = 0, d = 0;
const h = t.map((t => {
t.role === B ? c++ : t.role === P && d++;
let n = !1;
return t.lastPublishTime && (n = !e.isUnnecessaryCheck(t.lastPublishTime, this.checkBlacklist_ruleTime)),
{
...t,
isUnCheck: n
};
})).filter((e => !(a && !e.name.includes(a)) && (("normal" !== i || !e.isUnCheck) && (!("stop" === i && !e.isUnCheck) && (o ? e.role === o : !("hasT" === r && !e.url.includes("t=")) && ("noT" !== r || !e.url.includes("t=")))))));
s.html(`\n <option value="">所有 (${l})</option>\n <option value="actor">男演员 (${c})</option>\n <option value="actress">女演员 (${d})</option>\n `),
s.val(o);
const g = new Map;
for (const u of n) {
const e = u.starId;
g.has(e) || g.set(e, []), g.get(e).push(u);
}
const p = h.map((e => {
const t = e.starId, n = g.get(t) || [];
return {
...e,
carList: n,
count: n.length
};
}));
return this.currentCarCount = p.reduce(((e, t) => e + (t.count || 0)), 0), p;
}
async loadTableData() {
this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime") || 8760;
const e = await this.getTableData();
this.tableObj = new Tabulator("#table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: e,
pagination: !0,
paginationMode: "local",
paginationSize: 20,
paginationSizeSelector: [ 20, 50, 100, 1e3, 99999 ],
paginationCounter: (e, t, n, a, i) => `演员: ${a} 番号总数: ${this.currentCarCount} <span id="checkBlacklistMsg" style="margin-left: 10px"></span>`,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
index: "name",
columns: [ {
title: "演员",
field: "name",
sorter: "string",
minWidth: 100,
responsive: 0,
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData();
return `<a class="open-url" data-url="${a.url}" href="${a.url}" data-name="${a.name}" target="_blank">${a.name}</a>`;
}
}, {
title: "性别角色",
field: "role",
sorter: "string",
width: 120,
responsive: 5,
formatter: (e, t, n) => {
const a = e.getData().role;
let i = a;
return a === B ? i = "男演员" : a === P && (i = "女演员"), i;
}
}, {
title: "影视类别",
field: "movieType",
sorter: "string",
width: 120,
responsive: 5,
formatter: (e, t, n) => {
const a = e.getData().movieType;
let i = a;
return a === D ? i = "有码" : a === L && (i = "无码"), i;
}
}, {
title: "屏蔽类型",
field: "url",
sorter: "string",
minWidth: 120,
responsive: 4,
visible: r,
formatter: (e, t, n) => {
let a = e.getData().url.includes("t=");
return `<span style="${a ? "color:#cc4444" : ""}">${a ? "按所选分类屏蔽" : "未筛选分类"}</span>`;
}
}, {
title: "番号数量",
field: "count",
sorter: "string",
width: 170,
responsive: 1
}, {
title: "创建时间",
field: "createTime",
sorter: "string",
width: 170,
responsive: 5
}, {
title: "最后发行时间",
field: "lastPublishTime",
sorter: "string",
width: 170,
responsive: 1
}, {
title: "状态",
field: "isUnCheck",
sorter: "string",
width: 120,
responsive: 1,
formatter: (e, t, n) => {
let a = "", i = "正常检测";
return e.getData().isUnCheck && (a = `停更${this.checkBlacklist_ruleTime / 24 / 365}年以上, 下轮任务不再进行检测`,
i = "停止检测"), `<span data-tip="${a}" style="${a ? "color: #cc4444;" : ""}">${i}</span>`;
}
}, {
title: "操作",
sorter: "string",
cssClass: "action-cell-dropdown",
minWidth: 150,
responsive: 0,
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData();
return `\n <a class="a-normal delete-btn" data-starId="${a.starId}" data-name="${a.name}" > <span>✂️ 删除</span> </a>\n `;
}
} ],
initialSort: [ {
column: "createTime",
dir: "desc"
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
});
}
getCurrentStarUrl() {
let e = window.location.href.replace(/([&?])sort_type=[^&]+(&|$)/, "$1");
e = e.replace(/[&?]$/, ""), e = e.replace(/\?&/, "?");
let t = e;
return t = t.replace(/([&?])page=\d+(&|$)/, "$1"), t = t.replace(/[&?]$/, ""), t = t.replace(/\?&/, "?"),
t = t.replace(/\/(\d+)(?:\/(\d+))?(\?|$)/, ((e, t, n, a) => void 0 !== n ? `/${t}${a}` : e)),
t;
}
parseUrlId(e) {
if (!e) throw new Error("url未传入");
return new URL(e).pathname.split("/").filter((e => "" !== e.trim())).pop();
}
async filterAllVideo(e, t) {
let n, a;
if (t ? (l && t.find(".avatar-box").length > 0 && t.find(".avatar-box").parent().remove(),
n = t.find(this.getSelector().requestDomItemSelector), a = t.find(this.getSelector().nextPageSelector).attr("href")) : (n = $(this.getSelector().itemSelector),
a = $(this.getSelector().nextPageSelector).attr("href")), a && 0 === n.length) throw show.error("解析列表失败"),
new Error("解析列表失败");
for (const s of n) {
const t = $(s), {carNum: n, url: a, publishTime: o} = this.getBean("ListPagePlugin").findCarNumAndHref(t);
if (a && n) try {
if (await storageManager.getCar(n)) continue;
await storageManager.saveCar({
carNum: n,
url: a,
names: e,
actionType: d,
publishTime: o
}), clog.log("屏蔽演员番号", e, n);
} catch (i) {
console.error(`保存失败 [${n}]:`, i);
}
}
if (a) {
show.info("请不要关闭窗口, 正在解析下一页:" + a), await new Promise((e => setTimeout(e, 500)));
const t = await gmHttp.get(a), n = new DOMParser, i = $(n.parseFromString(t, "text/html"));
await this.filterAllVideo(e, i);
} else show.ok("执行结束!"), window.refresh();
}
async filterActorVideo(e, t, n) {
let {nextPageLink: a} = await this.parseAndSaveFilterInfo(n, e, t);
if (a) {
show.info("请不要关闭窗口, 正在解析下一页:" + a), await new Promise((e => setTimeout(e, 500))),
clog.log("正在请求下一页内容:", a);
const n = await gmHttp.get(a), i = utils.htmlTo$dom(n);
await this.filterActorVideo(e, t, i);
} else show.ok("执行结束!"), window.refresh();
}
async parseAndSaveFilterInfo(e, t, n) {
let a, i;
if (e) {
let t = !1, n = T;
e.text().includes(I) && (t = !0, n = I), t && e.find(".avatar-box").length > 0 && e.find(".avatar-box").parent().remove(),
a = e.find(this.getSelector(n).requestDomItemSelector), i = e.find(this.getSelector(n).nextPageSelector).attr("href");
} else a = $(this.getSelector().itemSelector), i = $(this.getSelector().nextPageSelector).attr("href");
if (i && 0 === a.length) throw clog.error("解析列表失败"), show.error("解析列表失败"), new Error("解析列表失败");
let s = [], o = null;
for (const l of a) {
const e = $(l), {carNum: a, url: i, publishTime: r} = this.getBean("ListPagePlugin").findCarNumAndHref(e);
o || (o = r), i && a && s.push({
carNum: a,
url: i,
names: t,
actionType: d,
starId: n,
publishTime: r
});
}
try {
await storageManager.batchSaveBlacklistCarList(s);
} catch (r) {
clog.error("保存失败:", r), console.error("保存失败:", r);
}
return {
nextPageLink: i,
lastPublishTime: o
};
}
}
class Se extends R {
getName() {
return "ListPageButtonPlugin";
}
async handle() {
if (!window.isListPage) return;
await this.createMenuBtn(), this.bindEvent();
await storageManager.getSetting("autoPage") === _ ? $("#sort-toggle-btn").hide() : this.sortItems().then();
}
async createMenuBtn() {
if (r) {
const e = o.includes("/actors/");
let t = $(".main-tabs, .tabs"), n = "加入黑名单", a = "#d22020", i = "";
if (e) {
t = $(".toolbar, .section-addition").filter(":last");
const e = await storageManager.getBlacklist(), i = this.getActressPageInfo();
e.find((e => e.starId === i.starId)) && (n = "已加入黑名单", a = "#885d5d");
}
const s = o.includes("advanced_search");
s ? t = $("h2.section-title") : i = "flex-grow:1;";
const r = localStorage.getItem("jhs_sortMethod"), l = "当前排序方式: " + ("rateCount" === r ? "评价人数" : "date" === r ? "时间" : "默认");
t.append(`\n <div style="display: flex;align-items: center; ${i} ">\n <a id="waitCheckBtn" class="menu-btn main-tab-btn" style="background-color:#56c938 !important;"><span>打开待鉴定</span></a>\n <a id="waitDownBtn" class="menu-btn main-tab-btn" style="background-color:#2caac0 !important;"><span>打开已收藏</span></a>\n ${e ? `\n <a id="addBlacklistBtn" class="menu-btn main-tab-btn" style="background-color:${a} !important;" data-tip="将演员加入黑名单, 后续有作品更新也会纳入屏蔽中"><span>${n}</span></a>\n <a id="filterAllVideo" class="menu-btn main-tab-btn" style="background-color:#e8ab39 !important;margin-right: 30px!important;" data-tip="一键屏蔽已选分类的视频列表至鉴定记录中"><span>一键屏蔽所有作品</span></a>\n ` : ""}\n </div>\n <div style="display: flex;align-items: center;">\n <a id="newVideoBtn" class="menu-btn main-tab-btn" style="background-color:#2c6cc0 !important;"><span>新作品检测 (<span id="newVideoCount">0</span>)</span></a>\n <a id="blacklistBtn" class="menu-btn main-tab-btn" style="background-color:#34393f !important;"><span>演员黑名单</span></a>\n ${c || s ? "" : `<a id="sort-toggle-btn" class="menu-btn main-tab-btn" style="background-color:#8783ab !important;"> ${l} </a>`}\n </div>\n `);
}
if (l) {
const e = o.includes("/star/");
let t = "加入黑名单", n = "#d22020";
if (e) {
const e = await storageManager.getBlacklist(), a = this.getActressPageInfo();
e.find((e => e.starId === a.starId)) && (t = "已加入黑名单", n = "#885d5d");
}
$(".masonry").parent().prepend(`\n <div style="margin: 10px; display: flex;">\n <a id="waitCheckBtn" class="menu-btn main-tab-btn" style="background-color:#56c938 !important;"><span>打开待鉴定</span></a>\n <a id="waitDownBtn" class="menu-btn main-tab-btn" style="background-color:#2caac0 !important;"><span>打开已收藏</span></a>\n \n ${e ? ` \n <a id="addBlacklistBtn" class="menu-btn main-tab-btn" style="background-color:${n} !important;" data-tip="将演员加入黑名单, 后续有作品更新也会纳入屏蔽中"><span>${t}</span></a>\n <a id="filterAllVideo" class="menu-btn main-tab-btn" style="background-color:#e8ab39 !important;" data-tip="一键屏蔽已选分类的视频列表至鉴定记录中"><span>一键屏蔽所有作品</span></a>\n ` : '<a id="blacklistBtn" class="menu-btn main-tab-btn" style="background-color:#34393f !important;"><span>演员黑名单</span></a>'}\n </div>\n `);
}
}
bindEvent() {
$("#waitCheckBtn").on("click", (e => {
this.openWaitCheck(e).then();
})), $("#waitDownBtn").on("click", (e => {
this.openFavorite(e).then();
})), $("#newVideoBtn").on("click", (e => {
this.getBean("NewVideoPlugin").openDialog();
})), $("#blacklistBtn").on("click", (e => {
this.getBean("BlacklistPlugin").openBlacklistDialog();
})), $("#sort-toggle-btn").on("click", (e => {
const t = localStorage.getItem("jhs_sortMethod");
let n;
n = t && "default" !== t ? "rateCount" === t ? "date" : "default" : "rateCount";
const a = {
default: "默认",
rateCount: "评价人数",
date: "时间"
}[n];
$(e.target).text(`当前排序方式: ${a}`), localStorage.setItem("jhs_sortMethod", n), this.sortItems().then();
}));
const e = this.getBean("TaskPlugin"), t = this.getBean("BlacklistPlugin");
$("#addBlacklistBtn").on("click", (async n => {
navigator.locks.request(e.singleTaskKey, {
ifAvailable: !0
}, (async e => {
e ? await t.addBlacklist(n) : show.error("当前有定时任务在后台执行中, 无法发起此操作");
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
}));
})), $("#filterAllVideo").on("click", (async e => {
let n = {
clientX: e.clientX,
clientY: e.clientY + 80
}, a = r ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
if (0 === a.length) return void show.error("获取演员名称失败");
let i = a.text().trim().split(",")[0];
utils.q(n, "一键屏蔽视频列表?", (async () => {
this.loadObj = loading();
try {
await t.filterAllVideo(i), window.refresh();
} catch (e) {
console.error(e);
} finally {
this.loadObj.close();
}
}));
}));
}
async sortItems() {
if (o.includes("handle") || o.includes("advanced_search")) return;
const e = await storageManager.getSetting("autoPage");
if (c || e === _) return;
const t = localStorage.getItem("jhs_sortMethod");
if (!t) return;
$(".movie-list .item").each((function(e) {
$(this).attr("data-original-index") || $(this).attr("data-original-index", e);
}));
const n = $(".movie-list"), a = $(".item", n);
if ("default" === t) a.sort((function(e, t) {
return $(e).data("original-index") - $(t).data("original-index");
})).appendTo(n); else {
const e = a.get();
e.sort((function(e, n) {
if ("rateCount" === t) {
const t = e => {
const t = $(e).find(".score .value").text().match(/由(\d+)人/);
return t ? parseFloat(t[1]) : 0;
};
return t(n) - t(e);
}
{
const t = e => {
const t = $(e).find(".meta").text().trim();
return new Date(t);
};
return t(n) - t(e);
}
})), n.empty().append(e);
}
}
async openWaitCheck() {
let e = this.getSelector();
const t = await storageManager.getSetting("waitCheckCount", 5), n = [ m, b, y, k ];
let a = 0;
$(`${e.itemSelector}:visible`).each(((e, i) => {
if (a >= t) return !1;
const s = $(i);
if (n.some((e => s.find(`span.tag:contains('${e}')`).length > 0))) return;
const {carNum: o, aHref: r} = this.getBean("ListPagePlugin").findCarNumAndHref(s);
if (o.includes("FC2-")) {
const e = this.parseMovieId(r);
this.getBean("Fc2Plugin").openFc2Page(e, o, r);
} else {
let e = r + (r.includes("?") ? "&autoPlay=1" : "?autoPlay=1");
window.open(e);
}
a++;
})), 0 === a && show.info("没有需鉴定的视频");
}
async openFavorite() {
let e = await storageManager.getSetting("waitCheckCount", 5);
const t = (await storageManager.getCarList()).filter((e => e.status === h)).sort(((e, t) => t.createDate - e.createDate));
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 e = this.parseMovieId(i);
await this.getBean("Fc2Plugin").openFc2Page(e, a, i);
} else window.open(i);
clog.debug("打开已收藏", a, i);
}
}
}
const Ce = async (e, t = "ja", n = "zh-CN") => {
if (!e) throw new Error("翻译文本不能为空");
const a = "https://translate-pa.googleapis.com/v1/translate?" + new URLSearchParams({
"params.client": "gtx",
dataTypes: "TRANSLATION",
key: "AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA",
"query.sourceLanguage": t,
"query.targetLanguage": n,
"query.text": e
}), i = await fetch(a);
if (!i.ok) throw new Error(`${i.status} ${i.statusText}`);
return (await i.json()).translation;
}, _e = {
IS_FILTERED: {
text: m,
color: f,
reasonType: "单番号屏蔽",
isCounted: !0,
countKey: "currentPageFilterCount"
},
IS_FAVORITE: {
text: b,
color: w,
reasonType: "",
isCounted: !0,
countKey: "currentPageFavoriteCount"
},
IS_HAS_DOWN: {
text: y,
color: x,
reasonType: "",
isCounted: !0,
countKey: "currentPageHasDownCount"
},
IS_HAS_WATCH: {
text: k,
color: S,
reasonType: "",
isCounted: !0,
countKey: "currentPageHasWatchCount"
},
IS_KEYWORD_FILTER: {
text: "❌ 关键词屏蔽",
color: "#de3333",
reasonType: "",
isCounted: !0,
countKey: "currentPageKeywordFilterCount"
},
IS_ACTOR_FILTER: {
text: "♂️ 男演员屏蔽",
color: "#b22222",
reasonType: "",
isCounted: !0,
countKey: "currentPageActorFilterCount"
},
IS_ACTRESS_FILTER: {
text: "♀️ 女演员屏蔽",
color: "#cd5c5c",
reasonType: "",
isCounted: !0,
countKey: "currentPageActorFilterCount"
},
IS_WAIT_CHECK: {
text: "",
color: "",
reasonType: "",
isCounted: !0,
countKey: "currentPageWaitCheckCount"
}
};
class Te extends R {
constructor() {
super(...arguments), i(this, "currentPageFilterCount", 0), i(this, "currentPageFavoriteCount", 0),
i(this, "currentPageHasDownCount", 0), i(this, "currentPageHasWatchCount", 0), i(this, "currentPageKeywordFilterCount", 0),
i(this, "currentPageActorFilterCount", 0), i(this, "currentPageWaitCheckCount", 0),
i(this, "currentPageTotalCount", 0), i(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}),
i(this, "writeQueue", Promise.resolve());
}
getName() {
return "ListPagePlugin";
}
async handle() {
new BroadcastChannel("channel-refresh").addEventListener("message", (async e => {
let t = e.data.type;
if ("refresh" === t) {
await this.doFilter();
const e = this.getBean("HistoryPlugin");
e.tableObj && e.tableObj.setData();
const t = this.getBean("NewVideoPlugin");
t && (t.showNewVideoCount().then(), t.loadData());
} else "cleanCache_filter_actor_actress_car_list" === t ? storageManager.cache_filter_actor_actress_car_list && (storageManager.cache_filter_actor_actress_car_list = null) : "clean_cacheSettingObj" === t && storageManager.cacheSettingObj && (storageManager.cacheSettingObj = null);
})), this.cleanRepeatId(), this.replaceHdImg(), this.fixBusTitleBox(), await this.doFilter(),
this.bindClick().then(), this.bindListPageHotKey().then(), this.rememberTagExpand(),
$(this.getSelector().itemSelector + " a").attr("target", "_blank"), this.checkDom();
}
rememberTagExpand() {
if (!window.location.href.includes("actors")) return;
const e = "jhs_tag_expand", t = $(".tag-expand");
if (0 === t.length) return;
"true" === localStorage.getItem(e) && t[0].click(), t.on("click", (function() {
const n = !t.closest(".content").hasClass("collapse");
localStorage.setItem(e, n.toString());
}));
}
checkDom() {
if (!window.isListPage) return;
const e = this.getSelector(), t = document.querySelector(e.boxSelector);
if (!t) return void console.error("没有找到容器节点!");
const n = new MutationObserver((async e => {
n.disconnect();
try {
this.replaceHdImg(), this.fixBusTitleBox(), await this.doFilter(), await this.getBean("ListPageButtonPlugin").sortItems(),
this.getBean("CoverButtonPlugin").addSvgBtn(), $(this.getSelector().itemSelector + " a").attr("target", "_blank"),
this.getBean("AutoPagePlugin").checkLoad();
} finally {
n.observe(t, a);
}
})), a = {
childList: !0,
subtree: !1
};
n.observe(t, a);
}
fixBusTitleBox() {
if (!l) return;
$(this.getSelector().itemSelector).toArray().forEach((e => {
var t;
let n = $(e);
if (n.find(".avatar-box").length > 0) return;
const a = (null == (t = n.find("img").attr("title")) ? void 0 : t.trim()) || "";
n.find(".photo-info span:first").contents().first().wrap(`<span class="video-title" title="${a}">${a}</span>`),
n.find("br").remove();
}));
}
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();
e.length && (await this.filterMovieList(e), await this.getBean("WangPan115MatchPlugin").matchMovieList(e),
l && await this.getBean("BusImgPlugin").logImageHeightsByRow());
}
async filterMovieList(e) {
utils.time("累计耗费时间"), utils.time("读取数据耗时");
const [t, n, a, i, s] = await Promise.all([ storageManager.getCarList(), storageManager.getTitleFilterKeyword(), storageManager.getBlacklist(), storageManager.getBlacklistCarList(), storageManager.getSetting() ]), o = utils.time("读取数据耗时"), u = t.reduce(((e, t) => {
const n = t.status;
return e.hasOwnProperty(n) && e[n].add(t.carNum), e;
}), {
[d]: new Set,
[h]: new Set,
[g]: new Set,
[p]: new Set
});
utils.time("组装数据耗时");
const m = new Map(a.map((e => [ e.starId, e.role ]))), {actorCarNumToNameMap: f, actressCarNumToNameMap: v} = i.reduce(((e, t) => {
const n = m.get(t.starId);
if (!n) return clog.error("黑名单数据源丢失演员信息", t), e;
const a = n === B ? e.actorCarNumToNameMap : e.actressCarNumToNameMap;
return a.has(t.carNum) || a.set(t.carNum, t.names), e;
}), {
actorCarNumToNameMap: new Map,
actressCarNumToNameMap: new Map
}), b = utils.time("组装数据耗时"), w = (null == s ? void 0 : s.showFilterItem) ?? C, y = (null == s ? void 0 : s.showFilterActorItem) ?? C, x = (null == s ? void 0 : s.showFilterKeywordItem) ?? C, k = (null == s ? void 0 : s.showFavoriteItem) ?? _, S = (null == s ? void 0 : s.showHasDownItem) ?? _, T = (null == s ? void 0 : s.showHasWatchItem) ?? _, I = (null == s ? void 0 : s.tagPosition) || "rightTop";
this.currentPageFilterCount = 0, this.currentPageFavoriteCount = 0, this.currentPageHasDownCount = 0,
this.currentPageHasWatchCount = 0, this.currentPageKeywordFilterCount = 0, this.currentPageActorFilterCount = 0,
this.currentPageWaitCheckCount = 0, this.currentPageTotalCount = 0, utils.time("处理页面耗时"),
await Promise.all(e.map((async e => {
let t = $(e);
if (l && t.find(".avatar-box").length > 0) return;
const {carNum: a, title: i} = this.findCarNumAndHref(t), {filter: s, favorite: o, hasDown: d, hasWatch: h} = u, g = o.has(a), p = d.has(a), m = h.has(a), b = s.has(a), B = f.has(a), P = v.has(a), D = B || P, L = n.find((e => i.includes(e) || a.startsWith(e))), A = !!L;
if (!c) {
const e = k === C && g || S === C && p || T === C && m || w === C && b && !(g || p || m) || y === C && D || x === C && A, n = t.attr("data-hide") === _;
e && !n ? t.hide().attr("data-hide", _) : !e && n && t.show().removeAttr("data-hide");
}
let M = _e.IS_WAIT_CHECK, N = null;
b ? M = _e.IS_FILTERED : g ? M = _e.IS_FAVORITE : p ? M = _e.IS_HAS_DOWN : m ? M = _e.IS_HAS_WATCH : A ? (M = _e.IS_KEYWORD_FILTER,
N = L || "未知") : B ? (M = _e.IS_ACTOR_FILTER, N = f.get(a) || "") : P && (M = _e.IS_ACTRESS_FILTER,
N = v.get(a) || ""), N || (N = M.reasonType), M.isCounted && this[M.countKey]++,
this.currentPageTotalCount++, t.find(".status-tag").remove();
const j = "rightTop" === I ? "right: 0; top:5px;" : "left: 0; top:5px;";
if (M.text) {
const e = r ? `<span class="tag is-success status-tag" data-tip="${N}" title=""\n style="margin-right: 5px; border-radius:10px; position:absolute; \n z-index:10; background-color: ${M.color} !important; ${j}">\n ${M.text}\n </span>` : `<a class="a-primary status-tag" data-tip="${N}" title=""\n style="margin-right: 5px; padding: 0 5px; color: #fff !important; border-radius:10px; position:absolute; \n z-index:10; background-color: ${M.color} !important; ${j}">\n <span class="tag" style="color:#fff !important;">${M.text}</span>\n </a>`;
if (r && t.find(".tags").append(e), l) {
const n = t.find(".item-tag");
n.length ? n.append(e) : t.find(".photo-info > span > div").append(e);
}
}
await this.translate(t);
})));
const P = utils.time("处理页面耗时"), D = utils.time("累计耗费时间");
$("#waitDownBtn span").text(`打开已收藏 (${u.favorite.size})`), clog.log(`\n <table class="countTable" style='border-collapse: collapse; width: 100%'>\n <tr>\n <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${o}</td>\n <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${b}</td>\n </tr>\n \n <tr>\n <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${P}</td>\n <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${D}</td>\n </tr>\n <tr>\n <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>项目</td>\n <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>数量</td>\n <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>项目</td>\n <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>数量</td>\n </tr>\n \n <tr>\n <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽单番号</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageFilterCount}</strong></td>\n <td style='padding: 3px; border: 1px solid #ccc;'>收藏</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageFavoriteCount}</strong></td>\n </tr>\n \n <tr>\n <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽演员</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageActorFilterCount}</strong></td>\n <td style='padding: 3px; border: 1px solid #ccc;'>已下载</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageHasDownCount}</strong></td>\n </tr>\n \n <tr>\n <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽关键词</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageKeywordFilterCount}</strong></td>\n <td style='padding: 3px; border: 1px solid #ccc;'>已观看</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageHasWatchCount}</strong></td>\n </tr>\n \n <tr>\n <td style='padding: 3px; border: 1px solid #ccc;'>待鉴定</td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageWaitCheckCount}</strong></td>\n <td style='padding: 3px; border: 1px solid #ccc;'></td>\n <td style='padding: 3px; border: 1px solid #ccc;'></td>\n </tr>\n \n <tr>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>总数</strong></td>\n <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageTotalCount}</strong></td>\n </tr>\n </table>\n `);
}
async bindClick() {
let e = this.getSelector();
$(e.boxSelector).on("click", ".item img", (async e => {
if (e.preventDefault(), e.stopPropagation(), $(e.target).closest("div.meta-buttons").length) return;
const t = $(e.target).closest(".item"), {carNum: n, aHref: a} = this.findCarNumAndHref(t);
let i = await storageManager.getSetting("dialogOpenDetail", _);
if (n.includes("FC2-")) {
let e = this.parseMovieId(a);
this.getBean("Fc2Plugin").openFc2Dialog(e, n, a);
} else i === _ ? (utils.openPage(a, n, !0, e), this.$currentImage = null) : window.open(a);
})), $(e.boxSelector).on("click", ".item video", (async e => {
const t = e.currentTarget;
t.paused ? t.play().catch((e => console.error("播放失败:", e))) : t.pause(), e.preventDefault(),
e.stopPropagation();
})), $(e.boxSelector).on("click", ".item .video-title", (async e => {
if ($(e.target).closest('[class^="jhs-match-"]').length) return;
const t = $(e.currentTarget).closest(".item"), {carNum: n, aHref: a} = this.findCarNumAndHref(t);
if (n.includes("FC2-")) {
e.preventDefault();
let t = this.parseMovieId(a);
this.getBean("Fc2Plugin").openFc2Dialog(t, n, a);
}
})), $(e.boxSelector).on("contextmenu", ".item img, .item video", (async e => {
e.preventDefault();
const t = $(e.target).closest(".item"), {carNum: n, url: a, publishTime: i} = this.findCarNumAndHref(t);
let s = r ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"), o = "";
s.length && (o = s.text().trim().split(",")[0].replace("(無碼)", "")), utils.q(e, `是否屏蔽番号 ${n}?`, (async () => {
setTimeout((async () => {
o || (o = await this.parseActressName(a)), await storageManager.saveCar({
carNum: n,
url: a,
names: o,
actionType: d,
publishTime: i
}), window.refresh(), show.ok("操作成功");
}));
}));
}));
}
async parseActressName(e) {
let t = null;
if (await storageManager.getSetting("enableSaveActressCarInfo", C) === _) {
clog.debug("鉴定补录演员信息-已启用, 开始解析详情页"), clog.debug("开始解析演员详情页", e);
const n = await gmHttp.get(e), a = utils.htmlTo$dom(n);
r ? t = a.find(".female").prev().map(((e, t) => $(t).text())).get().join(" ") : l && (t = a.find('span[onmouseover*="star_"] a').map(((e, t) => $(t).text())).get().join(" ")),
clog.debug("解析到名称:", t);
}
return t;
}
async bindListPageHotKey() {
this.$currentImage = null, $(document).on("mouseenter", this.getSelector().coverImgSelector, (e => {
this.$currentImage = $(e.currentTarget);
})).on("mouseleave", this.getSelector().coverImgSelector, (() => {
this.$currentImage = null;
}));
let e = await storageManager.getSetting();
if (this.filterHotKey = e.filterHotKey, this.favoriteHotKey = e.favoriteHotKey,
this.hasDownHotKey = e.hasDownHotKey, this.hasWatchHotKey = e.hasWatchHotKey, this.enableImageHotKey = e.enableImageHotKey || C,
this.clogHotKey = e.clogHotKey, this.enableImageHotKey === C) return;
const t = async (e, t) => {
setTimeout((async () => {
let n = await this.parseActressName(e.url);
await storageManager.saveCar({
carNum: e.carNum,
url: e.url,
names: n,
actionType: t,
publishTime: e.publishTime
}), window.refresh(), show.ok("操作成功");
}));
}, n = {};
this.filterHotKey && (n[this.filterHotKey] = e => {
t(e, d);
}), this.favoriteHotKey && (n[this.favoriteHotKey] = e => {
t(e, h);
}), this.hasDownHotKey && (n[this.hasDownHotKey] = e => {
t(e, g);
}), this.hasWatchHotKey && (n[this.hasWatchHotKey] = e => {
t(e, p);
}), this.clogHotKey && Q.registerHotkey(this.clogHotKey, (e => {
clog.toggleExpandCollapsed();
}));
const a = (e, t) => {
Q.registerHotkey(e, (e => {
const n = document.activeElement;
if (!("INPUT" === n.tagName || "TEXTAREA" === n.tagName || n.isContentEditable) && this.$currentImage) {
const e = this.$currentImage.closest(".item"), n = this.findCarNumAndHref(e);
t(n);
}
}));
};
Object.entries(n).forEach((([e, t]) => {
a(e, t);
}));
}
findCarNumAndHref(e) {
var t, n;
let a, i, s, o = e.find("a"), r = o.attr("href"), l = e.find(".video-title");
if (l.length > 0) {
let t = l.find("strong");
t.length > 0 && (a = t.text().trim()), i = o.attr("title") ? o.attr("title").trim() : a ? l.text().replace(a, "").trim() : l.text().trim(),
s = e.find(".meta").text().trim();
}
if (!a) {
let o = e.find("img");
r && o.length > 0 && (i = (null == (t = o.attr("title")) ? void 0 : t.trim()) || (null == (n = o.attr("data-title")) ? void 0 : n.trim()));
const l = e => /^\d{4}-\d{1,2}-\d{1,2}$/.test(e);
s = e.find("date").map(((e, t) => $(t).text().trim())).get().find(l), a = e.find("date").map(((e, t) => $(t).text().trim())).get().find((e => !l(e)));
}
if (!a) {
const e = "提取番号信息失败";
throw show.error(e), new Error(e);
}
return {
carNum: a,
aHref: r,
url: r,
title: i,
publishTime: s
};
}
showCarNumBox(e) {
const t = $(".movie-list .item").toArray().find((t => $(t).find(".video-title strong").text() === e));
if (t) {
const n = $(t);
n.attr("data-hide") === `${e}-hide` && (n.show(), n.removeAttr("data-hide"));
}
}
replaceHdImg(e) {
if (e || (e = document.querySelectorAll(this.getSelector().coverImgSelector)), r && e.forEach((e => {
e.src = e.src.replace("thumbs", "covers"), e.title = "";
})), l) {
const t = /\/(imgs|pics)\/(thumb|thumbs)\//, n = /(\.jpg|\.jpeg|\.png)$/i, a = e => {
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.dataset.title = e.title, e.title = "");
}, i = /ps(\.jpg|\.jpeg|\.png)$/i, s = e => {
e.src && i.test(e.src) && "true" !== e.dataset.hdReplaced && (e.src = e.src.replace(i, "pl$1"),
e.dataset.hdReplaced = "true", e.dataset.title = e.title, e.title = "");
};
e.forEach((e => {
a(e), s(e);
}));
}
storageManager.getSetting("hoverBigImg", C).then((e => {
e === _ && (window.imageHoverPreviewObj ? window.imageHoverPreviewObj.bindEvents() : window.imageHoverPreviewObj = new ImageHoverPreview({
selector: this.getSelector().coverImgSelector
}));
}));
}
async translate(e) {
if (await storageManager.getSetting("translateTitle", _) !== _) return;
let t, n, a = e.find(".video-title");
if (r ? (t = a.contents().filter(((e, t) => 3 === t.nodeType && "" !== t.textContent.trim())).text().trim(),
n = e.find(".video-title strong").text().trim()) : (t = e.find("img").attr("data-title").trim(),
n = e.find("a").attr("href").split("/").filter(Boolean).pop().trim()), this.cache[n]) {
let e = this;
return a.contents().each((function() {
3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + e.cache[n] + " ");
})), void a.attr("title", e.cache[n]);
}
Ce(t).then((e => {
r ? (a.contents().each((function() {
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(n) || (this.textContent = " " + e + " ");
})), a.attr("title", e)) : a.text(e), this.writeQueue = this.writeQueue.then((() => {
this.cache[n] = e, localStorage.setItem("jhs_translate", JSON.stringify(this.cache));
}));
})).catch((e => {
console.error("翻译失败:", e);
}));
}
async revertTranslation() {
$(this.getSelector().itemSelector).toArray().forEach((e => {
let t = $(e);
const n = t.find(".box").attr("title") || t.find(".video-title").attr("title") || t.find("img").attr("data-title");
let a;
r && (a = t.find(".video-title strong").text().trim());
const i = t.find(".video-title");
i.contents().each((function() {
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(a) || (this.textContent = " " + n + " ");
})), i.removeAttr("title");
}));
}
}
class Ie extends R {
constructor() {
super(...arguments), i(this, "preloadDistance", 500), i(this, "currentPage", this.getInitialPageNumber()),
i(this, "pageItems", []);
}
getName() {
return "AutoPagePlugin";
}
async initCss() {
return "\n <style>\n .jhs-scroll {\n text-align: center;\n padding-top: 20px;\n font-size: 14px;\n }\n .jhs-scroll.waterfall-loading { color: #000; }\n .jhs-scroll.waterfall-error { color: #f44336; cursor: pointer; }\n .jhs-scroll.waterfall-no-more { color: #4CAF50; }\n </style>\n ";
}
async handle() {
this.waterfall().then();
}
getInitialPageNumber() {
if (l) {
const e = o.match(/\/(page|star\/[^/]+)\/(\d+)/);
return e ? parseInt(e[2], 10) : 1;
}
if (r) {
const e = o.match(/[?&]page=(\d+)/);
return e ? parseInt(e[1], 10) : 1;
}
return 1;
}
async waterfall() {
if (await this.shouldDisablePaging()) return;
const e = this.getSelector();
if (this.container = document.querySelector(e.boxSelector), !this.container) return void console.error("没有找到容器节点,停止瀑布流!");
this.loader = document.createElement("div"), this.loader.className = "jhs-scroll",
this.container.parentNode.insertBefore(this.loader, this.container.nextSibling),
this.pageItems.push({
page: this.currentPage,
top: 0,
url: window.location.href
}), this.loader.addEventListener("click", (() => {
this.loader.classList.contains("waterfall-error") && this.loadNextPage().then();
})), window.addEventListener("scroll", (() => {
this.checkLoad(), this.checkScrollPosition();
}));
const t = document.querySelector(e.nextPageSelector);
this.nextUrl = null == t ? void 0 : t.href, this.hasMore = !!this.nextUrl, setTimeout((() => {
this.checkLoad();
}), 1e3), this.hasMore || this.setState("waterfall-no-more", "已经到底了");
}
async loadNextPage() {
var e;
if (await storageManager.getSetting("autoPage", _) === C) return void this.setState("waterfall-loading", "");
if (this.isLoading || !this.nextUrl) return;
this.isLoading = !0, this.setState("waterfall-loading", "加载中...");
const t = this.getSelector();
try {
clog.log("请求下一页内容:", this.nextUrl);
const n = await gmHttp.get(this.nextUrl), a = (new DOMParser).parseFromString(n, "text/html");
l && $(a).find(".avatar-box").length > 0 && $(a).find(".avatar-box").parent().remove();
let i = a.querySelectorAll(this.getSelector().requestDomItemSelector);
const s = this.container.scrollHeight;
this.pageItems.push({
page: this.currentPage + 1,
top: s,
url: this.nextUrl
});
const o = this.getBean("ListPagePlugin");
let r = a.querySelectorAll(this.getSelector().coverImgSelector);
o.replaceHdImg(r), $(this.getSelector().boxSelector).append(i), this.nextUrl = null == (e = a.querySelector(t.nextPageSelector)) ? void 0 : e.href,
this.hasMore = !!this.nextUrl;
let c = a.querySelectorAll(".pagination");
$(".pagination").replaceWith(c), this.setState("waterfall-loading", ""), this.hasMore || this.setState("waterfall-no-more", "已经到底了");
} catch (n) {
clog.error("加载失败:", n), this.setState("waterfall-error", "加载失败,点击重试");
} finally {
this.isLoading = !1;
}
}
checkScrollPosition() {
const e = window.scrollY;
for (let t = this.pageItems.length - 1; t >= 0; t--) {
const n = this.pageItems[t];
if (e >= n.top) {
this.currentPage !== n.page && (this.currentPage = n.page, this.updatePageUrl(n.url));
break;
}
}
}
checkLoad() {
if (!this.loader) return;
this.loader.getBoundingClientRect().top < window.innerHeight + this.preloadDistance && this.loadNextPage().then();
}
async shouldDisablePaging() {
if (!window.isListPage) return !0;
let e = await storageManager.getSetting("autoPage", _);
if (o.includes("/actors/") || o.includes("/star/")) {
let t = r ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
if (0 === t.length) return void show.error("获取演员名称失败");
let n = t.text().trim().split(",")[0];
if ((await storageManager.getBlacklist()).find((e => e.name === n)) && e === _) return clog.log("该演员已屏蔽, 停止瀑布流加载"),
!0;
}
return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100" ].some((e => o.includes(e)));
}
updatePageUrl_old(e) {
if (window.history.pushState({}, "", e), l) {
const t = e.match(/\/(page|star\/.*?)\/(\d+)/), n = t ? parseInt(t[2], 10) : null;
document.title = document.title.replace(/第\d+頁/, "第" + n + "頁");
}
}
updatePageUrl(e) {
window.history.replaceState({}, "", e), l && (document.title = document.title.replace(/第\d+頁/, `第${this.currentPage}頁`));
}
setState(e, t) {
this.loader.className = `jhs-scroll ${e}`, this.loader.textContent = t;
}
}
class Be {
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() {
return this.default_drive_id || (this.userInfo = await this.getUserInfo(), this.default_drive_id = this.userInfo.default_drive_id),
this.default_drive_id;
}
async getHeaders() {
return this.authorization || (this.authorization = await this.getAuthorization()),
{
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();
return await gmHttp.post(a, n, i), {};
}
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 s = await this.getHeaders();
return await gmHttp.post(a, i, s);
}
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) {
show.info("请求存储空间中...");
let i = this.baseApiUrl + "/adrive/v2/file/createWithFolders";
a || (a = await this.getDefaultDriveId());
let s = {
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(), r = await gmHttp.post(i, s, o), l = r.upload_id, c = r.file_id, d = r.part_info_list[0].upload_url;
clog.log("创建完成: ", r), show.info("开始上传文件..."), await this._doUpload(d, n);
const h = await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", s = {
drive_id: a,
file_id: c,
upload_id: l
}, o);
clog.log("标记完成:", h);
}
_doUpload(e, t) {
return new Promise(((n, a) => {
$.ajax({
type: "PUT",
url: e,
data: t,
contentType: " ",
processData: !1,
success: (e, t, i) => {
200 === i.status ? (clog.debug("上传成功:", e), n({})) : a(i);
},
error: e => {
clog.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;
}
}
n || (show.info("不存在备份目录, 进行创建"), n = await this.createFolder(e)), this.backupFolderId = n.file_id;
}
async backup(e, t, n) {
this.backupFolderId || await this._createBackupFolder(e), await this.uploadFile(this.backupFolderId, t, n);
}
async getBackupList(e) {
let t;
this.backupFolderId || await this._createBackupFolder(e), t = await this.getFileList(this.backupFolderId);
const n = [];
return t.forEach((e => {
n.push({
name: e.name,
fileId: e.file_id,
createTime: e.created_at,
size: e.size
});
})), n;
}
}
class Pe {
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, s) => {
const o = this.davUrl + t, r = {
...this._getAuthHeaders(),
...n
};
GM_xmlhttpRequest({
method: e,
url: o,
headers: r,
data: a,
onload: e => {
e.status >= 200 && e.status < 300 ? i(e) : (console.error(e), s(new Error(`请求失败 ${e.status}: ${e.statusText}`)));
},
onerror: e => {
console.error("请求WebDav发生错误:", e), s(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, a;
const i = (await this._sendRequest("PROPFIND", e, {
"Content-Type": "application/xml"
}, '<?xml version="1.0"?>\n <d:propfind xmlns:d="DAV:">\n <d:prop>\n <d:displayname />\n <d:getcontentlength />\n <d:creationdate />\n <d:getlastmodified />\n <d:iscollection />\n </d:prop>\n </d:propfind>\n ')).responseText, s = (new DOMParser).parseFromString(i, "text/xml").getElementsByTagNameNS("DAV:", "response"), o = [];
for (let r = 0; r < s.length; r++) {
if (0 === r) continue;
let e = s[r];
console.log(e);
const i = e.getElementsByTagNameNS("DAV:", "displayname")[0].textContent, l = (null == (t = e.getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : t.textContent) || "0", c = (null == (n = e.getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : n.textContent) || (null == (a = e.getElementsByTagNameNS("DAV:", "getlastmodified")[0]) ? void 0 : a.textContent) || "";
"0" !== l && o.push({
fileId: i,
name: i,
size: Number(l),
createTime: c
});
}
return o.reverse(), o;
}
async deleteFile(e) {
let t = this.folderName + "/" + encodeURI(e);
await this._sendRequest("DELETE", t, {
"Cache-Control": "no-cache"
});
}
async getBackupList(e) {
return this.folderName = e, await this._sendRequest("MKCOL", e), this.getFileList(e);
}
async getFileContent(e) {
let t = this.folderName + "/" + e;
return (await this._sendRequest("GET", t, {
Accept: "application/octet-stream"
})).responseText;
}
}
class De extends R {
constructor() {
super(...arguments), i(this, "folderName", "JHS-数据备份"), i(this, "cacheItems", [ {
key: "jhs_dmm_video",
text: "🎥 预览视频缓存",
title: "预览视频缓存"
}, {
key: "jhs_other_site",
text: "🌍 第三方站点缓存",
title: "第三方站点资源检测结果, 如missav,123Av等"
}, {
key: "jhs_screenShot",
text: "🖼️ 缩略图缓存",
title: "缩略图缓存"
}, {
key: "jhs_translate",
text: "🆎 标题翻译",
title: "标题翻译"
}, {
key: "jhs_actress_info",
text: "👩 演员信息",
title: "演员的年龄三围等数据信息"
}, {
key: "jhs_score_info",
text: "⭐ Top250|热播 评分数据",
title: "Top250及热播的评分数据"
} ]);
}
getName() {
return "SettingPlugin";
}
async initCss() {
const e = await storageManager.getSetting();
let t = (null == e ? void 0 : e.containerWidth) ?? "100", n = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == e ? void 0 : e.containerColumns) ?? 5;
this.applyImageMode().then();
let a = `\n section .container{\n max-width: 1000px !important;\n min-width: ${t}%;\n }\n .movie-list, .movie-list.v{\n grid-template-columns: repeat(${n}, minmax(0, 1fr));\n }\n `;
return l && (a = `\n .container-fluid .row{\n max-width: 1000px !important;\n min-width: ${t}%;\n margin: auto auto;\n }\n \n .container {\n max-width: 1000px !important;\n min-width: 80%;\n margin: auto auto;\n }\n \n .masonry {\n grid-template-columns: repeat(${n}, minmax(0, 1fr));\n }\n `),
`\n <style>\n ${a}\n .nav-btn::after {\n content:none !important;\n }\n \n #cache-data-display pre {\n font-family: Consolas, Monaco, 'Andale Mono', monospace;\n white-space: pre-wrap;\n word-wrap: break-word;\n line-height: 1.5;\n color: #333;\n border: 1px solid #ddd;\n }\n \n .cache-item {\n transition: all 0.2s ease;\n }\n .cache-item:hover {\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n transform: translateY(-2px);\n }\n\n .tooltip-icon {\n display: inline-block;\n width: 16px;\n height: 16px;\n line-height: 16px;\n text-align: center;\n border-radius: 50%;\n background-color: #ccc;\n color: white;\n font-size: 12px;\n margin-right: 5px;\n cursor: help;\n }\n .setting-item {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n margin-bottom: 3px;\n padding: 3px;\n /*border: 1px solid #ddd;\n border-radius: 5px;*/\n }\n .simple-setting .setting-item{\n align-items:center;\n }\n .setting-label {\n font-size: 14px;\n min-width: 160px;\n font-weight: bold;\n margin-right: 10px;\n }\n .form-content{\n max-width: 160px;\n min-width: 160px;\n }\n .form-content * {\n width: 100%;\n padding: 5px;\n margin-right: 10px;\n text-align: center;\n }\n \n .keyword-label {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 14px;\n position: relative;\n margin-left: 8px;\n margin-bottom: 5px;\n }\n .keyword-remove {\n margin-left: 6px;\n cursor: pointer;\n font-size: 12px;\n line-height: 1;\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 .add-tag-btn {\n padding: 6px 12px;\n background-color: #e2e8f0;\n color: #334155;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n margin-left: 8px;\n float:right;\n }\n .add-tag-btn:hover {\n background-color: #cbd5e1;\n }\n .tag-box {\n margin-top:15px;\n }\n \n \n #saveBtn,#moreBtn,#helpBtn,#clean-all {\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 #moreBtn {\n background-color: #5cb85c;\n color: white;\n }\n #moreBtn:hover {\n background-color: #4cae4c;\n }\n #helpBtn {\n background-color: #e67e22;\n color: white;\n }\n #helpBtn:hover {\n background-color: #d35400;\n }\n .simple-setting, .mini-simple-setting {\n display: none;\n background: rgba(255,255,255,1); \n position: absolute;\n top: ${r ? "35px" : "25px"};\n right: ${r ? "-300%" : "0"};\n z-index: 1000;\n border: 1px solid #ddd;\n border-radius: 4px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n padding: 0;\n margin-top: 5px; /* 稍微拉开一点距离 */\n color: #333;\n }\n \n .mini-switch {\n appearance: none;\n -webkit-appearance: none;\n width: 40px;\n height: 20px;\n background: #e0e0e0;\n border-radius: 20px;\n position: relative;\n cursor: pointer;\n outline: none;\n /*transition: all 0.2s ease;*/\n }\n \n .mini-switch:checked {\n background: #4CAF50;\n }\n \n .mini-switch::before {\n content: "";\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: white;\n top: 2px;\n left: 2px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.2);\n /*transition: all 0.2s ease;*/\n }\n \n .mini-switch:checked::before {\n left: calc(100% - 18px);\n }\n \n .side-menu-item {\n padding: 12px 12px;\n cursor: pointer;\n color: #333;\n border-left: 3px solid transparent;\n transition: all 0.2s;\n display: flex;\n gap: 5px;\n }\n \n .side-menu-item .icon {\n height: 24px; \n width: 24px;\n }\n \n .side-menu-item:hover {\n background-color: #e9e9e9;\n }\n \n .side-menu-item.active {\n background-color: #e0e0e0;\n border-left: 3px solid #5d87c2;\n font-weight: bold;\n }\n \n .content-panel {\n display: none;\n margin-top:20px;\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n }\n \n .content-panel.active {\n display: block;\n }\n </style>\n `;
}
async handle() {
if (await storageManager.getSetting("enableClog", _) === _ && clog.show(), r) {
let e = function() {
$(".navbar-search").is(":hidden") ? ($(".mini-setting-box").hide(), $(".setting-box").show()) : ($(".mini-setting-box").show(),
$(".setting-box").hide());
};
$("#navbar-menu-user .navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable setting-box" style="position:relative;">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-right:15px !important;">\n 设置\n </a>\n <div class="simple-setting"></div>\n </div>'),
utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => {
$(".miniHistoryBtnBox").before('\n <div class="navbar-item mini-setting-box" style="position:relative;margin-left: auto;">\n <a id="mini-setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-left:0 !important;padding-right:0 !important;">\n 设置\n </a>\n <div class="mini-simple-setting"></div>\n </div>\n '),
e();
})), $(window).resize(e);
}
l && utils.loopDetector((() => $("#waitCheckBtn").length), (() => {
$("#waitCheckBtn").parent().append('\n <div id="top-right-box" style="position: relative; display: flex; flex-grow: 1;justify-content: flex-end;">\n <div class="setting-box">\n <a id="setting-btn" class="menu-btn main-tab-btn" style="background-color:#6e685e !important;">\n <span>设置</span>\n </a>\n <div class="simple-setting"></div>\n </div>\n </div>\n ');
}), 1, 1e4, !1), $(".main-nav, .container-fluid").on("click", "#setting-btn, #mini-setting-btn", (() => {
clog.lowZIndex(), this.openSettingDialog();
})), $(".main-nav, .container-fluid").on("mouseenter", ".setting-box", (() => {
$(".simple-setting").html(this.simpleSetting()).show(), this.initSimpleSettingForm().then(),
clog.lowZIndex();
})).on("mouseleave", ".setting-box", (() => {
$(".simple-setting").html("").hide();
})), $(".main-nav, .container-fluid").on("mouseenter", ".mini-setting-box", (() => {
$(".mini-simple-setting").html(this.simpleSetting()).show(), this.initSimpleSettingForm().then(),
clog.lowZIndex();
})).on("mouseleave", ".mini-setting-box", (() => {
$(".mini-simple-setting").html("").hide();
}));
}
async openSettingDialog(e = "backup-panel", t) {
const n = this.cacheItems.map((e => `\n <div class="cache-item" style="border: 1px solid #eee; border-radius: 8px; padding: 12px;">\n <div style="font-weight: bold; margin-bottom: 8px;">${e.text}</div>\n <div style="display: flex; gap: 8px;">\n <a class="menu-btn clean-btn" data-key="${e.key}" style="background-color:#448cc2; flex:1; text-align:center;" title="${e.title}">\n <span>清理</span>\n </a>\n <a class="menu-btn view-btn" data-key="${e.key}" style="background-color:#b2bec0; flex:1; text-align:center;" >\n <span>查看</span>\n </a>\n </div>\n </div>\n `)).join("");
let a = "";
A.forEach((e => {
e.canSelect && (a += `<option value="${e.quality}">${e.text}</option>`);
}));
const i = this.getBean("CoverButtonPlugin");
let s = `\n <div style="display: flex; height: 100%;">\n <div style="width: 140px; flex-shrink: 0; padding: 15px 0; background: #f5f5f5; border-right: 1px solid #ddd;">\n <div class="side-menu-item ${"backup-panel" === e ? "active" : ""}" data-panel="backup-panel">💾 数据备份</div>\n <div class="side-menu-item ${"base-panel" === e ? "active" : ""}" data-panel="base-panel">⚙️ 基础配置</div>\n <div class="side-menu-item ${"filter-panel" === e ? "active" : ""}" data-panel="filter-panel">🚫 屏蔽配置</div>\n <div class="side-menu-item ${"task-panel" === e ? "active" : ""}" data-panel="task-panel">📋 定时任务</div>\n <div class="side-menu-item ${"domain-panel" === e ? "active" : ""}" data-panel="domain-panel" title="第三方视频资源域名配置">🌐 外部网站</div>\n <div class="side-menu-item ${"hotkey-panel" === e ? "active" : ""}" data-panel="hotkey-panel">⌨️ 快捷键配置</div>\n <div class="side-menu-item ${"cache-panel" === e ? "active" : ""}" data-panel="cache-panel">🧹 清理缓存</div>\n </div>\n \n <div style="flex: 1; display: flex; flex-direction: column; height: 100%; ">\n <div style="flex: 1; margin: 0 20px; padding-bottom: 20px;overflow: hidden">\n \n \x3c!-- 阿里云盘面板 --\x3e\n <div id="backup-panel" class="content-panel" style="display: ${"backup-panel" === e ? "block" : "none"};">\n <div style="margin-bottom: 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="getRefreshTokenBtn" class="menu-btn fr-btn" style="background-color:#c4a35e"><span>获取refresh_token</span></a>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">阿里云盘备份</span>\n <div>\n <a id="backupListBtn" class="menu-btn" style="background-color:#5d87c2"><span>查看备份</span></a>\n <a id="backupBtn" class="menu-btn" style="background-color:#64bb69"><span>备份数据</span></a>\n </div>\n </div>\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: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,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="webdavBackupListBtn" class="menu-btn" style="background-color:#5d87c2"><span>查看备份</span></a>\n <a id="webdavBackupBtn" class="menu-btn" style="background-color:#64bb69"><span>备份数据</span></a>\n </div>\n </div>\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 <div class="setting-item">\n <span class="setting-label">用户名:</span>\n <div class="form-content">\n <input id="webDavUsername">\n </div>\n </div>\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 </div>\n \n \n \x3c!-- 基础设置面板 --\x3e\n <div id="base-panel" class="content-panel" style="display: ${"base-panel" === e ? "block" : "none"};">\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="tagPosition">\n <option value="rightTop">右上</option>\n <option value="leftTop">左上</option>\n </select>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n 鉴定补录演员信息 <span data-tip="在列表页进行鉴定是获取不到演员名称的, 开启后, 额外解析详情页补录演员名称, 因发请求解析费时, 会被以往慢1秒左右">❓</span>\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableSaveActressCarInfo" class="mini-switch">\n </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item" style="margin-top:10px">\n <span class="setting-label">\n 封面快捷按钮\n </span>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n ${i.screenSvg}长缩略图:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableScreenSvg" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n ${i.videoSvg}预览视频:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableVideoSvg" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n ${i.handleSvg}鉴定按钮:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableHandleSvg" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n ${i.siteSvg}第三方跳转:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableSiteSvg" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label" style="display:flex; align-items:center; gap:5px">\n ${i.copySvg}复制按钮:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableCopySvg" class="mini-switch">\n </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,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="videoQuality">\n ${a}\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 ${r ? "" : "do-hide"}">\n <span class="setting-label">\n 高亮已收藏演员 <span data-tip="详情页, 对已收藏的演员进行边框高亮提醒">❓</span>\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableFavoriteActresses" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item ${r ? "" : "do-hide"}">\n <span id="highlightedTagLabel" class="setting-label">\n 分类标签|高亮演员-边框样式:\n </span>\n <div class="form-content" style="display: flex; align-items: center;">\n <input type="number" id="highlightedTagNumber" min="0" max="20">\n <input type="color" id="highlightedTagColor">\n </div>\n </div>\n\n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">\n 启用控制台日志:\n </span>\n <div class="form-content">\n <select id="enableClog">\n <option value="no">禁用</option>\n <option value="yes">开启</option>\n </select>\n </div>\n </div>\n </div>\n \n \x3c!-- 定时任务 --\x3e\n <div id="task-panel" class="content-panel" style="display: ${"task-panel" === e ? "block" : "none"};">\n \n <div class="setting-item">\n <span class="setting-label">请求并发数量:</span>\n <div class="form-content">\n <input type="number" id="checkConcurrencyCount" min="2" max="5" style="width: 100%;">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">请求间隔时间(毫秒):</span>\n <div class="form-content">\n <input type="number" id="checkRequestSleep" min="0" max="3000" style="width: 100%;">\n </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div id="setting-blacklist" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;">\n <span style="font-size: 14px; font-weight: bold; padding:3px">自动检测屏蔽黑名单演员</span>\n <div class="setting-item">\n <span class="setting-label">\n 任务开关:\n </span>\n <div class="form-content">\n <select id="enableCheckBlacklist">\n <option value="no">禁用</option>\n <option value="yes">开启</option>\n </select>\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">任务间隔时间:</span>\n <div class="form-content">\n <select id="checkBlacklist_intervalTime">\n <option value="2">每2小时</option>\n <option value="3">每3小时</option>\n <option value="6">每6小时</option>\n <option value="12">每12小时</option>\n <option value="24">每24小时</option>\n </select>\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">检测规则:</span>\n <div class="form-content">\n <select id="checkBlacklist_ruleTime">\n <option value="0">全部检测</option>\n <option value="8760">不检测停更1年以上</option>\n <option value="17520">不检测停更2年以上</option>\n <option value="26280">不检测停更3年以上</option>\n </select>\n </div>\n </div>\n </div>\n \n <div id="setting-checkFavoriteActress" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;" class="${r ? "" : "do-hide"}">\n <span style="font-size: 14px; font-weight: bold; padding:3px">自动同步已收藏的演员</span>\n <div class="setting-item">\n <span class="setting-label">\n 任务开关:\n </span>\n <div class="form-content">\n <select id="enableCheckFavoriteActress">\n <option value="no">禁用</option>\n <option value="yes">开启</option>\n </select>\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">任务间隔时间:</span>\n <div class="form-content">\n <select id="checkFavoriteActress_IntervalTime">\n <option value="12">每12小时</option>\n <option value="24">每24小时</option>\n </select>\n </div>\n </div>\n </div>\n \n <div id="setting-checkNewVideo" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;" class="${r ? "" : "do-hide"}">\n <span style="font-size: 14px; font-weight: bold; padding:3px">自动检测已收藏演员的最新作品</span>\n <div class="setting-item">\n <span class="setting-label">\n 任务开关:\n </span>\n <div class="form-content">\n <select id="enableCheckNewVideo">\n <option value="no">禁用</option>\n <option value="yes">开启</option>\n </select>\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">任务间隔时间:</span>\n <div class="form-content">\n <select id="checkNewVideo_intervalTime">\n <option value="2">每2小时</option>\n <option value="3">每3小时</option>\n <option value="6">每6小时</option>\n <option value="12">每12小时</option>\n <option value="24">每24小时</option>\n </select>\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">检测规则:</span>\n <div class="form-content">\n <select id="checkNewVideo_ruleTime">\n <option value="0">全部检测</option>\n <option value="8760">不检测停更1年以上</option>\n <option value="17520">不检测停更2年以上</option>\n <option value="26280">不检测停更3年以上</option>\n </select>\n </div>\n </div>\n </div>\n </div> \n \n \x3c!-- 域名设置面板 --\x3e\n <div id="domain-panel" class="content-panel" style="display: ${"domain-panel" === e ? "block" : "none"};">\n <div class="setting-item">\n <span class="setting-label">域名 - MissAv:</span>\n <div class="form-content">\n <input id="missAvUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - Jable:</span>\n <div class="form-content">\n <input id="jableUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - Avgle:</span>\n <div class="form-content">\n <input id="avgleUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - JavTrailer:</span>\n <div class="form-content">\n <input id="javTrailersUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - 123Av:</span>\n <div class="form-content">\n <input id="av123Url">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - JavDb:</span>\n <div class="form-content">\n <input id="javDbUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - JavBus:</span>\n <div class="form-content">\n <input id="javBusUrl">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">域名 - SupJav:</span>\n <div class="form-content">\n <input id="supJavUrl">\n </div>\n </div> \n </div>\n \n \x3c!-- 快捷键 --\x3e\n <div id="hotkey-panel" class="content-panel" style="display: ${"hotkey-panel" === e ? "block" : "none"};">\n <div class="setting-item">\n <span class="setting-label">${u}:</span>\n <div class="form-content">\n <input id="filterHotKey" placeholder="录入快捷键" data-default-hotkey="a">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">${v}:</span>\n <div class="form-content">\n <input id="favoriteHotKey" placeholder="录入快捷键" data-default-hotkey="s">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">${y}:</span>\n <div class="form-content">\n <input id="hasDownHotKey" placeholder="录入快捷键">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">${k}:</span>\n <div class="form-content">\n <input id="hasWatchHotKey" placeholder="录入快捷键">\n </div>\n </div>\n <div class="setting-item">\n <span class="setting-label">⏩ 快进:</span>\n <div class="form-content">\n <input id="speedVideoHotKey" placeholder="录入快捷键" data-default-hotkey="z">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">💻 控制台:</span>\n <div class="form-content">\n <input id="clogHotKey" placeholder="录入快捷键">\n </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="列表页,鼠标放置图片上时可使用快捷键">❓ </span> 对视频列表页启用快捷键:\n </span>\n <div class="form-content">\n <input type="checkbox" id="enableImageHotKey" class="mini-switch">\n </div>\n </div>\n\n </div>\n \n \x3c!-- 屏蔽设置面板 --\x3e\n <div id="filter-panel" class="content-panel" style="display: ${"filter-panel" === e ? "block" : "none"};">\n <div class="setting-item">\n <span class="setting-label">\n 启用划词屏蔽 <span data-tip="视频详情页中, 标题或评论区选中文字, 按右键可快捷加入屏蔽词">❓ </span>\n </span>\n <div style="display: flex">\n <input type="checkbox" id="enableTitleSelectFilter" class="mini-switch">\n </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div id="reviewKeywordContainer">\n <div class="setting-item">\n <span class="setting-label">评论区屏蔽词:</span>\n <div style="display: flex">\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n <button class="add-tag-btn">添加</button>\n </div>\n </div>\n <div class="tag-box"> </div>\n </div>\n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n \n <div id="filterKeywordContainer">\n <div class="setting-item">\n <span class="setting-label">视频标题屏蔽词:</span>\n <div style="display: flex">\n <input type="text" class="keyword-input" placeholder="添加屏蔽词">\n <button class="add-tag-btn">添加</button>\n </div>\n </div>\n <div class="tag-box"> </div>\n </div>\n </div>\n <div id="cache-panel" class="content-panel" style="display: ${"cache-panel" === e ? "block" : "none"};">\n <h1 style="text-align:center;font-size: 20px;font-weight: bold">以下操作, 不会对核心数据造成影响</h1>\n <br/> \n <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 20px;">\n ${n}\n </div> \n <div id="cache-data-display" style="margin-top: 20px; display: none;">\n <pre style="background: #f5f5f5; padding: 10px; border-radius: 5px; max-height: 400px; overflow: auto;"></pre>\n </div>\n </div>\n </div>\n \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 <button id="clean-all" style="display: none">♾️ 清理全部缓存</button>\n </div>\n </div>\n </div>\n `;
layer.open({
type: 1,
title: "设置",
content: s,
area: utils.getResponsiveArea([ "55%", "90%" ]),
scrollbar: !1,
success: (e, n) => {
$(e).find(".layui-layer-content").css("position", "relative"), this.loadForm(),
this.bindClick(), utils.setupEscClose(n), t && t();
},
end: () => {
this.getBean("CoverButtonPlugin").enableSvgBtn();
}
});
}
simpleSetting() {
return `\n <div style="margin-top:20px;max-height:90vh; overflow-y:auto;">\n <div style="margin: 0 10px;">\n <div class="setting-item">\n <span class="setting-label">\n 显示已鉴定内容:\n </span>\n <div class="form-content" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;">\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">屏蔽单番号: </span><input type="checkbox" id="showFilterItem" class="mini-switch"><br/>\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">屏蔽演员: </span><input type="checkbox" id="showFilterActorItem" class="mini-switch"><br/>\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">屏蔽关键词: </span><input type="checkbox" id="showFilterKeywordItem" class="mini-switch"><br/>\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">收藏: </span><input type="checkbox" id="showFavoriteItem" class="mini-switch"><br/>\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">已下载: </span><input type="checkbox" id="showHasDownItem" class="mini-switch"><br/>\n <span style="display:inline-block; width: 80px; font-size:13px; font-weight:bold; text-align: left">已观看: </span><input type="checkbox" id="showHasWatchItem" class="mini-switch"><br/>\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="点击封面的打开方式,弹窗|新窗口">❓ </span>弹窗方式打开页面:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="dialogOpenDetail" class="mini-switch">\n </div>\n </div> \n \n <div class="setting-item">\n <span class="setting-label">鉴定后立即关闭页面:</span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="needClosePage" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="使用瀑布流模式, 排序方式将调整为默认">❓ </span>瀑布流模式:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="autoPage" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">启用标题翻译:</span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="translateTitle" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">启用悬浮大图:</span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="hoverBigImg" class="mini-switch">\n </div>\n </div>\n \n \n <div class="setting-item">\n <span class="setting-label">启用115视频匹配: </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enable115Match" class="mini-switch">\n </div>\n </div>\n \n \n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n\n ${r ? '\n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="详情页是否展示女优年龄、三围等信息">❓ </span>加载女优信息:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableLoadActressInfo" class="mini-switch">\n </div>\n </div>' : ""}\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="详情页第三方资源检测,如missAv,123AV">❓ </span>加载第三方视频资源:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableLoadOtherSite" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="详情页图片区首列位置加载长缩略图">❓ </span>加载长缩略图:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableLoadScreenShot" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="详情页解析更多更高画质的预览视频">❓ </span>更高画质预览视频:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableLoadPreviewVideo" class="mini-switch">\n </div>\n </div>\n\n <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/>\n\n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="列数6以上,建议开启竖图">❓ </span>竖图模式:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableVerticalModel" class="mini-switch">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">页面列数: <span id="showContainerColumns"></span></span>\n <div class="form-content">\n <input type="range" id="containerColumns" min="2" max="10" step="1" style="padding:5px 0">\n </div>\n </div>\n \n <div class="setting-item">\n <span class="setting-label">页面宽度: <span id="showContainerWidth"></span></span>\n <div class="form-content">\n <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0">\n </div>\n </div>\n </div>\n <div style="padding: 0 20px 15px; text-align: right; border-top: 1px solid #eee;"> \n <button id="helpBtn" style="float:left;">常见问题</button>\n <button id="moreBtn">更多设置</button>\n </div>\n </div>\n `;
}
async loadForm() {
let e = await storageManager.getSetting();
$("#videoQuality").val(e.videoQuality), $("#reviewCount").val(e.reviewCount || 20),
$("#tagPosition").val(e.tagPosition || "rightTop"), $("#waitCheckCount").val(e.waitCheckCount || 5),
$("#checkConcurrencyCount").val(e.checkConcurrencyCount || 2), $("#checkRequestSleep").val(e.checkRequestSleep || 100),
$("#enableCheckBlacklist").val(e.enableCheckBlacklist || _), $("#checkBlacklist_intervalTime").val(e.checkBlacklist_intervalTime || 12),
$("#checkBlacklist_ruleTime").val(e.checkBlacklist_ruleTime || 8760), $("#enableCheckFavoriteActress").val(e.enableCheckFavoriteActress || _),
$("#checkFavoriteActress_IntervalTime").val(e.checkFavoriteActress_IntervalTime || 24),
$("#enableCheckNewVideo").val(e.enableCheckNewVideo || _), $("#checkNewVideo_intervalTime").val(e.checkNewVideo_intervalTime || 12),
$("#checkNewVideo_ruleTime").val(e.checkNewVideo_ruleTime || 8760);
const t = e.highlightedTagNumber || 1, n = e.highlightedTagColor || "#ce2222";
$("#highlightedTagNumber").val(e.highlightedTagNumber || 1), $("#highlightedTagColor").val(e.highlightedTagColor || "#ce2222"),
$("#highlightedTagLabel").css("border", `${t}px solid ${n}`), $("#enableClog").val(e.enableClog || _),
$("#refresh_token").val(e.refresh_token || ""), $("#webDavUrl").val(e.webDavUrl || ""),
$("#webDavUsername").val(e.webDavUsername || ""), $("#webDavPassword").val(e.webDavPassword || ""),
$("#enableTitleSelectFilter").prop("checked", !e.enableTitleSelectFilter || e.enableTitleSelectFilter === _),
$("#enableFavoriteActresses").prop("checked", !e.enableFavoriteActresses || e.enableFavoriteActresses === _),
$("#enableSaveActressCarInfo").prop("checked", !!e.enableSaveActressCarInfo && e.enableSaveActressCarInfo === _),
$("#enableScreenSvg").prop("checked", !e.enableScreenSvg || e.enableScreenSvg === _),
$("#enableVideoSvg").prop("checked", !e.enableVideoSvg || e.enableVideoSvg === _),
$("#enableHandleSvg").prop("checked", !e.enableHandleSvg || e.enableHandleSvg === _),
$("#enableSiteSvg").prop("checked", !e.enableSiteSvg || e.enableSiteSvg === _),
$("#enableCopySvg").prop("checked", !e.enableCopySvg || e.enableCopySvg === _);
const a = this.getBean("OtherSitePlugin"), i = await a.getMissAvUrl(), s = await a.getjableUrl(), o = await a.getAvgleUrl(), r = await a.getJavTrailersUrl(), l = await a.getAv123Url(), c = await a.getJavDbUrl(), d = await a.getJavBusUrl(), h = await a.getSupJavUrl();
$("#missAvUrl").val(i), $("#jableUrl").val(s), $("#avgleUrl").val(o), $("#javTrailersUrl").val(r),
$("#av123Url").val(l), $("#javDbUrl").val(c), $("#javBusUrl").val(d), $("#supJavUrl").val(h);
let g = await storageManager.getReviewFilterKeywordList(), p = await storageManager.getTitleFilterKeyword();
g && g.forEach((e => {
this.addLabelTag("#reviewKeywordContainer", e);
})), p && p.forEach((e => {
this.addLabelTag("#filterKeywordContainer", e);
})), [ "#reviewKeywordContainer", "#filterKeywordContainer" ].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);
}));
})), $("#hotkey-panel [id]").map(((e, t) => t.id)).get().forEach((t => {
const n = $(`#${t}`), a = void 0 !== e[t] ? e[t] : n.attr("data-default-hotkey") || "";
n.val(a).on("input", (e => {
let t = $(e.target).val();
(/[\u4e00-\u9fa5]/.test(t) || /^Shift[a-zA-Z0-9]+$/.test(t)) && ($(e.target).val(""),
show.error("非法输入:不能输入中文或输入法转换错误"));
})).on("keydown", (e => this.handleHotkeyInput(e, n)));
})), $("#enableImageHotKey").prop("checked", !!e.enableImageHotKey && e.enableImageHotKey === _);
}
handleHotkeyInput(e, t) {
e.preventDefault();
const n = this.parseHotkey(e);
"" !== n ? this.isDuplicateHotkey(n, t.attr("id")) ? show.error("该快捷键已被其他功能使用!") : t.val(n) : t.val("");
}
parseHotkey(e) {
if ("Backspace" === e.key || "Process" === e.key) return "";
const t = [];
e.ctrlKey && t.push("Ctrl"), e.shiftKey && t.push("Shift"), e.altKey && t.push("Alt"),
e.metaKey && t.push("Cmd");
const n = {
" ": "Space",
Control: "Ctrl",
Meta: "Cmd",
ArrowUp: "Up",
ArrowDown: "Down",
ArrowLeft: "Left",
ArrowRight: "Right"
}[e.key] || (e.key.length > 1 ? e.key.replace("Arrow", "") : e.key);
return [ "Control", "Shift", "Alt", "Meta" ].includes(e.key) || t.push(n), t.length > 0 ? t.join("+") : "";
}
isDuplicateHotkey(e, t) {
let n = !1;
return $("#hotkey-panel [id]").each(((a, i) => {
if (i.id !== t && e && e === $(i).val()) return n = !0, !1;
})), n;
}
async initSimpleSettingForm() {
let e = await storageManager.getSetting();
$("#containerColumns").val(e.containerColumns || 5), $("#showContainerColumns").text(e.containerColumns || 5),
$("#containerWidth").val((e.containerWidth || 100) - 70), $("#showContainerWidth").text((e.containerWidth || 100) + "%"),
$("#dialogOpenDetail").prop("checked", !e.dialogOpenDetail || e.dialogOpenDetail === _),
$("#needClosePage").prop("checked", !e.needClosePage || e.needClosePage === _),
$("#autoPage").prop("checked", !e.autoPage || e.autoPage === _), $("#translateTitle").prop("checked", !e.translateTitle || e.translateTitle === _),
$("#enableLoadActressInfo").prop("checked", !e.enableLoadActressInfo || e.enableLoadActressInfo === _),
$("#enableLoadOtherSite").prop("checked", !e.enableLoadOtherSite || e.enableLoadOtherSite === _),
$("#containerColumns").on("input", (async e => {
let t = $("#containerColumns").val();
if ($("#showContainerColumns").text(t), r) {
document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${t}, minmax(0, 1fr))`;
}
if (l) {
document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${t}, minmax(0, 1fr))`;
}
await storageManager.saveSettingItem("containerColumns", t), this.applyImageMode();
})), $("#containerWidth").on("input", (async e => {
let t = parseInt($(e.target).val());
const n = t + 70 + "%";
if ($("#showContainerWidth").text(n), r) {
document.querySelector("section .container").style.minWidth = n;
}
if (l) {
document.querySelector(".container-fluid .row").style.minWidth = n;
}
storageManager.saveSettingItem("containerWidth", t + 70);
})), $("#dialogOpenDetail").on("change", (e => {
let t = $("#dialogOpenDetail").is(":checked") ? _ : C;
storageManager.saveSettingItem("dialogOpenDetail", t);
})), $("#showFilterItem").prop("checked", !!e.showFilterItem && e.showFilterItem === _),
$("#showFilterActorItem").prop("checked", !!e.showFilterActorItem && e.showFilterActorItem === _),
$("#showFilterKeywordItem").prop("checked", !!e.showFilterKeywordItem && e.showFilterKeywordItem === _),
$("#showFavoriteItem").prop("checked", !e.showFavoriteItem || e.showFavoriteItem === _),
$("#showHasDownItem").prop("checked", !e.showHasDownItem || e.showHasDownItem === _),
$("#showHasWatchItem").prop("checked", !e.showHasWatchItem || e.showHasWatchItem === _),
$("#showFilterItem").on("change", (async e => {
let t = $("#showFilterItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showFilterItem", t), window.refresh();
})), $("#showFilterActorItem").on("change", (async e => {
let t = $("#showFilterActorItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showFilterActorItem", t), window.refresh();
})), $("#showFilterKeywordItem").on("change", (async e => {
let t = $("#showFilterKeywordItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showFilterKeywordItem", t), window.refresh();
})), $("#showFavoriteItem").on("change", (async e => {
let t = $("#showFavoriteItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showFavoriteItem", t), window.refresh();
})), $("#showHasDownItem").on("change", (async e => {
let t = $("#showHasDownItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showHasDownItem", t), window.refresh();
})), $("#showHasWatchItem").on("change", (async e => {
let t = $("#showHasWatchItem").is(":checked") ? _ : C;
await storageManager.saveSettingItem("showHasWatchItem", t), window.refresh();
})), $("#needClosePage").on("change", (async e => {
await storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? _ : C),
window.refresh();
})), $("#autoPage").on("change", (async e => {
const t = $("#autoPage").is(":checked") ? _ : C;
await storageManager.saveSettingItem("autoPage", t), t === _ ? $("#sort-toggle-btn").hide() : $("#sort-toggle-btn").show();
})), $("#translateTitle").on("change", (async e => {
const t = $("#translateTitle").is(":checked") ? _ : C;
await storageManager.saveSettingItem("translateTitle", t), t === _ ? (await this.getBean("ListPagePlugin").doFilter(),
isDetailPage && await this.getBean("TranslatePlugin").translate()) : (await this.getBean("ListPagePlugin").revertTranslation(),
$(".translated-title").remove());
})), $("#hoverBigImg").prop("checked", !!e.hoverBigImg && e.hoverBigImg === _),
$("#hoverBigImg").on("change", (async e => {
const t = $("#hoverBigImg").is(":checked") ? _ : C;
await storageManager.saveSettingItem("hoverBigImg", t), t === _ ? window.imageHoverPreviewObj = new ImageHoverPreview({
selector: this.getSelector().coverImgSelector
}) : window.imageHoverPreviewObj && window.imageHoverPreviewObj.destroy();
})), $("#enableLoadActressInfo").on("change", (async e => {
const t = $("#enableLoadActressInfo").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enableLoadActressInfo", t), t === _ ? this.getBean("ActressInfoPlugin").loadActressInfo() : $(".actress-info").remove();
})), $("#enableLoadOtherSite").on("change", (async e => {
const t = $("#enableLoadOtherSite").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enableLoadOtherSite", t), t === _ ? this.getBean("OtherSitePlugin").loadOtherSite().then() : $("#otherSiteBox").remove();
})), $("#enableLoadScreenShot").prop("checked", !e.enableLoadScreenShot || e.enableLoadScreenShot === _),
$("#enableLoadScreenShot").on("change", (async e => {
const t = $("#enableLoadScreenShot").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enableLoadScreenShot", t), t === _ ? this.getBean("ScreenShotPlugin").loadScreenShot().then() : $(".screen-container").remove();
})), $("#enableLoadPreviewVideo").prop("checked", !e.enableLoadPreviewVideo || e.enableLoadPreviewVideo === _),
$("#enableLoadPreviewVideo").on("change", (async e => {
const t = $("#enableLoadPreviewVideo").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enableLoadPreviewVideo", t);
})), $("#enable115Match").prop("checked", !!e.enable115Match && e.enable115Match === _),
$("#enable115Match").on("change", (async e => {
const t = $("#enable115Match").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enable115Match", t);
let n = $(this.getSelector().itemSelector).toArray();
await this.getBean("WangPan115MatchPlugin").matchMovieList(n);
})), $("#enableVerticalModel").prop("checked", !!e.enableVerticalModel && e.enableVerticalModel === _),
$("#enableVerticalModel").on("change", (async e => {
const t = $("#enableVerticalModel").is(":checked") ? _ : C;
await storageManager.saveSettingItem("enableVerticalModel", t), this.applyImageMode();
})), $("#moreBtn").on("click", (() => {
$(".simple-setting").html("").hide(), this.openSettingDialog("base-panel");
})), $("#helpBtn").on("click", (() => {
layer.open({
type: 1,
title: "",
shadeClose: !0,
scrollbar: !1,
content: '\n<style>\n .help-container {\n font-family: \'Segoe UI\', Tahoma, Geneva, Verdana, sans-serif;\n color: #333;\n padding: 15px;\n max-height: 100%;\n overflow-y: auto;\n }\n \n .help-section {\n margin-bottom: 25px;\n }\n \n .help-section summary {\n font-size: 18px;\n color: #3498db;\n margin-bottom: 12px;\n cursor: pointer;\n }\n \n .help-content {\n background-color: #f9f9f9;\n border-radius: 5px;\n padding: 15px;\n border-left: 4px solid #3498db;\n }\n \n .help-content p {\n line-height: 1.6;\n margin-bottom: 10px;\n }\n .help-section img {\n max-width: 100%;\n height: auto;\n border: 1px solid #ddd;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n</style>\n\n<div class="help-container">\n <h1 style="font-size: 22px; margin-bottom: 20px; color: #2c3e50; border-bottom: 1px solid #eee; padding-bottom: 10px;">使用说明</h1>\n \n <details class="help-section">\n <summary>1. 无法查看预览视频,提示分流?</summary>\n <div class="help-content">\n <p>JavDB限制日本IP的访问,而预览视频来自DMM,需要日本IP才能访问。</p>\n <p>这样会导致二者无法同时使用,需要对其一进行代理转发。</p>\n <p>将 cc3001.dmm.co.jp 及 dmm.co 分流到日本ip。</p>\n <p><a href="https://youtu.be/wQUK8z_YeU4?t=121" target="_blank">Clash Verge分流规则设置 </a> (如果你是别的代理软件,自行搜索如何分流)</p>\n </div>\n </details>\n \n <details class="help-section">\n <summary>2. 如何屏蔽某一系列的番号?</summary>\n <div class="help-content">\n <p>方法一:设置中-添加视频标题关键词,如: VENX-</p>\n <p>方法二:进入详情页,选中标题文字,右键可加入</p>\n <img src="https://i.imgur.com/lVnhK5A.png" alt="进入详情页,选中标题,进行右键"/>\n </div>\n </details>\n\n <details class="help-section">\n <summary>3. 屏蔽某演员,如何只屏蔽单体影片?</summary>\n <div class="help-content">\n <p>屏蔽演员前,先筛选分类,再点屏蔽</p>\n <img src="https://i.imgur.com/nr3Dwb8.png" alt="屏蔽演员前,先筛选分类,再点屏蔽"/>\n </div>\n </details>\n \n <details class="help-section">\n <summary>4. 如何多浏览器同时登录115网盘?</summary>\n <div class="help-content">\n <p>① 访问115登录页, 选择JHS-扫码面板, 并扫码登录</p>\n <img src="https://imgur.com/XbaisWD.png" alt=""/>\n </div>\n <div class="help-content">\n <p>② 进入网盘后, 右下角悬浮按钮, 复制Cookie</p>\n <img src="https://imgur.com/GvzJ2Gy.png" alt=""/>\n </div>\n <div class="help-content">\n <p>③ 打开另一个浏览器(需装JHS脚本), 进入登录页面, 选择JHS-扫码面板, 输入Cookie并回车</p>\n <img src="https://imgur.com/FX08qdO.png" alt=""/>\n </div>\n </details>\n</div>\n',
area: utils.getResponsiveArea([ "50%", "90%" ])
});
}));
}
async applyImageMode() {
$("#verticalImgStyle").remove();
if (await storageManager.getSetting("enableVerticalModel", C) === _) {
let e = "100% 50% !important";
window.location.href.includes("/advanced_search?type=100") && (e = "50% 50% !important");
const t = `\n .cover {\n min-height: 350px !important;\n overflow: hidden !important;\n padding-top: 142% !important;\n }\n \n .cover img {\n object-fit: cover !important;\n object-position: ${e};\n }\n \n /* bus的 */\n .masonry .movie-box img {\n min-height: 500px !important;\n object-fit: cover !important;\n object-position: top right;\n }\n `;
$("<style>").attr("id", "verticalImgStyle").text(t).appendTo("head");
} else {
const e = "\n .cover {\n min-height:auto !important;\n padding-top: 67% !important;\n }\n .cover img {\n object-fit: contain !important;\n object-position: 50% 50% !important\n }\n \n /* bus的 */\n .masonry .movie-box img {\n min-height:auto !important;\n object-fit: contain !important;\n object-position: top;\n }\n ";
$("<style>").attr("id", "verticalImgStyle").text(e).appendTo("head");
}
l && this.getBean("BusImgPlugin").logImageHeightsByRow();
}
bindClick() {
$(".side-menu-item").on("click", (function() {
$(".side-menu-item").removeClass("active"), $(this).addClass("active"), $(".content-panel").hide();
const e = $(this).data("panel");
$("#" + e).show(), "cache-panel" === e ? ($("#saveBtn").hide(), $("#clean-all").show()) : ($("#saveBtn").show(),
$("#clean-all").hide());
})), $("#importBtn").on("click", (e => this.importData(e))), $("#exportBtn").on("click", (e => this.exportData(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);
}
}))), $("#saveBtn").on("click", (() => this.saveForm())), $(".clean-btn").on("click", (e => {
const t = $(e.currentTarget).data("key"), n = this.cacheItems.find((e => e.key === t));
localStorage.removeItem(t), show.ok(`${n.text} 清理成功`), $("#cache-data-display").hide(),
"jhs_dmm_video" === t && localStorage.removeItem("jhs_other_site_dmm");
})), $("#clean-all").on("click", (() => {
this.cacheItems.forEach((e => localStorage.removeItem(e.key))), show.ok("全部缓存已清理"),
$("#cache-data-display").hide(), localStorage.removeItem("jhs_other_site_dmm");
})), $(".view-btn").on("click", (e => {
const t = $(e.currentTarget).data("key"), n = localStorage.getItem(t), a = $("#cache-data-display"), i = a.find("pre");
if (a.show(), n) try {
const e = JSON.parse(n);
i.text(JSON.stringify(e, null, 2));
} catch {
i.text(n);
} else i.text("无数据");
}));
const e = $("#highlightedTagNumber"), t = $("#highlightedTagColor"), n = $("#highlightedTagLabel");
function a() {
const a = e.val(), i = t.val();
n.css("border", `${a}px solid ${i}`);
}
e.on("input", a), t.on("input", a);
}
async saveForm() {
let e = await storageManager.getSetting();
e.videoQuality = $("#videoQuality").val(), e.reviewCount = $("#reviewCount").val(),
e.tagPosition = $("#tagPosition").val(), e.waitCheckCount = $("#waitCheckCount").val(),
e.refresh_token = $("#refresh_token").val(), e.highlightedTagNumber = $("#highlightedTagNumber").val(),
e.highlightedTagColor = $("#highlightedTagColor").val(), e.checkConcurrencyCount = $("#checkConcurrencyCount").val(),
e.checkRequestSleep = $("#checkRequestSleep").val(), e.enableCheckBlacklist = $("#enableCheckBlacklist").val(),
e.checkBlacklist_intervalTime = $("#checkBlacklist_intervalTime").val(), e.checkBlacklist_ruleTime = $("#checkBlacklist_ruleTime").val(),
e.enableCheckFavoriteActress = $("#enableCheckFavoriteActress").val(), e.checkFavoriteActress_IntervalTime = $("#checkFavoriteActress_IntervalTime").val(),
e.enableCheckNewVideo = $("#enableCheckNewVideo").val(), e.checkNewVideo_intervalTime = $("#checkNewVideo_intervalTime").val(),
e.checkNewVideo_ruleTime = $("#checkNewVideo_ruleTime").val(), e.enableClog = $("#enableClog").val(),
e.enableClog === _ ? clog.show() : clog.hide(), e.webDavUrl = $("#webDavUrl").val(),
e.webDavUsername = $("#webDavUsername").val(), e.webDavPassword = $("#webDavPassword").val(),
e.missAvUrl = $("#missAvUrl").val().replace(/\/$/, ""), e.jableUrl = $("#jableUrl").val().replace(/\/$/, ""),
e.avgleUrl = $("#avgleUrl").val().replace(/\/$/, ""), e.javTrailersUrl = $("#javTrailersUrl").val().replace(/\/$/, ""),
e.av123Url = $("#av123Url").val().replace(/\/$/, ""), e.javDbUrl = $("#javDbUrl").val().replace(/\/$/, ""),
e.javBusUrl = $("#javBusUrl").val().replace(/\/$/, ""), e.supJavUrl = $("#supJavUrl").val().replace(/\/$/, ""),
e.enableTitleSelectFilter = $("#enableTitleSelectFilter").is(":checked") ? _ : C,
e.enableFavoriteActresses = $("#enableFavoriteActresses").is(":checked") ? _ : C,
e.enableSaveActressCarInfo = $("#enableSaveActressCarInfo").is(":checked") ? _ : C,
e.enableScreenSvg = $("#enableScreenSvg").is(":checked") ? _ : C, e.enableVideoSvg = $("#enableVideoSvg").is(":checked") ? _ : C,
e.enableHandleSvg = $("#enableHandleSvg").is(":checked") ? _ : C, e.enableSiteSvg = $("#enableSiteSvg").is(":checked") ? _ : C,
e.enableCopySvg = $("#enableCopySvg").is(":checked") ? _ : C, $("#hotkey-panel [id]").map(((e, t) => t.id)).get().forEach((t => {
e[t] = $(`#${t}`).val();
})), e.enableImageHotKey = $("#enableImageHotKey").is(":checked") ? _ : C, await storageManager.saveSetting(e);
let t = [];
$("#reviewKeywordContainer .keyword-label").toArray().forEach((e => {
let n = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
t.push(n);
})), await storageManager.saveReviewFilterKeyword(t);
let n = [];
$("#filterKeywordContainer .keyword-label").toArray().forEach((e => {
let t = $(e).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
n.push(t);
})), await storageManager.saveTitleFilterKeyword(n), show.ok("保存成功"), window.refresh();
const a = this.getBean("NewVideoPlugin");
a && a.resetBtnTip(), this.getBean("BlacklistPlugin").resetBtnTip(), this.getBean("BlacklistPlugin").reloadTable();
}
async addLabelTag(e, t) {
const n = $(`${e} .tag-box`);
const a = $(`\n <div class="keyword-label" data-keyword="${t}" style="background-color: #cbd5e1; color: #333" target="_blank">\n ${t}\n <span class="keyword-remove">×</span>\n </div>\n `);
a.find(".keyword-remove").click((e => {
e.stopPropagation(), e.preventDefault();
const t = $(e.currentTarget);
const n = t.closest(".keyword-label").attr("data-keyword").split(" ")[0];
utils.q(e, `是否移除屏蔽词 ${n}?`, (async () => {
t.parent().remove();
}));
})), n.append(a);
}
addKeyword(e, t) {
let n = $(`${t} .keyword-input`);
const a = n.val().trim();
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) {
let t = loading();
try {
const e = await storageManager.getSetting("refresh_token");
if (!e) return void show.error("请填写refresh_token并保存后, 再试此功能");
show.info("正在整理数据...");
let t = utils.getNowStr("_", "_") + ".json", n = JSON.stringify(await storageManager.exportData());
n = Ae(n);
const a = new Be(e);
await a.backup(this.folderName, t, n), show.ok("备份完成");
} catch (n) {
console.error(n), show.error(n.toString());
} finally {
t.close();
}
}
async backupListBtn(e) {
const t = await storageManager.getSetting("refresh_token");
if (!t) return void show.error("请填写refresh_token并保存后, 再试此功能");
let n = loading();
try {
const e = new Be(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) return void show.error("请填写webDav服务地址并保存后, 再试此功能");
const a = t.webDavUsername;
if (!a) return void show.error("请填写webDav用户名并保存后, 再试此功能");
const i = t.webDavPassword;
if (!i) return void show.error("请填写webDav密码并保存后, 再试此功能");
let s = utils.getNowStr("_", "_") + ".json", o = JSON.stringify(await storageManager.exportData());
o = Ae(o);
let r = loading();
try {
const e = new Pe(n, a, i);
await e.backup(this.folderName, s, o), show.ok("备份完成");
} catch (l) {
console.error(l), show.error(l.toString());
} finally {
r.close();
}
}
async backupListBtnByWebDav(e) {
const t = await storageManager.getSetting(), n = t.webDavUrl;
if (!n) return void show.error("请填写webDav服务地址并保存后, 再试此功能");
const a = t.webDavUsername;
if (!a) return void show.error("请填写webDav用户名并保存后, 再试此功能");
const i = t.webDavPassword;
if (!i) return void show.error("请填写webDav密码并保存后, 再试此功能");
let s = loading();
try {
const e = new Pe(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 {
s.close();
}
}
openFileListDialog(e, t, n) {
layer.open({
type: 1,
title: n + "备份文件",
content: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="table-container" style="margin:auto auto !important;"></div>\n </div>\n ',
area: [ "800px", "70%" ],
anim: -1,
success: a => {
const i = new Tabulator("#table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: e,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
columns: [ {
title: "文件名",
field: "name",
width: 200,
headerSort: !1,
responsive: 0
}, {
title: "文件大小",
field: "size",
responsive: 1,
headerSort: !1,
formatter: (e, t, n) => {
const a = [ "B", "KB", "MB", "GB", "TB", "PB" ];
let i = 0, s = e.getData().size;
for (;s >= 1024 && i < a.length - 1; ) s /= 1024, i++;
return `${s % 1 == 0 ? s.toFixed(0) : s.toFixed(2)} ${a[i]}`;
}
}, {
title: "备份日期",
field: "createTime",
responsive: 2,
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData();
return `${utils.getNowStr("-", ":", a.createTime)}`;
}
}, {
title: "操作",
minWidth: 250,
responsive: 0,
headerSort: !1,
formatter: (e, a, s) => {
const o = e.getData();
return s((() => {
const a = e.getElement().querySelector(".a-danger"), s = e.getElement().querySelector(".a-primary"), r = e.getElement().querySelector(".a-success");
a && a.addEventListener("click", (e => {
layer.confirm(`是否删除 ${o.name} ?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async e => {
layer.close(e);
let a = loading();
try {
await t.deleteFile(o.fileId);
let e = await t.getBackupList(this.folderName);
i.replaceData(e), "阿里云盘" === n ? layer.alert("已移至回收站, 请到阿里云盘回收站二次删除") : layer.alert("删除成功");
} catch (s) {
console.error(s), show.error(`发生错误: ${s ? s.message : s}`);
} finally {
a.close();
}
}));
})), s && s.addEventListener("click", (async e => {
let a = loading();
try {
if ("阿里云盘" === n) {
show.info("获取下载地址...");
const e = await t.getDownloadUrl(o.fileId);
show.info("获取文件内容...");
const n = Me(await gmHttp.get(e, null, {
Referer: "https://www.aliyundrive.com/"
}));
utils.download(n, o.name);
} else {
const e = Me(await t.getFileContent(o.fileId));
utils.download(e, o.name);
}
} catch (i) {
console.error(i), show.error("下载失败: " + i);
} finally {
a.close();
}
})), r && r.addEventListener("click", (async e => {
layer.confirm(`是否将该云备份数据 ${o.name} 导入?`, {
icon: 3,
title: "提示",
btn: [ "确定", "取消" ]
}, (async e => {
layer.close(e);
let a = loading();
try {
let e;
if ("阿里云盘" === n) {
show.info("获取下载地址...");
const n = await t.getDownloadUrl(o.fileId);
show.info("获取文件内容..."), e = await gmHttp.get(n, null, {
Referer: "https://www.aliyundrive.com/"
});
} else e = await t.getFileContent(o.fileId);
show.info("解密文件内容..."), e = Me(e), show.info("解密完成, 开始导入...");
const a = JSON.parse(e);
await storageManager.importData(a), show.ok("导入成功!"), window.location.reload();
} catch (i) {
console.error(i), show.error(i);
} finally {
a.close();
}
}));
}));
})), '\n <a class="a-danger">删除</a>\n <a class="a-primary">下载</a>\n <a class="a-success">导入</a>\n ';
}
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
});
}
});
}
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);
}
}
}
const Le = "x7k9p3";
function Ae(e) {
return (Le + e + Le).split("").map((e => {
const t = e.codePointAt(0);
return String.fromCodePoint(t + 5);
})).join("");
}
function Me(e) {
return e.split("").map((e => {
const t = e.codePointAt(0);
return String.fromCodePoint(t - 5);
})).join("").slice(Le.length, -Le.length);
}
class Ne extends R {
getName() {
return "BusPreviewVideoPlugin";
}
async initCss() {
return "\n /* 弹窗/Modal 样式 */\n .bus-preview-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.95); \n /* 关键修改:更新 z-index */\n z-index: 12345699; \n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0; \n visibility: hidden; \n transition: opacity 0.2s ease;\n }\n .bus-preview-modal.is-open {\n opacity: 1;\n visibility: visible;\n }\n /* 垂直排列视频和按钮,并居中 */\n .bus-preview-modal-content {\n position: relative;\n max-width: 95%; \n max-height: 95%;\n display: flex; \n flex-direction: column; \n align-items: center; \n gap: 15px; \n }\n \n /* 移除 .bus-preview-close-btn 的样式 */\n\n /* 视频播放器容器 */\n .video-player-wrapper {\n /* 关键修改:更新 width 和 max-height */\n width: 80vw; \n max-height: 85vh; \n aspect-ratio: 16 / 9; \n position: relative; \n background-color: black; \n max-width: 100%; \n }\n /* 视频元素 */\n .video-player-wrapper #preview-video {\n position: absolute; \n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: block;\n }\n\n /* 画质控制盒 (底部按钮) */\n .video-control-box {\n display: flex;\n flex-direction: row; \n justify-content: center; \n flex-wrap: wrap; \n gap: 10px;\n padding: 10px 0; \n }\n\n /* 按钮样式 (保留) */\n .video-control-btn {\n min-width:80px;\n padding: 6px 12px;\n background: rgba(255,255,255,0.2);\n color: white;\n border: 1px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n cursor: pointer;\n text-align: center;\n font-size: 14px;\n transition: background-color 0.2s, border-color 0.2s;\n }\n .video-control-btn:hover {\n background: rgba(255,255,255,0.4);\n }\n .video-control-btn.active {\n background-color: #1890ff; \n color: white;\n font-weight: bold;\n border: 1px solid #096dd9;\n }\n ";
}
initModal() {
if (0 === $("#bus-preview-modal").length) {
$("body").append('\n <div id="bus-preview-modal" class="bus-preview-modal">\n <div class="bus-preview-modal-content">\n </div>\n </div>\n ');
const e = $("#bus-preview-modal");
e.on("click", (e => {
"bus-preview-modal" === e.target.id && this.closeVideoModal();
})), $(document).on("keydown", (t => {
"Escape" === t.key && e.hasClass("is-open") && this.closeVideoModal();
}));
}
}
closeVideoModal() {
const e = $("#preview-video");
e.length > 0 && e[0].pause(), $("#bus-preview-modal").removeClass("is-open");
}
async handle() {
if (!isDetailPage) return;
this.initModal();
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);
"yes" === await storageManager.getSetting("enableLoadPreviewVideo", "yes") && G(this.getPageInfo().carNum, !1).then();
let n = !1, a = $(".preview-video-container");
a.on("click", (async e => {
if (e.preventDefault(), e.stopPropagation(), n) show.info("正在加载中, 勿重复点击"); else {
n = !0;
try {
await this.handleVideo();
} finally {
n = !1;
}
}
})), window.location.href.includes("autoPlay=1") && a.trigger("click");
}
async handleVideo() {
const e = $("#bus-preview-modal"), t = e.find(".bus-preview-modal-content");
let n = $("#preview-video");
if (n.length > 0) return e.addClass("is-open"), void n[0].play().catch((e => console.warn("尝试播放失败 (可能被浏览器阻止):", e)));
let a = this.getPageInfo().carNum;
const i = await G(a);
0 !== Object.keys(i).length ? (await this.createVideoPlayerAndControls(i, t), n = $("#preview-video"),
n.length > 0 ? (e.addClass("is-open"), n[0].play().catch((e => console.warn("尝试播放失败 (可能被浏览器阻止):", e)))) : show.error("视频播放器创建失败。")) : show.error("未找到可用的视频源。");
}
async createVideoPlayerAndControls(e, t) {
let n = await storageManager.getSetting("videoQuality");
n = W(Object.keys(e), n);
let a = e[n];
t.html(`\n <div class="video-player-wrapper">\n <video id="preview-video" controls playsinline>\n <source src="${a}" />\n </video>\n </div>\n <div class="video-control-box">\n </div>\n `);
const i = $("#preview-video"), s = i.find("source"), o = t.find(".video-control-box");
if (!i.length || !s.length) return;
const r = i[0], l = localStorage.getItem("jhs_videoMuted");
r.muted = !l || "yes" === l, r.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", r.muted ? "yes" : "no");
}));
let c = "";
A.forEach((t => {
let a = e[t.quality];
if (a) {
const e = n === t.quality;
c += `\n <button class="video-control-btn${e ? " active" : ""}" \n data-quality="${t.quality}"\n data-video-src="${a}">\n ${t.text}\n </button>\n `;
}
})), o.html(c);
const d = o.find(".video-control-btn");
o.off("click").on("click", ".video-control-btn", (async e => {
try {
const t = $(e.currentTarget);
if (t.hasClass("active")) return;
let n = t.attr("data-video-src");
s.attr("src", n);
const a = r.currentTime;
r.load(), r.currentTime = a, await r.play(), d.removeClass("active"), t.addClass("active");
} catch (t) {
console.error("切换画质失败:", t);
}
}));
}
}
class je extends R {
constructor() {
super(...arguments), i(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"
} ]), i(this, "isUploading", !1);
}
getName() {
return "SearchByImagePlugin";
}
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 .search-img-site-btns-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 15px;\n }\n .search-img-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 .search-img-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 .search-img-site-btn img {\n width: 16px;\n height: 16px;\n margin-right: 6px;\n }\n .search-img-site-btn span {\n white-space: nowrap;\n }\n </style>\n ";
}
open(e) {
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="search-img-site-btns-container" id="search-img-site-btns-container"></div>\n </div>\n </div>\n \n </div>\n ',
area: utils.isMobile() ? utils.getResponsiveArea() : [ "40%", "80%" ],
success: async t => {
this.initEventListeners(), e && e();
},
end: () => {
$(document).off("paste.searchImg");
}
});
}
initEventListeners() {
const e = $("#upload-area"), t = $("#image-file"), n = $("#select-image-btn"), a = $("#preview-area"), i = $("#preview-image"), s = $("#action-btns"), o = $("#handle-btn"), r = $("#cancel-btn"), l = $("#url-input-container"), c = $("#image-url"), d = $("#search-results"), h = $("#search-img-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"), 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 => {
e.target.files && e.target.files[0] && (this.handleImageFile(e.target.files[0]),
this.resetSearchUI());
})), $(document).on("paste.searchImg", (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();
return this.handleImageFile(e), void this.resetSearchUI();
}
const n = e.originalEvent.clipboardData.getData("text");
n && utils.isUrl(n) && (l.show(), c.val(n), i.attr("src", n), a.show(), this.resetSearchUI());
})), o.on("click", (async () => {
const e = i.attr("src");
if (e) {
if (!this.isUploading) {
this.isUploading = !0;
try {
const t = await this.searchByImage(e);
s.hide(), d.show(), h.empty();
const n = "jhs_selectedSites", a = JSON.parse(localStorage.getItem(n) || "{}");
this.siteList.forEach((e => {
const n = e.url.replace("{占位符}", encodeURIComponent(t)), i = !1 !== a[e.name];
h.append(`\n <a href="${n}" class="search-img-site-btn" target="_blank" title="${e.name}">\n <input type="checkbox" \n class="site-checkbox" \n data-site-name="${e.name}" \n style="margin-right: 5px"\n ${i ? "checked" : ""}>\n <img src="${e.ico}" alt="${e.name}">\n <span>${e.name}</span>\n </a>\n `);
})), h.on("change", ".site-checkbox", (function() {
const e = $(this).data("site-name");
a[e] = $(this).is(":checked"), localStorage.setItem(n, JSON.stringify(a));
})), h.show();
} finally {
this.isUploading = !1;
}
}
} else show.info("请粘贴或上传图片");
})), r.on("click", (() => {
a.hide(), l.hide(), t.val(""), c.val("");
})), c.on("change", (() => {
utils.isUrl(c.val()) && (i.attr("src", c.val()), a.show());
})), $("#openAll").on("click", (() => {
$(".search-img-site-btn").each((function() {
$(this).find(".site-checkbox").is(":checked") && window.open($(this).attr("href"));
}));
}));
}
resetSearchUI() {
$("#action-btns").show(), $("#search-results").hide(), $("#search-img-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.*")) return void show.info("请选择图片文件");
const i = new FileReader;
i.onload = e => {
t.src = e.target.result, n.style.display = "block", a.style.display = "none", $("#handle-btn")[0].click();
}, 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], s = atob(i), o = new Array(s.length);
for (let g = 0; g < s.length; g++) o[g] = s.charCodeAt(g);
const r = new Uint8Array(o), l = new Blob([ r ], {
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
}), h = await d.json();
if (h.success && h.data && h.data.link) return h.data.link;
throw new Error((null == (t = h.data) ? void 0 : t.error) || "上传到Imgur失败");
}(e);
if (!n) return void show.error("上传到失败");
t = n;
}
return t;
} catch (n) {
show.error(`搜索失败: ${n.message}`), console.error("搜索失败:", n);
} finally {
t.close();
}
}
}
class Ee extends R {
getName() {
return "BusNavBarPlugin";
}
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 Fe extends R {
constructor() {
super(...arguments), i(this, "floorIndex", 1), i(this, "isInit", !1);
}
getName() {
return "RelatedPlugin";
}
async showRelated(e, t) {
const n = await storageManager.getSetting("enableLoadRelated", C), a = e;
t ? (a.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">${n === _ ? "折叠" : "展开"}</span>\n <span class="toggle-icon" style="margin-left: 4px;">${n === _ ? "▲" : "▼"}</span>\n </a>\n <span style="flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent);"></span>\n </div>\n `),
$("#relatedFold").on("click", (e => {
e.preventDefault(), e.stopPropagation();
const n = $("#relatedFold .toggle-text"), a = $("#relatedFold .toggle-icon"), i = "展开" === n.text();
n.text(i ? "折叠" : "展开"), a.text(i ? "▲" : "▼"), i ? ($("#relatedContainer").show(),
$("#relatedFooter").show(), this.isInit || (this.fetchAndDisplayRelateds(t), this.isInit = !0),
storageManager.saveSettingItem("enableLoadRelated", _)) : ($("#relatedContainer").hide(),
$("#relatedFooter").hide(), storageManager.saveSettingItem("enableLoadRelated", C));
})), a.append('<div id="relatedContainer"></div>'), a.append('<div id="relatedFooter"></div>'),
n === _ && await this.fetchAndDisplayRelateds(t)) : show.error("未传入movieId");
}
async fetchAndDisplayRelateds(e) {
const t = $("#relatedContainer"), n = $("#relatedFooter");
t.append('<div id="relatedLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单中...</div>');
let a = null;
try {
a = await se(e, 1, 20);
} catch (i) {
console.error("获取清单失败:", i);
} finally {
$("#relatedLoading").remove();
}
if (!a) return t.append('\n <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n 获取清单失败\n <a id="retryFetchRelateds" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n </div>\n '),
void $("#retryFetchRelateds").on("click", (async () => {
$("#retryFetchRelateds").parent().remove(), await this.fetchAndDisplayRelateds(e);
}));
if (0 !== a.length) if (this.displayRelateds(a, t), 20 === a.length) {
n.html('\n <button id="loadMoreRelateds" 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="relatedEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>\n ');
let a = 1, s = $("#loadMoreRelateds");
s.on("click", (async () => {
let n;
s.text("加载中...").prop("disabled", !0), a++;
try {
n = await se(e, a, 20);
} catch (i) {
console.error("加载更多清单失败:", i);
} finally {
s.text("加载失败, 请点击重试").prop("disabled", !1);
}
n && (this.displayRelateds(n, t), n.length < 20 ? (s.remove(), $("#relatedEnd").show()) : s.text("加载更多清单").prop("disabled", !1));
}));
} else n.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>'); else t.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>');
}
displayRelateds(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.movieCount}</p>\n <p style="margin-top: 5px;">收藏次数: ${e.collectionCount} 被查看次数: ${e.viewCount}</p>\n </div>\n `;
t.append(n);
}));
}
}
class He extends R {
constructor() {
super(...arguments), i(this, "type", null);
}
getName() {
return "WantAndWatchedVideosPlugin";
}
async handle() {
window.location.href.includes("/want_watch_videos") && ($("h3").append('<a class="a-primary" id="wantWatchBtn" style="padding:10px;">导入至 JHS</a>'),
$("#wantWatchBtn").on("click", (e => {
this.type = h, this.importWantWatchVideos(e, "是否将 想看的影片 导入到 JHS-收藏?");
}))), window.location.href.includes("/watched_videos") && ($("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JHS</a>'),
$("#wantWatchBtn").on("click", (e => {
this.type = g, this.importWantWatchVideos(e, "是否将 看过的影片 导入到 JHS-已下载?");
})));
}
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;
e ? (t = e.find(this.getSelector().itemSelector), n = e.find(".pagination-next").attr("href")) : (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(), s = e.find(".meta").text().trim();
if (t && n) try {
if (await storageManager.getCar(n)) {
show.info(`${n} 已存在, 跳过`);
continue;
}
await storageManager.saveCar({
carNum: n,
url: t,
names: null,
actionType: this.type,
publishTime: s
});
} catch (a) {
console.error(`保存失败 [${n}]:`, a);
}
}
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);
}
})) : (show.ok("导入结束!"), window.refresh());
}
}
class ze extends R {
getName() {
return "CoverButtonPlugin";
}
async initCss() {
return `\n <style>\n .box .tags {\n justify-content: space-between;\n }\n .tool-box span{\n opacity:.3\n }\n .tool-box span:hover{\n opacity:1\n }\n ${l ? ".tool-box .icon, .setting-label .icon{ height: 24px; width: 24px; }" : ""}\n .tool-box svg path {\n fill: blue;\n }\n [data-theme="dark"] .tool-box svg path {\n fill: white;\n }\n \n \n /* 鼠标移入时的弹性动画 */\n .elastic-in {\n animation: elasticIn 0.2s ease-out forwards; /* 动画名称 | 时长 | 缓动函数 | 保持最终状态 */\n }\n \n /* 鼠标移出时的弹性动画 */\n .elastic-out {\n animation: elasticOut 0.2s ease-in forwards;\n }\n /* 弹性进入动画(像果冻弹入) */\n @keyframes elasticIn {\n 0% {\n opacity: 0;\n transform: scale(0.8); /* 起始状态:80% 大小 */\n }\n 50% {\n opacity: 1;\n transform: scale(1.1); /* 弹到 110%(超调一点) */\n }\n 70% {\n transform: scale(0.95); /* 回弹到 95%(模拟弹性阻尼) */\n }\n 100% {\n opacity: 1;\n transform: scale(1); /* 最终恢复正常大小 */\n }\n }\n /* 弹性离开动画(像果冻弹出) */\n @keyframes elasticOut {\n 0% {\n opacity: 1;\n transform: scale(1); /* 起始状态:正常大小 */\n }\n 30% {\n transform: scale(1.05); /* 先弹大一点(105%) */\n }\n 100% {\n opacity: 0;\n transform: scale(0.8); /* 最终缩小并消失 */\n }\n }\n \n \n .loading {\n opacity: 0.7;\n filter: blur(1px);\n }\n .loading-spinner {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 40px;\n height: 40px;\n border: 3px solid rgba(255,255,255,.3);\n border-radius: 50%;\n border-top-color: #fff;\n animation: spin 1s ease-in-out infinite;\n z-index: 20;\n }\n @keyframes spin {\n to { transform: translate(-50%, -50%) rotate(360deg); }\n }\n </style>\n `;
}
handle() {
window.isListPage && (this.addSvgBtn(), this.bindClick().then());
}
async addSvgBtn() {
$(this.getSelector().itemSelector).toArray().forEach((e => {
let t = $(e);
if (!(t.find(".tool-box").length > 0) && (r && t.find(".tags").append(`\n <div class="tool-box" style="margin-left: auto; display: flex; align-items: center">\n <span class="screenSvg" title="长缩略图" style="margin-right: 15px;">${this.screenSvg}</span>\n \n <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span>\n \n <div class="more-tools-container handleSvg" style="position: relative; margin-right: 15px;">\n <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div>\n \n <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n background-color: rgba(255, 255, 255, 0);z-index: 10;">\n <a class="menu-btn hasWatchBtn" style="background-color:${S};color:white !important;margin-bottom: 5px"><span style="opacity: 1;">${k}</span></a>\n <a class="menu-btn hasDownBtn" style="background-color:${x}; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">${y}</span></a>\n <a class="menu-btn favoriteBtn" style="background-color:${w}; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">${v}</span></a>\n <a class="menu-btn filterBtn" style="background-color:${f}; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">${u}</span></a>\n </div>\n </div>\n \n <div class="more-tools-container siteSvg" style="position: relative; margin-right: 15px;">\n <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div>\n \n <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n background-color: rgba(255, 255, 255, 0);z-index: 10;">\n <a class="site-btn site-jable" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;">Jable</span>\n </a>\n <a class="site-btn site-avgle" style="margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;">Avgle</span>\n </a>\n <a class="site-btn site-miss-av" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;">MissAv</span>\n </a>\n <a class="site-btn site-123-av" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;">123Av</span>\n </a>\n </div>\n </div>\n \n <div class="more-tools-container copySvg" style="position: relative; margin-right: 15px;">\n <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.copySvg}</div>\n \n <div class="more-tools" style="\n position: absolute;\n bottom: 20px;\n right: -10px;\n display: none;\n background: white;\n box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n border-radius: 20px;\n padding: 10px 0;\n margin-bottom: 15px;\n z-index: 10;\n ">\n <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;">${this.carNumSvg}</span>\n <span class="titleSvg" title="复制标题" style="padding: 5px 10px; white-space: nowrap;">${this.titleSvg}</span>\n <span class="downSvg" title="下载封面" style="padding: 5px 10px; white-space: nowrap;">${this.downSvg}</span>\n </div>\n </div>\n </div>\n `),
l)) {
if (t.find(".avatar-box").length > 0) return;
t.find(".photo-info").append(`\n <div class="tool-box" style="display: flex; align-items: center;justify-content: flex-end">\n <span class="screenSvg" title="长缩略图" style="margin-right: 15px;">${this.screenSvg}</span>\n\n <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span>\n \n <div class="more-tools-container handleSvg" style="position: relative; margin-right: 15px;">\n <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div>\n \n <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n background-color: rgba(255, 255, 255, 0);z-index: 10;">\n <a class="menu-btn hasWatchBtn" style="background-color:${S};color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">${k}</span></a>\n <a class="menu-btn hasDownBtn" style="background-color:${x}; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">${y}</span></a>\n <a class="menu-btn favoriteBtn" style="background-color:${w}; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">${v}</span></a>\n <a class="menu-btn filterBtn" style="background-color:${f}; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">${u}</span></a>\n </div>\n </div>\n \n <div class="more-tools-container siteSvg" style="position: relative; margin-right: 15px;">\n <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div>\n \n <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none;\n background-color: rgba(255, 255, 255, 0);z-index: 10;">\n <a class="site-btn site-jable" style="color:white;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;display: inline; color:white !important">Jable</span>\n </a>\n <a class="site-btn site-avgle" style="margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;display: inline; color:white !important">Avgle</span>\n </a>\n <a class="site-btn site-miss-av" style="color:white;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;display: inline; color:white !important">MissAv</span>\n </a>\n <a class="site-btn site-123-av" style="color:white;margin-bottom: 5px;background-color:#71bb59;">\n <span style="opacity: 1;display: inline; color:white !important">123Av</span>\n </a>\n </div>\n </div>\n \n <div class="more-tools-container copySvg" style="position: relative;">\n <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.copySvg}</div>\n \n <div class="more-tools" style="\n max-width: 44px;\n position: absolute;\n bottom: 20px;\n right: -10px;\n display: none;\n background: white;\n box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n border-radius: 20px;\n padding: 10px 0;\n margin-bottom: 15px;\n z-index: 10;\n text-align: center;\n ">\n <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.carNumSvg}</span>\n <span class="titleSvg" title="复制标题" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.titleSvg}</span>\n <span class="downSvg" title="下载封面" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.downSvg}</span>\n </div>\n </div>\n </div>\n `);
}
})), this.enableSvgBtn();
}
async enableSvgBtn() {
const e = await storageManager.getSetting(), {enableScreenSvg: t = _, enableVideoSvg: n = _, enableHandleSvg: a = _, enableSiteSvg: i = _, enableCopySvg: s = _} = e;
[ {
selector: ".screenSvg",
enabled: t
}, {
selector: ".videoSvg",
enabled: n
}, {
selector: ".handleSvg",
enabled: a
}, {
selector: ".siteSvg",
enabled: i
}, {
selector: ".copySvg",
enabled: s
} ].forEach((({selector: e, enabled: t}) => {
$(e).toggle(t === _);
}));
}
async bindClick() {
const e = this.getSelector(), t = this.getBean("ListPagePlugin");
$(document).on("click", ".more-tools-container", (e => {
e.preventDefault();
var t = $(e.target).closest(".more-tools-container").find(".more-tools");
$(".more-tools").not(t).stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide(),
t.is(":visible") ? t.stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide() : t.stop(!0, !0).removeClass("elastic-out").addClass("elastic-in").show();
})), $(document).on("click", (function(e) {
$(e.target).closest(".more-tools-container").length || $(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
})), $(document).on("click", ".videoSvg", (n => {
n.preventDefault(), $('.videoSvg[title!="播放视频"]').each(((n, a) => {
const i = $(a);
let s = i.closest(".item"), o = s.find(e.coverImgSelector), {carNum: r} = t.findCarNumAndHref(s);
this.showImg(i, o, r), i.html(this.videoSvg).attr("title", "播放视频");
}));
const a = $(n.target).closest(".item"), i = a.find(".videoSvg");
if ("播放视频" === i.attr("title")) {
i.html(this.recoveryVideoSvg).attr("title", "切回封面");
const {carNum: n} = t.findCarNumAndHref(a);
let s = a.find(e.coverImgSelector);
s.length || show.error("没有找到图片"), this.showVideo(i, s, n).then();
}
})), $(document).on("click", ".screenSvg", (async e => {
e.preventDefault();
let n = loading();
try {
const a = $(e.currentTarget).closest(".item");
let {carNum: i} = t.findCarNumAndHref(a);
i = i.replace("FC2-", "");
const s = await this.getBean("ScreenShotPlugin").getScreenshot(i);
n.close(), showImageViewer(s);
} catch (a) {
console.error("图片预览出错:", a), show.error("图片预览出错:" + a);
} finally {
n.close();
}
})), $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", (e => {
e.preventDefault(), e.stopPropagation();
const n = $(e.target).closest(".menu-btn"), a = n.closest(".item"), {carNum: i, url: s, publishTime: o} = t.findCarNumAndHref(a), r = async e => {
let n = await t.parseActressName(s);
await storageManager.saveCar({
carNum: i,
url: s,
names: n,
actionType: e,
publishTime: o
}), window.refresh(), show.ok("操作成功");
};
n.hasClass("filterBtn") ? utils.q(e, `是否屏蔽${i}?`, (() => r(d))) : n.hasClass("favoriteBtn") ? r(h).then() : n.hasClass("hasDownBtn") ? r(g).then() : n.hasClass("hasWatchBtn") && r(p).then(),
$(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
}));
const n = this.getBean("OtherSitePlugin"), a = await n.getMissAvUrl(), i = await n.getjableUrl(), s = await n.getAvgleUrl(), o = await n.getAv123Url();
$(document).on("click", ".site-jable, .site-avgle, .site-miss-av, .site-123-av", (e => {
e.preventDefault(), e.stopPropagation();
const n = $(e.currentTarget), r = n.closest(".item"), {carNum: l} = t.findCarNumAndHref(r);
let c = null;
n.hasClass("site-jable") ? c = `${i}/search/${l}/` : n.hasClass("site-avgle") ? c = `${s}/vod/search.html?wd=${l}` : n.hasClass("site-miss-av") ? c = `${a}/search/${l}` : n.hasClass("site-123-av") && (c = `${o}/ja/search?keyword=${l}`),
e && (e.ctrlKey || e.metaKey) ? GM_openInTab(c, {
insert: 0
}) : window.open(c);
})), $(document).on("click", ".titleSvg, .carNumSvg, .downSvg", (e => {
e.preventDefault(), e.stopPropagation();
const n = $(e.currentTarget).closest(".item"), {carNum: a, title: i} = t.findCarNumAndHref(n), s = n.find(l ? ".photo-frame img" : ".cover img");
$(e.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", i) : $(e.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", a) : $(e.currentTarget).hasClass("downSvg") && fetch(s.attr("src")).then((e => e.blob())).then((e => {
utils.download(e, a + " " + i + ".jpg");
}));
}));
}
showImg(e, t, n) {
e.html(this.videoSvg).attr("title", "播放视频");
let a = $(`#${`${n}_preview_video`}`);
a.length > 0 && (a[0].pause(), a.parent().hide()), t.show(), t.removeClass("loading"),
t.next(".loading-spinner").remove();
}
async showVideo(e, t, n) {
const a = `${n}_preview_video`;
let i = $(`#${a}`);
if (i.length > 0) return i.parent().show(), i[0].play(), void t.hide();
t.addClass("loading"), t.after('<div class="loading-spinner"></div>');
const s = t.attr("src"), o = await G(n);
if (!o) return void this.showImg(e, t, n);
let r = await storageManager.getSetting("videoQuality");
r = W(Object.keys(o), r);
let c = o[r], d = `\n <div style="display: flex; justify-content: center; align-items: center; position: absolute; top:0; left:0; height: 100%; width: 100%; z-index: 10; overflow: hidden">\n <video \n src="${c}" \n poster="${s}" \n id="${a}" \n controls \n loop \n muted \n playsinline\n style="max-height: 100%; max-width: 100%; object-fit: contain"\n ></video>\n </div>\n `;
l && (d = `\n <div>\n <video \n src="${c}" \n poster="${s}" \n id="${a}" \n controls \n loop \n muted \n playsinline\n style="max-height: 100%; max-width: 100%; object-fit: contain"\n ></video>\n </div>\n `),
t.parent().append(d), t.hide(), t.removeClass("loading"), t.next(".loading-spinner").remove(),
i = $(`#${a}`);
let h = i[0];
h.load(), h.muted = !1, h.play(), i.trigger("focus");
}
}
class Ue extends R {
constructor() {
super(...arguments), i(this, "$contentBox", $(".section .container")), i(this, "urlParams", new URLSearchParams(window.location.search)),
i(this, "sortVal", this.urlParams.get("sort") || "release_date"), i(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1),
i(this, "maxPage", null), i(this, "keyword", this.urlParams.get("keyword") || null);
}
getName() {
return "Fc2By123AvPlugin";
}
async getBaseUrl() {
const e = this.getBean("OtherSitePlugin");
return await e.getAv123Url() + "/ja";
}
handle() {
$("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('<a class="navbar-item" href="/advanced_search?type=100&released_start=2099-09">123Av-Fc2</a>'),
$('.tabs li:contains("FC2")').after('<li><a href="/advanced_search?type=100&released_start=2099-09"><span>123Av-Fc2</span></a></li>'),
o.includes("/advanced_search?type=100") && (this.hookPage(), this.handleQuery().then());
}
hookPage() {
let e = $("h2.section-title");
e.contents().first().replaceWith("123Av"), e.css("marginBottom", "0"), e.append('\n <div style="margin-left: 100px; width: 400px;">\n <input id="search-123av-keyword" type="text" placeholder="搜索123Av Fc2ppv内容" style="padding: 4px 5px;margin-right: 0">\n <a id="search-123av-btn" class="a-primary" style="margin-left: 0">搜索</a>\n <a id="clear-123av-btn" class="a-info" style="margin-left: 0">重置</a>\n </div>\n '),
$("#search-123av-keyword").val(this.keyword), $("#search-123av-btn").on("click", (async () => {
let e = $("#search-123av-keyword").val().trim();
e && (this.keyword = e, utils.setHrefParam("keyword", e), await this.handleQuery());
})), $("#clear-123av-btn").on("click", (async () => {
$("#search-123av-keyword").val(""), this.keyword = "", utils.setHrefParam("keyword", ""),
$(".page-box").show(), $(".tool-box").show(), await this.handleQuery();
})), $(".empty-message").remove(), $("#foldCategoryBtn").remove(), $(".section .container .box").remove(),
$("#sort-toggle-btn").remove(), this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'),
this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>'),
this.$contentBox.append('<div class="page-box"></div>');
$(".tool-box").append('\n <div class="button-group">\n <div class="buttons has-addons" id="conditionBox">\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="release_date">发布日期</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="recent_update">最近更新</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="trending">热门</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_today">今天最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_week">本周最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_month">本月最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed">最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_favourited">最受欢迎</a>\n </div>\n </div>\n '),
$(`#conditionBox a[data-sort="${this.sortVal}"]`).addClass("is-info"), utils.setHrefParam("sort", this.sortVal),
utils.setHrefParam("page", this.currentPage), $("#conditionBox").on("click", "a.button", (e => {
let t = $(e.target);
this.sortVal = t.data("sort"), utils.setHrefParam("sort", this.sortVal), t.siblings().removeClass("is-info"),
t.addClass("is-info"), this.handleQuery();
}));
$(".page-box").append('\n <nav class="pagination">\n <a class="pagination-previous">上一页</a>\n <ul class="pagination-list"></ul>\n <a class="pagination-next">下一页</a>\n </nav>\n '),
$(document).on("click", ".pagination-link", (e => {
e.preventDefault(), this.currentPage = parseInt($(e.target).data("page")), utils.setHrefParam("page", this.currentPage),
this.renderPagination(), this.handleQuery();
})), $(".pagination-previous").on("click", (e => {
e.preventDefault(), this.currentPage > 1 && (this.currentPage--, utils.setHrefParam("page", this.currentPage),
this.renderPagination(), this.handleQuery());
})), $(".pagination-next").on("click", (e => {
e.preventDefault(), this.currentPage < this.maxPage && (this.currentPage++, utils.setHrefParam("page", this.currentPage),
this.renderPagination(), this.handleQuery());
}));
}
renderPagination() {
const e = $(".pagination-list");
e.empty();
let t = Math.max(1, this.currentPage - 2), n = Math.min(this.maxPage, this.currentPage + 2);
this.currentPage <= 3 ? n = Math.min(6, this.maxPage) : this.currentPage >= this.maxPage - 2 && (t = Math.max(this.maxPage - 5, 1)),
t > 1 && (e.append('<li><a class="pagination-link" data-page="1">1</a></li>'), t > 2 && e.append('<li><span class="pagination-ellipsis">…</span></li>'));
for (let a = t; a <= n; a++) {
const t = a === this.currentPage ? " is-current" : "";
e.append(`<li><a class="pagination-link${t}" data-page="${a}">${a}</a></li>`);
}
n < this.maxPage && (n < this.maxPage - 1 && e.append('<li><span class="pagination-ellipsis">…</span></li>'),
e.append(`<li><a class="pagination-link" data-page="${this.maxPage}">${this.maxPage}</a></li>`));
}
async handleQuery() {
let e = loading();
try {
let e = [];
e = 1 === this.currentPage ? [ 1, 2 ] : [ 2 * this.currentPage - 1, 2 * this.currentPage ],
this.keyword && (e = [ 1 ], $(".page-box").hide(), $(".tool-box").hide());
const t = await this.getBaseUrl(), n = e.map((e => {
let n = `${t}/tags/fc2?sort=${this.sortVal}&page=${e}`;
return this.keyword && (n = `${t}/search?keyword=${this.keyword}`), gmHttp.get(n);
})), a = await Promise.all(n);
let i = [];
for (const o of a) {
let e = $(o);
if (e.find(".box-item").each(((e, n) => {
const a = $(n), s = a.find("img").attr("data-src");
let o = a.find("img").attr("title");
const r = a.find(".detail a"), l = r.attr("href"), c = t + (l.startsWith("/") ? l : "/" + l), d = r.text().trim().replace(o + " - ", "");
o = o.replace("FC2-PPV", "FC2"), i.push({
imgSrc: s,
carNum: o,
href: c,
title: d
});
})), !this.maxPage) {
let t, n = e.find(".page-item:not(.disabled)").last();
if (n.find("a.page-link").length) {
let e = n.find("a.page-link").attr("href");
t = parseInt(e.split("page=")[1]);
} else t = parseInt(n.find("span.page-link").text());
this.maxPage = Math.ceil(t / 2), this.renderPagination();
}
}
if (0 === i.length) {
console.log(i), show.error("无结果");
let e = `${t}/dm4/tags/fc2?sort=${this.sortVal}`;
this.keyword && (e = `${t}/search?keyword=${this.keyword}`), console.error("获取数据失败!", e);
}
let s = this.markDataListHtml(i);
$(".movie-list").html(s), await utils.smoothScrollToTop();
} catch (t) {
console.error(t);
} finally {
e.close();
}
}
async open123AvFc2Dialog(e, t) {
let n = "";
await storageManager.getSetting("enableLoadOtherSite", _) === _ && (n = '<div class="movie-panel-info fc2-movie-panel-info" style="margin-top:20px"><strong>第三方站点: </strong></div>');
let a = `\n <div class="movie-detail-container">\n \x3c!-- <div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>\n <div class="right-box">--\x3e\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n \n ${n}\n \n <div style="margin: 10px 0">\n <a id="filterBtn" class="menu-btn" style="background-color:${f}"><span>${u}</span></a>\n <a id="favoriteBtn" class="menu-btn" style="background-color:${w}"><span>${v}</span></a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:${x}"><span>${y}</span></a>\n <a id="hasWatchBtn" class="menu-btn" style="background-color:${S};"><span>${k}</span></a>\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 <div class="message video-panel" style="margin-top:20px">\n <div id="magnets-content" class="magnet-links">\n </div>\n </div>\n <div id="reviews-content">\n </div>\n <div id="related-content">\n </div>\n <span id="data-actress" style="display: none"></span>\n \x3c!-- </div>--\x3e\n </div>\n `;
layer.open({
type: 1,
title: e,
content: a,
area: utils.getDefaultArea(),
skin: "movie-detail-layer",
scrollbar: !1,
success: (n, a) => {
utils.setupEscClose(a), this.loadData(e, t);
let i = e.replace("FC2-", "");
$("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(i)),
$("#favoriteBtn").on("click", (async n => {
const a = $("#data-actress").text(), i = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: e,
url: t,
names: a,
actionType: h,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#filterBtn").on("click", (n => {
utils.q(n, `是否屏蔽${e}?`, (async () => {
const n = $("#data-actress").text(), a = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: e,
url: t,
names: n,
actionType: d,
publishTime: a
}), window.refresh(), layer.closeAll(), window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
})), $("#hasDownBtn").on("click", (async n => {
const a = $("#data-actress").text(), i = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: e,
url: t,
names: a,
actionType: g,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#hasWatchBtn").on("click", (async n => {
const a = $("#data-actress").text(), i = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: e,
url: t,
names: a,
actionType: p,
publishTime: i
}), window.refresh(), layer.closeAll();
})), $("#search-subtitle-btn").on("click", (t => utils.openPage(`https://subtitlecat.com/index.php?search=${e}`, e, !1, t))),
$("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(e)));
let s = e.replace("FC2-", "");
this.getBean("OtherSitePlugin").loadOtherSite(s, e).then();
}
});
}
async loadData(e, t) {
let n = loading();
try {
const {id: n, publishDate: a, title: i, moviePoster: s} = await this.get123AvVideoInfo(t);
$(".movie-info-container").html(`\n <h3 class="movie-title" style="margin-bottom: 10px"><strong class="current-title">${i || "无标题"}</strong></h3>\n <div class="movie-meta" style="margin-bottom: 10px">\n <span><strong>番号: </strong>${e || "未知"}</span>\n <span><strong>年份: </strong>${a || "未知"}</span>\n <span>\n <strong>站点: </strong>\n <a href="https://fc2ppvdb.com/articles/${e.replace("FC2-", "")}" target="_blank">fc2ppvdb</a>\n <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${e.replace("FC2-", "")}/" target="_blank">fc2电子市场</a>\n </span>\n </div>\n <div class="movie-actors" style="margin-bottom: 10px">\n <div class="actor-list"><strong>主演: </strong></div>\n </div>\n <div class="movie-seller" style="margin-bottom: 10px">\n <span><strong>販売者: </strong></span>\n </div>\n <div class="movie-gallery" style="margin-bottom: 10px">\n <strong>剧照: </strong>\n <div class="image-list"></div>\n </div>\n \n <div id="data-publishTime" style="display: none">${a || ""}</div>\n\n `),
this.getImgList(e).then(), this.getActressInfo(e).then(), this.getBean("TranslatePlugin").translate(e, !1).then();
} catch (a) {
console.error(a);
} finally {
n.close();
}
}
handleLongImg(e) {
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
$(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
const t = await this.getBean("ScreenShotPlugin").getScreenshot(e);
t && ($(".screen-container").html(`<img src="${t}" alt="" loading="lazy" style="width: 100%;">`),
$(".screen-container").on("click", (e => {
e.stopPropagation(), e.preventDefault(), showImageViewer(e.currentTarget);
})));
}));
}
async get123AvVideoInfo(e) {
const t = await gmHttp.get(e), n = t.match(/v-scope="Movie\({id:\s*(\d+),/), a = n ? n[1] : null, i = $(t);
return {
id: a,
publishDate: i.find('span:contains("リリース日:")').next("span").text(),
title: i.find("h1").text().trim(),
moviePoster: i.find("#player").attr("data-poster")
};
}
async getActressInfo(e) {
let t = `https://fc2ppvdb.com/articles/${e.replace("FC2-", "")}`;
const n = await gmHttp.get(t), a = $(n), i = a.find("div").filter((function() {
return 0 === $(this).text().trim().indexOf("女優:");
}));
if (0 === i.length || i.length > 1) return void show.error("解析女优信息失败");
const s = $(i[0]).find("a");
let o = "<strong>主演: </strong>";
if (s.length > 0) {
let e = "";
s.each(((t, n) => {
let a = $(n), i = a.text(), s = a.attr("href");
o += `<span class="actor-tag"><a href="https://fc2ppvdb.com${s}" target="_blank">${i}</a></span>`,
e += i + " ";
})), $("#data-actress").text(e);
} else o += "<span>暂无演员信息</span>";
$(".actor-list").html(o);
const r = a.find("div").filter((function() {
return 0 === $(this).text().trim().indexOf("販売者:");
}));
if (r.length > 0) {
const e = $(r[0]).find("a");
if (e.length > 0) {
const t = $(e[0]);
let n = t.text(), a = t.attr("href");
$(".movie-seller").html(`<span><strong>販売者: </strong><a href="https://fc2ppvdb.com${a}" target="_blank">${n}</a></span>`);
}
}
}
async getImgList(e) {
let t = e.replace("FC2-", ""), n = `https://adult.contents.fc2.com/article/${e.replace("FC2-", "")}/`;
const a = await gmHttp.get(n, null, {
referer: n
});
let i = $(a).find(".items_article_SampleImagesArea img").map((function() {
return $(this).attr("src");
})).get(), s = "";
Array.isArray(i) && i.length > 0 ? s = i.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("") : $(".movie-gallery").html("<h4>剧照: 暂无剧照</h4>"),
$(".image-list").html(s), this.handleLongImg(t);
}
async getMovie(e, t) {
let n = `${await this.getBaseUrl()}/ajax/v/${e}/videos`, a = loading();
try {
let e = (await gmHttp.get(n)).result.watch;
return e.length > 0 ? (e.forEach((e => {
e.url = e.url + "?poster=" + t;
})), e) : null;
} catch (i) {
console.error(i);
} finally {
a.close();
}
}
markDataListHtml(e) {
let t = "";
return e.forEach((e => {
t += `\n <div class="item">\n <a href="${e.href}" class="box" title="${e.title}">\n <div class="cover ">\n <img loading="lazy" src="${e.imgSrc.replace("/s360", "")}" alt="">\n </div>\n <div class="video-title"><strong>${e.carNum}</strong> ${e.title}</div>\n <div class="score">\n </div>\n <div class="meta">\n </div>\n <div class="tags has-addons">\n </div>\n </a>\n </div>\n `;
})), t;
}
}
class Oe extends R {
getName() {
return "video123AvPlugin";
}
async handle() {
if (!o.includes("5masterzzz")) return;
localStorage.setItem("__pul", Date.now().toString()), setInterval((() => {
localStorage.setItem("__pul", Date.now().toString());
}), 5e3);
document.querySelector("video").play().then();
}
}
class Ve extends R {
constructor() {
super(...arguments), i(this, "currentEngine", null), i(this, "searchEngines", [ {
name: "U9A9",
id: "u9a9",
url: "https://u9a9.com/?type=2&search={keyword}",
targetPage: "https://u9a9.com/?type=2&search={keyword}",
parseHtml: this.parseU3C3
}, {
name: "U3C3",
id: "u3c3",
url: "https://u3c3.com/?search2=a8lr16lo&search={keyword}",
targetPage: "https://u3c3.com/?search2=a8lr16lo&search={keyword}",
parseHtml: this.parseU3C3
}, {
name: "Sukebei",
id: "Sukebei",
url: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}",
targetPage: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}",
parseHtml: this.parseSukebei
} ]);
}
getName() {
return "MagnetHubPlugin";
}
async initCss() {
return "\n <style>\n .magnet-container {\n margin: 20px auto;\n width: 100%;\n font-family: Arial, sans-serif;\n }\n .magnet-tabs {\n display: flex;\n border-bottom: 1px solid #ddd;\n margin-bottom: 15px;\n justify-content: space-between;\n }\n .magnet-tab {\n padding: 5px 12px;\n cursor: pointer;\n border: 1px solid transparent;\n border-bottom: none;\n margin-right: 5px;\n background: #f5f5f5;\n border-radius: 5px 5px 0 0;\n }\n .magnet-tab.active {\n background: #fff;\n border-color: #ddd;\n border-bottom: 1px solid #fff;\n margin-bottom: -1px;\n font-weight: bold;\n }\n .magnet-tab:hover:not(.active) {\n background: #e9e9e9;\n }\n \n .magnet-results {\n min-height: 200px;\n }\n .magnet-result {\n padding: 15px;\n border-bottom: 1px solid #eee;\n position: relative; \n }\n .magnet-result:hover {\n background-color: #f9f9f9;\n }\n .magnet-title {\n font-weight: bold;\n margin-bottom: 5px;\n white-space: nowrap;\n overflow: hidden; \n text-overflow: ellipsis;\n padding-right: 80px; \n }\n .magnet-info {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: #666;\n margin-bottom: 5px;\n }\n .magnet-loading {\n text-align: center;\n padding: 20px;\n }\n .magnet-error {\n color: #f44336;\n padding: 10px;\n }\n \n .magnet-copy {\n position: absolute;\n right: 15px;\n top: 12px;\n }\n .magnet-hub-btn {\n background-color: #f0f0f0;\n color: #555;\n border: 1px solid #ddd;\n padding: 3px 8px;\n border-radius: 3px;\n cursor: pointer;\n font-size: 12px;\n transition: all 0.2s;\n margin-left: 10px;\n }\n .magnet-hub-btn:hover {\n background-color: #e0e0e0;\n border-color: #ccc;\n }\n .magnet-hub-btn.copied {\n background-color: #4CAF50;\n color: white;\n border-color: #4CAF50;\n }\n </style>\n ";
}
createMagnetHub(e) {
e = e.replace("FC2-", "");
const t = $('<div class="magnet-container"></div>'), n = $('<div class="magnet-tabs"></div>'), a = "jhs_magnetHub_selectedEngine", i = localStorage.getItem(a);
let s = 0;
const o = $('<div style="display: flex;"></div>');
this.searchEngines.forEach(((e, t) => {
const n = $(`<div class="magnet-tab" data-engine="${e.id}">${e.name}</div>`);
i && e.id === i ? (n.addClass("active"), this.currentEngine = e, s = t) : 0 !== t || i || (n.addClass("active"),
this.currentEngine = e), o.append(n);
})), n.append(o), n.append(`<a style="margin-right: 20px;margin-top:3px" id="targetBox" href="${this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(e))}" target="_blank">原网页</a>`),
t.append(n);
const r = $('<div class="magnet-results"></div>');
return t.append(r), t.on("click", ".magnet-tab", (n => {
const i = $(n.target).data("engine");
this.currentEngine = this.searchEngines.find((e => e.id === i)), $("#targetBox").attr("href", this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(e))),
localStorage.setItem(a, i), t.find(".magnet-tab").removeClass("active"), $(n.target).addClass("active"),
this.searchEngine(r, this.currentEngine, e);
})), this.searchEngine(r, this.currentEngine || this.searchEngines[s], e), t;
}
searchEngine(e, t, n) {
e.html(`<div class="magnet-loading">正在从 ${t.name} 搜索 "${n}"...</div>`);
const a = `${t.name}_${n}`;
sessionStorage.getItem(a);
const i = t.url.replace("{keyword}", encodeURIComponent(n));
t.parseHtml && GM_xmlhttpRequest({
method: "GET",
url: i,
onload: i => {
try {
const s = t.parseHtml.call(this, i.responseText, n);
s.length > 0 && sessionStorage.setItem(a, JSON.stringify(s)), this.displayResults(e, s, t.name);
} catch (s) {
e.html(`<div class="magnet-error">解析 ${t.name} 结果失败: ${s.message}</div>`);
}
},
onerror: n => {
e.html(`<div class="magnet-error">从 ${t.name} 获取数据失败: ${n.statusText}</div>`);
}
}), t.parseJson && t.parseJson.call(this, e, t, n, a);
}
displayResults(e, t, n) {
function a(e) {
const t = e.text();
e.addClass("copied").text("已复制"), setTimeout((() => {
e.removeClass("copied").text(t);
}), 2e3);
}
function i(e, t) {
const n = document.createElement("textarea");
n.value = e, n.style.position = "fixed", document.body.appendChild(n), n.select();
try {
document.execCommand("copy"), a(t);
} catch (i) {
console.error("复制失败:", i), alert("复制失败,请手动复制链接");
}
document.body.removeChild(n);
}
e.empty(), 0 !== t.length ? (t.forEach((t => {
const n = $(`\n <div class="magnet-result">\n <div class="magnet-title"><a href="${t.magnet}">${t.title}</a></div>\n <div class="magnet-info">\n <span>大小: ${t.size || "未知"}</span>\n <span>日期: ${t.date || "未知"}</span>\n </div>\n <div class="magnet-copy">\n <button class="magnet-hub-btn copy-btn" data-magnet="${t.magnet}">复制链接</button>\n <button class="magnet-hub-btn down-115" data-magnet="${t.magnet}">115离线下载</button>\n </div>\n </div>\n `);
e.append(n);
})), e.on("click", ".copy-btn", (function() {
const e = $(this), t = e.data("magnet");
navigator.clipboard ? navigator.clipboard.writeText(t).then((() => {
a(e);
})).catch((n => {
i(t, e);
})) : i(t, e);
})), e.on("click", ".down-115", (async e => {
const t = $(e.currentTarget).data("magnet");
let n = loading();
try {
await this.getBean("WangPan115TaskPlugin").handleAddTask(t);
} catch (a) {
show.error("发生错误:" + a), console.error(a);
} finally {
n.close();
}
}))) : e.append('<div class="magnet-error">没有找到相关结果</div>');
}
parseBTSOW(e, t, n, a) {
const i = this;
GM_xmlhttpRequest({
method: "POST",
url: t.url,
headers: {
"Content-Type": "application/json"
},
data: `[{"search":"${n}"},50,1]`,
onload: n => {
try {
const s = JSON.parse(n.responseText).data, o = [];
for (let e = 0; e < s.length; e++) {
let t = s[e];
o.push({
title: t.name,
magnet: "magnet:?xt=urn:btih:" + t.hash,
size: (t.size / 1073741824).toFixed(2) + " GB",
date: utils.formatDate(new Date(1e3 * t.lastUpdateTime))
});
}
o.length > 0 && sessionStorage.setItem(a, JSON.stringify(o)), i.displayResults(e, o, t.name);
} catch (s) {
e.html(`<div class="magnet-error">解析 ${t.name} 结果失败: ${s.message}</div>`);
}
},
onerror: n => {
e.html(`<div class="magnet-error">从 ${t.name} 获取数据失败: ${n.statusText}</div>`);
}
});
}
parseU3C3(e, t) {
const n = utils.htmlTo$dom(e), a = [];
return n.find(".torrent-list tbody tr").each(((e, n) => {
const i = $(n);
if (i.text().includes("置顶")) return;
const s = i.find("td:nth-child(2) a").attr("title") || i.find("td:nth-child(2) a").text().trim();
if (!s.toLowerCase().includes(t.toLowerCase())) return;
const o = i.find("td:nth-child(3) a[href^='magnet:']").attr("href"), r = i.find("td:nth-child(4)").text().trim(), l = i.find("td:nth-child(5)").text().trim();
o && a.push({
title: s,
magnet: o,
size: r,
date: l
});
})), a;
}
parseSukebei(e, t) {
const n = utils.htmlTo$dom(e), a = [];
return n.find(".torrent-list tbody tr").each(((e, n) => {
const i = $(n);
if (i.text().includes("置顶")) return;
const s = i.find("td:nth-child(2) a").attr("title") || i.find("td:nth-child(2) a").text().trim();
if (!s.toLowerCase().includes(t.toLowerCase())) return;
const o = i.find("td:nth-child(3) a[href^='magnet:']").attr("href"), r = i.find("td:nth-child(4)").text().trim(), l = i.find("td:nth-child(5)").text().trim();
o && a.push({
title: s,
magnet: o,
size: r,
date: l
});
})), a;
}
}
class Re extends R {
getName() {
return "ScreenShotPlugin";
}
async handle() {
this.loadScreenShot().then();
}
async loadScreenShot() {
if (!isDetailPage) return;
if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return;
let e = this.getPageInfo().carNum;
r && $(".preview-images .tile-item").first().before(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 215px;text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> '),
l && $("#sample-waterfall .sample-box:first").after(' <a class="sample-box screen-container" style="overflow:hidden; height: 110px; text-align:center;"><div style="margin-top: 30px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
try {
const t = await this.getScreenshot(e);
this.addImg("缩略图", t), clog.log("加载缩略图:", t);
} catch (t) {
this.showErrorFallback(e, t);
}
}
async getScreenshot(e) {
const t = localStorage.getItem("jhs_screenShot") ? JSON.parse(localStorage.getItem("jhs_screenShot")) : {};
if (t[e]) return clog.debug("缓存中存在缩略图:", e, t[e]), t[e];
let n;
try {
n = await Promise.any([ this.getJavStoreScreenShot(e) ]);
} catch (i) {
throw clog.error("获取缩略图资源失败:", n, i), show.error("获取缩略图资源失败"), i;
}
if (!n) return this.showErrorFallback(e, null), null;
const a = n.indexOf("https://");
return -1 !== a && (n = n.substring(a)), t[e] = n, clog.log("缩略图获取成功:", n), localStorage.setItem("jhs_screenShot", JSON.stringify(t)),
n;
}
async getJavStoreScreenShot(e) {
let t = `https://javstore.net/search/${e}.html`;
clog.log("正在解析缩略图:", t);
let n = await gmHttp.get(t);
const a = utils.htmlTo$dom(n);
let i = null;
if (a.find("#content_news h3 span a").each((function() {
if ($(this).attr("title").toLowerCase().includes(e.toLowerCase())) return i = $(this).attr("href"),
!1;
})), !i) return clog.error("JavStore, 查询番号失败:", t), null;
let s = await gmHttp.get(i);
const o = utils.htmlTo$dom(s);
let r = o.find("a:contains('CLICK HERE')").attr("href") || o.find("img[src*='_s.jpg']").attr("src");
return r ? r.replace(".th", "") : (clog.error("JavStore, 解析预览图失败:", t), null);
}
async getJavBestScreenShot(e) {
let t = `https://javbest.net/?s=${e}`;
clog.log("正在解析缩略图:", t);
let n = await gmHttp.get(t);
const a = utils.htmlTo$dom(n), i = a.find(".app_loop_thumb a").first().attr("href");
if (!i) throw clog.error("解析JavBest搜索页失败:", t), new Error("解析JavBest搜索页失败");
const s = a.find(".app_loop_thumb a").first().attr("title");
if (!s.toLowerCase().includes(e.toLowerCase())) throw clog.error("解析JavBest搜索页失败:", s),
new Error("解析JavBest搜索页失败");
const o = await gmHttp.get(i);
let r = $(o).find('#content a img[src*="_t.jpg"]').attr("src");
if (!r) throw clog.error("解析JavBest缩略图失败:", t), new Error("解析JavBest缩略图失败");
return r = r.replace("_t", "").replace("http:", "https:"), r;
}
addImg(e, t) {
t && (r && $(".screen-container").html(`<img src="${t}" alt="${e}" loading="lazy" style="width: 100%;">`),
l && $(".screen-container").html(`<div class="photo-frame"><img src="${t}" style="height: inherit;width: 100%;" title="${e}" alt="${e}"></div>`),
$(".screen-container").on("click", (e => {
e.stopPropagation(), e.preventDefault(), showImageViewer(e.currentTarget);
})));
}
showErrorFallback(e, t) {
var n;
console.error("获取缩略图失败:", null == (n = null == t ? void 0 : t.message) ? void 0 : n.substring(0, 100));
let a = l ? "margin-top: 30px" : "margin-top: 50px";
$(".screen-container").html(`<div style="${a}; cursor:auto;color:#000;">获取缩略图失败</div><br/><a href='#' class='retry-link'>点击重试</a> 或 <a class="check-link" href='https://javstore.net/search/${e}.html' target='_blank'>前往确认</a>`).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", (async t => {
t.stopPropagation(), t.preventDefault(), $(".screen-container").html(`<div style="${a};cursor:auto;color:#000;">正在重新加载...</div>`);
try {
const t = await this.getScreenshot(e);
this.addImg("缩略图", t);
} catch (n) {
this.showErrorFallback(e, n);
}
})).on("click", ".check-link", (async t => {
t.stopPropagation(), t.preventDefault(), window.open(`https://javstore.net/search/${e}.html`, "_blank");
}));
}
}
const Ke = async () => {
const e = await gmHttp.get("https://webapi.115.com/offine/downpath");
return "object" == typeof e ? e.data : null;
}, We = async (e, t = 0, n = 30) => {
const a = `https://webapi.115.com/files/search?search_value=${encodeURIComponent(e)}&offset=${t}&limit=${n}`;
return await gmHttp.get(a);
};
class qe extends R {
getName() {
return "WangPan115TaskPlugin";
}
async handle() {
$(".buttons button[data-clipboard-text*='magnet:']").each(((e, t) => {
$(t).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async e => {
e.stopPropagation(), e.preventDefault();
let n = loading();
try {
await this.handleAddTask($(t).attr("data-clipboard-text"));
} catch (a) {
show.error("发生错误:" + a), console.error(a);
} finally {
n.close();
}
})));
})), l && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
this.bus115Down();
}));
}
async bus115Down() {
$("#magnet-table tr").each(((e, t) => {
const n = $(t).find("td:nth-child(1) a").attr("href");
if (n && n.includes("magnet:")) {
const e = $("<td>").addClass("action-cell");
$("<button>").text("115离线下载").addClass("button is-info is-small").click((async e => {
e.stopPropagation(), e.preventDefault();
let t = loading();
try {
await this.handleAddTask(n);
} catch (a) {
show.error("发生错误:" + a), console.error(a);
} finally {
t.close();
}
})).appendTo(e), $(t).append(e);
}
})), $("#magnet-table tbody").length > 0 && $("#magnet-table tbody tr").append($("<td>").text("操作"));
}
async getSavePathId(e) {
let t = await storageManager.getSetting("savePath115", "云下载");
e && (t = t.replaceAll("{ny}", e)), t = t.replaceAll("{date}", utils.formatDate(new Date));
}
async handleAddTask(e, t) {
const n = await (async () => {
const e = await gmHttp.get("https://115.com/?ct=offline&ac=space&_=" + (new Date).getTime());
return "object" == typeof e ? e : null;
})();
if (!n) return void show.error("未登录115网盘", {
close: !0,
duration: -1,
callback: async () => {
window.open("https://115.com");
}
});
const a = n.sign, i = n.time, s = this.getUserId(), o = await (async (e, t = "", n, a, i) => {
const s = {
url: encodeURIComponent(e),
wp_path_id: "",
uid: n,
sign: a,
time: i
};
return await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=add_task_url", s);
})(e, s, a, i);
console.log("离线下载返回值:", o);
let r = o.info_hash, l = await this.getFileId(s, a, i, r), c = "https://115.com/?tab=offline&mode=wangpan";
l && (c = `https://115.com/?cid=${l}&offset=0&mode=wangpan`);
let d = "添加成功, 是否前往查看?";
!1 === o.state && (d = o.error_msg + " 是否前往查看?"), utils.q(null, d, (async () => {
let e = await this.getFileId(s, a, i, r);
e && (c = `https://115.com/?cid=${e}&offset=0&mode=wangpan`), window.open(c);
}));
}
async getUserId() {
let e = await Ke();
if (e && e.length > 0) return e[0].id;
{
show.info("没有默认离线目录, 正在创建中...");
const t = (await (async (e, t = 0) => {
const n = {
pid: t,
cname: e
};
return await gmHttp.postFormData("https://webapi.115.com/files/add", n);
})("云下载")).file_id;
if (await (async e => {
const t = {
file_id: e
};
return await gmHttp.postFormData("https://webapi.115.com/offine/downpath", t);
})(t), show.info("创建完成, 开始执行离线下载"), e = await Ke(), e && e.length > 0) return e[0].id;
throw new Error("获取115用户Id失败");
}
}
async getFileId(e, t, n, a) {
const i = await (async (e, t, n) => {
const a = {
page: 1,
uid: e,
sign: t,
time: n
};
return (await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=task_lists", a)).tasks;
})(e, t, n);
console.log("云离线列表:", i);
let s = null;
for (let o = 0; o < i.length; o++) {
let e = i[o];
if (e.info_hash === a) {
s = e.file_id;
break;
}
}
return s;
}
}
class Je extends R {
constructor() {
super(...arguments), i(this, "JHS_115_COOKIE", "jhs_115_cookie"), i(this, "JHS_115_MAX_AGE", "jhs_115_max_age");
}
getName() {
return "WangPan115Plugin";
}
async initCss() {
return "\n <style>\n .login-box .ltab-office {\n border: 1px solid #DEE4EE;\n }\n \n .change-bg::before {\n background-color:#F9FAFB !important;\n }\n \n .site-login-wrap {\n height: auto;\n }\n \n #jhs-cookie-panel {\n width: 200px;\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 10000;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n cursor: pointer;\n background-color: #FFFFFF;\n color: #333333;\n padding: 0;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: all 0.3s ease;\n border: 1px solid #E0E0E0;\n }\n \n #jhs-cookie-panel.expanded {\n padding: 0;\n border-radius: 8px;\n background-color: #FFFFFF;\n color: #333333;\n box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);\n }\n \n #jhs-cookie-header {\n padding: 10px 15px;\n background-color: #0078D4;\n color: white;\n border-radius: 6px 6px 0 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-weight: 600;\n }\n \n #jhs-cookie-panel:not(.expanded) #jhs-cookie-header {\n border-radius: 6px;\n padding: 8px 15px;\n }\n \n #jhs-cookie-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease-out;\n padding: 0 15px;\n }\n \n #jhs-cookie-panel.expanded #jhs-cookie-content {\n max-height: 250px;\n padding: 15px;\n }\n \n #jhs-cookie-value {\n max-height: 100px;\n overflow-y: auto;\n white-space: pre-wrap;\n word-break: break-all;\n margin-bottom: 15px;\n padding: 10px;\n border: 1px solid #CCCCCC;\n background-color: #F8F8F8;\n font-size: 12px;\n border-radius: 4px;\n color: #555;\n }\n \n #jhs-copy-btn {\n background-color: #10B981;\n color: white;\n border: none;\n padding: 8px 15px;\n text-align: center;\n text-decoration: none;\n display: inline-block;\n font-size: 14px;\n margin: 0;\n cursor: pointer;\n border-radius: 4px;\n width: 100%;\n font-weight: 600;\n transition: background-color 0.2s ease;\n }\n \n #jhs-copy-btn:hover {\n background-color: #059669;\n }\n </style>\n ";
}
async handle() {
o.includes("&ac=userfile") || o.includes("115") && (utils.loopDetector((() => $("#js-login-box").length > 0), (() => {
0 !== $("#js-login-box").length && (this.reLogin(), this.hookPage(), this.bindClick());
}), 20, 4e3, !0), this.createCookiePanel());
}
reLogin() {
utils.loopDetector((() => $(".login-finished").length > 0), (() => {
if ($(".login-finished").length > 0 || 0 === $("#js-login-box").length) return;
const e = localStorage.getItem(this.JHS_115_COOKIE), t = localStorage.getItem(this.JHS_115_MAX_AGE);
document.cookie.includes("SEID") || null === e || utils.q(null, "检测到上次登录已有缓存cookie, 是否使用并登录?", (() => {
utils.addCookie(e, {
maxAge: parseInt(t),
domain: ".115.com"
}), window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}));
}), 20, 1500, !0);
}
hookPage() {
const e = $('<a id="jhs-cookie"><s>🔰 JHS-扫码</s></a>');
$(".ltab-office").after(e);
const t = $(`\n <div id="jhs_cookie_box" style="display: none; padding: 0 20px; max-width: 300px; margin: auto;">\n <div style="margin-bottom: 15px; text-align: center;">\n <span style="font-size: 18px; font-weight: bold; color: #333; display: block; margin-bottom: 10px;"> 使用115App扫码登录 </span>\n <div style="text-align: left;">\n <select id="login-115-type" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;">\n <option value="" style="color: #999;">请选择登录方式</option>\n <option value="wechatmini">微信小程序</option>\n <option value="alipaymini">支付宝小程序</option>\n </select>\n </div>\n </div>\n \n <div style="text-align: left;">\n <select id="cookie-expiry-select" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;">\n ${[ {
label: "有效期: 会话 (关闭浏览器)",
value: 0
}, {
label: "有效期: 1 天",
value: 86400
}, {
label: "有效期: 7 天",
value: 604800
}, {
label: "有效期: 30 天",
value: 2592e3,
default: !0
}, {
label: "有效期: 60 天",
value: 5184e3
}, {
label: "有效期: 180 天",
value: 15552e3
} ].map((e => `<option value="${e.value}" ${e.default ? "selected" : ""} > ${e.label} </option>`)).join("")}\n </select>\n </div>\n \n <div id="qrcode-box" style="display: none; justify-content:center; min-height: 100px; border: 1px dashed #aaa; padding: 15px; text-align: center; margin-top: 15px; border-radius: 4px; background-color: #fff; line-height: 70px; color: #666;">\n 二维码占位区域\n </div>\n \n \n <div style="margin-bottom: 15px; text-align: center; margin-top:50px">\n <span style="font-size: 18px; font-weight: bold; color: #333; display: block; margin-bottom: 10px;">已有Cookie? 在此输入并回车</span>\n <div style="text-align: left;">\n <input type="text" id="cookie-str-input" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;">\n </div>\n </div>\n </div>\n `);
$("#js-login_box").find(".login-footer").before(t);
}
bindClick() {
$("#jhs-cookie").on("click", (() => {
const e = document.querySelector('[lg_rel="finished"]');
e ? e.style.display = "none" : (document.querySelector('[lg_rel="qrcode"]').style.display = "none",
document.querySelector(".login-footer").style.display = "none", document.querySelector(".list-other-login").style.display = "none"),
document.querySelectorAll("#js-login_way > *").forEach((e => {
e.classList.remove("current");
})), document.querySelector("#jhs_cookie_box").style.display = "block", $("#jhs-cookie").css("background", "#fff"),
$(".ltab-cloud").addClass("change-bg");
})), $(".ltab-cloud").on("click", (() => {
document.querySelector("#jhs_cookie_box").style.display = "none";
const e = document.querySelector('[lg_rel="finished"]');
e ? e.style.display = "flex" : (document.querySelector('[lg_rel="qrcode"]').style.display = "block",
document.querySelector(".login-footer").style.display = "block", document.querySelector(".list-other-login").style.display = "block"),
$("#jhs-cookie").css("background", "#F9FAFB"), $(".ltab-cloud").removeClass("change-bg");
}));
let e = null;
$("#login-115-type").on("change", (async t => {
let n = $("#login-115-type").val();
if (!n) return;
const a = (await (async e => {
let t = `https://qrcodeapi.115.com/api/1.0/${e}/1.0/token/`;
return await gmHttp.get(t);
})(n)).data, i = a.qrcode, s = a.sign, o = a.time, r = a.uid;
console.log("生成二维码:", a);
const l = $("#qrcode-box");
l.css("display", "flex"), l.html(""), new QRCode(l[0], {
text: i,
width: 150,
height: 150,
correctLevel: QRCode.CorrectLevel.H
}), e && clearTimeout(e);
const c = async () => {
try {
const t = await (async (e, t, n) => {
let a = `https://qrcodeapi.115.com/get/status/?uid=${e}&time=${t}&sign=${n}`;
return await gmHttp.get(a);
})(r, o, s);
console.log("已扫码, 正在获取结果:", t);
let a = t.data, i = a.msg, l = a.status;
if (i && (console.log(i), show.info(i)), 2 === l) {
show.ok("扫码登录成功");
const e = await (async (e, t) => {
const n = {
app: e,
account: t
}, a = `https://passportapi.115.com/app/1.0/${e}/1.0/login/qrcode/`;
return await gmHttp.postFormData(a, n);
})(n, r);
if (console.log("扫码登录成功:", e), e.data && e.data.cookie) {
const t = e.data.cookie, n = `UID=${t.UID}; CID=${t.CID}; SEID=${t.SEID}; KID=${t.KID}`;
console.log("解析出cookie:", n), localStorage.setItem(this.JHS_115_COOKIE, n), localStorage.setItem(this.JHS_115_MAX_AGE, $("#cookie-expiry-select").val()),
window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}
return;
}
e = setTimeout(c, 500);
} catch (t) {
console.error("登录检查失败:", t);
}
};
await c();
}));
const t = document.getElementById("cookie-str-input");
t.addEventListener("keydown", (function(e) {
if ("Enter" === e.key) {
e.preventDefault();
const n = t.value, a = document.getElementById("cookie-expiry-select");
let i = parseInt(a.value);
utils.addCookie(n, {
maxAge: i,
domain: ".115.com"
}), window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}
}));
}
showMessage(e) {
const t = document.createElement("div");
t.textContent = e, t.style.cssText = "\n position: fixed;\n top: 20px;\n right: 20px;\n background-color: #333;\n color: white;\n padding: 10px 20px;\n border-radius: 5px;\n z-index: 20000;\n opacity: 0;\n transition: opacity 0.5s ease-in-out;\n ",
document.body.appendChild(t), setTimeout((() => {
t.style.opacity = "1";
}), 10), setTimeout((() => {
t.style.opacity = "0", setTimeout((() => t.remove()), 500);
}), 3e3);
}
createCookiePanel() {
const e = localStorage.getItem(this.JHS_115_COOKIE);
if (!e) return;
const t = document.createElement("div");
t.id = "jhs-cookie-panel", t.innerHTML = `\n <div id="jhs-cookie-header">\n <span>JHS-115-Cookie</span>\n <span id="jhs-toggle-icon">▼</span>\n </div>\n <div id="jhs-cookie-content">\n <div id="jhs-cookie-value">${e}</div>\n <button id="jhs-copy-btn">复制 Cookie</button>\n </div>\n `,
document.body.appendChild(t);
const n = document.getElementById("jhs-cookie-header");
document.getElementById("jhs-cookie-content");
const a = document.getElementById("jhs-toggle-icon"), i = document.getElementById("jhs-copy-btn");
n.addEventListener("click", (() => {
const e = t.classList.toggle("expanded");
a.textContent = e ? "▲" : "▼";
})), i.addEventListener("click", (async t => {
t.stopPropagation();
try {
await navigator.clipboard.writeText(e), this.showMessage("Cookie 已成功复制到剪贴板!");
} catch (n) {
console.error("Failed to copy text using clipboard API: ", n);
const t = document.createElement("textarea");
t.value = e, document.body.appendChild(t), t.select(), document.execCommand("copy"),
document.body.removeChild(t), this.showMessage("Cookie 已复制! (回退方案)");
}
})), t.classList.remove("expanded");
}
}
const Ge = class e extends R {
constructor() {
super(...arguments), i(this, "loginStatus", e.LoginStatus.UNCHECKED);
}
getName() {
return "WangPan115MatchPlugin";
}
async initCss() {
return "\n <style>\n [class^='jhs-match-'] {\n padding: 1px 2px;\n margin-left: 0;\n margin-right: 5px;\n }\n \n .jhs-match-detail {\n z-index: 1000;\n background: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 10px;\n max-width: 800px;\n max-height: 500px;\n overflow-y: auto;\n }\n .jhs-match-detail.isListPage{\n position: absolute;\n box-shadow: 0 2px 10px rgba(0,0,0,0.2);\n }\n .jhs-match-detail table {\n width: 100%;\n border-collapse: collapse;\n }\n .jhs-match-detail th, .jhs-match-detail td {\n padding: 4px 8px;\n border: 1px solid #eee;\n text-align: left;\n }\n .jhs-match-detail th {\n background-color: #f5f5f5;\n }\n .jhs-match-detail tr:hover {\n background-color: #f9f9f9;\n }\n </style>\n ";
}
async handle() {
this.$box115 = l ? $(".container .info") : $(".movie-panel-info"), $(document).on("click", ".jhs-match-no-login-btn", (async e => {
e.preventDefault(), e.stopPropagation(), await this.handleLoginRedirect();
})), $(document).on("click", ".jhs-match-btn", (e => {
e.preventDefault(), e.stopImmediatePropagation(), this.showMatchDetail(e.currentTarget);
})), $(document).on("click", ".jhs-match-error-btn", (async e => {
e.preventDefault(), e.stopPropagation(), await this.retryMatch(e.currentTarget);
})), await this.matchDetailPage(), $(document).on("click", ".jhs-match-detail-error-btn", (async e => {
e.preventDefault(), e.stopPropagation();
$(e.currentTarget).replaceWith("<a class='jhs-match-btn' title=\"匹配中...\">匹配中...</a>");
try {
const e = this.getPageInfo().carNum, t = await this.searchFiles(e);
$(".jhs-match-detail").remove(), await this.matchDetailPage(t);
} catch (t) {
console.error(`重新匹配失败 [${carNum}]:`, t), this.showMatchError($box, carNum, t);
}
}));
}
async matchDetailPage(t) {
if (!isDetailPage) return;
if (await storageManager.getSetting("enable115Match", C) === C) return;
const n = $('\n <div class="jhs-match-detail">\n <table>\n <thead>\n <tr style="text-align: center">\n <th colspan="4">115匹配</th>\n </tr>\n <tr>\n <th>名称</th>\n <th>大小</th>\n <th>时间</th>\n <th>播放</th>\n </tr>\n </thead>\n <tbody>\n </tbody>\n </table>\n </div>\n '), a = n.find("tbody");
try {
const n = this.getPageInfo().carNum;
if (t || (t = await this.searchFiles(n)), await this.checkLoginStatus(), this.loginStatus === e.LoginStatus.LOGGED_OUT) a.append(`<tr><td colspan="4">\n <a class='jhs-match-no-login-btn a-info'\n data-keyword="${n}"\n title="未登录115网盘">未登录</a>\n </td></tr>`); else if (t.length > 0) {
const e = t.map((e => `\n <tr>\n <td>${e.name}</td>\n <td>${this.formatSize(e.size)}</td>\n <td>${e.createTime}</td>\n <td>\n <a href="https://115vod.com/?pickcode=${e.videoId}&share_id=0"\n target="_blank"\n class="a-success"\n title="播放">播放</a>\n </td>\n </tr>\n `)).join("");
a.append(e);
} else a.append(`<tr><td colspan="4">\n <a class='jhs-match-detail-error-btn a-info'\n data-keyword="${n}"\n title="未匹配,点击重试">未匹配</a>\n </td></tr>`);
} catch (i) {
a.append(`<tr><td colspan="4">\n <a class="a-danger jhs-match-detail-error-btn" title="${i.message || "加载失败"}">加载失败,请重试</a>\n </td></tr>`),
console.error("加载文件列表时发生错误:", i);
}
this.$box115.append(n);
}
async matchMovieList(e) {
await storageManager.getSetting("enable115Match", C) !== C ? (await this.checkLoginStatus(),
await this.processMovieElements(e)) : $(".video-title [class^='jhs-match-']").remove();
}
showMatchDetail(e) {
const t = $(e), n = t.attr("data-match");
$(".jhs-match-detail").remove();
const a = this.parseMatchData(n);
if (0 === a.length) return;
if (1 === a.length) {
const e = a[0].videoId;
return void window.open(`https://115vod.com/?pickcode=${e}&share_id=0`, "_blank");
}
const i = this.createMatchDetailElement(a);
this.positionDetailElement(i, t), this.addOutsideClickHandler(i), i.on("click", (e => {
e.stopPropagation();
}));
}
parseMatchData(e) {
try {
return JSON.parse(e) || [];
} catch (t) {
return console.error("解析匹配数据失败:", t), [];
}
}
createMatchDetailElement(e) {
const t = $(`\n <div class="jhs-match-detail isListPage">\n <table>\n <thead>\n <tr>\n <th>名称</th>\n <th>大小</th>\n <th>时间</th>\n <th>播放</th>\n </tr>\n </thead>\n <tbody>\n ${e.map((e => `\n <tr>\n <td>${e.name}</td>\n <td>${this.formatSize(e.size)}</td>\n <td>${e.createTime}</td>\n <td>\n <a href="https://115vod.com/?pickcode=${e.videoId}&share_id=0" \n target="_blank" \n class="a-success"\n title="播放">播放</a>\n </td>\n </tr>\n `)).join("")}\n </tbody>\n </table>\n </div>\n `);
return $("body").append(t), t;
}
positionDetailElement(e, t) {
const n = t.offset();
e.css({
top: n.top - e.outerHeight() + 20,
left: n.left
});
}
addOutsideClickHandler(e) {
const t = "click.jhs-match-detail";
setTimeout((() => {
$(document).on(t, (n => {
e.is(n.target) || 0 !== e.has(n.target).length || (e.remove(), $(document).off(t));
}));
}), 100);
}
async retryMatch(e) {
const t = $(e), n = t.closest(".movie-box, .item"), a = t.attr("data-keyword");
t.replaceWith("<a class='jhs-match-btn' title=\"匹配中...\">匹配中...</a>");
try {
const e = await this.searchFiles(a);
this.updateMatchStatus(n, a, e);
} catch (i) {
console.error(`重新匹配失败 [${a}]:`, i), this.showMatchError(n, a, i);
}
}
updateMatchStatus(e, t, n) {
n.length > 0 ? e.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-btn a-success' \n data-keyword="${t}"\n data-match='${JSON.stringify(n)}'\n title="点击查看匹配详情">匹配${n.length}个</a>`) : e.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-error-btn a-info' data-keyword="${t}" \n title="点击重新尝试匹配">未匹配</a>`);
}
async handleLoginRedirect() {
window.open("https://115.com");
}
async searchFiles(e) {
var t;
let n = e.toLowerCase().replace("fc2-", "");
return (null == (t = (await We(n)).data) ? void 0 : t.map((e => ({
folderId: e.fid,
videoId: e.pc,
name: e.n,
createTime: utils.formatDate(new Date(1e3 * e.te)),
size: e.s,
isVideo: [ ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv" ].some((t => {
var n;
return null == (n = e.n) ? void 0 : n.toLowerCase().endsWith(t);
}))
}))).filter((e => e.folderId && e.isVideo && e.name.toLowerCase().includes(n)))) || [];
}
showMatchError(e, t, n) {
e.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-error-btn' data-keyword="${t}" \n title="匹配失败,点击重试">匹配失败</a>`),
show.error(`${t} 匹配失败: ${n.message || "网络错误"}`);
}
async checkLoginStatus() {
var t;
if (this.loginStatus === e.LoginStatus.UNCHECKED) try {
const n = await We("test");
this.loginStatus = (null == (t = n.error) ? void 0 : t.includes("登录")) ? e.LoginStatus.LOGGED_OUT : e.LoginStatus.LOGGED_IN;
} catch {
this.loginStatus = e.LoginStatus.LOGGED_OUT;
}
}
async processMovieElements(e) {
const t = Array.from(e).filter((e => !utils.isHidden(e))).filter((e => !(l && $(e).find(".avatar-box").length > 0))).map((e => this.processSingleMovieElement(e)));
await Promise.all(t);
}
async processSingleMovieElement(t) {
const n = $(t), {carNum: a} = this.getBean("ListPagePlugin").findCarNumAndHref(n);
if (!(n.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus !== e.LoginStatus.LOGGED_OUT) try {
const e = await this.searchFiles(a);
this.addTag(n, a, e);
} catch (i) {
console.error(`搜索失败 [${a}]:`, i), this.addTag(n, a, []);
} else this.addTag(n, a, []);
}
addTag(t, n, a) {
if (!(t.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus === e.LoginStatus.LOGGED_OUT) t.find(".video-title").prepend(`<a class='jhs-match-no-login-btn a-info' \n data-keyword="${n}" \n title="未登录115网盘">未登录</a>`); else if (a.length > 0) {
const e = 1 === a.length ? "点击直接播放" : `点击查看${a.length}个匹配结果`;
t.find(".video-title").prepend(`<a class='jhs-match-btn a-success' \n data-keyword="${n}"\n data-match='${JSON.stringify(a)}'\n title="${e}">匹配${a.length}个</a>`);
} else t.find(".video-title").prepend(`<a class='jhs-match-error-btn a-info' \n data-keyword="${n}" \n title="未匹配,点击重试">未匹配</a>`);
}
formatSize(e) {
if (!e) return "-";
const t = [ "B", "KB", "MB", "GB", "TB" ];
let n = parseFloat(e), a = 0;
for (;n >= 1024 && a < t.length - 1; ) n /= 1024, a++;
return `${n.toFixed(2)} ${t[a]}`;
}
};
i(Ge, "LoginStatus", {
UNCHECKED: -1,
LOGGED_OUT: 0,
LOGGED_IN: 1
});
let Ye = Ge;
class Xe extends R {
getName() {
return "FavoriteActressesPlugin";
}
async handle() {
this.bindEvent(), await this.highlightActress(), this.replaceActressAvatar();
}
async highlightActress() {
if (!isDetailPage) return;
if (await storageManager.getSetting("enableFavoriteActresses", _) !== _) return;
const e = await storageManager.getFavoriteActressList();
if (!e || 0 === e.length) return;
const t = new Set;
e.forEach((e => {
e.starId && t.add(String(e.starId).trim());
})), 0 !== t.size && $(".female").prev().each(((e, n) => {
const a = $(n), i = a.attr("href");
let s = null;
if (i) {
const e = (i.endsWith("/") ? i.slice(0, -1) : i).split("/"), t = e[e.length - 1];
t && (s = t.trim());
}
let o = !1;
s && (o = t.has(s)), o && (a.addClass("highlighted"), a.attr("title", "高亮已收藏演员, 可在设置-基础配置中关闭"));
}));
}
async removeActorFromStorage(e) {
await storageManager.removeFavoriteActress(e) && clog.log("移除演员成功");
}
bindEvent() {
const e = /\/actors\/(\w+)\/(collect|uncollect)/;
$(document).on("confirm:complete", 'a[href*="/actors/"][href*="/uncollect"]', (async t => {
const [n] = t.detail;
if (!n) return;
const a = $(t.currentTarget).attr("href").match(e), i = a ? a[1] : null;
i && await this.removeActorFromStorage(i);
})), $("#button-collect-actor").click((async t => {
const n = $("#button-collect-actor").attr("href").match(e), a = n ? n[1] : null;
let i = [], s = $(".actor-section-name");
s.length && s.text().trim().split(",").forEach((e => {
i.push(e.trim());
}));
let o = $(".section-meta:not(:contains('影片'))");
if (o.length && o.text().trim().split(",").forEach((e => {
i.push(e.trim());
})), !i) return void clog.error("获取演员名称失败");
const r = i[0];
if (!a) return void clog.error("无法获取演员ID进行收藏操作。");
const l = ($(".avatar").first().css("background-image") || "").replace(/^url\(["']?|["']?\)$/g, ""), c = {
starId: a,
name: r,
allName: i,
avatar: l
};
1 === await storageManager.addFavoriteActressList([ c ]) ? clog.log(`收藏演员成功: ${r} (ID: ${a})`) : clog.log(`收藏演员失败: ${r} (ID: ${a})`);
})), $("#button-uncollect-actor").click((async t => {
const n = $("#button-uncollect-actor").attr("href").match(e), a = n ? n[1] : null;
a ? await this.removeActorFromStorage(a) : clog.error("无法获取演员ID进行取消收藏操作。");
}));
}
async replaceActressAvatar() {
const e = this.getActressId();
if (!e) return;
const t = (await storageManager.getFavoriteActressList()).find((t => t.starId === e));
if (t && t.avatar) {
const e = `url('${t.avatar}')`;
let n = $(".avatar").first();
if (0 === n.length) {
const e = '<div class="column actor-avatar"> <div class="image"> <span class="avatar"></span> </div> </div>';
$(".section-columns").prepend(e), n = $(".avatar").first();
}
if (0 === n.length) return;
n.css("background-image").trim().toLowerCase() !== e.trim().toLowerCase() && (n.css("background-image", e),
n.css("background-size", "cover"), n.css("background-position", "top center"), n.css("background-repeat", "no-repeat"));
}
}
}
class Qe extends R {
getName() {
return "BusImgPlugin";
}
handle() {}
async getVisibleImageItems(e, t) {
let n = [];
const a = document.querySelectorAll(e);
for (const i of a) {
if (!utils.isHidden(i)) {
const e = i.querySelector(t);
if (!(e instanceof HTMLImageElement)) continue;
e.style.removeProperty("height");
let a = e.offsetHeight;
a > 0 && n.push({
element: i,
imgElement: e,
height: a
});
}
}
return n;
}
async logImageHeightsByRow() {
if (await storageManager.getSetting("enableVerticalModel", C) === _) return;
const e = this.getSelector().itemSelector, t = await storageManager.getSetting("containerColumns", 5), n = await this.getVisibleImageItems(e, "img");
if (0 === n.length) return;
const a = [];
for (let i = 0; i < n.length; i++) {
const e = Math.floor(i / t);
a[e] || (a[e] = []), a[e].push(n[i]);
}
a.forEach(((e, t) => {
const n = e.map((e => e.height));
if (n.length < 2) return;
const a = Math.min(...n), i = Math.max(...n);
let s = 0;
i - a > 50 && (s = a, e.forEach((e => {
if (e.height !== s) {
const t = `${s}px`;
e.imgElement.style.setProperty("height", t, "important");
}
})));
}));
}
}
class Ze extends R {
getName() {
return "TranslatePlugin";
}
async initCss() {
return "\n <style> \n .translated-title {\n margin-top: 8px; \n padding: 12px; \n border-radius: 5px; \n border-left: 4px solid rgb(76, 175, 80);\n background: linear-gradient(135deg, rgb(255, 255, 255) 0%, rgb(245, 245, 245) 100%); \n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);\n font-size: 20px;\n }\n </style>\n ";
}
handle() {
isDetailPage && this.translate();
}
async translate(e, t = !0) {
if (await storageManager.getSetting("translateTitle", _) !== _) return;
l && (t = !1);
let n = $(".origin-title");
if (n.length || (n = $(".current-title")), n.length || (n = $("h3")), !n.length) return;
const a = n.text().trim();
if (!a) return void show.error("获取标题失败, 无法进行翻译");
n.after('<div class="translated-title">翻译中...</div>');
const i = n.next(".translated-title");
e || (e = this.getPageInfo().carNum);
const s = localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {};
s[e] ? i.html(t ? e + " " + s[e] : s[e]) : Ce(a, "ja", "zh-CN").then((n => {
i.html(t ? e + " " + n : n);
})).catch((e => {
console.error("翻译失败:", e), i.replaceWith(`<div class="translated-title" style="color: red;">翻译失败: ${e.message}</div>`);
}));
}
}
class et extends R {
constructor() {
super(...arguments), i(this, "singleTaskKey", "checkNewActressActorFilterCar"),
i(this, "taskConfig", null), i(this, "storageQueue", new fe), i(this, "lastCheckFavoriteActressTimeKey", "jhs_time_checkFavoriteActress"),
i(this, "lastCheckBlacklistTimeKey", "jhs_time_checkBlacklist"), i(this, "lastCheckNewVideoTimeKey", "jhs_time_checkNewVideo");
}
getName() {
return "TaskPlugin";
}
async limitConcurrency(e, t, n, a) {
this.showIsRun();
const i = [], s = e.length;
let o = 0;
for (const r of e) {
const e = a(r).finally((() => {
i.splice(i.indexOf(e), 1);
}));
if (i.push(e), o++, i.length >= t) {
const e = s - o;
clog.log(`剩余任务数: <span style="color: #f40">${e}</span>`), await Promise.race(i),
await utils.sleep(n);
}
}
await Promise.all(i);
}
isUnnecessaryCheck(e, t) {
if (!t) throw new Error("未传入checkIntervalTime");
t = parseInt(t);
return utils.getHourDifference(new Date(e), new Date) < t;
}
handle() {
this.doTask().then();
}
showIsRun() {
show.info("正在执行检测任务中, 请勿关闭当前窗口"), clog.show(), clog.window.classList.remove("collapsed"),
clog.toggleBtn.classList.remove("collapsed");
}
async doTask() {
if (isListPage) return await this.loadConfig(), this.javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl(),
navigator.locks.request(this.singleTaskKey, {
ifAvailable: !0
}, (async e => {
if (e) {
if (isListPage && (this.taskConfig.enableCheckBlacklist === _ ? await this.checkBlacklist() : clog.warn("自动检测屏蔽黑名单-禁用"),
!l)) {
if (this.taskConfig.enableCheckFavoriteActress === _) {
const e = localStorage.getItem(this.lastCheckFavoriteActressTimeKey), t = this.taskConfig.checkFavoriteActress_IntervalTime, n = e && this.isUnnecessaryCheck(e, t), a = $('a[href*="/users/profile"]').length > 0;
n && clog.debug(`检测同步演员, 上次检测时间: ${e} 检测间隔时间: ${t}小时 未到时间`), !n && a && await this.checkFavoriteActress();
} else clog.warn("自动同步已收藏的演员-禁用");
this.taskConfig.enableCheckNewVideo === _ ? await this.checkNewVideo() : clog.warn("自动检测已收藏演员的最新作品-禁用");
}
} else clog.debug("争夺任务锁失败, 跳过执行");
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
})).finally((() => {
setTimeout((() => {
this.doTask();
}), 3e5);
}));
}
async loadConfig() {
const e = await storageManager.getSetting();
this.taskConfig = {
checkConcurrencyCount: e.checkConcurrencyCount ? Number(e.checkConcurrencyCount) : 2,
checkRequestSleep: e.checkRequestSleep ? Number(e.checkRequestSleep) : 100,
enableCheckBlacklist: e.enableCheckBlacklist || _,
checkBlacklist_intervalTime: e.checkBlacklist_intervalTime ? Number(e.checkBlacklist_intervalTime) : 12,
checkBlacklist_ruleTime: e.checkBlacklist_ruleTime ? Number(e.checkBlacklist_ruleTime) : 8760,
enableCheckFavoriteActress: e.enableCheckFavoriteActress || _,
checkFavoriteActress_IntervalTime: e.checkFavoriteActress_IntervalTime ? Number(e.checkFavoriteActress_IntervalTime) : 24,
enableCheckNewVideo: e.enableCheckNewVideo || _,
checkNewVideo_intervalTime: e.checkNewVideo_intervalTime ? Number(e.checkNewVideo_intervalTime) : 12,
checkNewVideo_ruleTime: e.checkNewVideo_ruleTime ? Number(e.checkNewVideo_ruleTime) : 8760
};
}
async checkBlacklist(e) {
let t = await storageManager.getBlacklist();
if (0 === t.length) return;
t = t.sort(((e, t) => e.createTime < t.createTime ? 1 : e.createTime > t.createTime ? -1 : 0));
const n = this.taskConfig.checkConcurrencyCount, a = this.taskConfig.checkRequestSleep, i = this.taskConfig.checkBlacklist_intervalTime, s = this.taskConfig.checkBlacklist_ruleTime, o = localStorage.getItem(this.lastCheckBlacklistTimeKey);
if (!e && o && this.isUnnecessaryCheck(o, i)) return void clog.debug(`检测黑名单, 上次检测时间: ${o} 检测间隔时间: ${i}小时 未到时间`);
const r = [], l = [];
for (const h of t) {
let t = h.name, n = h.checkTime, a = h.lastPublishTime, o = h.url;
if (new URL(window.location.href).hostname === new URL(o).hostname) {
if (e || !n || !this.isUnnecessaryCheck(n, i)) if (!a || 0 === s || this.isUnnecessaryCheck(a, s)) r.push(h); else {
let e = `检测黑名单: ${t} ${a} 停更超过${s / 24 / 365}年,跳过检测`;
l.push(e), $("#checkBlacklistMsg").text(e);
}
} else clog.log("黑名单地址非同域名,跳过", o);
}
if (0 === r.length) return;
l.forEach((e => {
clog.log(e);
})), clog.log(`<span style='color: #f40'>检测屏蔽黑名单, 总任务数: ${r.length}, 并发限制:${n}, 请求间隔时间:${a}ms</span>`);
const c = this.getBean("BlacklistPlugin");
await this.limitConcurrency(r, n, a, (async e => {
let {starId: t, name: n, url: a} = e;
try {
clog.log("正在检屏黑名单演员:", n, a), $("#checkBlacklistMsg").text(`正在检屏黑名单演员: ${n} ${a}`);
const e = await gmHttp.get(a), i = utils.htmlTo$dom(e);
this.storageQueue.addTask((async () => {
let {lastPublishTime: e} = await c.parseAndSaveFilterInfo(i, n, t);
await storageManager.updateBlacklistItem({
starId: t,
name: n,
checkTime: utils.getNowStr(),
lastPublishTime: e
});
}));
} catch (i) {
$("#checkBlacklistMsg").text(`检测屏蔽演员信息, 发生错误: ${a}`), clog.error("检测屏蔽演员信息, 发生错误:", a, i),
show.error("检测屏蔽演员信息, 发生错误:" + i, "bottom", "right");
}
})), await this.storageQueue.waitAllFinished();
const d = utils.getNowStr();
localStorage.setItem(this.lastCheckBlacklistTimeKey, d), clog.log('<span style="color: #f40">-------- END 检测屏蔽黑名单 END --------</span>'),
$("#checkBlacklistMsg").text("检测屏蔽黑名单, 结束"), this.getBean("BlacklistPlugin").resetBtnTip().then();
}
async checkFavoriteActress() {
const e = `${this.javDbUrl}/users/collection_actors`, t = [];
await this.scrapeActorInfo(e, t), clog.log("所有演员信息已收集, 总计数量:", t.length), $("#checkNewVideoMsg").text("同步完成"),
t.length > 0 && (await storageManager.addFavoriteActressList(t), localStorage.setItem(this.lastCheckFavoriteActressTimeKey, utils.getNowStr()),
this.getBean("NewVideoPlugin").resetBtnTip().then());
}
async scrapeActorInfo(e, t) {
clog.log(`正在抓取页面: ${e}`), $("#checkNewVideoMsg").text(`正在解析已收藏的演员: ${e}`);
try {
const n = await http.get(e), a = utils.htmlTo$dom(n);
a.find("#actors .actor-box a").each(((e, n) => {
const a = $(n), i = a.attr("title"), s = a.attr("href");
if (i && s) {
const e = i.split(",").map((e => e.trim())).filter((e => e.length > 0)), n = e[0] || "", o = new URL(s, this.javDbUrl).pathname.split("/").filter((e => e.length > 0));
let r = "";
o.length > 0 && (r = o[o.length - 1]);
let l = D;
const c = a.find("img").attr("src"), d = a.find(".info");
d.length && d.text().trim().includes("無碼") && (l = L), t.push({
starId: r,
name: n,
allName: e,
avatar: c,
actressType: l,
lastCheckTime: null,
lastUpdateTime: null
});
}
}));
const i = a.find(".pagination-next").attr("href");
if (i) {
const e = new URL(i, this.javDbUrl).href;
await this.scrapeActorInfo(e, t);
}
} catch (n) {
clog.error(`抓取 ${e} 时发生错误:`, n);
}
}
async checkNewVideo(e) {
const t = await storageManager.getFavoriteActressList(), n = utils.genericSort(t, [ {
key: e => {
var t;
return (null == (t = e.newVideoList) ? void 0 : t.length) ?? 0;
},
order: "desc"
}, {
key: "lastPublishTime",
order: "desc"
} ]), a = this.taskConfig.checkConcurrencyCount, i = this.taskConfig.checkRequestSleep, s = this.taskConfig.checkNewVideo_intervalTime, o = this.taskConfig.checkNewVideo_ruleTime, r = localStorage.getItem(this.lastCheckNewVideoTimeKey);
if (!e && r && this.isUnnecessaryCheck(r, s)) return void clog.debug(`检测新作品, 上次检测时间: ${r} 检测间隔时间: ${s}小时 未到时间`);
const l = [], c = [];
for (const u of n) {
const {lastCheckTime: t, lastPublishTime: n, name: a} = u;
!e && t && this.isUnnecessaryCheck(t, s) || (!n || 0 === o || this.isUnnecessaryCheck(n, o) ? l.push(u) : c.push(`检测新作品: ${a} ${n} 停更超过${o / 24 / 365}年,跳过检测`));
}
if (0 === l.length) return;
c.forEach((e => {
clog.log(e);
})), clog.log(`<span style='color: #f40'>检测最新作品, 总任务数: ${l.length}, 并发限制:${a}, 请求间隔时间:${i}ms</span>`);
const d = await storageManager.getTitleFilterKeyword(), h = await storageManager.getBlacklistCarList(), g = new Set(h.map((e => e.carNum)));
await this.limitConcurrency(l, a, i, (async e => {
const {lastCheckTime: t, name: n, starId: a} = e;
let i = `${this.javDbUrl}/actors/${a}?t=d`;
try {
clog.log("正在检测最新作品, 演员:", n, i), $("#checkNewVideoMsg").text(`正在检测最新作品, 演员: ${n}`);
const e = await gmHttp.get(i), t = utils.htmlTo$dom(e);
this.storageQueue.addTask((async () => {
await this.parsePage(t, a, n, d, g);
}));
} catch (s) {
clog.error("检测屏蔽演员信息, 发生错误:", i, s), console.error("检测屏蔽演员信息, 发生错误:", i, s), show.error("检测屏蔽演员信息, 发生错误:" + s, "bottom", "right");
}
})), await this.storageQueue.waitAllFinished(), localStorage.setItem(this.lastCheckNewVideoTimeKey, utils.getNowStr()),
clog.log('<span style="color: #f40">检测最新作品---结束</span>'), $("#checkNewVideoMsg").text("检测完毕");
const p = this.getBean("NewVideoPlugin");
p.loadData(), p.resetBtnTip().then();
}
async parsePage(e, t, n, a, i) {
let s, o, r = !1, l = T;
if (e.text().includes(I) && (r = !0, l = I), r && e.find(".avatar-box").length > 0 && e.find(".avatar-box").parent().remove(),
s = e.find(this.getSelector(l).requestDomItemSelector), o = e.find(this.getSelector(l).nextPageSelector).attr("href"),
o && 0 === s.length) throw clog.error("新作品检测-解析列表失败"), show.error("新作品检测-解析列表失败"),
new Error("新作品检测-解析列表失败");
let c = [], d = null;
for (const u of s) {
const e = $(u), {carNum: t, url: n, title: s, publishTime: o} = this.getBean("ListPagePlugin").findCarNumAndHref(e);
if (!t) continue;
a.find((e => s.includes(e) || t.includes(e))) || (i.has(t) || (d || (d = o), c.push(t)));
}
const h = await storageManager.getCarList(), g = new Set(h.map((e => e.carNum))), p = c.filter((e => !g.has(e)));
p.length > 0 && clog.log(`<span style='color: #f40'>检测出新作品, ${n}, 共${p.length}部</span>`),
await storageManager.updateFavoriteActress({
starId: t,
lastCheckTime: utils.getNowStr(),
newVideoList: p,
lastPublishTime: d
});
}
async checkOneNewVideo(e) {
const t = await storageManager.getTitleFilterKeyword(), n = await storageManager.getBlacklistCarList(), a = new Set(n.map((e => e.carNum))), {lastCheckTime: i, name: s, starId: o} = e;
let r = `${this.javDbUrl}/actors/${o}?t=d`;
const l = $("#checkNewVideoMsg");
try {
clog.log("正在检测最新作品, 演员:", s, r), l.text(`正在检测最新作品, 演员: ${s}`);
const e = await gmHttp.get(r), n = utils.htmlTo$dom(e);
await this.parsePage(n, o, s, t, a), clog.log('<span style="color: #f40">检测最新作品---结束</span>'),
l.text("检测完毕");
this.getBean("NewVideoPlugin").loadData();
} catch (c) {
clog.error("检测屏蔽演员信息, 发生错误:", r, c), show.error("检测屏蔽演员信息, 发生错误:" + c, "bottom", "right"),
l.text(`检测屏蔽演员信息, 发生错误: ${r}`);
}
}
}
const tt = [ {
name: "jsDelivr (全球CDN)",
json: "https://cdn.jsdelivr.net/gh/gfriends/gfriends/Filetree.json",
base: "https://cdn.jsdelivr.net/gh/gfriends/gfriends/Content/"
}, {
name: "GitHub Raw (备用)",
json: "https://raw.githubusercontent.com/gfriends/gfriends/master/Filetree.json",
base: "https://raw.githubusercontent.com/gfriends/gfriends/master/Content/"
} ], nt = "jhs_img_cdn_index";
let at = parseInt(localStorage.getItem(nt) || "0", 10);
(at >= tt.length || at < 0) && (at = 0);
let it = tt[at].json, st = tt[at].base;
const ot = "filetreeStore", rt = "filetree_data", lt = {
db: null,
async open() {
return this.db ? this.db : new Promise(((e, t) => {
const n = indexedDB.open("GfriendsAvatarDB", 1);
n.onupgradeneeded = e => {
this.db = e.target.result, this.db.objectStoreNames.contains(ot) || this.db.createObjectStore(ot);
}, n.onsuccess = t => {
this.db = t.target.result, e(this.db);
}, n.onerror = e => {
console.error("IndexedDB open error:", e.target.errorCode), t(new Error("Failed to open IndexedDB"));
};
}));
},
async get(e) {
return await this.open(), new Promise((t => {
const n = this.db.transaction([ ot ], "readonly").objectStore(ot).get(e);
n.onsuccess = () => t(n.result), n.onerror = () => t(null);
}));
},
async set(e, t) {
return await this.open(), new Promise(((n, a) => {
const i = this.db.transaction([ ot ], "readwrite").objectStore(ot).put(t, e);
i.onsuccess = () => n(), i.onerror = e => {
console.error("IndexedDB set error:", e.target.errorCode), a(new Error("Failed to write to IndexedDB"));
};
}));
}
};
let ct = null, dt = null;
function ht(e) {
if (!e || !e.Content) return null;
const t = {}, n = e.Content;
for (const a in n) {
const e = encodeURIComponent(a);
for (const i in n[a]) {
let s = i.replace(/\.jpg$/i, "").split("-")[0];
s.startsWith("AI-Fix-") && (s = s.substring(7));
const o = s.toLowerCase().trim();
if (o.length > 0) {
const s = n[a][i], r = s.indexOf("?");
let l, c = "";
r > -1 ? (l = encodeURIComponent(s.substring(0, r)), c = s.substring(r)) : l = encodeURIComponent(s);
const d = `${st}${e}/${l}${c}`;
t[o] || (t[o] = []), t[o].includes(d) || t[o].push(d);
}
}
}
return t;
}
async function gt(e) {
let t = loading();
try {
await async function() {
if (ct && dt) return ct;
let e = null;
try {
e = await lt.get(rt);
} catch (a) {
console.error("读取 IndexedDB 失败:", a);
}
if (e && e.Content && (ct = e, dt = ht(e), dt)) return ct;
show.info("正在载入头像数据源...");
const t = await fetch(it);
if (!t.ok) throw new Error(`请求头像源失败: ${t.status}`);
const n = await t.json();
if (n && n.Content) {
ct = n, dt = ht(n);
try {
await lt.set(rt, n), clog.debug("载入头像数据源并写入缓存成功!");
} catch (a) {
clog.error(a), show.error("头像数据源写入缓存失败,可能磁盘已满或其他权限问题。");
}
return ct;
}
throw console.log(n), new Error("解析头像数据源失败");
}();
} catch (i) {
return show.error(i), [];
} finally {
t.close();
}
if (!dt) return [];
const n = new Set, a = e.map((e => e.toLowerCase().trim())).filter((e => e.length > 0));
if (0 === a.length) return [];
for (const s of a) {
const e = dt[s];
e && e.forEach((e => n.add(e)));
}
return Array.from(n);
}
class pt extends R {
constructor() {
super(...arguments), i(this, "currentPage", 1), i(this, "pageSize", 30);
}
getName() {
return "NewVideoPlugin";
}
async initCss() {
return "\n <style>\n #actress-card-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(243px, 1fr)); /* 响应式3-5列 */\n gap: 20px;\n padding-bottom: 20px;\n padding-right: 10px;\n background: #f9f9f9;\n border-radius: 5px;\n overflow-y: auto;\n }\n .actress-card {\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);\n padding: 15px;\n text-align: center;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n min-height: 365px;\n position: relative;\n overflow: hidden;\n }\n .actress-card:hover {\n box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);\n }\n .actress-card-name {\n font-size: 1.2em;\n font-weight: bold;\n color: #007bff;\n margin-top: 10px;\n }\n .actress-card-allname {\n font-size: 0.9em;\n color: #999;\n margin-top: 5px;\n height: 30px; /* 保证高度一致性 */\n overflow: hidden;\n white-space: nowrap; /* 防止文字换行 */\n text-overflow: ellipsis; /* 当文本溢出时,显示省略号 */\n }\n .actress-card-avatar {\n width: 100px;\n height: 100px;\n border-radius: 50%;\n object-fit: contain;\n margin: 0 auto;\n border: 4px solid #f0f0f0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n \n .card-tag {\n position: absolute;\n top: 15px; /* 调整标签距离顶部的距离 */\n right: -50px; /* 调整标签距离右侧的距离,负值让它移到外面一点 */\n \n width: 150px; /* 标签的宽度,影响斜角长度 */\n padding: 5px 0; /* 上下内边距 */\n text-align: center;\n \n background-color: #ff4757; /* 标签颜色 */\n color: white; /* 文字颜色 */\n font-size: 14px;\n font-weight: bold;\n z-index: 10; /* 确保标签在其他内容之上 */\n \n /* 3. 核心:旋转标签,使其倾斜 */\n transform: rotate(45deg); /* 45度斜角 */\n \n /* 可选:添加一些阴影或边框效果 */\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);\n }\n \n #actress-pagination {\n padding-top: 10px;\n text-align: center;\n border-top: 1px solid #ddd;\n }\n @media (max-width: 600px) {\n .page-number-btn {\n display: none !important;\n }\n }\n \n \n .card-btn {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: none;\n cursor: pointer;\n background: linear-gradient(145deg, #e0e0e0 0%, #f7f7f7 100%);\n box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.08),\n -8px -8px 16px rgba(255, 255, 255, 1.0);\n transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n }\n \n .card-btn svg,\n .card-btn svg path {\n transition: fill 0.3s ease;\n }\n \n .card-btn:hover {\n box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1),\n inset -5px -5px 10px rgba(255, 255, 255, 0.9);\n transform: scale(0.97);\n background: #e0e0e0;\n }\n \n .btn-check-actress svg path {\n fill: #4CAF50;\n }\n .btn-check-actress:hover svg path {\n fill: #388E3C;\n }\n \n .btn-edit-actress svg path {\n fill: #FFC107;\n }\n .btn-edit-actress:hover svg path {\n fill: #FFB300;\n }\n \n .btn-delete-actress svg path {\n fill: #F44336;\n }\n .btn-delete-actress:hover svg path {\n fill: #D32F2F;\n }\n </style>\n ";
}
async handle() {
await this.showNewVideoCount();
}
async showNewVideoCount() {
const e = (await storageManager.getFavoriteActressList()).reduce(((e, t) => {
var n;
return e + ((null == (n = t.newVideoList) ? void 0 : n.length) ?? 0);
}), 0);
$("#newVideoCount").text(`${e}`);
}
async resetBtnTip() {
const e = this.getBean("TaskPlugin"), t = await storageManager.getSetting(), n = localStorage.getItem(e.lastCheckFavoriteActressTimeKey) || "无", a = t.checkFavoriteActress_IntervalTime, i = localStorage.getItem(e.lastCheckNewVideoTimeKey) || "无", s = t.checkNewVideo_intervalTime;
$("#checkFavoriteActress").attr("data-tip", `上次自动同步时间: ${n}; 检测间隔时间: ${a}小时`), $("#checkNewVideo").attr("data-tip", `上次检测时间: ${i}; 检测间隔时间: ${s}小时`);
}
async openDialog() {
const e = this.getBean("TaskPlugin"), t = await storageManager.getSetting(), n = localStorage.getItem(e.lastCheckFavoriteActressTimeKey) || "无", a = t.checkFavoriteActress_IntervalTime, i = localStorage.getItem(e.lastCheckNewVideoTimeKey) || "无", s = t.checkNewVideo_intervalTime;
let o = `\n <div class="newVideoToolBox" style="display: flex; flex-direction: column; height: 100%; overflow: hidden; padding:10px">\n <div style="margin-bottom: 15px;display: flex; justify-content: space-between;">\n <div>\n <a class="a-danger" id="checkFavoriteActress" data-tip="上次自动同步时间: ${n}; 检测间隔时间: ${a}小时">${this.actressSvg} 手动同步演员</a>\n <a class="a-warning" id="checkNewVideo" data-tip="上次检测时间: ${i}; 检测间隔时间: ${s}小时">${this.newSvg} 手动检测最新作品</a>\n <a class="a-info" id="toSetting">${this.settingSvg} 配置</a>\n <span id="checkNewVideoMsg"></span>\n </div>\n <div style="display: flex; align-items: flex-start;">\n <select id="paramActressType" style="text-align: center; height: 100%; min-width: 150px; border: 1px solid #ddd; margin-right: 10px">\n <option value="all" selected>所有</option>\n <option value="uncensored">无码</option>\n <option value="censored">有码</option>\n <option value="">未知</option>\n </select>\n \n <a class="a-normal" id="reLoad">${this.refreshSvg} 刷新</a>\n </div>\n\n </div>\n <div id="actress-card-container" class="jhs-scrollbar"></div>\n <div id="actress-pagination"></div>\n </div>\n `;
layer.open({
type: 1,
title: '<span style="padding: 0 10px;" data-tip="数据来源: 女优页面首页,含磁链分类">新作品检测 ❓</span>',
content: o,
scrollbar: !1,
area: utils.getResponsiveArea([ "80%", "90%" ]),
anim: -1,
success: async (e, t) => {
this.loadData(), this.bindClick(), utils.setupEscClose(t);
}
});
}
bindClick() {
const e = this.getBean("TaskPlugin");
$("#reLoad").on("click", (e => {
this.loadData(), $("#checkNewVideoMsg").text("");
})), $("#toSetting").on("click", (e => {
this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => {
$("#setting-checkFavoriteActress").css({
border: "1px solid #f40"
}), $("#setting-checkNewVideo").css({
border: "1px solid #f40"
});
}));
}));
$("#checkFavoriteActress").on("click", (t => {
utils.q({
clientX: t.clientX,
clientY: t.clientY + 20
}, "是否手动同步演员?", (() => {
navigator.locks.request(e.singleTaskKey, {
ifAvailable: !0
}, (async t => {
if (!t) return void show.error("当前有定时任务在后台执行中, 无法发起手动任务");
$('a[href*="/users/profile"]').length > 0 ? (await e.checkFavoriteActress(), this.loadData()) : show.error("未登录JavDb, 同步失败");
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
}));
}));
})), $("#checkNewVideo").on("click", (t => {
utils.q({
clientX: t.clientX,
clientY: t.clientY + 20
}, "是否手动检测最新作品?", (() => {
navigator.locks.request(e.singleTaskKey, {
ifAvailable: !0
}, (async t => {
t ? await e.checkNewVideo(!0) : show.error("当前有定时任务在后台执行中, 无法发起手动任务");
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
}));
}));
})), $("#paramActressType").on("change", (e => {
this.loadData();
}));
}
loadData() {
this.currentPage = 1, this.renderActressCards().then();
}
async renderActressCards() {
const e = $("#actress-card-container");
if (!e.length) return;
let t = await storageManager.getFavoriteActressList();
const n = $("#paramActressType").val();
"all" !== n && (t = t.filter((e => e.actressType === n)));
const a = utils.genericSort(t, [ {
key: e => {
var t;
return (null == (t = e.newVideoList) ? void 0 : t.length) ?? 0;
},
order: "desc"
}, {
key: "lastPublishTime",
order: "desc"
} ]), i = a.length, s = Math.ceil(i / this.pageSize), o = (this.currentPage - 1) * this.pageSize, r = o + this.pageSize, l = a.slice(o, r), c = await this.getBean("OtherSitePlugin").getJavDbUrl(), d = this.getBean("TaskPlugin"), h = await storageManager.getSetting("checkNewVideo_ruleTime") || 8760, g = l.map((e => {
var t, n;
const a = Array.isArray(e.allName) ? e.allName.join(",") : "", i = Array.isArray(e.newVideoList) ? e.newVideoList.join(",") : "", s = `${c}/actors/${e.starId}?t=d`;
let o = !1;
e.lastPublishTime && (o = !d.isUnnecessaryCheck(e.lastPublishTime, h));
let r = "未知", l = "#9E9E9E";
e.actressType === L ? (r = "无码", l = "#4CAF50") : e.actressType === D && (r = "有码",
l = "#FF9800");
let g = "";
return o && (g = "background: linear-gradient(145deg, #e0e0e0 0%, #cabdbd 100%);box-shadow: none"),
`\n <div class="actress-card" data-starId="${e.starId}" style="${o ? "background: #d4cece" : ""}">\n <a href="${s}" target="_blank" style="text-decoration: none; color: inherit; display: block; flex-grow: 1;">\n <img src="${e.avatar || "https://c0.jdbstatic.com/images/actor_unknow.jpg"}" alt="${a}" class="actress-card-avatar">\n </a>\n\n <div>\n <a href="${s}" target="_blank" style="text-decoration: none; color: inherit; display: block; flex-grow: 1;">\n <div class="actress-card-name">${e.name}</div>\n </a>\n <div class="actress-card-allname" title="${a}">${a}</div>\n </div>\n \n <div style="font-size: 0.8em; margin-top: 5px;">\n <span style="color: ${(null == (t = e.newVideoList) ? void 0 : t.length) > 0 ? "red" : "inherit"};" title="${i}">\n 🔔 最新作品: ${(null == (n = e.newVideoList) ? void 0 : n.length) || 0}\n </span>\n </div>\n \n <div style="font-size: 0.8em; margin-top: 5px;">\n <span>上次检测: ${e.lastCheckTime || ""}</span>\n </div>\n <div style="font-size: 0.8em; margin-top: 5px;">\n <span>最后发行作品: ${e.lastPublishTime || ""}</span>\n </div>\n \n <div style="font-size: 0.7em; color: #cc4444; margin-top: 5px; min-height: 18px">\n <span>${o ? "停更" + h / 24 / 365 + "年以上, 下轮任务不再进行检测" : ""}</span>\n </div>\n \n \n <div style="margin-top: 5px;display: flex; justify-content:center; gap: 10px;">\n <a title="编辑" class="card-btn btn-edit-actress" style="${g}" data-starId="${e.starId}">${this.editSvg}</a>\n <a title="取消收藏" class="card-btn btn-delete-actress" style="${g}" data-starId="${e.starId}">${this.deleteSvg}</a>\n <a title="重新检测该演员" class="card-btn btn-check-actress" style="${g}" data-starId="${e.starId}">${this.checkSvg}</a>\n </div>\n \n <div class="card-tag" style="background-color:${l}">${r}</div>\n </div>\n `;
})).join("");
e.html(g), $(".btn-delete-actress").off("click").on("click", (e => {
e.preventDefault();
const t = $(e.currentTarget).attr("data-starId"), n = a.find((e => e.starId === t));
utils.q(e, `是否取消收藏 ${n.name}?`, (async () => {
let e = `${await this.getBean("OtherSitePlugin").getJavDbUrl()}/actors/${t}/uncollect`;
const n = document.querySelector("meta[name=csrf-token]").content, a = await gmHttp.post(e, null, {
"x-csrf-token": n
});
a.includes("removeClass") ? (await storageManager.removeFavoriteActress(t), this.loadData()) : (show.error("移除失败"),
clog.error("移除失败,返回值:", a));
}));
})), $(".btn-edit-actress").off("click").on("click", (e => {
e.preventDefault();
const t = $(e.currentTarget).attr("data-starId"), n = a.find((e => e.starId === t));
n ? this.editActress(n) : show.error(`未找到 starId 为 ${t} 的女优记录。`);
})), $(".btn-check-actress").off("click").on("click", (e => {
e.preventDefault(), navigator.locks.request(d.singleTaskKey, {
ifAvailable: !0
}, (async t => {
if (!t) return void show.error("当前有定时任务在后台执行中, 无法发起手动任务");
const n = $(e.currentTarget).attr("data-starId"), i = a.find((e => e.starId === n));
await d.checkOneNewVideo(i);
})).catch((e => {
console.error("锁任务出现错误:", e), clog.error("锁任务出现错误:", e);
}));
})), this.renderPagination(i, s), show.ok("加载完成");
}
async editActress(e) {
const t = e.name, n = e.avatar, a = Array.isArray(e.allName) ? e.allName.join(",") : "", i = Array.isArray(e.newVideoList) ? e.newVideoList.join(",") : "", s = e.starId, o = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", r = e.actressType || "", l = `\n <div style="padding: 20px;">\n <div style="margin-bottom: 15px; text-align: center;">\n <img id="edit-avatar-preview" src="${n}" alt="Avatar Preview" \n style="width: 100px; height: 100px; border-radius: 50%; object-fit: cover; margin-bottom: 10px; border: 2px solid #ddd;">\n <div style="text-align: left">\n <label style="display: block; margin-bottom: 5px; font-weight: bold;">头像链接:</label>\n <input type="text" id="edit-actress-avatar" value="${n}" \n style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">\n <div style="display: flex; gap: 5px; margin-top: 5px;">\n <button type="button" id="search-avatar-btn" \n style="flex-grow: 1; padding: 8px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">\n 搜索头像\n </button>\n <button type="button" id="select-cdn-btn" \n style="width: 100px; padding: 8px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">\n 选择 CDN 源\n </button>\n </div>\n </div>\n </div>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 5px; font-weight: bold;">主名称:</label>\n <input type="text" id="edit-actress-name" value="${t}" \n style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">\n </div>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 5px; font-weight: bold;">所有别名(用逗号隔开):</label>\n <textarea id="edit-actress-allname" style="${o}">${a}</textarea>\n </div>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 5px; font-weight: bold;">演员类别:</label>\n <select id="actressType" style="width: 100%; padding: 10px; border: 1px solid #ddd;">\n <option value="" ${"" === r ? "selected" : ""}>未知</option>\n <option value="censored" ${"censored" === r ? "selected" : ""}>有码</option>\n <option value="uncensored" ${"uncensored" === r ? "selected" : ""}>无码</option>\n </select>\n </div>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 5px; font-weight: bold;">最新作品(用逗号隔开):</label>\n <textarea id="edit-actress-newvideolist" style="${o}">${i}</textarea>\n </div>\n </div>\n `;
layer.open({
type: 1,
title: `编辑女优: ${t} (${s})`,
area: [ "500px", "750px" ],
content: l,
btn: [ "保存", "取消" ],
success: (e, t) => {
const n = e => {
e.css("height", "auto"), e.css("height", e[0].scrollHeight + 15 + "px");
};
$("#edit-actress-avatar").on("input", (function() {
const e = $(this).val();
$("#edit-avatar-preview").attr("src", e);
}));
const a = $("#edit-actress-allname");
a.on("input", (function() {
n($(this));
})), n(a);
const i = $("#edit-actress-newvideolist");
i.on("input", (function() {
n($(this));
})), n(i), $("#search-avatar-btn").on("click", (async () => {
await this.searchAvatar();
})), $("#select-cdn-btn").on("click", (async () => {
await async function() {
const e = at, t = tt.map(((t, n) => `\n <div style="margin-bottom: 10px;">\n <input type="radio" id="cdn-${n}" name="cdn-source" value="${n}" ${n === e ? "checked" : ""} style="margin-right: 10px;">\n <label for="cdn-${n}">${t.name} ${t.json.includes("jsdelivr") ? "(推荐)" : ""}</label>\n </div>\n `)).join(""), n = `\n <div style="padding: 20px;">\n <p style="margin-bottom: 15px; font-weight: bold; color: #333;">请选择头像数据源 (当前: ${tt[e].name}):</p>\n ${t}\n <p style="margin-top: 20px; color: #555; font-size: 12px;">切换源会清除本地缓存的数据,并在下次搜索时重新加载。</p>\n </div>\n `;
layer.open({
type: 1,
title: "选择 CDN 源",
area: [ "400px", "auto" ],
content: n,
btn: [ "确定", "取消" ],
success: (e, t) => {
utils.setupEscClose(t);
},
yes: async e => {
const t = $('input[name="cdn-source"]:checked').val(), n = parseInt(t, 10);
if (n !== at) {
at = n, localStorage.setItem(nt, n.toString()), it = tt[n].json, st = tt[n].base,
ct = null, dt = null;
try {
await lt.set(rt, null);
} catch (a) {
clog.error("清除 IndexedDB 缓存失败:", a);
}
show.ok(`CDN 源已切换为: ${tt[n].name}`), layer.close(e);
} else layer.close(e);
}
});
}();
})), utils.setupEscClose(t);
},
yes: async t => {
const n = $("#edit-actress-avatar").val().trim(), a = $("#edit-actress-name").val().trim(), i = $("#edit-actress-allname").val().trim(), s = $("#edit-actress-newvideolist").val().trim(), o = $("#actressType").val();
if (!a) return show.error("主名称不能为空"), !1;
const r = i.split(/[\uff0c,]/).map((e => e.trim())).filter((e => e.length > 0)), l = s.split(/[\uff0c,]/).map((e => e.trim())).filter((e => e.length > 0));
e.avatar = n, e.name = a, e.allName = r, e.newVideoList = l, e.actressType = o;
await storageManager.updateFavoriteActress(e) ? show.error("修改失败") : (this.renderActressCards().then(),
show.ok(`女优 ${a} 信息已更新`), layer.close(t));
}
});
}
renderPagination(e, t) {
const n = this.currentPage;
let a = "";
const i = $("#actress-pagination");
if (0 === t) return a = '<span style="color: #666;">共 0 条记录</span>', void i.html(a);
n > 1 && t > 5 && (a += '<button class="pagination-btn" data-page="1" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">首页</button>'),
n > 1 && (a += `<button class="pagination-btn" data-page="${n - 1}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">上一页</button>`);
let s = Math.max(1, n - Math.floor(2.5)), o = Math.min(t, s + 5 - 1);
o - s < 4 && (s = Math.max(1, o - 5 + 1));
for (let r = s; r <= o; r++) {
a += `<button class="pagination-btn page-number-btn ${r === n ? "active" : ""}" data-page="${r}" style="padding: 8px 12px; margin: 0 3px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; ${r === n ? "background: #007bff; color: white; border-color: #007bff;" : ""}">${r}</button>`;
}
n < t && (a += `<button class="pagination-btn" data-page="${n + 1}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">下一页</button>`),
n < t && t > 5 && (a += `<button class="pagination-btn" data-page="${t}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">尾页</button>`),
a += `<span style="margin-left: 20px; color: #666;">共 ${e} 条记录 (第 ${n}/${t} 页)</span>`,
i.html(a), $(".pagination-btn").off("click").on("click", (e => {
if ($(e.currentTarget).is("[disabled]")) return;
const n = parseInt($(e.currentTarget).data("page"));
n >= 1 && n <= t && n !== this.currentPage && (this.currentPage = n, this.renderActressCards());
}));
}
async searchAvatar() {
const e = $("#edit-actress-name"), t = $("#edit-actress-allname"), n = e.val().trim(), a = t.val().trim().split(/[\uff0c,]/).map((e => e.trim())).filter((e => e.length > 0));
if (n && a.unshift(n), 0 === a.length) return void show.error("请先填写女优主名称或别名进行搜索。");
const i = loading("正在搜索头像...");
let s = [];
try {
s = await gt(a);
} catch (c) {
return void show.error(`头像数据加载或搜索失败: ${c.message || c}`);
} finally {
i.close();
}
if (0 === s.length) return void show.error(`未找到与 '${a.join("、")}' 相关的头像。请检查名称。`);
const o = s.map(((e, t) => `\n <div id="wrapper-${t}" class="gfriends-image-item-wrapper">\n <img alt="" src="${e}" data-url="${e}" class="gfriends-selectable-img" data-wrapper-id="wrapper-${t}" >\n <div class="gfriends-size-tag" data-size-for="wrapper-${t}">...</div> \n </div>\n `)).join(""), r = `\n <style>\n /* 保持上一个回答的美化样式 */\n #gfriends-image-list-container { padding: 15px; height: 100%; box-sizing: border-box; background-color: #f8f9fa; }\n #gfriends-prompt { color: #555; font-weight: 500; border-bottom: 1px solid #eee; padding-bottom: 10px; }\n #gfriends-image-list { display: flex; flex-wrap: wrap; gap: 15px; justify-content: center; }\n .gfriends-image-item-wrapper {\n width: 160px; height: 225px; /* 增加高度以容纳尺寸标签 */\n overflow: hidden; border-radius: 6px;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease, box-shadow 0.2s ease;\n cursor: pointer; position: relative; \n padding-bottom: 25px; /* 为尺寸标签留出空间 */\n }\n .gfriends-selectable-img {\n width: 100%; height: 200px; /* 固定图片高度 */\n object-fit: cover; border: 3px solid transparent; \n border-radius: 6px; transition: border 0.2s ease;\n }\n .gfriends-image-item-wrapper:hover {\n transform: translateY(-4px) scale(1.02);\n box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);\n }\n .gfriends-selectable-img.is-selected {\n border-color: #ff6347;\n box-shadow: 0 0 0 3px #ff6347;\n }\n /* 新增:尺寸标签样式 */\n .gfriends-size-tag {\n position: absolute;\n bottom: 0; /* 定位到图片容器底部 */\n left: 0;\n right: 0;\n height: 25px;\n line-height: 25px;\n text-align: center;\n background-color: rgba(0, 0, 0, 0.7); /* 半透明背景 */\n color: #fff;\n font-size: 11px;\n font-weight: bold;\n border-bottom-left-radius: 6px;\n border-bottom-right-radius: 6px;\n user-select: none;\n }\n </style>\n \n <div id="gfriends-image-list-container">\n <p id="gfriends-prompt" style="text-align: center; font-size: 15px; margin-bottom: 15px;">\n 点击图片即可选择(初始共 ${s.length} 张)\n </p>\n <div style="overflow-y: auto; height: calc(100% - 40px);">\n <div id="gfriends-image-list">\n ${o}\n </div>\n </div>\n </div>\n `;
let l = 0;
layer.open({
type: 1,
title: `选择女优头像 (${s.length} 张)`,
area: utils.getResponsiveArea([ "900px", "85%" ]),
content: r,
btn: [ "关闭" ],
success: (e, t) => {
const n = $(e), a = n.find(".gfriends-selectable-img"), i = n.find("#gfriends-prompt");
a.each((function() {
const e = $(this), a = e.data("wrapper-id"), o = n.find(`#${a}`), r = n.find(`.gfriends-size-tag[data-size-for="${a}"]`);
e.on("load", (function() {
const e = this.naturalWidth, t = this.naturalHeight;
r.text(`${e} x ${t}`);
})), e.on("error", (function() {
o.remove(), l++;
const e = s.length - l;
i.text(`点击图片即可选择(已移除 ${l} 张错误图片,剩余 ${e} 张)`), 0 === e && (show.error("所有搜索到的头像链接均已失效,无法选择。"),
layer.close(t));
})), this.complete && (this.naturalWidth > 0 ? e.trigger("load") : e.trigger("error"));
})), a.on("click", (function() {
const e = $(this), n = e.data("url");
$("#edit-actress-avatar").val(n), $("#edit-avatar-preview").attr("src", n), a.removeClass("is-selected"),
e.addClass("is-selected"), setTimeout((() => {
layer.close(t);
}), 150);
})), utils.setupEscClose(t);
}
});
}
}
class ut extends R {
getName() {
return "LocalPlugin";
}
async handle() {
if (r && !window.location.href.includes("/actors/")) {
this.baseUrl = "http://127.0.0.1:7890", this.canRun = !1;
try {
200 === await gmHttp.checkUrlStatus(this.baseUrl + "/ping", null, 1e3) && (this.canRun = !0);
} catch (e) {}
this.canRun && isListPage && utils.loopDetector((() => $("#waitCheckBtn").length), (() => {
this.createBtn();
}), 1, 1e4, !1);
}
}
createBtn() {
$("#waitDownBtn").after('\n <a id="archiveBtn" class="menu-btn main-tab-btn" style="background-color:#39babe !important;margin-left: 20px!important;"><span>视频归档</span></a>\n <a id="checkSubtitleBtn" class="menu-btn main-tab-btn" style="background-color:#d08736 !important;"><span>检查字幕</span></a>\n '),
$("#archiveBtn").on("click", (e => {
this.archiveFile().then();
})), $("#checkSubtitleBtn").on("click", (e => {
this.checkSubTitle().then();
}));
}
async archiveFile() {
let e = await storageManager.getCarList();
const t = await http.post(this.baseUrl + "/archiveFile", {
carList: e
});
let n = t.dataList, a = t.updateHasDownCarNumList;
if (a && a.length) {
const t = new Set(a), n = Array.from(t);
for (const a of n) {
const t = e.find((e => e.carNum === a));
t && (await storageManager.saveCar({
carNum: a,
url: t.url,
actionType: g
}), show.ok(`归档成功, ${a}标记为已下载`));
}
}
n.length > 0 ? layer.open({
type: 1,
title: "归档信息",
shadeClose: !0,
scrollbar: !1,
content: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="archive-container" style="height: 100%;"></div>\n </div>\n ',
anim: -1,
area: [ "50%", "70%" ],
success: e => {
new Tabulator("#archive-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: n,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
columns: [ {
title: "信息",
field: "msg",
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData();
return "ok" === a.type ? `<span style="color:#58ad67">${a.msg}</span>` : `<span style="color:#c52323">${a.msg}</span>`;
}
}, {
title: "操作",
headerSort: !1,
width: 200,
formatter: (e, t, n) => {
const a = e.getData();
return n((() => {
var t;
null == (t = e.getElement().querySelector(".a-primary")) || t.addEventListener("click", (e => {
http.get(this.baseUrl + "/openFilePath", {
filePath: a.file
});
}));
})), '<a class="a-primary">打开路径</a>';
}
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
});
},
end() {
window.refresh();
}
}) : show.info("没有可归档文件");
}
async checkSubTitle() {
let e = await storageManager.getCarList();
let t = (await gmHttp.post(this.baseUrl + "/checkSubTitle", {
dataList: e
})).data;
0 !== t.length ? layer.open({
type: 1,
title: "检测缺失字幕",
shadeClose: !0,
scrollbar: !1,
content: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="checkSubTitle-table-container" style="height: 100%;padding-bottom: 10px"></div>\n </div>\n ',
anim: -1,
area: [ "70%", "70%" ],
success: e => {
new Tabulator("#checkSubTitle-table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: t,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
columns: [ {
title: "番号",
width: 150,
field: "carNum",
headerSort: !1,
formatter: (e, t, n) => {
const a = e.getData(), i = a.type;
return a.msg, "error" === i ? `<span style="color: #f40">${a.msg}</span>` : a.carNum;
}
}, {
title: "文件路径",
field: "filePath",
headerSort: !1,
formatter: (e, t, n) => e.getData().filePath
}, {
title: "操作",
headerSort: !1,
responsive: 0,
formatter: (e, t, n) => {
const a = e.getData();
return n((() => {
var t, n, i, s, o;
null == (t = e.getElement().querySelector(".a-success")) || t.addEventListener("click", (e => {
gmHttp.get(this.baseUrl + "/openFilePath", {
filePath: a.filePath
});
})), null == (n = e.getElement().querySelector(".a-info")) || n.addEventListener("click", (e => {
let t = a.carNum, n = a.url;
if (n) if (t.includes("FC2-")) {
let e = this.parseMovieId(n);
this.getBean("Fc2Plugin").openFc2Dialog(e, t, n);
} else utils.openPage(n, t, !0, e); else show.error("没有找到url");
})), null == (i = e.getElement().querySelector(".a-warning")) || i.addEventListener("click", (e => {
this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(a.carNum);
})), null == (s = e.getElement().querySelector(".a-primary")) || s.addEventListener("click", (e => {
utils.openPage("" + ("https://subtitlecat.com/index.php?search=" + a.carNum.replace("FC2-", "")), a.carNum.replace("FC2-", ""), !0, e);
})), null == (o = e.getElement().querySelector(".a-danger")) || o.addEventListener("click", (e => {
const t = a.filePath.split("<br/>").filter((e => "" !== e.trim()));
utils.q(e, `是否调用AI程序生成字幕,共${t.length}个视频文件`, (() => {
this.aiSubtitle(t);
}));
}));
})), '\n <a class="a-success">打开路径</a>\n <a class="a-info">详情页</a>\n <a class="a-warning">迅雷字幕</a>\n <a class="a-primary">SubTitleCat字幕</a>\n <a class="a-danger">AI字幕</a>\n ';
}
} ],
locale: "zh-cn",
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
});
},
end() {
window.refresh();
}
}) : show.info("视频字幕完整");
}
async aiSubtitle(e) {
const t = await gmHttp.post(this.baseUrl + "/aiSubtitle", {
fileList: e
});
200 === t.code ? show.info("已调用后台程序, 请自行确认") : show.error(t.msg);
}
checkHasDown() {
this.allowRepeatDown = !1;
$("#enable-magnets-filter").after('<a id="allowRepeatDown" class="menu-btn" style="background-color:#b8d747;margin-left: 5px"><span>关闭重复下载检验</span></a>'),
$("#allowRepeatDown").on("click", (e => {
this.allowRepeatDown = !this.allowRepeatDown, $("#allowRepeatDown span").text(this.allowRepeatDown ? "开启重复下载检验" : "关闭重复下载检验");
}));
let e = $('a[title="複製番號"]').attr("data-clipboard-text"), t = !1;
$("#magnets-content .item a").on("click", (n => {
let a = $(n.target).closest("a, button")[0] || n.target;
if (t) t = !1; else {
if (n.preventDefault(), this.allowRepeatDown) return t = !0, void a.click();
http.get(baseUrl + "/checkHasDown?carNum=" + e).then((e => {
"no" === e.data ? (t = !0, a.click()) : show.info(e.msg, {
icon: 2
});
}));
}
}));
}
}
const mt = layer.close;
layer.close = function(e) {
const t = mt.call(this, e);
return function(e = 10) {
setTimeout((() => {
const e = document.querySelectorAll(".layui-layer-shade").length;
document.documentElement.style.overflow = e > 0 ? "hidden" : "";
}), e);
}(), t;
};
const ft = layer.open;
layer.open = function(e) {
const t = (e = e || {}).success;
return e.success = function(e, n) {
"function" == typeof t && t.call(this, e, n), utils.setupEscClose(n);
}, ft.call(this, e);
}, 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"),
utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/viewer.min.css"),
utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/css/tabulator_semanticui.min.css");
const vt = function() {
const e = new V;
let t = window.location.hostname;
return r && (e.register(Te), e.register(Ie), e.register(le), e.register(de), e.register(Se),
e.register(ye), e.register(De), e.register(me), e.register(pe), e.register(ue),
e.register(je), e.register(ze), e.register(Ue), e.register(Ye), e.register(K), e.register(xe),
e.register(Fe), e.register(we), e.register(ce), e.register(Y), e.register($e), e.register(he),
e.register(ve), e.register(qe), e.register(Ze), e.register(He), e.register(Ve),
e.register(Re), e.register(ke), e.register(Xe), e.register(pt), e.register(et),
e.register(ut)), l && (e.register(Te), e.register(Se), e.register(De), e.register(ye),
e.register(Ie), e.register(je), e.register(Ee), e.register(ze), e.register(Ye),
e.register(Qe), e.register(be), e.register(we), e.register(xe), e.register($e),
e.register(ce), e.register(Ne), e.register(Ve), e.register(Re), e.register(ve),
e.register(qe), e.register(Ze), e.register(ke), e.register(et)), t.includes("javtrailers") && e.register(Z),
t.includes("subtitlecat") && e.register(ee), (t.includes("aliyundrive") || t.includes("alipan")) && e.register(ge),
t.includes("5masterzzz") && e.register(Oe), t.includes("115.com") && e.register(Je),
e;
}();
vt.processCss().then(), async function() {
window.isDetailPage = function() {
let e = window.location.href;
return r ? e.split("?")[0].includes("/v/") : !!l && $("#magnet-table").length > 0;
}(), window.isListPage = function() {
let e = window.location.href;
return r ? $(".movie-list").length > 0 || e.includes("advanced_search") : !!l && $(".masonry > div .item").length > 0;
}(), window.isFc2Page = function() {
let e = window.location.href;
return e.includes("advanced_search?type=3") || e.includes("advanced_search?type=100");
}(), await storageManager.merge_table_name(), await storageManager.clean_no_url_blacklist(),
await storageManager.async_merge_other(), await storageManager.merge_blacklist(),
await storageManager.merge_favoriteActress(), await storageManager.merge_tow_car_list_table(),
r && /(^|;)\s*locale\s*=\s*en\s*($|;)/i.test(document.cookie) && show.error("请切换到中文语言下才可正常使用本脚本", {
duration: -1
}), vt.processPlugins().then();
}();