// ==UserScript==
// @name JAV-JHS
// @namespace JAV-JHS
// @version 3.3.6-fix
// @author xie bro
// @description Jav-鉴黄师 收藏、屏蔽、标记已下载; 屏蔽标签、屏蔽演员、同步收藏演员、新作品检测; 免VIP查看热播、Top250排行榜、Fc2ppv、可查看所有评论信息、相关清单; 支持云盘备份; 以图识图; 字幕搜索; JavDb|JavBus
// @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://javtrailers.com/*
// @include https://subtitlecat.com/*
// @include https://www.aliyundrive.com/*
// @include https://www.alipan.com/*
// @include https://115.com/*
// @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://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/js/tabulator.min.js
// @require https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/dist/layer.min.js
// @require https://cdn.jsdelivr.net/npm/blueimp-md5@2.19.0/js/md5.min.js
// @require https://cdn.jsdelivr.net/npm/toastify-js@1.12.0/src/toastify.min.js
// @require https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js
// @require https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.js
// @require https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/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 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 jianguoyun.com
// @connect 127.0.0.1
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==
var _a, _StorageManager_instances, setItem_fn, saveFilterItem_fn, __defProp = Object.defineProperty, __typeError = msg => {
throw TypeError(msg);
}, __publicField = (obj, key, value) => ((obj, key, value) => key in obj ? __defProp(obj, key, {
enumerable: !0,
configurable: !0,
writable: !0,
value: value
}) : obj[key] = value)(obj, "symbol" != typeof key ? key + "" : key, value), __privateMethod = (obj, member, method) => (((obj, member, msg) => {
member.has(obj) || __typeError("Cannot " + msg);
})(obj, member, "access private method"), method);
const currentHref = window.location.href, isJavDb = currentHref.includes("javdb"), isJavBus = currentHref.includes("javbus") || currentHref.includes("seejav") || currentHref.includes("bus") || currentHref.includes("javsee") || "javbus" === (null == (_a = $(".hidden-xs").attr("alt")) ? void 0 : _a.trim().toLowerCase()), isSearchPage = currentHref.includes("/search?q") || currentHref.includes("/search/") || currentHref.includes("/users/"), Status_FILTER = "filter", Status_FAVORITE = "favorite", Status_HAS_DOWN = "hasDown", Status_HAS_WATCH = "hasWatch", NO = "no", YES = "yes", qualityOptions = [ {
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 detailPageCss$1 = "";
window.location.href.includes("hideNav=1") && (detailPageCss$1 = "\n .navbar-default {\n display: none !important;\n }\n body {\n padding-top:0px!important;\n }\n ");
const javBusStyle = `\n\n`;
let detailPageCss = "";
window.location.href.includes("hideNav=1") && (detailPageCss = "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n ");
const javdbStyle = `\n\n`;
const mainCss = `\n\n`;
function insertStyle(css) {
if (css) if (css.includes("\n");
insertStyle(mainCss);
_StorageManager_instances = new WeakSet;
setItem_fn = async function(key, data) {
key === this.favorite_actresses_key && window.clean_cacheFavoriteActressList();
key === this.blacklist_car_list_key && window.clean_cacheBlacklistCarList();
key === this.setting_key && window.clean_cacheSettingObj();
key === this.car_list_key && window.clean_cacheCarList();
await this.forage.setItem(key, data);
};
saveFilterItem_fn = async function(items, storageKey, itemName) {
let itemList;
if (Array.isArray(items)) itemList = [ ...items ]; else {
itemList = await this.forage.getItem(storageKey) || [];
if (itemList.includes(items)) {
const errorMsg = `${items} ${itemName}已存在`;
show.error(errorMsg);
throw new Error(errorMsg);
}
itemList.push(items);
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, storageKey, itemList);
return itemList;
};
let StorageManager = class _StorageManager {
constructor() {
obj = this, (member = _StorageManager_instances).has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var obj, member, value;
__publicField(this, "car_list_key", "car_list");
__publicField(this, "filter_keyword_title_key", "filter_keyword_title");
__publicField(this, "filter_keyword_review_key", "filter_keyword_review");
__publicField(this, "setting_key", "setting");
__publicField(this, "blacklist_key", "blacklist");
__publicField(this, "blacklist_car_list_key", "blacklist_car_list");
__publicField(this, "favorite_actresses_key", "favorite_actresses");
__publicField(this, "highlighted_tags_key", "highlighted_tags");
__publicField(this, "forage", localforage.createInstance({
driver: localforage.INDEXEDDB,
name: "JAV-JHS",
version: 1,
storeName: "appData"
}));
__publicField(this, "cacheCarList", null);
__publicField(this, "cacheBlacklistCarList", null);
__publicField(this, "cacheFavoriteActressList", null);
__publicField(this, "cacheSettingObj", null);
if (_StorageManager.instance) throw new Error("StorageManager已被实例化过了!");
_StorageManager.instance = this;
}
async getCarList() {
if (this.cacheCarList) return utils.copyObj(this.cacheCarList);
this.cacheCarList = await this.forage.getItem(this.car_list_key) || [];
return utils.copyObj(this.cacheCarList);
}
async getCar(carNum2) {
return (await this.getCarList()).find((item => item.carNum === carNum2));
}
_handleSingleCar(carParam, carList) {
let {carNum: carNum2, url: url, names: names, actionType: actionType, publishTime: publishTime, starId: starId} = carParam;
if (!carNum2) {
show.error("番号为空!");
throw new Error("番号为空!");
}
if (!url) {
show.error("url为空!");
throw new Error("url为空!");
}
url.includes("http") || (url = window.location.origin + url);
names && (names = names.trim());
let carData = carList.find((item => item.carNum === carNum2));
if (carData) {
names && (carData.names = names);
url && (carData.url = url);
publishTime && (carData.publishTime = publishTime);
carData.updateDate = utils.getNowStr();
} else {
let nowStr = utils.getNowStr();
carData = {
carNum: carNum2,
url: url,
names: names,
status: "",
createDate: nowStr,
updateDate: nowStr,
publishTime: publishTime
};
starId && (carData.starId = starId);
carList.push(carData);
}
switch (actionType) {
case Status_FILTER:
if (carData.status === Status_FILTER) {
const msg2 = `${carNum2} 已在屏蔽列表中`;
show.error(msg2);
throw new Error(msg2);
}
carData.status = Status_FILTER;
break;
case Status_FAVORITE:
if (carData.status === Status_FAVORITE) {
const msg2 = `${carNum2} 已在收藏列表中`;
show.error(msg2);
throw new Error(msg2);
}
carData.status = Status_FAVORITE;
break;
case Status_HAS_DOWN:
carData.status = Status_HAS_DOWN;
break;
case Status_HAS_WATCH:
carData.status = Status_HAS_WATCH;
break;
default:
const msg = "actionType错误, 请联系作者更正: " + actionType;
show.error(msg);
throw new Error(msg);
}
}
async saveCar(carData) {
const carList = await this.forage.getItem(this.car_list_key) || [];
this._handleSingleCar(carData, carList);
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList);
await this.removeNewVideoList([ carData.carNum ]);
}
async updateCarInfo(carParam) {
let {carNum: carNum2, url: url, names: names, actionType: actionType, publishTime: publishTime, remark: remark} = carParam;
if (!carNum2) {
show.error("番号为空!");
throw new Error("番号为空!");
}
if (!url) {
show.error("url为空!");
throw new Error("url为空!");
}
names && (names = names.trim());
const carList = await this.forage.getItem(this.car_list_key) || [];
let carData = carList.find((item => item.carNum === carNum2));
if (!carData) {
const msg = "数据不存在: " + carNum2;
show.error(msg);
throw new Error(msg);
}
carData.names = names;
carData.url = url;
carData.remark = remark;
carData.updateDate = utils.getNowStr();
switch (actionType) {
case Status_FILTER:
carData.status = Status_FILTER;
break;
case Status_FAVORITE:
carData.status = Status_FAVORITE;
break;
case Status_HAS_DOWN:
carData.status = Status_HAS_DOWN;
break;
case Status_HAS_WATCH:
carData.status = Status_HAS_WATCH;
break;
default:
const msg = "actionType错误, 请联系作者更正: " + actionType;
show.error(msg);
throw new Error(msg);
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList);
}
async saveCarList(carRecords) {
if (!carRecords || !Array.isArray(carRecords) || 0 === carRecords.length) {
show.error("记录列表为空!");
throw new Error("记录列表为空!");
}
const carList = await this.forage.getItem(this.car_list_key) || [];
for (const record of carRecords) try {
this._handleSingleCar(record, carList);
} catch (error) {
throw error;
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList);
}
async removeNewVideoList(carNumList) {
const favoriteActressesList = await this.getFavoriteActressList();
let hasAnyListBeenModified = !1;
const updatedActressList = favoriteActressesList.map((actress => {
if (!actress.newVideoList || !Array.isArray(actress.newVideoList)) return actress;
const newFilteredVideoList = actress.newVideoList.filter((carNumString => {
const isRemoved = carNumList.includes(carNumString);
if (isRemoved) {
clog.log("移除关联女优新作品", actress.name, carNumString);
hasAnyListBeenModified = !0;
}
return !isRemoved;
}));
return {
...actress,
newVideoList: newFilteredVideoList
};
}));
hasAnyListBeenModified && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedActressList);
}
async removeCar(carNum2) {
const carList = await this.getCarList(), initialLength = carList.length, updatedList = carList.filter((car => car.carNum !== carNum2));
if (updatedList.length === initialLength) {
show.error(`${carNum2} 不存在`);
return !1;
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, updatedList);
return !0;
}
async batchRemoveCars(carNumList) {
if (!carNumList || 0 === carNumList.length) throw new Error("未传入参数");
const carList = await this.getCarList(), initialLength = carList.length, carNumsSet = new Set(carNumList), updatedList = carList.filter((car => !carNumsSet.has(car.carNum))), removedCount = initialLength - updatedList.length;
if (0 === removedCount) return removedCount;
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, updatedList);
return removedCount;
}
async getBlacklist() {
return await this.forage.getItem(this.blacklist_key) || [];
}
async addBlacklistItem(item) {
let {starId: starId, name: name2, allName: allName, role: role, movieType: movieType, url: url} = item;
if (!starId) throw new Error("缺失starId");
if (!name2) throw new Error("缺失name");
if (!role) throw new Error("缺失role");
const blacklist = await this.getBlacklist(), existData = blacklist.find((item2 => item2.starId === starId));
if (existData) {
existData.url = url;
existData.role = role;
existData.movieType = movieType;
clog.log("更新黑名单演员信息", existData);
} else {
const info = {
starId: starId,
name: name2,
allName: allName || [ name2 ],
createTime: utils.getNowStr(),
role: role,
movieType: movieType,
url: url
};
blacklist.push(info);
clog.log("增加黑名单演员信息", info);
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, blacklist);
}
async updateBlacklistItem(item) {
if (!item || !item.starId) throw new Error("参数不全");
const filterActorActressInfoList = await this.getBlacklist(), waitUpdateObj = filterActorActressInfoList.find((i => i.starId === item.starId));
if (!waitUpdateObj) throw new Error(`未找到黑名单演员信息:${item.name} ${item.starId}`);
item.checkTime && (waitUpdateObj.checkTime = item.checkTime);
item.lastPublishTime && (waitUpdateObj.lastPublishTime = item.lastPublishTime);
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, filterActorActressInfoList);
}
async deleteBlacklistItem(starId) {
const blacklist = await this.getBlacklist(), updatedList = blacklist.filter((item => item.starId !== starId));
blacklist.length !== updatedList.length && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, updatedList);
}
async getBlacklistCarList() {
if (this.cacheBlacklistCarList && this.cacheBlacklistCarList.length > 0) return utils.deepFreeze(this.cacheBlacklistCarList);
this.cacheBlacklistCarList = await this.forage.getItem(this.blacklist_car_list_key) || [];
setTimeout((() => {
utils.deepFreeze(this.cacheBlacklistCarList);
}), 0);
return this.cacheBlacklistCarList;
}
async batchSaveBlacklistCarList(carDataList) {
const carList = await this.getCarList();
let blacklistCarList = await this.getBlacklistCarList(), hasChanged = !1, copyBlacklistCarList = null, carNumList = [];
for (const carData of carDataList) {
let existBlacklistCarData = blacklistCarList.find((item => item.carNum === carData.carNum));
if (existBlacklistCarData) {
console.log("已在鉴定记录中", existBlacklistCarData);
continue;
}
let existCarData = carList.find((item => item.carNum === carData.carNum));
if (existCarData) console.log("已在鉴定记录中", existCarData); else {
copyBlacklistCarList || (copyBlacklistCarList = utils.copyObj(blacklistCarList));
this._handleSingleCar(carData, copyBlacklistCarList);
clog.log(`屏蔽演员番号: ${carData.names} ${carData.carNum}`);
hasChanged = !0;
carNumList.push(carData.carNum);
}
}
if (hasChanged) {
if (!copyBlacklistCarList) throw new Error("程序异常, 黑名单番号数据对象为空!");
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, copyBlacklistCarList);
await this.removeNewVideoList(carNumList);
}
}
async removeBlacklistCarList(starId) {
const filterActorActressCarList = await this.getBlacklistCarList(), newList = filterActorActressCarList.filter((car => car.starId !== starId));
newList.length !== filterActorActressCarList.length && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, newList);
}
async batchRemoveBlacklistCars(carNumList) {
if (!carNumList || 0 === carNumList.length) throw new Error("未传入参数");
const blacklistCarList = await this.getBlacklistCarList(), initialLength = blacklistCarList.length, carNumsSet = new Set(carNumList), updatedList = blacklistCarList.filter((car => !carNumsSet.has(car.carNum))), removedCount = initialLength - updatedList.length;
if (0 === removedCount) return removedCount;
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, updatedList);
return removedCount;
}
async getFavoriteActressList() {
if (this.cacheFavoriteActressList) return utils.copyObj(this.cacheFavoriteActressList);
this.cacheFavoriteActressList = await this.forage.getItem(this.favorite_actresses_key) || [];
return utils.copyObj(this.cacheFavoriteActressList);
}
async addFavoriteActressList(actressList) {
const favoriteActressesInfoList = await this.getFavoriteActressList();
let resultCount = 0;
for (const actress of actressList) {
let {starId: starId, name: name2, allName: allName, avatar: avatar, lastCheckTime: lastCheckTime, lastPublishTime: lastPublishTime, actressType: actressType} = actress;
if (!starId) throw new Error("缺失starId");
if (!name2) throw new Error("缺失name");
allName || (allName = [ name2 ]);
const uncensoredText = "(無碼)";
if (!actressType) {
actressType = name2.includes(uncensoredText) || allName.some((element => element.includes(uncensoredText))) ? "uncensored" : "censored";
}
name2 = name2.replace(uncensoredText, "");
allName = allName.map((n => n.replace(uncensoredText, "")));
let favoriteActresses = favoriteActressesInfoList.find((item => item.starId === starId));
if (favoriteActresses) {
if ((!favoriteActresses.avatar || !favoriteActresses.avatar.includes("https")) && avatar) {
clog.log(avatar);
favoriteActresses.avatar = avatar;
clog.log(`补全女优头像: ${name2}`);
resultCount++;
}
if (!favoriteActresses.actressType && actressType) {
favoriteActresses.actressType = actressType;
clog.log(`补全女优类别: ${name2} ${actressType}`);
resultCount++;
}
if (favoriteActresses.name.includes(uncensoredText)) {
favoriteActresses.name = name2;
favoriteActresses.allName = allName;
clog.log(`更正女优名字: ${name2} ${allName}`);
resultCount++;
}
continue;
}
const nowStr = utils.getNowStr();
favoriteActressesInfoList.push({
starId: starId,
name: name2,
allName: allName,
avatar: avatar,
lastCheckTime: lastCheckTime,
lastPublishTime: lastPublishTime,
createDate: nowStr,
updateDate: nowStr,
actressType: actressType
});
clog.log(`同步JavDB已收藏的演员: ${name2}`);
resultCount++;
}
resultCount > 0 ? await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, favoriteActressesInfoList) : clog.log("信息已记录, 无需要进行同步收藏的演员");
return resultCount;
}
async removeFavoriteActress(starId) {
const favoriteActressesInfoList = await this.getFavoriteActressList(), initialLength = favoriteActressesInfoList.length, updatedList = favoriteActressesInfoList.filter((car => car.starId !== starId));
if (updatedList.length === initialLength) {
clog.error(`移除演员失败, ${starId} 不存在`);
return !1;
}
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedList);
return !0;
}
async updateFavoriteActress(actress) {
const favoriteActressesInfoList = await this.getFavoriteActressList(), {starId: starId, name: name2, allName: allName, avatar: avatar, lastCheckTime: lastCheckTime, newVideoList: newVideoList, lastPublishTime: lastPublishTime, actressType: actressType, remark: remark} = actress;
if (!starId) throw new Error("缺失starId");
let favoriteActresses = favoriteActressesInfoList.find((item => item.starId === starId));
if (!favoriteActresses) {
clog.error("未找到演员信息", starId, name2);
return !1;
}
name2 && (favoriteActresses.name = name2);
allName && (favoriteActresses.allName = allName);
avatar && (favoriteActresses.avatar = avatar);
null != actressType && (favoriteActresses.actressType = actressType);
lastCheckTime && (favoriteActresses.lastCheckTime = lastCheckTime);
newVideoList && (favoriteActresses.newVideoList = newVideoList);
lastPublishTime && (favoriteActresses.lastPublishTime = lastPublishTime);
remark && (favoriteActresses.remark = remark);
favoriteActresses.updateDate = utils.getNowStr();
await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, favoriteActressesInfoList);
}
async getHighlightedTags() {
return await this.forage.getItem(this.highlighted_tags_key) || [];
}
async setHighlightedTags(highlightedTags) {
return await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.highlighted_tags_key, highlightedTags);
}
async saveTitleFilterKeyword(keywords) {
await __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_keyword_title_key, "标题关键词");
if (Array.isArray(keywords)) return null;
const favoriteActressesList = await this.getFavoriteActressList();
let hasAnyListBeenModified = !1;
const updatedActressList = favoriteActressesList.map((actress => {
if (!actress.newVideoList || !Array.isArray(actress.newVideoList)) return actress;
const newFilteredVideoList = actress.newVideoList.filter((carNumString => {
const isRemoved = carNumString.startsWith(keywords);
if (isRemoved) {
clog.log("移除关联女优新作品", actress.name, carNumString);
hasAnyListBeenModified = !0;
}
return !isRemoved;
}));
return {
...actress,
newVideoList: newFilteredVideoList
};
}));
hasAnyListBeenModified && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedActressList);
}
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(keywords) {
return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_keyword_review_key, "评论关键词");
}
async getSetting(attribute = null, defaultVal) {
this.cacheSettingObj || (this.cacheSettingObj = await this.forage.getItem(this.setting_key) || {});
let settingObj = utils.copyObj(this.cacheSettingObj);
if (null === attribute) return settingObj;
const value = settingObj[attribute];
return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || "" === value.trim() || isNaN(Number(value)) ? value : Number(value) : defaultVal;
}
async saveSetting(settingObj) {
settingObj ? await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.setting_key, settingObj) : show.error("设置对象为空");
}
async saveSettingItem(key, value) {
if (!key) {
show.error("key 不能为空");
return;
}
let settingObj = await this.getSetting();
settingObj[key] = value;
await this.saveSetting(settingObj);
}
async importData(jsonData) {
await this.forage.clear();
const importPromises = [];
for (const key in jsonData) {
const value = jsonData[key], setPromise = __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, key, value);
importPromises.push(setPromise);
}
await Promise.all(importPromises);
}
async exportData() {
const dataMap = {};
await this.forage.iterate(((value, key) => {
dataMap[key] = value;
}));
if (0 === Object.keys(dataMap).length) throw new Error("没有可导出的数据");
return dataMap;
}
};
const apiUrl = "https://jdforrepam.com/api";
function buildSignature() {
const curr = Math.floor(Date.now() / 1e3), stored_sign = localStorage.getItem("jhs_jdsignature");
if (stored_sign) {
const parts = stored_sign.split(".");
if (3 === parts.length) {
if (curr - parseInt(parts[0]) <= 300) return stored_sign;
}
}
const sign = `${curr}.lpw6vgqzsp.${md5(`${curr}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`;
localStorage.setItem("jhs_jdsignature", sign);
return sign;
}
const javDbApi = {
getReviews: async (movieId, pageNum = 1, pageSize = 20) => {
let url = `${apiUrl}/v1/movies/${movieId}/reviews`, headers = {
jdSignature: await buildSignature()
};
return (await gmHttp.get(url, {
page: pageNum,
sort_by: "hotly",
limit: pageSize
}, headers)).data.reviews;
},
searchMovie: async keyword => {
let url = `${apiUrl}/v2/search`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
jdsignature: await buildSignature()
}, params = {
q: keyword,
page: 1,
type: "movie",
limit: 1,
movie_type: "all",
from_recent: "false",
movie_filter_by: "all",
movie_sort_by: "relevance"
};
return (await gmHttp.get(url, params, headers)).data.movies;
},
getMovieDetail: async movieId => {
let url = `${apiUrl}/v4/movies/${movieId}`, headers = {
jdSignature: await buildSignature()
};
const res = await gmHttp.get(url, null, headers);
if (!res.data) {
show.error("获取视频详情失败: " + res.message);
throw new Error(res.message);
}
const movie = res.data.movie, preview_images = movie.preview_images, imgList = [];
preview_images.forEach((item => {
imgList.push(item.large_url.replace("https://tp.spfcas.com/rhe951l4q", "https://c0.jdbstatic.com"));
}));
return {
movieId: movie.id,
actors: movie.actors,
duration: movie.duration,
title: movie.origin_title,
carNum: movie.number,
score: movie.score,
releaseDate: movie.release_date,
watchedCount: movie.watched_count,
imgList: imgList
};
},
related: async (movieId, page = 1, limit = 20) => {
let url = `${apiUrl}/v1/lists/related?movie_id=${movieId}&page=${page}&limit=${limit}`, headers = {
jdSignature: await buildSignature()
};
const res = await gmHttp.get(url, null, headers), dataList = [];
res.data.lists.forEach((item => {
dataList.push({
relatedId: item.id,
name: item.name,
movieCount: item.movies_count,
collectionCount: item.collections_count,
viewCount: item.views_count,
createTime: utils.formatDate(item.created_at)
});
}));
return dataList;
},
getMagnets: async movieId => {
let url = `${apiUrl}/v1/movies/${movieId}/magnets`, headers = {
jdSignature: await buildSignature()
};
return (await gmHttp.get(url, null, headers)).data.magnets;
},
playback: async (period = "daily", filter_by = "high_score") => {
let url = `${apiUrl}/v1/rankings/playback?period=${period}&filter_by=${filter_by}`, headers = {
jdSignature: await buildSignature()
};
return (await gmHttp.get(url, null, headers)).data.movies;
},
login: async (username, password) => {
let url = `${apiUrl}/v1/sessions?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
"content-type": "multipart/form-data; boundary=--dio-boundary-2210433284",
jdsignature: await buildSignature()
};
return await gmHttp.post(url, null, headers);
},
top250: async (type = "all", type_value = "", page = 1, limit = 40) => {
let url = `${apiUrl}/v1/movies/top?start_rank=1&type=${type}&type_value=${type_value}&ignore_watched=false&page=${page}&limit=${limit}`, headers = {
"user-agent": "Dart/3.5 (dart:io)",
"accept-language": "zh-TW",
host: "jdforrepam.com",
authorization: "Bearer " + localStorage.getItem("jhs_appAuthorization"),
jdsignature: await buildSignature()
};
return await gmHttp.get(url, null, headers);
},
buildSignature: buildSignature,
markDataListHtml: movies => {
let moviesHtml = "";
movies.forEach((movie => {
moviesHtml += `\n
\n `;
}));
return moviesHtml;
}
};
class Utils {
constructor() {
__publicField(this, "intervalContainer", {});
__publicField(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"
});
__publicField(this, "timers", new Map);
__publicField(this, "insertStyle", (css => {
if (css) {
-1 === css.indexOf("");
$("head").append(css);
}
}));
__publicField(this, "layerIndexStack", []);
Utils.instance || (Utils.instance = this);
return Utils.instance;
}
importResource(url) {
let tag;
if (url.indexOf("css") >= 0) {
tag = document.createElement("link");
tag.setAttribute("rel", "stylesheet");
tag.href = url;
} else {
tag = document.createElement("script");
tag.setAttribute("type", "text/javascript");
tag.src = url;
}
document.documentElement.appendChild(tag);
}
openPage(url, title, shadeClose, event) {
if (!url) throw new Error("未传入url");
shadeClose = shadeClose ?? !0;
if (event && (event.ctrlKey || event.metaKey)) {
GM_openInTab(url.includes("http") ? url : window.location.origin + url, {
insert: 0
});
return;
}
let finalUrl = url;
url.includes("/actors/") || url.includes("/star/") || (finalUrl = url.includes("?") ? `${url}&hideNav=1` : `${url}?hideNav=1`);
layer.open({
type: 2,
title: title,
content: finalUrl,
scrollbar: !1,
shadeClose: shadeClose,
area: this.getResponsiveArea([ "85%", "90%" ]),
isOutAnim: !1,
anim: -1,
success: (layero, index) => {
this.setupEscClose(index);
}
});
}
_handleGlobalEscKey(e) {
if ("Escape" !== e.key && 27 !== e.keyCode) return;
if (0 === this.layerIndexStack.length) return;
const topLayerIndex = this.layerIndexStack[this.layerIndexStack.length - 1], $layer = $(`#layui-layer${topLayerIndex}`);
let viewerExists = !1;
if ($layer.find(".viewer-container").length > 0) viewerExists = !0; else {
const iframe = $layer.find(`#layui-layer-iframe${topLayerIndex}`)[0];
if (iframe && iframe.contentDocument) try {
$(iframe.contentDocument).find(".viewer-container").length > 0 && (viewerExists = !0);
} catch (error) {
clog.warn("无法检查跨域 iframe 内的 .viewer-container");
}
}
if (!viewerExists) {
this.layerIndexStack.pop();
layer.close(topLayerIndex);
}
}
setupEscClose(layerIndex) {
var _a2;
if (!this._boundHandler) {
this._boundHandler = this._handleGlobalEscKey.bind(this);
$(document).off("keydown.globalLayerEsc");
$(document).on("keydown.globalLayerEsc", this._boundHandler);
}
-1 === this.layerIndexStack.indexOf(layerIndex) && this.layerIndexStack.push(layerIndex);
const $iframe = $(`#layui-layer-iframe${layerIndex}`), eventNamespace = `keydown.layerEsc${layerIndex}`;
try {
const iframeDocument = null == (_a2 = $iframe[0]) ? void 0 : _a2.contentDocument;
if (iframeDocument) {
if ("yes" === $iframe.attr("data-esc-bound")) return;
$(iframeDocument).off(eventNamespace);
$(iframeDocument).on(eventNamespace, this._boundHandler);
$iframe.attr("data-esc-bound", "yes");
}
} catch (e) {
clog.error("iframe监听失败 (跨域或未加载完毕):", e);
}
}
closePage() {
storageManager.getSetting("needClosePage", "yes").then((needClosePage => {
if ("yes" !== needClosePage) return;
parent.document.documentElement.style.overflow = "auto";
[ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
const elements = parent.document.querySelectorAll(selector);
if (elements.length > 0) {
const elementToRemove = elements.length > 1 ? elements[elements.length - 1] : elements[0];
elementToRemove.parentNode.removeChild(elementToRemove);
}
}));
window.close();
}));
}
loopDetector(condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = !0) {
const uuid = Math.random(), start = (new Date).getTime(), stopAndRun = shouldRun => {
clearInterval(this.intervalContainer[uuid]);
shouldRun && after && after();
delete this.intervalContainer[uuid];
};
this.intervalContainer[uuid] = setInterval((() => {
const timeElapsed = (new Date).getTime() - start;
condition() ? stopAndRun(!0) : timeElapsed >= timeout && stopAndRun(runWhenTimeout);
}), detectInterval);
}
rightClick(container, targetSelector, callback) {
let containerElement;
"string" == typeof container ? containerElement = document.querySelector(container) : container instanceof HTMLElement && (containerElement = container);
if (!containerElement) {
console.warn("rightClick(), 容器无效或未提供,将使用 document.body 进行全局委托。");
containerElement = document.body;
}
"string" == typeof targetSelector && "" !== targetSelector.trim() ? containerElement.addEventListener("contextmenu", (event => {
const targetElement = event.target.closest(targetSelector);
targetElement && callback(event, targetElement);
})) : console.error("rightClick(), 必须提供有效的 targetSelector。");
}
q(event, msg, fun, cancelFun) {
let x, y;
if (event) {
x = event.clientX - 130;
y = event.clientY - 120;
} else {
x = window.innerWidth / 2 - 120;
y = window.innerHeight / 2 - 120;
}
let confirmIndex = layer.confirm(msg, {
offset: [ y, x ],
title: "提示",
btn: [ "确定", "取消" ],
shade: 0,
zIndex: 999999991
}, (function() {
fun && fun();
layer.close(confirmIndex);
}), (function() {
cancelFun && cancelFun();
}));
}
alert(event, msg, yesFun) {
let offset;
event && (offset = [ event.clientX - 200, event.clientY - 120 ]);
let confirmIndex = layer.alert(msg, {
offset: offset,
shade: 0,
zIndex: 999999991
}, (function() {
yesFun && yesFun();
layer.close(confirmIndex);
}));
}
getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) {
let now;
now = dateString ? new Date(dateString) : new Date;
const year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, "0"), day = String(now.getDate()).padStart(2, "0"), hours = String(now.getHours()).padStart(2, "0"), minutes = String(now.getMinutes()).padStart(2, "0"), seconds = String(now.getSeconds()).padStart(2, "0");
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
}
formatDate(date, dateSplitStr = "-", timeSplitStr = ":") {
let targetDate;
if (date instanceof Date) targetDate = date; else {
if ("string" != typeof date) throw new Error("Invalid date input: must be Date object or date string");
targetDate = new Date(date);
if (isNaN(targetDate.getTime())) throw new Error("Invalid date string");
}
const year = targetDate.getFullYear(), month = String(targetDate.getMonth() + 1).padStart(2, "0"), day = String(targetDate.getDate()).padStart(2, "0"), hours = String(targetDate.getHours()).padStart(2, "0"), minutes = String(targetDate.getMinutes()).padStart(2, "0"), seconds = String(targetDate.getSeconds()).padStart(2, "0");
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
}
getHourDifference(date1, date2) {
const timestamp1 = date1.getTime(), timestamp2 = date2.getTime(), differenceInHours = Math.abs(timestamp2 - timestamp1) / 36e5;
return Math.floor(differenceInHours);
}
download(data, fileName) {
show.info("开始请求下载...");
const fileExtension = fileName.split(".").pop().toLowerCase();
let blob, mimeType = this.mimeTypes[fileExtension] || "application/octet-stream";
if (data instanceof Blob) blob = data; else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) blob = new Blob([ data ], {
type: mimeType
}); else if ("string" == typeof data && data.startsWith("data:")) {
const byteString = atob(data.split(",")[1]), arrayBuffer = new ArrayBuffer(byteString.length), uintArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) uintArray[i] = byteString.charCodeAt(i);
blob = new Blob([ uintArray ], {
type: mimeType
});
} else blob = new Blob([ data ], {
type: mimeType
});
const url = URL.createObjectURL(blob), a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout((() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}), 100);
}
smoothScrollToTop(duration = 500) {
return new Promise((resolve => {
const start = performance.now(), startPosition = window.pageYOffset;
window.requestAnimationFrame((function scrollStep(timestamp) {
const elapsed = timestamp - start, progress = Math.min(elapsed / duration, 1), easeInOutCubic = progress < .5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
window.scrollTo(0, startPosition * (1 - easeInOutCubic));
progress < 1 ? window.requestAnimationFrame(scrollStep) : resolve();
}));
}));
}
simpleId() {
return crypto.randomUUID().replace("-", "");
}
isUrl(urlString) {
try {
new URL(urlString);
return !0;
} catch (_) {
return !1;
}
}
setHrefParam(key, val) {
const newUrl = new URL(window.location.href);
newUrl.searchParams.set(key, val);
window.history.pushState({}, "", newUrl.toString());
}
getUrlParam(url, key) {
const searchString = url.split("?")[1];
if (!searchString) return null;
const regex = new RegExp(`(?:^|&)${key}=([^&]*)`), match = searchString.match(regex);
let value = "";
match && match[1] && (value = decodeURIComponent(match[1].replace(/\+/g, " ")));
return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || "" === value.trim() || isNaN(Number(value)) ? value : Number(value) : value;
}
reBuildSignature() {
return javDbApi.buildSignature();
}
getResponsiveArea(defaultArea) {
const screenWidth = window.innerWidth;
return screenWidth >= 1200 ? defaultArea || this.getDefaultArea() : screenWidth >= 768 ? [ "70%", "90%" ] : [ "95%", "95%" ];
}
getDefaultArea() {
return [ "85%", "90%" ];
}
isMobile() {
const userAgent = navigator.userAgent.toLowerCase();
return [ "iphone", "ipod", "ipad", "android", "blackberry", "windows phone", "nokia", "webos", "opera mini", "mobile", "mobi", "tablet" ].some((keyword => userAgent.includes(keyword)));
}
copyToClipboard(type, text) {
navigator.clipboard.writeText(text).then((() => show.info(`${type}已复制到剪切板, ${text}`))).catch((err => console.error("复制失败: ", err)));
}
htmlTo$dom(html) {
const parser = new DOMParser;
return $(parser.parseFromString(html, "text/html"));
}
addCookie(cookieStr, options = {}) {
const {maxAge: maxAge = 604800, path: path = "/", domain: domain = "", secure: secure = !1, sameSite: sameSite = "Lax"} = options;
cookieStr.split(";").forEach((cookie => {
const trimmed = cookie.trim();
if (trimmed) {
const parts = trimmed.split("=");
if (parts.length >= 2 && parts[0].trim()) {
let cookieParts = [ `${parts[0].trim()}=${parts.slice(1).join("=")}` ];
maxAge > 0 && cookieParts.push(`max-age=${maxAge}`);
cookieParts.push(`path=${path}`);
domain && cookieParts.push(`domain=${domain}`);
secure && cookieParts.push("Secure");
sameSite && cookieParts.push(`SameSite=${sameSite}`);
console.log("document.cookie = '" + cookieParts.join("; ") + "'");
document.cookie = cookieParts.join("; ");
}
}
}));
}
isHidden(el) {
const element = el.jquery ? el[0] : el;
return !element || (element.offsetWidth <= 0 && element.offsetHeight <= 0 || "none" === window.getComputedStyle(element).display);
}
time(label = "default", unit = "s", precision = 2) {
if (this.timers.has(label)) {
const timer = this.timers.get(label), elapsedTime = performance.now() - timer.startTime;
let formattedTime, unitLabel;
if ("s" === timer.unit) {
formattedTime = (elapsedTime / 1e3).toFixed(timer.precision);
unitLabel = "秒";
} else {
formattedTime = elapsedTime.toFixed(timer.precision);
unitLabel = "毫秒";
}
this.timers.delete(label);
return `${label}: ${formattedTime}${unitLabel}`;
}
this.timers.set(label, {
startTime: performance.now(),
unit: unit,
precision: precision
});
}
sleep(ms = 1e3) {
return new Promise((resolve => setTimeout(resolve, ms)));
}
genericSort(arr, sortRules, emptyLast = !0) {
if (!Array.isArray(arr) || 0 === arr.length) return [];
if (!Array.isArray(sortRules) || 0 === sortRules.length) return [ ...arr ];
const sortedArr = [ ...arr ], safeDateConvert = value => {
if (value instanceof Date) return value;
if ("string" == typeof value) {
const date = new Date(value);
if (!isNaN(date.getTime())) return date;
}
return value;
};
return sortedArr.sort(((a, b) => {
for (const rule of sortRules) {
const {key: key, order: order = "asc"} = rule;
let valA = a, valB = b;
if (null != key) if ("function" == typeof key) {
valA = key(a);
valB = key(b);
} else {
valA = a && "object" == typeof a ? a[key] : void 0;
valB = b && "object" == typeof b ? b[key] : void 0;
}
const dateA = safeDateConvert(valA), dateB = safeDateConvert(valB);
let comparison = 0;
const aIsNull = null == valA, bIsNull = null == valB;
if (aIsNull && bIsNull) return 0;
if (aIsNull) return emptyLast ? 1 : -1;
if (bIsNull) return emptyLast ? 1 : -1;
comparison = dateA instanceof Date && dateB instanceof Date ? dateA.getTime() - dateB.getTime() : "number" == typeof valA && "number" == typeof valB ? valA - valB : "string" == typeof valA && "string" == typeof valB ? valA.localeCompare(valB) : String(valA).localeCompare(String(valB));
"desc" === order && (comparison *= -1);
if (0 !== comparison) return comparison;
}
return 0;
}));
}
async retry(fun, tryCount = 3) {
let runCount = 0;
for (;runCount < tryCount; ) try {
const result = await fun();
runCount > 0 && clog.debug(`[重试] 成功,共发起 ${runCount + 1} 次。`);
return result;
} catch (e) {
let errorString = String(e);
errorString.startsWith("Error: ") && (errorString = errorString.replace("Error: ", ""));
if (errorString.includes("Just a moment") || errorString.includes("重定向") || errorString.toLowerCase().includes("404 page not found") || errorString.toLowerCase().includes("404 not found")) throw e;
runCount++;
if (runCount === tryCount) {
clog.debug(`[重试] 达到最大重试次数 (${tryCount}),最终失败:`, e);
throw e;
}
clog.debug(`[重试] 准备第 ${runCount + 1} 次重试, 错误信息: ${errorString}`);
}
}
copyObj(data) {
return JSON.parse(JSON.stringify(data));
}
deepFreeze(obj) {
if (null === obj || "object" != typeof obj || Object.isFrozen(obj)) return obj;
const propNames = Object.getOwnPropertyNames(obj);
for (const name2 of propNames) {
const value = obj[name2];
value && "object" == typeof value && this.deepFreeze(value);
}
return Object.freeze(obj);
}
}
unsafeWindow.utils = window.utils = new Utils;
unsafeWindow.gmHttp = window.gmHttp = new class {
async get(url, params = {}, headers = {}, noRedirect) {
return this.gmRequest("GET", url, null, params, headers, noRedirect);
}
post(url, data = {}, headers = {}) {
headers = {
"Content-Type": "application/json",
...headers
};
let jsonData = JSON.stringify(data);
return this.gmRequest("POST", url, jsonData, null, headers);
}
postForm(url, data = {}, headers = {}) {
headers || (headers = {});
headers["Content-Type"] || (headers["Content-Type"] = "application/x-www-form-urlencoded");
let body = "";
data && Object.keys(data).length > 0 && (body = Object.entries(data).map((([key, value]) => `${key}=${value}`)).join("&"));
return this.gmRequest("POST", url, body, null, headers);
}
postFileFormData(url, data = {}, headers = {}) {
headers || (headers = {});
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
headers["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
let body = "";
data && Object.keys(data).length > 0 && (body = Object.entries(data).map((([key, value]) => `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`)).join(""));
body += `--${boundary}--`;
return this.gmRequest("POST", url, body, null, headers);
}
async downloadFileInChunks(url, headers = {}) {
const httpTimeout = await storageManager.getSetting("httpTimeout", 5e3), httpRetryCount = await storageManager.getSetting("httpRetryCount", 3);
clog.log("正在获取文件大小...");
let fileSize, mimeType;
try {
const sizeResponse = await utils.retry((() => new Promise(((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
...headers,
Range: "bytes=0-0"
},
timeout: httpTimeout,
onload: resolve,
onerror: e => reject(new Error("网络错误:无法获取文件大小")),
ontimeout: () => reject(new Error("超时:获取文件大小"))
});
}))), httpRetryCount);
if (206 !== sizeResponse.status && 200 !== sizeResponse.status) throw new Error(`请求文件大小失败,状态码: ${sizeResponse.status}`);
{
const rangeHeader = sizeResponse.responseHeaders.match(/content-range:\s*bytes\s*\d+-\d+\/(\d+)/i), typeHeader = sizeResponse.responseHeaders.match(/content-type:\s*([^\s;]+)/i);
if (rangeHeader && rangeHeader[1]) fileSize = parseInt(rangeHeader[1], 10); else {
if (!sizeResponse.responseHeaders.match(/content-length:\s*(\d+)/i) || 200 !== sizeResponse.status) throw new Error("无法从响应头中获取文件总大小,服务器可能不支持 Range 请求。");
{
const lengthHeader = sizeResponse.responseHeaders.match(/content-length:\s*(\d+)/i);
fileSize = parseInt(lengthHeader[1], 10);
clog.warn("服务器返回 200 状态码,可能不支持 Range 请求。将尝试完整下载。");
}
}
typeHeader && typeHeader[1] && (mimeType = typeHeader[1]);
clog.log(`文件总大小:${(fileSize / 1024 / 1024).toFixed(2)} MB, MIME 类型: ${mimeType || "未知"}`);
}
} catch (e) {
clog.error("获取文件大小失败:", e.message);
throw e;
}
if (!fileSize || fileSize <= 0) throw new Error("获取到的文件大小无效或服务器拒绝提供大小信息。");
const numChunks = Math.ceil(fileSize / 1048576), chunkPromises = [], downloadedChunks = new Array(numChunks);
clog.log(`文件将被分为 ${numChunks} 块进行下载 (每块约 ${1..toFixed(2)} MB)`);
for (let i = 0; i < numChunks; i++) {
const start = 1048576 * i, rangeHeader = `bytes=${start}-${Math.min(start + 1048576 - 1, fileSize - 1)}`, chunkPromise = await utils.retry((() => new Promise(((resolve, reject) => {
const currentHeaders = {
...headers,
Range: rangeHeader,
Accept: "application/octet-stream"
};
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: currentHeaders,
timeout: httpTimeout,
responseType: "arraybuffer",
onload: response => {
if (206 === response.status || 200 === response.status) if (response.response instanceof ArrayBuffer) {
downloadedChunks[i] = response.response;
clog.log(`成功下载第 ${i + 1}/${numChunks} 块 (${rangeHeader})`);
resolve();
} else reject(new Error(`第 ${i + 1} 块响应不是 ArrayBuffer。`)); else reject(new Error(`第 ${i + 1} 块请求失败,状态码: ${response.status}`));
},
onerror: error => reject(new Error(`第 ${i + 1} 块网络错误: ${error.error}`)),
ontimeout: () => reject(new Error(`第 ${i + 1} 块超时。`))
});
}))), httpRetryCount);
chunkPromises.push(chunkPromise);
}
try {
await Promise.all(chunkPromises);
clog.log("所有分块下载完成,开始合并...");
} catch (e) {
clog.error("分块下载过程中发生错误:", e.message);
throw e;
}
const finalBlob = new Blob(downloadedChunks);
finalBlob.size !== fileSize && clog.warn(`警告:合并后的 Blob 大小 (${finalBlob.size}) 与预期文件大小 (${fileSize}) 不匹配!`);
return await finalBlob.text();
}
async gmRequest(method, url, data = {}, params = {}, headers = {}, noRedirect = !1) {
if (params && Object.keys(params).length) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes("?") ? "&" : "?") + queryString;
}
const httpTimeout = await storageManager.getSetting("httpTimeout", 5e3), httpRetryCount = await storageManager.getSetting("httpRetryCount", 3);
data || (data = void 0);
return await utils.retry((() => new Promise(((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url,
headers: headers,
timeout: httpTimeout,
data: data,
onload: response => {
try {
noRedirect && response.finalUrl !== url && reject("请求被重定向了,URL是:" + response.finalUrl);
if (response.status >= 200 && response.status < 300) if (response.responseText) try {
resolve(JSON.parse(response.responseText));
} catch (e) {
resolve(response.responseText);
} else resolve(response.responseText || response); else {
clog.error("请求失败,状态码:", response.status, url);
if (response.responseText) try {
const errorData = JSON.parse(response.responseText);
reject(errorData);
} catch {
reject(new Error(response.responseText || `请求发生错误 ${response.status}`));
} else reject(new Error(`请求发生错误 ${response.status}`));
}
} catch (e) {
reject(e);
}
},
onerror: error => {
clog.error("网络错误:", url);
reject(new Error(error.error || "网络错误"));
},
ontimeout: () => {
reject(new Error("请求超时: " + url));
}
});
}))), httpRetryCount);
}
};
unsafeWindow.storageManager = window.storageManager = new StorageManager;
const senderChannel = new BroadcastChannel("jhs-channel");
window.refresh = function() {
senderChannel.postMessage({
type: "refresh"
});
};
window.clean_cacheBlacklistCarList = function() {
storageManager.cacheBlacklistCarList && (storageManager.cacheBlacklistCarList = null);
senderChannel.postMessage({
type: "clean_cacheBlacklistCarList"
});
};
window.clean_cacheSettingObj = function() {
storageManager.cacheSettingObj && (storageManager.cacheSettingObj = null);
senderChannel.postMessage({
type: "clean_cacheSettingObj"
});
};
window.clean_cacheFavoriteActressList = function() {
storageManager.cacheFavoriteActressList && (storageManager.cacheFavoriteActressList = null);
senderChannel.postMessage({
type: "clean_cacheFavoriteActressList"
});
};
window.clean_cacheCarList = function() {
storageManager.cacheCarList && (storageManager.cacheCarList = null);
senderChannel.postMessage({
type: "clean_cacheCarList"
});
};
new BroadcastChannel("jhs-channel").addEventListener("message", (async event => {
let dataType = event.data.type;
if ("refresh" === dataType) {
const listPagePlugin = window.pluginManager.getBean("ListPagePlugin");
await listPagePlugin.doFilter();
const historyPlugin = window.pluginManager.getBean("HistoryPlugin");
historyPlugin.tableObj && historyPlugin.tableObj.setData();
const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin");
if (newVideoPlugin) {
newVideoPlugin.showNewVideoCount().then();
newVideoPlugin.loadData();
}
} else "clean_cacheBlacklistCarList" === dataType ? storageManager.cacheBlacklistCarList && (storageManager.cacheBlacklistCarList = null) : "clean_cacheSettingObj" === dataType ? storageManager.cacheSettingObj && (storageManager.cacheSettingObj = null) : "clean_cacheFavoriteActressList" === dataType ? storageManager.cacheFavoriteActressList && (storageManager.cacheFavoriteActressList = null) : "clean_cacheCarList" === dataType && storageManager.cacheCarList && (storageManager.cacheCarList = null);
}));
!function() {
document.head.insertAdjacentHTML("beforeend", '\n \n ');
unsafeWindow.loading = window.loading = function() {
const container = document.createElement("div");
container.className = "loading-container";
const animation = document.createElement("div");
animation.className = "loading-animation";
const closeBtn = document.createElement("button");
closeBtn.className = "loading-close-btn";
closeBtn.textContent = "关闭";
closeBtn.addEventListener("click", (() => {
close();
}));
setTimeout((() => {
closeBtn.classList.add("visible");
}), 3e3);
container.appendChild(animation);
container.appendChild(closeBtn);
document.body.appendChild(container);
const close = () => {
container && container.parentNode && container.parentNode.removeChild(container);
};
return {
close: close
};
};
}();
!function() {
const showMessage = (msg, type, gravityOrOptions, positionOrOptions, options) => {
let finalOptions;
if ("object" == typeof gravityOrOptions) finalOptions = gravityOrOptions; else {
finalOptions = "object" == typeof positionOrOptions ? positionOrOptions : options || {};
finalOptions.gravity = gravityOrOptions || "top";
finalOptions.position = "string" == typeof positionOrOptions ? positionOrOptions : "center";
}
finalOptions.gravity && "center" !== finalOptions.gravity || (finalOptions.offset = {
y: "calc(50vh - 150px)"
});
const colors_infoStart = "#60A5FA", colors_infoEnd = "#93C5FD", colors_successStart = "#10B981", colors_successEnd = "#6EE7B7", colors_errorStart = "#EF4444", colors_errorEnd = "#FCA5A5", commonStyles = {
borderRadius: "12px",
color: "white",
padding: "12px 16px",
boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
minWidth: "150px",
textAlign: "center",
zIndex: 999999999
}, defaultConfig = {
text: msg,
duration: 1e3,
close: !1,
gravity: "top",
position: "center",
style: {
info: {
...commonStyles,
background: `linear-gradient(to right, ${colors_infoStart}, ${colors_infoEnd})`
},
success: {
...commonStyles,
background: `linear-gradient(to right, ${colors_successStart}, ${colors_successEnd})`
},
error: {
...commonStyles,
background: `linear-gradient(to right, ${colors_errorStart}, ${colors_errorEnd})`
}
}[type],
stopOnFocus: !0,
oldestFirst: !1,
...finalOptions
};
-1 === defaultConfig.duration && (defaultConfig.close = !0);
const toast = Toastify(defaultConfig);
toast.showToast();
toast.closeShow = () => {
toast.toastElement.remove();
};
return toast;
};
unsafeWindow.show = window.show = {
ok: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "success", gravityOrOptions, positionOrOptions, options),
error: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "error", gravityOrOptions, positionOrOptions, options),
info: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "info", gravityOrOptions, positionOrOptions, options)
};
}();
!function() {
document.head.insertAdjacentHTML("beforeend", "\n \n ");
function smartToggleBodyScroll2(waitTime = 10) {
setTimeout((() => {
const openLayerCount = document.querySelectorAll(".layui-layer-shade").length;
document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : "";
}), waitTime);
}
window.showImageViewer = function(imgUrlOrContainer, altText = "") {
let $container = null, isTempContainer = !1;
if ("string" == typeof imgUrlOrContainer || imgUrlOrContainer instanceof String) {
$container = $('').append(`

`).appendTo("body");
isTempContainer = !0;
} else $container = $(imgUrlOrContainer);
const options = {
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() {
viewerInstance.zoomTo(1.4);
let left = (viewerInstance.viewerData.width - viewerInstance.imageData.width) / 2;
viewerInstance.moveTo(left, 0);
},
shown() {
isTempContainer && $container.remove();
document.documentElement.style.overflow = "hidden";
document.body.style.overflow = "hidden";
viewerInstance.handleKeydown = function(e) {
if ("Escape" === e.key || " " === e.key) {
e.preventDefault();
e.stopPropagation();
viewerInstance.destroy();
document.removeEventListener("keydown", viewerInstance.handleKeydown);
document.documentElement.style.overflow = "";
document.body.style.overflow = "";
smartToggleBodyScroll2();
}
};
document.addEventListener("keydown", viewerInstance.handleKeydown);
},
hidden() {
viewerInstance && viewerInstance.handleKeydown && document.removeEventListener("keydown", viewerInstance.handleKeydown);
viewerInstance.destroy();
document.documentElement.style.overflow = "";
document.body.style.overflow = "";
smartToggleBodyScroll2();
}
}, viewerInstance = new Viewer($container[0], options);
viewerInstance.show();
};
}();
window.ImageHoverPreview = class {
constructor(options = {}) {
this.config = {
selector: ".hover-preview",
dataAttribute: "data-full",
maxWidth: 1e3,
maxHeight: 1e3,
offsetX: 20,
offsetY: 20,
zIndex: 9999999999,
transition: .2,
autoAdjustPosition: !0,
...options
};
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 css = `\n \n `;
document.head.insertAdjacentHTML("beforeend", css);
}
createPreviewElement() {
this.preview = document.createElement("div");
this.preview.className = "image-hover-preview";
document.body.appendChild(this.preview);
}
bindEvents() {
document.querySelectorAll(this.config.selector).forEach((el => {
if (!this.boundElements.has(el)) {
el.addEventListener("mouseenter", (e => this.handleMouseEnter(e)));
el.addEventListener("mouseleave", (e => this.handleMouseLeave(e)));
el.addEventListener("mousemove", (e => this.handleMouseMove(e)));
this.boundElements.add(el);
}
}));
}
handleMouseEnter(e) {
clearTimeout(this.timer);
this.currentTarget = e.currentTarget;
const imgUrl = this.currentTarget.getAttribute(this.config.dataAttribute) || this.currentTarget.src;
if (!imgUrl) return;
this.preview.innerHTML = "";
this.preview.classList.add("loading");
this.preview.style.display = "block";
this.preview.classList.remove("active");
const img = new Image;
img.onload = () => {
this.preview.classList.remove("loading");
this.preview.innerHTML = `

`;
this.imgElement = this.preview.querySelector("img");
const {width: width, height: height} = this.calculateImageSize(img);
this.preview.style.width = `${width}px`;
this.preview.style.height = `${height}px`;
this.preview.offsetHeight;
this.preview.classList.add("active");
this.handleMouseMove(e);
};
img.onerror = () => {
this.preview.classList.remove("loading");
this.preview.innerHTML = '
图片加载失败
';
};
img.src = imgUrl;
}
calculateImageSize(img) {
let width = img.naturalWidth, height = img.naturalHeight;
if (width > this.config.maxWidth || height > this.config.maxHeight) {
const ratio = Math.min(this.config.maxWidth / width, this.config.maxHeight / height);
width *= ratio;
height *= ratio;
}
return {
width: width,
height: height
};
}
handleMouseMove(e) {
if (!this.currentTarget || !this.preview.classList.contains("active")) return;
let {offsetX: offsetX, offsetY: offsetY} = this.config, left = e.clientX + offsetX, top = e.clientY + offsetY;
if (this.config.autoAdjustPosition) {
const previewWidth = this.preview.offsetWidth, previewHeight = this.preview.offsetHeight;
left + previewWidth > window.innerWidth && (left = e.clientX - previewWidth - offsetX);
top + previewHeight > window.innerHeight && (top = e.clientY - previewHeight - offsetY);
left = Math.max(0, left);
top = Math.max(0, top);
}
this.preview.style.left = `${left}px`;
this.preview.style.top = `${top}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((el => {
if (this.boundElements.has(el)) {
el.removeEventListener("mouseenter", this.handleMouseEnter);
el.removeEventListener("mouseleave", this.handleMouseLeave);
el.removeEventListener("mousemove", this.handleMouseMove);
this.boundElements.delete(el);
}
}));
this.preview && this.preview.parentNode && this.preview.parentNode.removeChild(this.preview);
}
};
!async function() {
document.head.insertAdjacentHTML("beforeend", "\n \n ");
const groupTypes = {
base: {
label: "信息",
background: "#e8f4fd",
borderLeftColor: "#3498db"
},
warn: {
label: "警告",
background: "#fef9e7",
borderLeftColor: "#f39c12"
},
error: {
label: "错误",
background: "#fdedec",
borderLeftColor: "#e74c3c"
},
debug: {
label: "调试",
background: "#f4f6f6",
borderLeftColor: "#95a5a6"
}
}, filterMap = {
base: [ "base", "warn", "error" ],
warn: [ "warn" ],
error: [ "error" ],
debug: [ "base", "warn", "error", "debug" ]
}, maxEntries = await storageManager.getSetting("clogMsgCount", 2e3);
class CLog {
constructor() {
const storedFilter = localStorage.getItem("jhs_clog_filter");
this.currentFilter = storedFilter && groupTypes[storedFilter] ? storedFilter : "base";
this.logs = [];
this.isInitialized = !1;
this.userScrolledUp = !1;
}
tryInitialize() {
if ("loading" === document.readyState) return !1;
if (this.isInitialized) return !0;
this.init();
this.isInitialized = !0;
return !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 header = document.createElement("div");
header.className = "console-logger-header";
const title = document.createElement("div");
title.className = "console-logger-title";
title.textContent = "JHS V3.3.6";
const controls = document.createElement("div");
controls.className = "console-logger-controls";
this.maximizeBtn = document.createElement("button");
this.maximizeBtn.textContent = "";
this.maximizeBtn.classList.add("console-logger-maximize-toggle");
controls.appendChild(this.maximizeBtn);
const clearBtn = document.createElement("button");
clearBtn.textContent = "清空";
clearBtn.addEventListener("click", (() => this.clear()));
controls.appendChild(clearBtn);
header.appendChild(title);
header.appendChild(controls);
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(header);
this.window.appendChild(this.filtersContainer);
this.window.appendChild(this.content);
this.container.appendChild(this.window);
document.body.appendChild(this.container);
Object.keys(groupTypes).forEach((type => {
const filterBtn = document.createElement("div");
filterBtn.className = "console-logger-filter";
type === this.currentFilter && filterBtn.classList.add("active");
filterBtn.textContent = groupTypes[type].label;
filterBtn.dataset.type = type;
filterBtn.addEventListener("click", (() => this.setFilter(type)));
this.filterButtonGroup.appendChild(filterBtn);
}));
}
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 isAtBottom = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 5;
this.userScrolledUp = !isAtBottom;
}));
this.content.addEventListener("wheel", (e => {
const isAtTop = 0 === this.content.scrollTop, isAtBottom = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 1;
if (isAtTop && e.deltaY < 0 || isAtBottom && e.deltaY > 0) {
e.preventDefault();
e.stopPropagation();
}
}), {
passive: !1
});
}
toggleExpandCollapsed() {
const isCollapsed = this.window.classList.toggle("collapsed");
this.toggleBtn.classList.toggle("collapsed");
if (isCollapsed) localStorage.setItem("jhs_clog_expand", "no"); else {
localStorage.setItem("jhs_clog_expand", "yes");
this.reRenderAllLogs();
}
}
checkInitialCollapseState() {
const expandStatus = localStorage.getItem("jhs_clog_expand");
if (expandStatus && "no" !== expandStatus) {
this.window.classList.toggle("collapsed");
this.toggleBtn.classList.toggle("collapsed");
setTimeout((() => {
this.content.scrollTop = this.content.scrollHeight;
}), 0);
} else {
this.window.classList.add("collapsed");
this.toggleBtn.classList.add("collapsed");
}
}
checkInitialMaximizeState() {
if ("maximized" === localStorage.getItem("jhs_clog_maximize")) {
this.window.classList.add("maximized");
this.maximizeBtn.classList.add("active");
}
}
toggleMaximize() {
const isMaximized = this.window.classList.toggle("maximized");
this.maximizeBtn.classList.toggle("active", isMaximized);
isMaximized ? localStorage.setItem("jhs_clog_maximize", "maximized") : localStorage.setItem("jhs_clog_maximize", "minimized");
this.window.classList.contains("collapsed") || (this.content.scrollTop = this.content.scrollHeight);
}
addLog(primaryContent, type = "base", ...optionalParams) {
const initialized = this.tryInitialize();
let logType, extraParams = [];
if (groupTypes[type]) {
logType = type;
extraParams = optionalParams;
} else {
logType = "base";
extraParams = [ type, ...optionalParams ];
}
logType = groupTypes[logType] ? logType : "base";
const allContents = [ primaryContent, ...extraParams ];
let messageType = "msg";
const processedParts = [];
allContents.forEach((content => {
if ("[object Error]" === Object.prototype.toString.call(content)) processedParts.push(String(content)); else if ("object" == typeof content && null !== content) try {
processedParts.push("
" + JSON.stringify(content, null, 2));
messageType = "json";
} catch (e) {
processedParts.push(String(content));
messageType = "msg";
} else processedParts.push(String(content));
}));
let formattedMessage = processedParts.join(" ");
formattedMessage = formattedMessage.replace(/(?:(?:https?|ftp):\/\/|www\.|(?:\/\/))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]/gi, (url => {
const isFullProtocol = url.startsWith("http") || url.startsWith("ftp"), isProtocolRelative = url.startsWith("//"), isWww = url.startsWith("www.");
let fullUrl = url;
isProtocolRelative ? fullUrl = `http:${url}` : !isFullProtocol && isWww && (fullUrl = `http://${url}`);
return `
${url}`;
}));
const logEntry = {
message: formattedMessage,
messageType: messageType,
type: logType,
timestamp: new Date,
id: Date.now() + Math.random()
};
this.logs.push(logEntry);
if (this.logs.length > maxEntries) {
const firstLog = this.logs[0];
if (initialized) {
const elementToRemove = this.content.querySelector(`.console-logger-entry[data-id="${firstLog.id}"]`);
if (elementToRemove) {
this.logs.shift();
this.content.removeChild(elementToRemove);
}
}
}
initialized && this.renderLog(logEntry);
}
log(...message) {
const [mainMessage, ...optionalParams] = message;
setTimeout((() => {
this.addLog(mainMessage, "base", ...optionalParams);
}), 0);
}
error(...message) {
const [mainMessage, ...optionalParams] = message;
console.error(...message);
setTimeout((() => {
this.addLog(mainMessage, "error", ...optionalParams);
}), 0);
}
warn(...message) {
const [mainMessage, ...optionalParams] = message;
setTimeout((() => {
this.addLog(mainMessage, "warn", ...optionalParams);
}), 0);
}
debug(...message) {
const [mainMessage, ...optionalParams] = message;
setTimeout((() => {
this.addLog(mainMessage, "debug", ...optionalParams);
}), 0);
}
renderLog(logEntry) {
if ("none" === this.container.style.display) return;
if (this.window.classList.contains("collapsed")) return;
if (!(filterMap[this.currentFilter] || []).includes(logEntry.type)) return;
const entryEl = this._createLogElement(logEntry);
this.content.appendChild(entryEl);
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((() => {
this.content.innerHTML = "";
if (0 === this.logs.length) return;
const allowedTypes = filterMap[this.currentFilter] || [], fragment = document.createDocumentFragment();
this.logs.forEach((logEntry => {
if (allowedTypes.includes(logEntry.type)) {
const entryEl = this._createLogElement(logEntry);
fragment.appendChild(entryEl);
}
}));
this.content.appendChild(fragment);
this.content.scrollTop = this.content.scrollHeight;
}), 0));
}
_createLogElement(logEntry) {
const entryEl = document.createElement("div");
entryEl.className = "console-logger-entry";
entryEl.dataset.type = logEntry.type;
entryEl.dataset.id = logEntry.id;
const config = groupTypes[logEntry.type] || groupTypes.base;
entryEl.style.borderLeft = "3px solid " + config.borderLeftColor;
entryEl.style.background = config.background;
const timeStr = (logEntry.timestamp instanceof Date ? logEntry.timestamp : new Date(logEntry.timestamp)).toTimeString().split(" ")[0];
entryEl.innerHTML = `\n
[${timeStr}]\n
${logEntry.message}\n `;
return entryEl;
}
setFilter(type) {
if (this.currentFilter === type) return;
this.currentFilter = type;
localStorage.setItem("jhs_clog_filter", type);
this.filterButtonGroup.querySelectorAll(".console-logger-filter").forEach((btn => {
btn.dataset.type === type ? btn.classList.add("active") : btn.classList.remove("active");
}));
this.reRenderAllLogs();
}
clear() {
this.logs = [];
this.content.innerHTML = "";
}
show() {
if (this.isInitialized && this.container) {
this.container.style.display = "";
this.reRenderAllLogs();
} else if (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 = "999999999");
}
}
if (isJavBus || isJavDb) {
try {
unsafeWindow.parent.clog && "function" == typeof unsafeWindow.parent.clog.log ? window.clog = unsafeWindow.clog = unsafeWindow.parent.clog : window.clog = unsafeWindow.clog = new CLog;
} catch (e) {
console.error("创建日志控制台出现异常", e);
window.clog = unsafeWindow.clog = new CLog;
}
!function() {
const clog2 = window.clog || console;
window.addEventListener("error", (function(errorEvent) {
const filename = errorEvent.filename, message = errorEvent.message;
filename.includes("javdb") || filename.includes("javbus") || clog2.error(`[全局 Error 异常捕获] ${message} 来源: ${filename}`);
}));
window.addEventListener("unhandledrejection", (function(event) {
const reason = event.reason, message = (null == reason ? void 0 : reason.message) ?? "";
if (message.includes("play()")) return;
if (message.includes("The element has no supported sources")) {
show.error("播放失败, 请检查是否已对节点分流?");
clog2.error("播放失败, 请检查是否已对节点分流?");
return;
}
if (message.includes("
1005") && message.includes("fc2ppvdb")) return;
const errorMessage = `[全局 Promise 异常捕获] ${reason.message || reason}`;
clog2.error(errorMessage, reason);
event.preventDefault();
}));
}();
document.addEventListener("mousedown", (event => {
const clog2 = window.clog;
if (!clog2.isInitialized || !clog2.container) return;
const target = event.target, whitelistSelector = [ ".console-logger-container", ".layui-layer-shade", ".loading-container" ].join(",");
target.closest(whitelistSelector) ? clog2.highZIndex() : clog2.lowZIndex();
}));
}
}();
!function() {
document.head.insertAdjacentHTML("beforeend", "\n \n ");
function positionTooltip(target, content, preferredPosition) {
const el = function(content) {
const el = document.createElement("div");
el.classList.add("js-tooltip");
const contentEl = document.createElement("div");
contentEl.innerHTML = content;
el.appendChild(contentEl);
document.body.appendChild(el);
return el;
}(content);
el.style.display = "block";
const targetRect = target.getBoundingClientRect(), tooltipRect = el.getBoundingClientRect();
el.style.display = "none";
const viewportWidth = window.innerWidth, viewportHeight = window.innerHeight;
let finalLeft, finalTop, finalPosition = preferredPosition;
const fitsVertical = y => y >= 8 && y + tooltipRect.height <= viewportHeight - 8, fitsHorizontal = x => x >= 8 && x + tooltipRect.width <= viewportWidth - 8, centerHorizontal = targetRect.left + targetRect.width / 2 - tooltipRect.width / 2, centerVertical = targetRect.top + targetRect.height / 2 - tooltipRect.height / 2;
switch (preferredPosition) {
case "top":
finalTop = targetRect.top - tooltipRect.height - 0;
if (finalTop < 8 && fitsVertical(targetRect.bottom + 0)) {
finalTop = targetRect.bottom + 0;
finalPosition = "bottom";
}
break;
case "bottom":
finalTop = targetRect.bottom + 0;
if (finalTop + tooltipRect.height > viewportHeight - 8 && fitsVertical(targetRect.top - tooltipRect.height - 0)) {
finalTop = targetRect.top - tooltipRect.height - 0;
finalPosition = "top";
}
break;
case "left":
finalLeft = targetRect.left - tooltipRect.width - 0;
if (finalLeft < 8 && fitsHorizontal(targetRect.right + 0)) {
finalLeft = targetRect.right + 0;
finalPosition = "right";
}
break;
case "right":
finalLeft = targetRect.right + 0;
if (finalLeft + tooltipRect.width > viewportWidth - 8 && fitsHorizontal(targetRect.left - tooltipRect.width - 0)) {
finalLeft = targetRect.left - tooltipRect.width - 0;
finalPosition = "left";
}
}
const isHorizontal = "left" === finalPosition || "right" === finalPosition;
if ("top" === finalPosition || "bottom" === finalPosition) {
finalLeft = centerHorizontal;
finalLeft < 8 ? finalLeft = 8 : finalLeft + tooltipRect.width > viewportWidth - 8 && (finalLeft = viewportWidth - tooltipRect.width - 8);
} else if (isHorizontal) {
finalTop = centerVertical;
finalTop < 8 ? finalTop = 8 : finalTop + tooltipRect.height > viewportHeight - 8 && (finalTop = viewportHeight - tooltipRect.height - 8);
}
el.style.left = `${finalLeft}px`;
el.style.top = `${finalTop}px`;
el.classList.add("is-active");
target.tooltipElement = el;
}
const selector = "[data-tip-top], [data-tip-bottom], [data-tip-left], [data-tip-right], [data-tip]";
document.addEventListener("mouseover", (event => {
const target = event.target.closest(selector);
if (target && !target.tooltipElement) {
let tipContent, preferredPosition = "top";
if (target.hasAttribute("data-tip-bottom")) {
tipContent = target.getAttribute("data-tip-bottom");
preferredPosition = "bottom";
} else if (target.hasAttribute("data-tip-left")) {
tipContent = target.getAttribute("data-tip-left");
preferredPosition = "left";
} else if (target.hasAttribute("data-tip-right")) {
tipContent = target.getAttribute("data-tip-right");
preferredPosition = "right";
} else if (target.hasAttribute("data-tip-top")) {
tipContent = target.getAttribute("data-tip-top");
preferredPosition = "top";
} else if (target.hasAttribute("data-tip")) {
tipContent = target.getAttribute("data-tip");
preferredPosition = "top";
}
if (!tipContent) return;
target.hoverTimeout = setTimeout((() => {
target.matches(":hover") && !target.tooltipElement && positionTooltip(target, tipContent, preferredPosition);
}), 50);
}
}));
document.addEventListener("mouseout", (event => {
const target = event.target.closest(selector);
if (target) {
if (target.hoverTimeout) {
clearTimeout(target.hoverTimeout);
target.hoverTimeout = null;
}
if (!target.contains(event.relatedTarget) && target.tooltipElement) {
(el = target.tooltipElement) && el.parentNode && el.remove();
target.tooltipElement = null;
}
}
var el;
}));
}();
class PluginManager {
constructor() {
this.plugins = new Map;
}
register(pluginClass) {
if ("function" != typeof pluginClass) throw new Error("插件必须是一个类");
const instance = new pluginClass;
instance.pluginManager = this;
const lowerName = instance.getName();
if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`);
this.plugins.set(lowerName, instance);
}
getBean(name2) {
return this.plugins.get(name2);
}
async processCss() {
const failedCssLoads = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => {
try {
if ("function" == typeof instance.initCss) {
const css = await instance.initCss();
css && utils.insertStyle(css);
return {
name: name2,
status: "fulfilled"
};
}
return {
name: name2,
status: "skipped"
};
} catch (e) {
console.error(`插件 ${name2} 加载 CSS 失败`, e);
return {
name: name2,
status: "rejected",
error: e
};
}
})))).filter((r => "rejected" === r.status));
failedCssLoads.length && console.error("以下插件的 CSS 加载失败:", failedCssLoads.map((p => p.value.name)));
}
async processPlugins() {
const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => {
try {
if ("function" == typeof instance.handle) {
await instance.handle();
return {
name: name2,
status: "fulfilled"
};
}
} catch (e) {
clog.error(`插件 ${name2} 执行失败`, e);
return {
name: name2,
status: "rejected",
error: e
};
}
})))).filter((r => "rejected" === r.status));
failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map((p => p.value.name)));
}
}
class BasePlugin {
constructor() {
__publicField(this, "pluginManager", null);
__publicField(this, "settingSvg", '
');
__publicField(this, "editSvg", '
');
__publicField(this, "deleteSvg", '
');
__publicField(this, "checkSvg", '
');
__publicField(this, "actressSvg", '
');
__publicField(this, "newSvg", '
');
__publicField(this, "refreshSvg", '
');
__publicField(this, "blacklistSvg", '
');
__publicField(this, "removeSvg", '
');
__publicField(this, "copySvg", '
');
__publicField(this, "titleSvg", '
');
__publicField(this, "carNumSvg", '
');
__publicField(this, "downSvg", '
');
__publicField(this, "handleSvg", '
');
__publicField(this, "siteSvg", '
');
__publicField(this, "videoSvg", '
');
__publicField(this, "screenSvg", '
');
__publicField(this, "recoveryVideoSvg", '
');
}
getName() {
throw new Error(`${this.constructor.name} 未显示getName()`);
}
getBean(name2) {
return this.pluginManager.getBean(name2);
}
async initCss() {
return "";
}
async handle() {}
getPageInfo() {
let carNum2, url, actress, actors, publishTime, currentHref2 = window.location.href;
if (isJavDb) {
carNum2 = $('a[title="複製番號"]').attr("data-clipboard-text");
url = currentHref2.split("?")[0].split("#")[0];
actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" ");
actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" ");
publishTime = $('strong:contains("日期:")').parent(".panel-block").find(".value").text().trim();
}
if (isJavBus) {
url = currentHref2.split("?")[0];
carNum2 = url.split("/").filter(Boolean).pop().replace(/_\d{4}-\d{2}-\d{2}$/, "");
actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ");
actors = "";
publishTime = $('span.header:contains("發行日期:")').parent("p").text().trim().replace("發行日期:", "").trim();
}
return {
carNum: carNum2,
url: url,
actress: actress,
actors: actors,
publishTime: publishTime
};
}
getActressId() {
const match = currentHref.match(/\/actors\/([^/?]+)/);
return match && match.length > 1 ? match[1] : null;
}
getActressPageInfo() {
let currentHref2 = window.location.href;
if (!currentHref2.includes("/actors/") && !currentHref2.includes("/star/")) throw new Error("接口调用错误, 非演员详情页");
let nameList = [], $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
$actor.length && $actor.text().trim().split(",").forEach((name2 => {
nameList.push(name2.trim());
}));
let $sectionMeta = $(".section-meta:not(:contains('影片'))");
$sectionMeta.length && $sectionMeta.text().trim().split(",").forEach((name2 => {
nameList.push(name2.trim());
}));
let role = $(".section-meta:contains('男優')").length > 0 ? "actor" : "actress", movieType = "censored";
nameList.some((n => n.includes("無碼"))) && (movieType = "uncensored");
currentHref2.includes("uncensored") && (movieType = "uncensored");
let blacklistUrl = null, starId = null;
const urlObj = new URL(currentHref2);
if (isJavDb) {
starId = urlObj.pathname.split("/").filter((segment => "" !== segment.trim())).pop();
const params = urlObj.searchParams;
params.delete("sort_type");
params.delete("page");
blacklistUrl = urlObj.toString();
} else if (isJavBus) {
const splitStr = "/star/", parts = currentHref2.split(splitStr);
if (parts.length < 2) throw new Error("提取演员url失败");
const host = parts[0];
starId = parts[1].split("/")[0];
blacklistUrl = host + splitStr + starId;
}
return {
starId: starId,
name: nameList[0],
allName: nameList,
role: role,
movieType: movieType,
blacklistUrl: blacklistUrl
};
}
getSelector(type) {
const key = type || (isJavDb ? "javdb" : isJavBus ? "javbus" : null), selectors = {
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 (!key || !selectors[key]) throw new Error("类型错误: 无法确定选择器类型 (JavDb 或 JavBus)");
return selectors[key];
}
parseMovieId(href) {
return href.split("/").pop().split(/[?#]/)[0];
}
getBoxCarInfo($box2) {
var _a2, _b, _c;
const $aLink = $box2.find("a"), url = $aLink.attr("href");
let carNum2 = null, title = null, publishTime = null;
const $videoTitleEle = $box2.find(".video-title");
if ($videoTitleEle.length > 0) {
const $strongEle = $videoTitleEle.find("strong");
$strongEle.length > 0 && (carNum2 = $strongEle.text().trim());
title = null == (_a2 = $aLink.attr("title")) ? void 0 : _a2.trim();
if (!title) {
const fullTitleText = $videoTitleEle.text().trim();
title = carNum2 && fullTitleText.includes(carNum2) ? fullTitleText.replace(carNum2, "").trim() : fullTitleText;
}
publishTime = $box2.find(".meta").text().trim();
}
if (!carNum2) {
const $imgEle = $box2.find("img");
$imgEle.length > 0 && (title = (null == (_b = $imgEle.attr("title")) ? void 0 : _b.trim()) || (null == (_c = $imgEle.attr("data-title")) ? void 0 : _c.trim()));
const dateElements = $box2.find("date").map(((i, el) => $(el).text().trim())).get(), isDate = s => /^\d{4}-\d{1,2}-\d{1,2}$/.test(s);
publishTime = dateElements.find(isDate) || null;
carNum2 = dateElements.find((i => !isDate(i))) || null;
}
if (!carNum2) {
const msg = "提取番号信息失败: carNum 为空";
console.error("Error in getBoxCarInfo:", msg, "Box Element:", $box2.get(0));
show.error(msg);
throw new Error(msg);
}
return {
carNum: carNum2,
url: url || "",
aHref: url || "",
title: title || "",
publishTime: publishTime || ""
};
}
getBoxCarInfoList($boxList = null) {
$boxList || ($boxList = $(this.getSelector().itemSelector));
if (0 === $boxList.length) {
clog.error("获取当前列表页所有item的番号信息失败!");
return [];
}
const results = [];
$boxList.each(((index, element) => {
const $box2 = $(element);
try {
const carInfo = this.getBoxCarInfo($box2);
results.push(carInfo);
} catch (error) {
clog.error("[getBoxCarInfoList] 提取单个 boxCar 信息失败:", error.message, "元素索引:", index);
}
}));
return results;
}
checkDuplicateCarNumbers(currentList, newList) {
if (!isJavDb) return !1;
if (!currentList || 0 === currentList.length || !newList || 0 === newList.length) return !1;
const currentCarNums = new Set(currentList.map((item => item.carNum)).filter((n => n)));
if (0 === currentCarNums.size) return !1;
let consecutiveDuplicates = 0;
for (let i = 0; i < newList.length; i++) {
const newCarNum = newList[i] ? newList[i].carNum : null;
if (newCarNum && currentCarNums.has(newCarNum)) {
consecutiveDuplicates++;
if (consecutiveDuplicates >= 2) {
clog.warn("警告: 检测到连续番号信息重复, 该类别可能已被限制页码。");
return !0;
}
} else consecutiveDuplicates = 0;
}
return !1;
}
}
class DetailPagePlugin extends BasePlugin {
getName() {
return "DetailPagePlugin";
}
constructor() {
super();
}
handle() {
if (window.isDetailPage) {
$(".video-meta-panel a").each((function() {
const href = $(this).attr("href");
href && (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("/")) && $(this).attr("target", "_blank");
}));
this.handleFancyBox();
}
}
handleFancyBox() {
document.addEventListener("click", (function(e) {
if (e.target.closest(".fancybox-button--thumbs")) {
const isVisible = !$(".fancybox-thumbs").is(":hidden");
localStorage.setItem("jhs_fancyboxThumbs", isVisible.toString());
unsafeWindow.$.fancybox.defaults.thumbs.autoStart = isVisible;
}
}));
if (void 0 !== unsafeWindow.$.fancybox) {
const savedState = localStorage.getItem("jhs_fancyboxThumbs");
unsafeWindow.$.fancybox.defaults.thumbs.autoStart = "true" === savedState;
}
}
}
const selectDefaultQuality = (dmmVideoQualityList, intendedDefault) => {
if (!dmmVideoQualityList || 0 === dmmVideoQualityList.length) return null;
const availableSet = new Set(dmmVideoQualityList);
if (availableSet.has(intendedDefault)) return intendedDefault;
const priorityOrder = qualityOptions.map((option => option.quality)).reverse();
for (const quality of priorityOrder) if (availableSet.has(quality)) return quality;
return dmmVideoQualityList[0];
}, CACHE_KEY = "jhs_dmm_video";
class DmmVideoFetcher {
constructor(carNum2, showErrorMessages = !0) {
this.carNum = carNum2;
this.showErrorMessages = showErrorMessages;
}
_checkCache() {
const cachedData = localStorage.getItem(CACHE_KEY) ? JSON.parse(localStorage.getItem(CACHE_KEY)) : {};
if (cachedData[this.carNum]) {
clog.debug("缓存中存在预览视频信息", cachedData[this.carNum]);
return cachedData[this.carNum];
}
return null;
}
_updateCache(videoMap) {
const cachedData = localStorage.getItem(CACHE_KEY) ? JSON.parse(localStorage.getItem(CACHE_KEY)) : {};
cachedData[this.carNum] = videoMap;
clog.debug("成功解析出预览视频并已缓存:", videoMap);
localStorage.setItem(CACHE_KEY, JSON.stringify(cachedData));
}
async _searchContentIds() {
const carNum2 = this.carNum, carNumNoHyphen = carNum2.replace(/-/g, ""), keywordAttempts = [ {
keyword: carNum2.replace("-", "00"),
name: "00-替换关键词"
}, {
keyword: carNum2,
name: "原始番号关键词"
}, {
keyword: carNumNoHyphen,
name: "无连字符关键词"
} ], carNumLower = carNum2.toLowerCase();
for (const attempt of keywordAttempts) {
const {keyword: keyword, name: name2} = attempt, currentTempCarNumLower = keyword.toLowerCase(), apiUrl2 = `https://api.dmm.com/affiliate/v3/ItemList?${new URLSearchParams({
api_id: "UrwskPfkqQ0DuVry2gYL",
affiliate_id: "10278-996",
output: "json",
site: "FANZA",
sort: "match",
keyword: keyword
}).toString()}`;
let response;
try {
response = await gmHttp.get(apiUrl2);
} catch (e) {
clog.error(`API 请求失败,跳过 ${name2}:`, e);
continue;
}
if (!response || !response.result || !response.result.result_count) {
clog.debug(`使用 ${name2} (${keyword}) 进行 API 搜索 返回无结果,尝试下一个关键词。`);
continue;
}
const newItems = [];
for (const item of response.result.items) {
if (newItems.length >= 2) break;
const contentId = item.content_id || "", makerProduct = item.maker_product || "";
if (contentId.includes(currentTempCarNumLower.replace("-", "")) || carNumLower === makerProduct.toLowerCase() || contentId.includes(carNumNoHyphen.toLowerCase())) {
newItems.push({
serviceCode: item.service_code,
floorCode: item.floor_code,
contentId: contentId,
pageUrl: item.URL
});
clog.debug(`[${name2}] cid|makerProduct 匹配成功:`, contentId, makerProduct);
}
}
if (newItems.length > 0) {
clog.debug(`--- 成功通过 ${name2} 找到 Content IDs ---`);
const $btn2 = $("#fanzaBtn");
let url = `https://www.dmm.co.jp/search/=/searchstr=${keyword}`, type = "single";
if (newItems.length > 1) {
$btn2.attr("href", url);
$btn2.append('
多结果');
$btn2.css("backgroundColor", "#7bc73b");
type = "multiple";
} else {
url = newItems[0].pageUrl;
$btn2.attr("href", url);
$btn2.css("backgroundColor", "#7bc73b");
}
const dmmCacheKey = "jhs_other_site_dmm", dmmCacheData = localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {};
dmmCacheData[this.carNum] = {
type: type,
url: url
};
localStorage.setItem(dmmCacheKey, JSON.stringify(dmmCacheData));
return newItems;
}
clog.debug(`[${name2}] API 返回结果数 ${response.result.result_count},但无精确匹配的 Content ID。`);
}
clog.warn("所有关键词尝试均未找到匹配的Content ID, 解析Dmm视频失败");
const $btn = $("#fanzaBtn");
$btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`);
$btn.attr("title", "未查询到, 点击前往搜索页");
$btn.css("backgroundColor", "#de3333");
return null;
}
async _extractTrailerLinks({contentId: contentId, serviceCode: serviceCode, floorCode: floorCode}) {
const trailerPageUrl = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${contentId}/mtype=AhRVShI_/service=${serviceCode}/floor=${floorCode}/mode=/`, htmlContent = await gmHttp.get(trailerPageUrl, null, {
"accept-language": "ja-JP,ja;q=0.9",
Cookie: "age_check_done=1"
});
if ("string" != typeof htmlContent) {
clog.error(htmlContent);
throw new Error("解析播放页内容失败, 非文本内容");
}
if (htmlContent.includes("このサービスはお住まいの地域からは")) throw new Error("节点不可用,请将DMM域名分流到日本ip");
const match = htmlContent.match(/const\s+args\s+=\s+(.*);/);
if (!match) throw new Error("未在脚本中找到 const args = ... 变量");
let bitrates;
try {
({bitrates: bitrates} = JSON.parse(match[1]));
} catch (e) {
throw new Error(`解析播放器脚本 JSON 失败: ${e.message}`);
}
const finalQualityMap = {}, qualityKeys = qualityOptions.map((o => o.quality)).join("|"), qualityNameRegex = new RegExp(`(${qualityKeys})\\.mp4$`);
if (!Array.isArray(bitrates)) {
clog.error("解析画质链接失败: bitrates 字段不是一个数组或不存在");
throw new Error("解析画质链接失败: bitrates 字段不是一个数组或不存在");
}
clog.debug("原始数据返回:", bitrates);
for (const item of bitrates) {
const url = null == item ? void 0 : item.src;
if (!url || "string" != typeof url || !url.endsWith(".mp4")) continue;
const qualityMatch = url.match(qualityNameRegex);
let qualityKey = "";
qualityMatch && qualityMatch[1] && (qualityKey = qualityMatch[1]);
qualityKey && !finalQualityMap[qualityKey] && (finalQualityMap[qualityKey] = url);
}
if (0 === Object.keys(finalQualityMap).length) throw new Error("未找到匹配要求的预览画质视频");
return finalQualityMap;
}
async fetchVideo() {
const cachedResult = this._checkCache();
if (cachedResult) return cachedResult;
let contentItems;
try {
const testCarNum = this.carNum.toLowerCase();
if (testCarNum.startsWith("heyzo") || /^(n\d+|\d+(-\d+)*)$/.test(testCarNum) || /^n\d+$/.test(testCarNum)) throw new Error("无码番号类型, 取消dmm解析");
if (this.carNum.includes("VR-")) throw new Error("VR类型, 取消dmm解析");
contentItems = await this._searchContentIds();
} catch (e) {
clog.error("DMM API 搜索失败:", e);
const $btn = $("#fanzaBtn");
$btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`);
$btn.attr("title", "未查询到, 点击前往搜索页");
$btn.css("backgroundColor", "#de3333");
return null;
}
if (!contentItems || 0 === contentItems.length) return null;
try {
const finalVideoMap = await Promise.any(contentItems.map((item => this._extractTrailerLinks(item))));
this._updateCache(finalVideoMap);
return finalVideoMap;
} catch (error) {
const errors = error.errors || [ error ];
if (errors.some((err => err.message.includes("节点不可用")))) this.showErrorMessages && show.error("节点不可用,请将DMM域名分流到日本ip"); else {
const displayError = errors[0].message || errors[0];
clog.error(`解析失败: ${displayError}`, errors);
this.showErrorMessages && show.error(`解析失败: ${displayError}`);
}
const $btn = $("#fanzaBtn");
$btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`);
$btn.attr("title", "未查询到, 点击前往搜索页");
$btn.css("backgroundColor", "#de3333");
return null;
}
}
}
const getDmmVideo = async (carNum2, showErrorMessages = !0) => new DmmVideoFetcher(carNum2, showErrorMessages).fetchVideo();
class PreviewVideoPlugin extends BasePlugin {
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 settingObj = await storageManager.getSetting();
this.filterHotKey = settingObj.filterHotKey;
this.favoriteHotKey = settingObj.favoriteHotKey;
this.speedVideoHotKey = settingObj.speedVideoHotKey;
let $preview = $(".preview-video-container");
$preview.on("click", (event => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
this.handleVideo().then();
}));
}));
await storageManager.getSetting("enableLoadPreviewVideo", YES) !== YES || currentHref.includes("autoPlay=1") || this.initDmm().then();
let href = window.location.href;
(href.includes("gallery-1") || href.includes("gallery-2")) && utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => {
$(".fancybox-content #preview-video").length > 0 && this.handleVideo().then();
}));
href.includes("autoPlay=1") && $preview.length > 0 && $preview[0].click();
}
async initDmm() {
try {
const dmmVideoMap = await getDmmVideo(this.getPageInfo().carNum, !1);
if (!dmmVideoMap) return;
let settingDefaultVideoQuality = await storageManager.getSetting("videoQuality");
clog.debug("解析其它画质预览视频", "设置-期望画质", settingDefaultVideoQuality);
const defaultVideoUrl = dmmVideoMap[selectDefaultQuality(Object.keys(dmmVideoMap), settingDefaultVideoQuality)];
clog.log("切换其它画质预览视频: ", defaultVideoUrl);
const $previewVideoEL = $("#preview-video"), videoEl = $previewVideoEL.length ? $previewVideoEL[0] : null, isVideoElementHidden = !videoEl || utils.isHidden($previewVideoEL);
if ($previewVideoEL.length) {
if (videoEl) {
const currentTime = videoEl.currentTime;
$previewVideoEL.attr("src", defaultVideoUrl);
if (!isVideoElementHidden) {
clog.debug("播放器已手动打开, 变更进度条");
videoEl.currentTime = currentTime;
videoEl.play();
}
}
} else {
clog.debug("JavDB没有视频播放元素, 开始创建...");
const videoCoverSrc = $(".column-video-cover img").attr("src");
$(".preview-images").prepend(`\n
\n 預告片\n
\n \n `);
$(".preview-video-container").on("click", (event => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (async () => {
await this.handleVideo();
}));
}));
}
} catch (error) {
clog.error("预加载dmm失败:", error);
}
}
async handleVideo() {
if (await storageManager.getSetting("enableLoadPreviewVideo", YES) === NO) return;
const $videoEl = $("#preview-video");
if (!$videoEl.length) return;
const $videoContainer = $videoEl.parent();
$videoContainer.css("position", "relative");
const videoEl = $videoEl[0], jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
jhs_videoMuted && (videoEl.muted = "yes" === jhs_videoMuted);
videoEl.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no");
}));
videoEl.play();
let carNum2 = this.getPageInfo().carNum;
const dmmVideoMap = await getDmmVideo(carNum2);
let $bottomToolbar = $("
").attr("id", "video-bottom-toolbar").css({
display: "flex",
gap: "5px",
"align-items": "center",
"flex-wrap": "wrap"
}), $qualityButtonGroup = $("
").css({
display: "flex",
gap: "5px",
"align-items": "center"
}), defaultVideoQuality = null;
if (dmmVideoMap) {
let storedQuality = await storageManager.getSetting("videoQuality");
defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), storedQuality);
let defaultVideoUrl = dmmVideoMap[defaultVideoQuality];
if ($videoEl.attr("src") !== defaultVideoUrl) {
$videoEl.attr("src", defaultVideoUrl);
videoEl.load();
videoEl.play();
}
qualityOptions.forEach((option => {
let dmmVideoUrl = dmmVideoMap[option.quality];
if (dmmVideoUrl) {
const isActive = defaultVideoQuality === option.quality;
let qualityButton = $(`\n
\n `);
$qualityButtonGroup.append(qualityButton);
}
}));
}
$bottomToolbar.append($qualityButtonGroup);
let $functionButtonGroup = $("
").css({
display: "flex",
gap: "5px",
"align-items": "center",
"margin-left": "auto"
}), filterButton = $(``);
$functionButtonGroup.append(filterButton);
let favoriteButton = $(``);
$functionButtonGroup.append(favoriteButton);
let speedButton = $(``);
$functionButtonGroup.append(speedButton);
$bottomToolbar.append($functionButtonGroup);
$videoContainer.append($bottomToolbar);
$bottomToolbar.on("click", ".video-control-btn", (async e => {
const $button = $(e.currentTarget), videoSrc = $button.data("video-src");
if (!$button.hasClass("active")) try {
const currentTime = videoEl.currentTime;
$videoEl.attr("src", videoSrc);
videoEl.load();
videoEl.currentTime = currentTime;
await videoEl.play();
$bottomToolbar.find(".video-control-btn").removeClass("active").css({
"background-color": "#fff",
color: "black"
});
$button.addClass("active").css({
"background-color": "#007bff",
color: "white"
});
} catch (error) {
console.error("切换画质失败:", error);
}
}));
$("#speed-btn").on("click", (() => {
this.getBean("DetailPageButtonPlugin").speedVideo();
}));
utils.rightClick(document.body, "#speed-btn", (event => {
this.getBean("DetailPageButtonPlugin").filterOne(event);
}));
$("#video-filterBtn").on("click", (event => {
this.getBean("DetailPageButtonPlugin").filterOne(event);
}));
$("#video-favoriteBtn").on("click", (event => {
this.getBean("DetailPageButtonPlugin").favoriteOne(event);
}));
}
}
const _HotkeyManager = class _HotkeyManager {
constructor() {
if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated.");
}
static registerHotkey(hotkeyString, callback, keyupCallback = null) {
if (Array.isArray(hotkeyString)) {
let id_list = [];
hotkeyString.forEach((hotkey => {
if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误");
let id = this.recordHotkey(hotkey, callback, keyupCallback);
id_list.push(id);
}));
return id_list;
}
if (!this.isHotkeyFormat(hotkeyString)) throw new Error("快捷键格式错误");
return this.recordHotkey(hotkeyString, callback, keyupCallback);
}
static recordHotkey(hotkeyString, callback, keyupCallback) {
let id = Math.random().toString(36).substr(2);
this.registerHotKeyMap.set(id, {
hotkeyString: hotkeyString,
callback: callback,
keyupCallback: keyupCallback
});
return id;
}
static unregisterHotkey(id) {
this.registerHotKeyMap.has(id) && this.registerHotKeyMap.delete(id);
}
static isHotkeyFormat(hotkeyString) {
return hotkeyString.toLowerCase().split("+").map((k => k.trim())).every((k => [ "ctrl", "shift", "alt" ].includes(k) || 1 === k.length));
}
static judgeHotkey(hotkeyString, event) {
const keyList = hotkeyString.toLowerCase().split("+").map((k => k.trim())), ctrl = keyList.includes("ctrl"), shift = keyList.includes("shift"), alt = keyList.includes("alt"), key = keyList.find((k => "ctrl" !== k && "shift" !== k && "alt" !== k));
return (this.isMac ? event.metaKey : event.ctrlKey) === ctrl && event.shiftKey === shift && event.altKey === alt && event.key.toLowerCase() === key;
}
};
__publicField(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac"));
__publicField(_HotkeyManager, "registerHotKeyMap", new Map);
__publicField(_HotkeyManager, "handleKeydown", (event => {
for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
let hotkeyString = data.hotkeyString, callback = data.callback;
_HotkeyManager.judgeHotkey(hotkeyString, event) && callback(event);
}
}));
__publicField(_HotkeyManager, "handleKeyup", (event => {
for (const [id, data] of _HotkeyManager.registerHotKeyMap) {
let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback;
keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event) && keyupCallback(event));
}
}));
let HotkeyManager = _HotkeyManager;
document.addEventListener("keydown", (event => {
HotkeyManager.handleKeydown(event);
}));
document.addEventListener("keyup", (event => {
HotkeyManager.handleKeyup(event);
}));
class JavTrailersPlugin extends BasePlugin {
getName() {
return "JavTrailersPlugin";
}
constructor() {
super();
this.hasBand = !1;
}
handle() {
let href = window.location.href;
if (!href.includes("handle=1")) return;
if ($("h1:contains('Page not found')").length) {
console.log("番号无法匹配, 跳搜索");
let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-");
window.location.href = "/search/" + encodeURIComponent(keyword) + window.location.search;
return;
}
let findList = $(".videos-list .video-link").toArray();
if (findList.length) {
const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword)));
if (matchedLink) {
window.location.href = $(matchedLink).attr("href") + window.location.search;
return;
}
}
this.handlePlayJavTrailers();
$("#videoPlayerContainer").on("click", (() => {
this.handlePlayJavTrailers();
}));
window.addEventListener("message", (event => {
let videoEl = document.getElementById("vjs_video_3_html5_api");
videoEl && (videoEl.currentTime += 5);
}));
const urlParams = new URLSearchParams(window.location.search), filterHotKey = urlParams.get("filterHotKey"), favoriteHotKey = urlParams.get("favoriteHotKey"), speedVideoHotKey = urlParams.get("speedVideoHotKey");
filterHotKey && HotkeyManager.registerHotkey(filterHotKey, (() => window.parent.postMessage(filterHotKey, "*")));
favoriteHotKey && HotkeyManager.registerHotkey(favoriteHotKey, (() => window.parent.postMessage(favoriteHotKey, "*")));
speedVideoHotKey && HotkeyManager.registerHotkey(speedVideoHotKey, (() => {
const videoEl = document.getElementById("vjs_video_3_html5_api");
videoEl && (videoEl.currentTime += 5);
}));
}
handlePlayJavTrailers() {
if (!this.hasBand) {
utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => {
setTimeout((() => {
this.hasBand = !0;
let videoEl = document.getElementById("vjs_video_3_html5_api");
console.log(videoEl);
videoEl.play();
videoEl.currentTime = 5;
videoEl.addEventListener("timeupdate", (function() {
videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.currentTime += 2);
}));
$("#vjs_video_3_html5_api").css({
position: "fixed",
width: "100vw",
height: "100vh",
objectFit: "cover",
zIndex: "999999999"
});
$(".vjs-control-bar").css({
position: "fixed",
bottom: "20px",
zIndex: "999999999"
});
}), 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 SubTitleCatPlugin extends BasePlugin {
getName() {
return "SubTitleCatPlugin";
}
handle() {
$(".t-banner-inner").hide();
$("#navbar").hide();
let keyword = new URLSearchParams(window.location.search).get("search").toLowerCase(), findList = $(".sub-table tr td a").toArray(), visibleCount = 0;
findList.forEach((el => {
let item = $(el);
item.text().toLowerCase().includes(keyword) ? visibleCount++ : item.parent().parent().hide();
}));
0 === visibleCount && show.error("该番号无字幕!");
const $secTitle = $(".sec-title"), newHTML = $secTitle.html().replace(/^\d+/, visibleCount);
$secTitle.html(newHTML);
}
}
class Fc2Plugin extends BasePlugin {
getName() {
return "Fc2Plugin";
}
async initCss() {
return "\n \n ";
}
handle() {
let fc2Url = "/advanced_search?type=3&score_min=0&d=1";
$('.navbar-item:contains("FC2")').attr("href", fc2Url);
$('.tabs a:contains("FC2")').attr("href", fc2Url);
if (currentHref.includes("advanced_search?type=3")) {
$("h2.section-title").contents().first().replaceWith("Fc2PPV");
$(".section .container > .box").remove();
}
if (currentHref.includes("collection_codes?movieId")) {
$("section").html("");
const urlParams = new URLSearchParams(window.location.search);
let movieId = urlParams.get("movieId"), carNum2 = urlParams.get("carNum"), url = urlParams.get("url");
movieId && carNum2 && url && this.openFc2Dialog(movieId, carNum2, url);
}
}
openFc2Dialog(movieId, carNum2, href) {
let tempCarNum = carNum2.replace("FC2-", "");
if (href.includes("123av")) {
this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum2, href);
return;
}
layer.open({
type: 1,
title: carNum2,
content: '\n
\n \x3c!--
\n \n
--\x3e\n \x3c!--
--\x3e\n
\n \n
第三方资源:
\n \n
\n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n \x3c!--
--\x3e\n
\n ',
area: utils.getResponsiveArea([ "70%", "90%" ]),
skin: "movie-detail-layer",
scrollbar: !1,
success: (layero, index) => {
this.loadData(movieId, carNum2);
$("#favoriteBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_FAVORITE,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#filterBtn").on("click", (event => {
utils.q(event, `是否屏蔽${carNum2}?`, (async () => {
const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_FILTER,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
}));
$("#hasDownBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_HAS_DOWN,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#hasWatchBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_HAS_WATCH,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event)));
$("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2)));
$("#magnetSearchBtn").on("click", (() => {
let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2);
layer.open({
type: 1,
title: "磁力搜索",
content: '
',
area: utils.getResponsiveArea([ "60%", "80%" ]),
scrollbar: !1,
success: () => {
$("#magnetHubBox").append(magnetHub);
}
});
}));
this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then();
utils.setupEscClose(index);
},
end() {
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}
});
}
loadData(movieId, carNum2) {
let tempCarNum = carNum2.replace("FC2-", "");
this.handleMovieDetail(movieId);
this.handleLongImg(tempCarNum);
this.handleMagnets(movieId);
this.getBean("ReviewPlugin").showReview(movieId, $("#reviews-content")).then();
this.getBean("RelatedPlugin").showRelated($("#related-content"), movieId).then();
}
handleMovieDetail(movieId) {
javDbApi.getMovieDetail(movieId).then((res => {
const actors = res.actors || [], imgList = res.imgList || [];
let actorsHtml = "";
if (actors.length > 0) {
let actress = "";
for (let i = 0; i < actors.length; i++) {
let actor = actors[i];
actorsHtml += `
${actor.name}`;
0 === actor.gender && (actress += actor.name + " ");
}
$("#data-actress").text(actress);
} else actorsHtml = '
暂无演员信息';
let imagesHtml = "";
imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => `\n
\n
\n \n `)).join("") : '
暂无剧照
';
$(".movie-info-container").html(`\n
${res.title || "无标题"}
\n
\n 番号: ${res.carNum || "未知"}\n 年份: ${res.releaseDate || "未知"}\n 评分: ${res.score || "无"}\n 时长: ${res.duration + " m" || "无"}\n
\n
\n
\n
\n
剧照: \n
${imagesHtml}
\n
\n
${res.releaseDate || ""}
\n `);
this.getBean("TranslatePlugin").translate(res.carNum, !1).then();
})).catch((err => {
console.error(err);
$(".movie-info-container").html(`\n
加载失败: ${err.message}
\n `);
}));
}
handleLongImg(carNum2) {
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
$(".movie-gallery .image-list").prepend('
正在加载缩略图
');
const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2);
imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl);
}));
}
handleMagnets(movieId) {
javDbApi.getMagnets(movieId).then((magnetList => {
let magnetsHtml = "";
if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) {
let magnet = magnetList[i], oddClass = "";
i % 2 == 0 && (oddClass = "odd");
magnetsHtml += `\n
\n
\n
\n \n
\n
${magnet.created_at}
\n
\n `;
} else magnetsHtml = '
暂无磁力信息';
$("#magnets-content").html(magnetsHtml);
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
$(el).parent().append($("
');
$(".navbar-search").css("margin-left", "0").before('\n \n ');
this.handleResize();
$(window).resize((() => {
this.handleResize();
}));
$("#historyBtn,#miniHistoryBtn").on("click", (event => this.openHistory()));
}
isJavBus && utils.loopDetector((() => $("#setting-btn").length), (() => {
$("#top-right-box").append('\n \n 鉴定记录\n \n ');
$("#historyBtn,#miniHistoryBtn").on("click", (event => this.openHistory()));
}), 1, 1e4, !1);
this.bindClick();
}
openHistory() {
layer.open({
type: 1,
title: "鉴定记录",
content: '\n \n
\n
\n
\n
重置\n
\n
\n \n \n \n \n \n
\n
\n
\n ',
scrollbar: !1,
shadeClose: !0,
area: utils.getResponsiveArea([ "70%", "90%" ]),
anim: -1,
success: async layero => {
await this.loadTableData();
$(".layui-layer-content").on("click", "#clearSearchbtn", (async event => {
$("#searchCarNum").val("");
$("#dataType").val("all");
await this.reloadTable();
$("#allSelectBox").hide();
})).on("focusout keydown", "#searchCarNum", (async event => {
if ("focusout" === event.type || "Enter" === event.key) {
"Enter" === event.key && event.preventDefault();
if ("keydown" === event.type && "Enter" !== event.key) return;
await this.reloadTable();
}
})).on("click", ".table-link-param", (async event => {
let $targetEl = $(event.currentTarget);
$("#searchCarNum").val($targetEl.text());
await this.reloadTable();
})).on("change", "#dataType", (async () => {
await this.reloadTable();
}));
},
end: () => {
if (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 menu = e.target.closest(".sub-btns").querySelector(".sub-btns-menu");
document.querySelectorAll(".sub-btns-menu.show").forEach((openMenu => {
openMenu !== menu && openMenu.classList.remove("show");
}));
menu.classList.toggle("show");
} else document.querySelectorAll(".sub-btns-menu.show").forEach((menu => {
menu.classList.remove("show");
}));
}));
$(document).on("click", ".history-deleteBtn, .history-filterBtn, .history-favoriteBtn, .history-hasDownBtn, .history-hasWatchBtn, .history-detailBtn", (event => {
event.preventDefault();
event.stopPropagation();
const $btn = $(event.currentTarget), $menuBox = $btn.closest(".action-btns"), carNum2 = $menuBox.attr("data-car-num"), aHref = $menuBox.attr("data-href"), handleAction = async status => {
await storageManager.saveCar({
carNum: carNum2,
url: aHref,
names: null,
actionType: status
});
window.refresh();
await this.reloadTable();
};
$btn.hasClass("history-filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER))) : $btn.hasClass("history-favoriteBtn") ? handleAction(Status_FAVORITE).then() : $btn.hasClass("history-hasDownBtn") ? handleAction(Status_HAS_DOWN).then() : $btn.hasClass("history-hasWatchBtn") ? handleAction(Status_HAS_WATCH).then() : $btn.hasClass("history-deleteBtn") ? this.handleDelete(event, carNum2) : $btn.hasClass("history-detailBtn") && this.handleClickDetail(event, {
carNum: carNum2,
url: aHref
}).then();
}));
$(document).on("click", ".multiple-history-deleteBtn, .multiple-history-filterBtn, .multiple-history-favoriteBtn, .multiple-history-hasDownBtn, .multiple-history-hasWatchBtn", (event => {
event.preventDefault();
event.stopPropagation();
const $btn = $(event.currentTarget);
let selectedRows = this.tableObj.getSelectedData(), handleText = "", handleStatus = "";
if ($btn.hasClass("multiple-history-filterBtn")) {
handleText = "屏蔽";
handleStatus = Status_FILTER;
} else if ($btn.hasClass("multiple-history-favoriteBtn")) {
handleText = "收藏";
handleStatus = Status_FAVORITE;
} else if ($btn.hasClass("multiple-history-hasDownBtn")) {
handleText = "已下载";
handleStatus = Status_HAS_DOWN;
} else if ($btn.hasClass("multiple-history-hasWatchBtn")) {
handleText = "已观看";
handleStatus = Status_HAS_WATCH;
} else if ($btn.hasClass("multiple-history-deleteBtn")) {
handleText = "移除";
handleStatus = "delete";
}
utils.q(event, `当前已勾选${selectedRows.length}条数据, 是否全标记为 ${handleText}?`, (async () => {
let loadObj = loading();
try {
if ("delete" === handleStatus) {
const deleteCarNumList = selectedRows.map((row => row.carNum)), removedCount = await storageManager.batchRemoveCars(deleteCarNumList);
removedCount > 0 ? show.ok(`已成功删除 ${removedCount} 个番号`) : !1 === removedCount && show.error("提供的番号中没有一个存在于列表中。");
} else {
const carRecords = JSON.parse(JSON.stringify(selectedRows));
carRecords.forEach((record => {
record.actionType = handleStatus;
}));
await storageManager.saveCarList(carRecords);
show.ok("操作成功");
}
this.tableObj.deselectRow();
this.reloadTable().then();
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}));
}));
}
async getDataList(page, size, sort) {
let dataList = await storageManager.getCarList();
this.allCount = dataList.length;
this.filterCount = 0;
this.favoriteCount = 0;
this.hasDownCount = 0;
this.hasWatchCount = 0;
dataList.forEach((item => {
switch (item.status) {
case Status_FILTER:
this.filterCount++;
break;
case Status_FAVORITE:
this.favoriteCount++;
break;
case Status_HAS_DOWN:
this.hasDownCount++;
break;
case Status_HAS_WATCH:
this.hasWatchCount++;
}
}));
$('#dataType option[value="all"]').text(`所有 (${this.allCount})`);
$('#dataType option[value="filter"]').text(`🚫 已屏蔽 (${this.filterCount})`);
$('#dataType option[value="favorite"]').text(`⭐ 已收藏 (${this.favoriteCount})`);
$('#dataType option[value="hasDown"]').text(`📥️ 已下载 (${this.hasDownCount})`);
$('#dataType option[value="hasWatch"]').text(`🔍 已观看 (${this.hasWatchCount})`);
const dataType = $("#dataType").val();
let filterDataList = "all" === dataType ? dataList : dataList.filter((item => item.status === dataType));
const searchCarNum = $("#searchCarNum").val().trim();
if (searchCarNum) {
let tempCarNum = searchCarNum.toLowerCase().replace("-c", "").replace("-uc", "").replace("-4k", "");
filterDataList = filterDataList.filter((item => {
const result1 = item.carNum.toLowerCase().includes(tempCarNum);
const result2 = (item.names ? item.names : "").toLowerCase().includes(tempCarNum);
return result1 || result2;
}));
}
if (sort && sort.length > 0) {
const sorter = sort[0], field = sorter.field, dir = sorter.dir;
filterDataList.sort(((a, b) => {
const valA = a[field], valB = b[field], isValANullish = null == valA || "" === valA, isValBNullish = null == valB || "" === valB;
return isValANullish && !isValBNullish ? 1 : !isValANullish && isValBNullish ? -1 : isValANullish && isValBNullish ? 0 : valA < valB ? "asc" === dir ? -1 : 1 : valA > valB ? "asc" === dir ? 1 : -1 : 0;
}));
}
const totalCount = filterDataList.length, maxPage = Math.ceil(totalCount / size), startIndex = (page - 1) * size, endIndex = startIndex + size;
filterDataList = filterDataList.slice(startIndex, endIndex);
return {
maxPage: maxPage,
dataList: filterDataList,
totalCount: totalCount
};
}
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 (url, ajaxConfig, params) => {
const page = params.page, size = params.size, sort = params.sort;
return await this.getDataList(page, size, sort);
},
dataReceiveParams: {
last_page: "maxPage",
last_row: "totalCount",
data: "dataList"
},
paginationSize: 50,
paginationSizeSelector: [ 50, 100, 1e3, 99999 ],
paginationCounter: (pageSize, currentRow, currentPage, totalRows, totalPages) => `共 ${totalRows} 条记录`,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
selectableRowsPersistence: !1,
index: "carNum",
columns: [ {
formatter: "rowSelection",
titleFormatter: "rowSelection",
hozAlign: "center",
headerSort: !1,
responsive: 0,
width: 40,
titleFormatterParams: {
rowRange: "active"
},
cellClick: (e, cell) => {
cell.getRow().toggleSelect();
}
}, {
title: "番号",
field: "carNum",
width: 120,
sorter: "string",
responsive: 0,
formatter: (cell, formatterParams, onRendered) => {
const carNumString = cell.getData().carNum, hyphenIndex = carNumString.indexOf("-");
if (-1 === hyphenIndex) return carNumString;
return `${carNumString.substring(0, hyphenIndex + 1)}${carNumString.substring(hyphenIndex + 1)}`;
}
}, {
title: "演员",
field: "names",
minWidth: 200,
sorter: "string",
responsive: 5,
headerSort: !0,
formatter: (cell, formatterParams, onRendered) => (cell.getData().names || "").split(" ").filter((name2 => "" !== name2.trim())).map((name2 => `${name2}`)).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: (cell, formatterParams, onRendered) => {
let url = cell.getData().url;
return url ? url.includes("javdb") ? 'Javdb' : url.includes("javbus") ? 'JavBus' : url.includes("123av") ? '123Av' : `${url}` : "";
}
}, {
title: "状态",
field: "status",
width: 100,
sorter: "string",
responsive: 1,
headerSort: !1,
formatter: (cell, formatterParams, onRendered) => {
const statusValue = cell.getData().status;
let color = "", text = "";
switch (statusValue) {
case "filter":
color = "#de3333";
text = "🚫 屏蔽";
break;
case "favorite":
color = "#25b1dc";
text = "⭐ 收藏";
break;
case "hasDown":
color = "#7bc73b";
text = "📥️ 已下载";
break;
case "hasWatch":
color = "#d7a80c";
text = "🔍 已观看";
break;
default:
text = statusValue;
}
return `${text}`;
}
}, {
title: "备注",
field: "remark",
width: 100,
sorter: "string",
responsive: 6
}, {
title: "操作",
sorter: "string",
minWidth: 150,
cssClass: "action-cell-dropdown",
responsive: 0,
headerSort: !1,
formatter: (cell, formatterParams, onRendered) => {
const item = cell.getData();
onRendered((() => {
var _a2;
null == (_a2 = cell.getElement().querySelector(".history-editBtn")) || _a2.addEventListener("click", (e => {
this.editRecord(item);
}));
}));
return `\n \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", ((data, rows, selected, deselected) => {
const $allSelectBox = $("#allSelectBox"), $filterBox = $("#filterBox");
if (data && data.length > 0) {
$filterBox.hide();
$allSelectBox.show();
} else {
$filterBox.show();
$allSelectBox.hide();
}
}));
this.tableObj.on("rowDblClick", (function(e, row) {
row.toggleSelect();
}));
this.tableObj.on("tableBuilt", (async () => {}));
}
handleDelete(event, carNum2) {
utils.q(event, `是否移除${carNum2}?`, (async () => {
await storageManager.removeCar(carNum2);
this.reloadTable().then();
}));
}
async handleClickDetail(event, data) {
if (isJavDb) if (data.carNum.includes("FC2-")) {
const movieId = this.parseMovieId(data.url);
this.getBean("Fc2Plugin").openFc2Dialog(movieId, data.carNum, data.url);
} else {
if (!data.url) {
window.open("/search?q=" + data.carNum, "_blank");
return;
}
utils.openPage(data.url, data.carNum, !1, event);
}
if (isJavBus) {
let url = data.url;
if (url.includes("javdb")) if (data.carNum.includes("FC2-")) {
const movieId = this.parseMovieId(url);
await this.getBean("Fc2Plugin").openFc2Page(movieId, data.carNum, url);
} else window.open(url, "_blank"); else utils.openPage(data.url, data.carNum, !1, event);
}
}
async editRecord(item) {
const initialCarNum = item.carNum, initialNames = item.names || "", initialUrl = item.url || "", initialStatus = item.status, initialRemark = item.remark || "", textareaStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", inputStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;", editFormHtml = `\n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n
\n \n \n
\n
\n `;
layer.open({
type: 1,
title: `编辑记录: ${initialCarNum}`,
area: [ "500px", "650px" ],
content: editFormHtml,
btn: [ "保存", "取消" ],
success: (layero, index) => {
const autoResizeTextarea = $textarea => {
$textarea.css("height", "auto");
$textarea.css("height", $textarea[0].scrollHeight + 15 + "px");
}, $namesTextarea = $("#edit-names");
$namesTextarea.on("input", (function() {
autoResizeTextarea($(this));
}));
autoResizeTextarea($namesTextarea);
const $remarkTextarea = $("#edit-remark");
$remarkTextarea.on("input", (function() {
autoResizeTextarea($(this));
}));
autoResizeTextarea($remarkTextarea);
},
yes: async index => {
const newNames = $("#edit-names").val().trim(), newStatus = $("#edit-status").val(), newUrl = $("#edit-url").val().trim(), newRemark = $("#edit-remark").val().trim(), updatedRecord = {
...item,
names: newNames,
actionType: newStatus,
url: newUrl,
remark: newRemark
};
await storageManager.updateCarInfo(updatedRecord);
this.tableObj.setData();
layer.close(index);
}
});
}
}
class ReviewPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "floorIndex", 1);
__publicField(this, "isInit", !1);
}
getName() {
return "ReviewPlugin";
}
async handle() {
$(document).on("click", ".down-115", (async event => {
const magnet = $(event.currentTarget).data("magnet");
let loadObj = loading();
try {
await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet);
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
}));
if (window.isDetailPage) {
if (isJavDb) {
const movieId = this.parseMovieId(window.location.href);
await this.showReview(movieId);
await this.getBean("RelatedPlugin").showRelated($("#magnets-content"), movieId);
}
if (isJavBus) {
let carNum2 = this.getPageInfo().carNum;
const movies = await javDbApi.searchMovie(carNum2);
let movieId = null;
for (let i = 0; i < movies.length; i++) {
let item = movies[i];
if (item.number.toLowerCase() === carNum2.toLowerCase()) {
movieId = item.id;
break;
}
}
if (!movieId) return;
this.showReview(movieId, $("#sample-waterfall")).then();
}
}
}
async showReview(movieId, $eleBox) {
const enableLoadReview = await storageManager.getSetting("enableLoadReview", YES), $magnets = $eleBox || $("#magnets-content");
$magnets.append(`\n \n `);
$("#reviewsFold").on("click", (event => {
event.preventDefault();
event.stopPropagation();
const $text = $("#reviewsFold .toggle-text"), $icon = $("#reviewsFold .toggle-icon"), isFolded = "展开" === $text.text();
$text.text(isFolded ? "折叠" : "展开");
$icon.text(isFolded ? "▲" : "▼");
if (isFolded) {
$("#reviewsContainer").show();
$("#reviewsFooter").show();
if (!this.isInit) {
this.fetchAndDisplayReviews(movieId);
this.isInit = !0;
}
storageManager.saveSettingItem("enableLoadReview", YES);
} else {
$("#reviewsContainer").hide();
$("#reviewsFooter").hide();
storageManager.saveSettingItem("enableLoadReview", NO);
}
}));
$magnets.append('');
$magnets.append('');
enableLoadReview === YES && await this.fetchAndDisplayReviews(movieId);
}
async fetchAndDisplayReviews(movieId) {
const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter");
$reviewsContainer.append('获取评论中...
');
const reviewCount = await storageManager.getSetting("reviewCount", 20);
let dataList = null;
try {
dataList = await javDbApi.getReviews(movieId, 1, reviewCount);
} catch (e) {
e.toString().includes("簽名已過期") && show.error("生成签名失败, 请检查系统时间及时区是否正确!");
clog.error("获取评论失败:", e);
console.error("获取评论失败:", e);
} finally {
$("#reviewsLoading").remove();
}
if (!dataList) {
$reviewsContainer.append('\n \n ');
$("#retryFetchReviews").on("click", (async () => {
$("#retryFetchReviews").parent().remove();
await this.fetchAndDisplayReviews(movieId);
}));
return;
}
if (0 === dataList.length) {
$reviewsContainer.append('无评论
');
return;
}
const reviewKeywordList = await storageManager.getReviewFilterKeywordList();
this.displayReviews(dataList, $reviewsContainer, reviewKeywordList);
if (dataList.length === reviewCount) {
$reviewsFooter.html('\n \n 加载更多评论\n \n 已加载全部评论
\n ');
let currentPage = 1, $loadMoreReviews = $("#loadMoreReviews");
$loadMoreReviews.on("click", (async () => {
$loadMoreReviews.text("加载中...").prop("disabled", !0);
currentPage++;
let moreData;
try {
moreData = await javDbApi.getReviews(movieId, currentPage, reviewCount);
} catch (e) {
console.error("加载更多评论失败:", e);
} finally {
$loadMoreReviews.text("加载失败, 请点击重试").prop("disabled", !1);
}
if (moreData) {
this.displayReviews(moreData, $reviewsContainer, reviewKeywordList);
if (moreData.length < reviewCount) {
$loadMoreReviews.remove();
$("#reviewsEnd").show();
} else $loadMoreReviews.text("加载更多评论").prop("disabled", !1);
}
}));
} else $reviewsFooter.html('已加载全部评论
');
}
displayReviews(dataList, $container, reviewKeywordList) {
if (dataList.length) {
dataList.forEach((item => {
if (reviewKeywordList.some((keyword => item.content.includes(keyword)))) return;
const starsHtml = Array(item.score).fill('').join(""), content = item.content.replace(/ed2k:\/\/\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/|magnet:\?[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+|https?:\/\/[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+/g, (match => match.startsWith("ed2k://") ? `\n ${match}\n 115离线下载\n ` : match.startsWith("magnet:") ? `\n ${match}\n 115离线下载\n ` : match.startsWith("http://") || match.startsWith("https://") ? `\n ${match}\n ` : match)), commentHtml = `\n \n
#${this.floorIndex++}楼\n ${item.username}
${starsHtml} \n
${utils.formatDate(item.created_at)} \n 点赞:${item.likes_count}\n
${content}
\n
\n `;
$container.append(commentHtml);
}));
this.rightClickFilter();
}
}
async rightClickFilter() {
await storageManager.getSetting("enableTitleSelectFilter", YES) === YES && utils.rightClick(document.body, ".review-content", (async event => {
const selectedText = window.getSelection().toString();
if (selectedText) {
event.preventDefault();
await utils.q(event, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => {
await storageManager.saveReviewFilterKeyword(selectedText);
show.ok("操作成功, 刷新页面后生效");
}));
}
}));
}
}
class FilterTitleKeywordPlugin extends BasePlugin {
getName() {
return "FilterTitleKeywordPlugin";
}
async handle() {
if (!isDetailPage && !isFc2Page) return;
if (await storageManager.getSetting("enableTitleSelectFilter", YES) !== YES) return;
let targetSelector;
isJavDb ? targetSelector = ".title strong, .current-title" : isJavBus && (targetSelector = "h3");
utils.rightClick(document.body, targetSelector, (event => {
const selectedText = window.getSelection().toString();
if (selectedText) {
event.preventDefault();
let tempEvent = {
clientX: event.clientX,
clientY: event.clientY + 80
};
utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => {
await storageManager.saveTitleFilterKeyword(selectedText);
window.refresh();
utils.closePage();
}));
}
}));
}
}
const categoryMap = {
p: "可播放",
s: "單體作品",
d: "含磁鏈",
c: "含字幕",
1: "高中女生",
2: "其他戀物癖",
3: "眼鏡",
4: "制服外套",
5: "美少女",
6: "白人",
7: "處女",
8: "精選綜合",
9: "國外進口",
10: "4小時以上作品",
11: "戲劇",
12: "成人電影",
13: "性愛",
14: "乳交",
15: "熟女",
16: "婆婆",
17: "巨乳",
18: "中出",
19: "蘿莉塔",
20: "已婚婦女",
21: "凌辱",
22: "無毛",
23: "淫亂真實",
24: "多P",
25: "69",
26: "介紹影片",
27: "藝人",
28: "單體作品",
29: "捆綁",
30: "女大學生",
31: "緊縛",
32: "偶像",
33: "學校作品",
34: "妄想",
36: "淫語",
37: "女上位",
38: "自慰",
39: "跳蛋",
41: "輪姦",
42: "放尿",
43: "角色扮演",
44: "姐姐",
45: "第一人稱攝影",
46: "顏射",
47: "各種職業",
48: "蕩婦",
49: "內衣",
50: "玩具",
51: "出軌",
52: "強姦",
53: "監禁",
54: "亂倫",
55: "母親",
56: "溫泉",
57: "美臀",
58: "制服",
60: "SM",
61: "足交",
62: "高挑",
63: "M男",
64: "女同性戀",
65: "苗條",
66: "戶外",
67: "辣妹",
68: "潮吹",
69: "按摩",
70: "妓女",
71: "乳液",
72: "口交",
73: "舔陰",
74: "肛門・肛交",
75: "跳舞",
76: "按摩棒",
77: "手指插入",
78: "水手服",
79: "羞恥",
80: "首次亮相",
81: "吞精",
82: "明星臉",
83: "薄馬賽克",
84: "泳裝",
85: "數位馬賽克",
86: "企畫",
87: "投稿",
88: "業餘",
89: "裸體圍裙",
90: "女優按摩棒",
91: "美乳",
92: "孕婦",
93: "拘束",
94: "調教",
95: "和服,喪服",
96: "女忍者",
97: "紀錄片",
98: "巨大陰莖",
99: "身體意識",
100: "母乳",
101: "新娘,年輕妻子",
102: "手淫",
103: "戀物癖",
104: "胖女人",
105: "大小姐",
106: "連褲襪",
107: "戀腿癖",
108: "秘書",
109: "戀乳癖",
110: "濫交",
111: "惡作劇",
112: "獵豔",
113: "女傭",
114: "OL",
115: "護士",
116: "貓耳女",
117: "去背影片",
118: "角色扮演者",
120: "插入異物",
121: "獨立製作",
123: "深喉",
124: "接吻",
125: "短裙",
126: "學校泳裝",
127: "運動短褲",
128: "運動",
129: "迷你裙",
130: "倒追",
131: "大屁股",
132: "女同接吻",
133: "賽車女郎",
134: "瘦小身型",
135: "美容院",
136: "家教",
137: "黑人演員",
138: "灌腸",
139: "妹妹",
140: "寡婦",
141: "拳交",
142: "女醫生",
143: "浴衣",
144: "老闆娘,女主人",
145: "偷窺",
146: "平胸",
147: "變性者",
148: "主觀視角",
149: "奴隸",
150: "戰鬥行動",
151: "女戰士",
152: "特效",
153: "女主播",
154: "校服",
155: "藥物",
156: "猥褻穿著",
157: "白天出軌",
158: "女教師",
159: "露出",
160: "流汗",
161: "16小時以上作品",
162: "性騷擾",
163: "其他學生",
164: "故事集",
165: "洗澡",
166: "肌肉",
168: "局部特寫",
169: "模特兒",
170: "立即口交",
171: "汽車性愛",
172: "格鬥家",
173: "情侶",
175: "經典",
176: "催眠",
177: "緊身衣",
178: "戀愛",
179: "鴨嘴",
180: "糞便",
181: "娃娃",
182: "蘿莉角色扮演",
184: "爛醉如泥的",
185: "魔鬼系",
187: "處男",
188: "展場女孩",
189: "重印版",
190: "禮儀小姐",
191: "歷史劇",
192: "女裝人妖",
194: "寫真偶像",
195: "飲尿",
196: "殘忍畫面",
197: "騎乗位",
198: "絲襪、過膝襪",
199: "泡泡襪",
200: "性感的",
201: "女檢察官",
202: "排便",
203: "空中小姐",
204: "講師",
206: "曬黑",
207: "旗袍",
209: "食糞",
211: "雙性人",
212: "VR",
213: "感謝祭",
216: "兔女郎",
217: "超乳",
218: "全裸",
221: "正太控",
223: "女祭司",
224: "服務生",
225: "脫衣",
227: "3D",
230: "動畫人物",
232: "子宮頸",
233: "伴侶",
234: "觸手",
236: "給女性觀眾",
237: "車掌小姐",
238: "原作改編",
242: "訪問",
243: "正常",
244: "奇異的",
245: "女兒",
246: "年輕女孩",
248: "迷你裙警察",
249: "蠻橫嬌羞",
250: "教學",
253: "恐怖",
254: "西洋片",
258: "性轉換·女體化",
264: "公主",
265: "童年朋友",
266: "科幻",
267: "行動",
270: "修女",
271: "綜合短篇",
273: "滑稽模仿",
274: "飛特族",
277: "男同性戀",
280: "男性",
282: "亞洲女演員",
284: "冒險",
285: "模擬",
291: "韓國",
292: "愛好,文化",
293: "偷窺",
296: "懸疑",
297: "COSPLAY服飾",
300: "形象俱樂部",
305: "友誼",
311: "R-15",
312: "美少女電影",
314: "感官作品",
315: "導尿",
316: "亞洲",
318: "暗黑系",
323: "天賦",
325: "觸摸打字",
330: "素人作品",
335: "HDTV",
338: "素人",
339: "美腳",
340: "剃毛",
341: "痴漢",
342: "二穴同入",
343: "被外國人幹",
344: "刺青紋身",
345: "無碼流出",
346: "蒙面・面罩",
347: "4K",
348: "無碼破解",
349: "黑白配",
350: "唾液敷面",
351: "綜藝",
352: "高跟鞋",
353: "乳釘、穿孔、乳環",
354: "兩女一男",
355: "兩男兩女",
356: "兩男一女",
357: "口球",
358: "靴子",
360: "絕頂高潮",
361: "運動",
362: "純欲",
363: "御宅族",
364: "輔助自慰",
365: "老太婆",
366: "老年男性",
367: "心理驚悚",
368: "打屁股",
369: "夫妻交換",
370: "養尊處優",
371: "拉拉隊",
372: "假陽具",
373: "約會",
374: "不穿內褲",
375: "不穿胸罩",
376: "後入",
377: "媽媽的朋友",
378: "瑜伽·健身",
379: "白眼失神",
380: "鼻鉤",
381: "共演",
382: "蠟燭",
383: "養女",
384: "女王",
385: "搔癢",
386: "經歷告白",
387: "濕身",
388: "多毛",
389: "站立後入",
2001: "2001",
2002: "2002",
2003: "2003",
2004: "2004",
2005: "2005",
2006: "2006",
2007: "2007",
2008: "2008",
2009: "2009",
2010: "2010",
2011: "2011",
2012: "2012",
2013: "2013",
2014: "2014",
2015: "2015",
2016: "2016",
2017: "2017",
2018: "2018",
2019: "2019",
2020: "2020",
2021: "2021",
2022: "2022",
2023: "2023",
2024: "2024",
2025: "2025",
"lt-45": "45分鍾以內",
"45-90": "45-90分鍾",
"90-120": "90-120分鍾",
"gt-120": "120分鍾以上"
};
class BlacklistPlugin extends BasePlugin {
getName() {
return "BlacklistPlugin";
}
async addBlacklist(event) {
let tempEvent = {
clientX: event.clientX,
clientY: event.clientY + 80
};
const hasAddBlacklist = $("#addBlacklistBtn span").text().includes("已加入");
let dataInfo, title;
if (currentHref.includes("/tags")) {
const urlObj = new URL(currentHref);
urlObj.searchParams.delete("page");
const checkTag = $("#jhs-check-tag").text().trim();
dataInfo = {
starId: "no-" + checkTag,
name: "虚拟演员-" + checkTag,
allName: [ "虚拟演员" ],
role: "虚拟演员",
movieType: "虚拟演员",
blacklistUrl: urlObj.toString()
};
title = `是否将分类 ${checkTag} 加入到黑名单中?`;
hasAddBlacklist && (title = `分类 ${checkTag} 已在黑名单中, 是否从当前页开始追加屏蔽?`);
} else {
dataInfo = this.getActressPageInfo();
title = `是否将该演员 ${dataInfo.name} 加入到黑名单中?`;
hasAddBlacklist && (title = `演员 ${dataInfo.name} 已在黑名单中, 是否从当前页开始追加屏蔽?`);
}
const {starId: starId, name: name2, allName: allName, role: role, movieType: movieType, blacklistUrl: blacklistUrl} = dataInfo;
currentHref.includes("page") && !currentHref.includes("page=1") && (title += "
注意: 当前页面非第一页, 屏蔽数据将从此页面开始");
if (isJavBus) {
const afterStarParts = currentHref.split("/star/")[1].split("/");
if (afterStarParts.length > 1) {
parseInt(afterStarParts[1]) > 1 && (title += "
注意: 当前页面非第一页, 屏蔽数据将从此页面开始");
}
}
utils.q(tempEvent, title, (async () => {
const taskPlugin = this.getBean("TaskPlugin");
navigator.locks.request(taskPlugin.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
if (lock) {
this.loadObj = loading();
try {
await storageManager.addBlacklistItem({
starId: starId,
name: name2,
allName: allName,
role: role,
movieType: movieType,
url: blacklistUrl
});
await this.filterActorVideo(name2, starId);
const toast = show.ok(`屏蔽结束,是否跳转到最后一页: ${this.lastPageLink || "无"}`, {
duration: -1,
close: !0,
onClick: () => {
toast.closeShow();
this.lastPageLink && (window.location.href = this.lastPageLink);
}
});
} catch (e) {
clog.error(e);
const toast = show.error("发生错误, 是否填转到解析失败的那一页? (点击并跳转)", {
duration: -1,
close: !0,
onClick: () => {
toast.closeShow();
window.location.href = this.nextPageLink;
}
});
} finally {
this.loadObj.close();
}
} else show.error("当前有定时任务在后台执行中, 无法发起此操作");
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
}));
}));
}
async resetBtnTip() {
const taskPlugin = this.getBean("TaskPlugin"), lastCheckBlacklistTimeStr = localStorage.getItem(taskPlugin.lastCheckBlacklistTimeKey) || "无", checkBlacklist_intervalTime = await storageManager.getSetting("checkBlacklist_intervalTime", 12);
this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime", 8760);
$("#checkBlacklistBtn").attr("data-tip", `上次检测时间: ${lastCheckBlacklistTimeStr}; 检测间隔时间: ${checkBlacklist_intervalTime}小时`);
}
async openBlacklistDialog() {
const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting();
let html = `\n \n
\n
\n
\n
\n
\n
\n
\n
重置\n
\n\n
\n
\n
\n `;
layer.open({
type: 1,
title: "演员黑名单",
content: html,
scrollbar: !1,
area: utils.getResponsiveArea([ "80%", "90%" ]),
anim: -1,
success: async layero => {
await this.loadTableData();
$(".layui-layer-content").on("click", "#cleanQueryBtn", (async event => {
$("#searchValue").val("");
$("#dataType").val("");
$("#statusType").val("");
await this.reloadTable();
})).on("focusout keydown", "#searchValue", (async event => {
if ("focusout" === event.type || "Enter" === event.key) {
"Enter" === event.key && event.preventDefault();
if ("keydown" === event.type && "Enter" !== event.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", "#clearKeywordBlacklist", (async () => {
await this.clearKeywordBlacklist();
})).on("click", ".open-url", (event => {
event.preventDefault();
const $el = $(event.currentTarget), url = $el.attr("data-url"), name2 = $el.attr("data-name");
utils.openPage(url, name2, !0, event);
})).on("click", "#checkBlacklistBtn", (event => {
utils.q({
clientX: event.clientX,
clientY: event.clientY + 20
}, "是否手动检测黑名单?", (() => {
navigator.locks.request(taskPlugin.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
if (lock) {
await taskPlugin.loadConfig();
await taskPlugin.checkBlacklist(!0);
} else show.error("当前有定时任务在后台执行中, 无法发起手动任务");
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
}));
}));
}));
},
end: () => {
if (this.tableObj) {
this.tableObj.destroy();
this.tableObj = null;
}
window.refresh();
}
});
}
async reloadTable() {
if (!this.tableObj) return;
const tableData = await this.getTableData();
this.tableObj.setData(tableData);
}
async getTableData() {
const taskPlugin = this.getBean("TaskPlugin"), blacklist = await storageManager.getBlacklist(), blacklistCarList = await storageManager.getBlacklistCarList(), searchValue = $("#searchValue").val(), statusType = $("#statusType").val(), $dataType = $("#dataType"), dataType = $dataType.val(), urlType = $("#urlType").val(), blacklistCount = blacklist.length;
let actorCount = 0, actressCount = 0, noStarCount = 0;
const filteredBlacklist = blacklist.map((item => {
"actor" === item.role ? actorCount++ : "actress" === item.role ? actressCount++ : "虚拟演员" === item.role && noStarCount++;
let isUnCheck = !1;
item.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(item.lastPublishTime, this.checkBlacklist_ruleTime));
return {
...item,
isUnCheck: isUnCheck
};
})).filter((item => !(searchValue && !item.name.includes(searchValue)) && (("normal" !== statusType || !item.isUnCheck) && (!("stop" === statusType && !item.isUnCheck) && (dataType ? item.role === dataType : !("hasT" === urlType && !item.url.includes("t=") && !item.url.includes("/tags")) && ("noT" !== urlType || !item.url.includes("t=") && !item.url.includes("/tags")))))));
$dataType.html(`\n \n \n \n \n `);
$dataType.val(dataType);
const carListMap = new Map;
for (const car of blacklistCarList) {
const starId = car.starId;
carListMap.has(starId) || carListMap.set(starId, []);
carListMap.get(starId).push(car);
}
const finalResult = filteredBlacklist.map((item => {
const starId = item.starId, carList = carListMap.get(starId) || [];
return {
...item,
carList: carList,
count: carList.length
};
}));
this.currentCarCount = finalResult.reduce(((accumulator, currentObject) => accumulator + (currentObject.count || 0)), 0);
return finalResult;
}
async loadTableData() {
this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime") || 8760;
const tableData = await this.getTableData();
this.tableObj = new Tabulator("#table-container", {
layout: "fitColumns",
placeholder: "暂无数据",
virtualDom: !0,
data: tableData,
pagination: !0,
paginationMode: "local",
paginationSize: 20,
paginationSizeSelector: [ 20, 50, 100, 1e3, 99999 ],
paginationCounter: (pageSize, currentRow, currentPage, totalRows, totalPages) => `演员: ${totalRows} 番号总数: ${this.currentCarCount} `,
responsiveLayout: "collapse",
responsiveLayoutCollapse: !0,
columnDefaults: {
headerHozAlign: "center",
hozAlign: "center"
},
index: "starId",
columns: [ {
title: "演员",
field: "name",
sorter: "string",
minWidth: 100,
responsive: 0,
headerSort: !1,
formatter: (cell, formatterParams, onRendered) => {
const item = cell.getData();
return `${item.name}`;
}
}, {
title: "性别角色",
field: "role",
sorter: "string",
width: 120,
responsive: 5,
formatter: (cell, formatterParams, onRendered) => {
const role = cell.getData().role;
let content = role;
"actor" === role ? content = "男演员" : "actress" === role && (content = "女演员");
return content;
}
}, {
title: "影视类别",
field: "movieType",
sorter: "string",
width: 120,
responsive: 5,
formatter: (cell, formatterParams, onRendered) => {
const movieType = cell.getData().movieType;
let content = movieType;
"censored" === movieType ? content = "有码" : "uncensored" === movieType && (content = "无码");
return content;
}
}, {
title: "屏蔽类型",
field: "url",
sorter: "string",
minWidth: 120,
responsive: 4,
visible: isJavDb,
formatter: (cell, formatterParams, onRendered) => {
const item = cell.getData();
if ("虚拟演员" === item.role) {
const nameSplitList = item.name.split("-");
return `${nameSplitList.length > 0 ? nameSplitList[1] : item.name}`;
}
const tParam = utils.getUrlParam(item.url, "t");
if (!tParam) return "所有分类";
{
const tParamList = tParam.toString().split(",");
let selectTypeStr = "";
tParamList.forEach((i => {
let value = categoryMap[i];
selectTypeStr += value ? value + " " : "未知类别:" + i + " ";
}));
selectTypeStr = selectTypeStr.trim();
if (selectTypeStr) return `${selectTypeStr}`;
}
}
}, {
title: "番号数量",
field: "count",
sorter: "number",
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: (cell, formatterParams, onRendered) => {
let dataTip = "", content = "正常检测";
if (cell.getData().isUnCheck) {
dataTip = `停更${this.checkBlacklist_ruleTime / 24 / 365}年以上, 下轮任务不再进行检测`;
content = "停止检测";
}
return `${content}`;
}
}, {
title: "操作",
sorter: "string",
cssClass: "action-cell-dropdown",
minWidth: 150,
responsive: 0,
headerSort: !1,
formatter: (cell, formatterParams, onRendered) => {
const item = cell.getData();
onRendered((() => {
var _a2, _b;
null == (_a2 = cell.getElement().querySelector(".delete-btn")) || _a2.addEventListener("click", (e => {
const name2 = item.name, starId = item.starId;
name2 ? starId ? utils.q(e, `是否移除对 ${name2} 的屏蔽?`, (async () => {
await storageManager.removeBlacklistCarList(starId);
await storageManager.deleteBlacklistItem(starId);
show.info("操作成功");
this.reloadTable().then();
})) : show.error("获取starId失败") : show.error("获取名称失败");
}));
null == (_b = cell.getElement().querySelector(".keyword-btn")) || _b.addEventListener("click", (e => {
const prefixMap = item.carList.reduce(((dataMap, carItem) => {
const prefix = carItem.carNum.split("-")[0] + "-";
dataMap[prefix] = (dataMap[prefix] || 0) + 1;
return dataMap;
}), {}), sortedPrefixList = Object.entries(prefixMap).map((([prefix, count]) => ({
prefix: prefix,
count: count
}))).sort(((a, b) => b.count - a.count));
console.log(sortedPrefixList);
}));
}));
return '\n \x3c!-- 提取屏蔽词 --\x3e\n ✂️ 删除 \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 urlWithoutSortType = window.location.href.replace(/([&?])sort_type=[^&]+(&|$)/, "$1");
urlWithoutSortType = urlWithoutSortType.replace(/[&?]$/, "");
urlWithoutSortType = urlWithoutSortType.replace(/\?&/, "?");
let urlWithoutPageNumber = urlWithoutSortType;
urlWithoutPageNumber = urlWithoutPageNumber.replace(/([&?])page=\d+(&|$)/, "$1");
urlWithoutPageNumber = urlWithoutPageNumber.replace(/[&?]$/, "");
urlWithoutPageNumber = urlWithoutPageNumber.replace(/\?&/, "?");
urlWithoutPageNumber = urlWithoutPageNumber.replace(/\/(\d+)(?:\/(\d+))?(\?|$)/, ((match, id, page, suffix) => void 0 !== page ? `/${id}${suffix}` : match));
return urlWithoutPageNumber;
}
parseUrlId(url) {
if (!url) throw new Error("url未传入");
return new URL(url).pathname.split("/").filter((segment => "" !== segment.trim())).pop();
}
async filterAllVideo(actorName, $dom) {
let movieList, nextPageLink;
if ($dom) {
isJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove();
movieList = $dom.find(this.getSelector().requestDomItemSelector);
nextPageLink = $dom.find(this.getSelector().nextPageSelector).attr("href");
} else {
movieList = $(this.getSelector().itemSelector);
nextPageLink = $(this.getSelector().nextPageSelector).attr("href");
}
if (nextPageLink && 0 === movieList.length) {
show.error("解析列表失败");
throw new Error("解析列表失败");
}
for (const element of movieList) {
const $item = $(element), {carNum: carNum2, url: url, publishTime: publishTime} = this.getBoxCarInfo($item);
if (url && carNum2) try {
if (await storageManager.getCar(carNum2)) continue;
await storageManager.saveCar({
carNum: carNum2,
url: url,
names: actorName,
actionType: Status_FILTER,
publishTime: publishTime
});
clog.log("屏蔽演员番号", actorName, carNum2);
} catch (error) {
console.error(`保存失败 [${carNum2}]:`, error);
}
}
if (nextPageLink) {
show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink);
await new Promise((resolve => setTimeout(resolve, 500)));
const html = await gmHttp.get(nextPageLink), parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html"));
await this.filterAllVideo(actorName, next$dom);
} else {
show.ok("执行结束!");
window.refresh();
}
}
async filterActorVideo(actorName, starId, $dom) {
let {nextPageLink: nextPageLink} = await this.parseAndSaveFilterInfo($dom, actorName, starId);
this.nextPageLink = nextPageLink;
if (nextPageLink) {
this.lastPageLink = nextPageLink;
show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink);
let next$dom;
const pageNum = utils.getUrlParam(nextPageLink, "page") || 0, beyond60Plugin = this.getBean("Beyond60Plugin");
if (isJavDb && beyond60Plugin && pageNum > 60) {
let {html: html, nextUrl: nextUrl, hasMore: hasMore} = await beyond60Plugin.handleBeyond60(nextPageLink), mergeHtml = `\n ${html}
\n ${nextUrl ? `` : ""}\n `;
next$dom = utils.htmlTo$dom(mergeHtml);
} else {
clog.log("正在请求下一页内容:", nextPageLink);
const html = await gmHttp.get(nextPageLink);
next$dom = utils.htmlTo$dom(html);
}
await this.filterActorVideo(actorName, starId, next$dom);
} else {
show.ok("执行结束!");
window.refresh();
}
}
async parseAndSaveFilterInfo($dom, actorName, starId) {
let movieList, nextPageLink;
if ($dom) {
let tempIsJavBus = !1, selectorType = "javdb";
if ($dom.text().includes("javbus")) {
tempIsJavBus = !0;
selectorType = "javbus";
}
tempIsJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove();
movieList = $dom.find(this.getSelector(selectorType).requestDomItemSelector);
nextPageLink = $dom.find(this.getSelector(selectorType).nextPageSelector).attr("href");
} else {
movieList = $(this.getSelector().itemSelector);
nextPageLink = $(this.getSelector().nextPageSelector).attr("href");
}
if (nextPageLink && 0 === movieList.length) return {
nextPageLink: null,
lastPublishTime: null
};
const filterKeywordList = await storageManager.getTitleFilterKeyword();
let carDataList = [], lastPublishTime = null;
for (const element of movieList) {
const $item = $(element), {carNum: carNum2, url: url, publishTime: publishTime, title: title} = this.getBoxCarInfo($item);
lastPublishTime || (lastPublishTime = publishTime);
filterKeywordList.find((kw => title.includes(kw) || carNum2.startsWith(kw))) || url && carNum2 && carDataList.push({
carNum: carNum2,
url: url,
names: actorName,
actionType: Status_FILTER,
starId: starId,
publishTime: publishTime
});
}
await storageManager.batchSaveBlacklistCarList(carDataList);
return {
nextPageLink: nextPageLink,
lastPublishTime: lastPublishTime
};
}
async clearKeywordBlacklist() {
let filterKeywordList = await storageManager.getTitleFilterKeyword();
const processedKeywordsSet = new Set;
filterKeywordList.forEach((item => {
/^[a-z]{2,}-/i.test(item) && processedKeywordsSet.add(item);
}));
if (0 === processedKeywordsSet.size) {
show.info("没有需要清理的关键词数据");
return;
}
const carList = await storageManager.getCarList(), blacklistCarList = await storageManager.getBlacklistCarList(), carNumSet = new Set(carList.map((car => car.carNum))), keywords = Array.from(processedKeywordsSet), waitRemoveCarNumList = [], removableCarData = [];
for (const car of blacklistCarList) {
const carNum2 = car.carNum;
let matched = !1;
for (const keyword of keywords) if (carNum2.startsWith(keyword)) {
removableCarData.push({
carNum: carNum2,
names: car.names,
matchedKeyword: keyword
});
waitRemoveCarNumList.push(carNum2);
matched = !0;
break;
}
if (!matched && carNumSet.has(carNum2)) {
removableCarData.push({
carNum: carNum2,
names: car.names,
matchedKeyword: "已在鉴定记录中"
});
waitRemoveCarNumList.push(carNum2);
}
}
const carNumCount = waitRemoveCarNumList.length;
0 !== carNumCount ? layer.open({
type: 1,
title: `确认清理黑名单数据 (共 ${carNumCount} 个番号)`,
area: [ "80%", "70%" ],
anim: -1,
content: `\n \n
\n
以下匹配到 ${carNumCount} 个黑名单番号存在于屏蔽关键词中, 是否移除?
\n
\n
\n
\n `,
btn: [ "确定清理", "取消" ],
success: function(layero, index) {
new Tabulator("#wait-remove-table", {
data: removableCarData,
virtualDom: !0,
layout: "fitColumns",
pagination: !0,
paginationMode: "local",
paginationSize: 50,
paginationSizeSelector: [ 50, 100, 1e3, 99999 ],
columns: [ {
title: "演员",
field: "names"
}, {
title: "番号",
field: "carNum"
}, {
title: "匹配关键词",
field: "matchedKeyword"
} ],
locale: "zh-cn",
initialSort: [ {
column: "names",
dir: "asc"
} ],
langs: {
"zh-cn": {
pagination: {
first: "首页",
first_title: "首页",
last: "尾页",
last_title: "尾页",
prev: "上一页",
prev_title: "上一页",
next: "下一页",
next_title: "下一页",
all: "所有",
page_size: "每页行数"
}
}
}
});
},
yes: async index => {
const removedCount = await storageManager.batchRemoveBlacklistCars(waitRemoveCarNumList);
if (0 === removedCount) show.error("移除失败"); else {
show.ok(`🎉 清理完成!已移除${removedCount}个相关黑名单番号数据`);
await this.reloadTable();
layer.close(index);
}
},
btn2: function(index) {
layer.close(index);
}
}) : show.info("没有需要清理的黑名单番号数据");
}
}
class ListPageButtonPlugin extends BasePlugin {
getName() {
return "ListPageButtonPlugin";
}
async handle() {
if (!window.isListPage) return;
await this.createMenuBtn();
this.bindEvent();
await storageManager.getSetting("autoPage") === YES ? $("#sort-toggle-btn").hide() : this.sortItems().then();
}
async createMenuBtn() {
const showWaitCheckBtn = await storageManager.getSetting("showWaitCheckBtn", YES), showWaitDownBtn = await storageManager.getSetting("showWaitDownBtn", YES);
if (isJavDb) {
const isStarPage = currentHref.includes("/actors/");
let $el = $(".main-tabs, .tabs"), addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020", otherCss = "", blacklistItem = null;
if (isStarPage) {
$el = $(".toolbar, .section-addition").filter(":last");
const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo();
if (blacklist.find((item => item.starId === actressPageInfo.starId))) {
addBlacklistBtnText = "已加入黑名单";
addBlacklistBtnColor = "#885d5d";
}
} else currentHref.includes("/tags") && utils.loopDetector((() => $("#jhs-check-tag").text().trim()), (async () => {
const $addBlacklistBtn = $("#addBlacklistBtn");
$addBlacklistBtn.attr("data-tip", "将当前分类标签加入到黑名单, 后续有作品更新也会纳入屏蔽中");
const checkTag = $("#jhs-check-tag").text().trim();
if (!checkTag) return;
const tagStarId = "no-" + checkTag, blacklist = await storageManager.getBlacklist();
blacklistItem = blacklist.find((item => item.starId === tagStarId));
if (blacklistItem) {
$addBlacklistBtn.css("backgroundColor", "#885d5d");
$("#addBlacklistBtn span").text("已加入黑名单");
}
}));
const isFc2Page2 = currentHref.includes("advanced_search");
isFc2Page2 ? $el = $("h2.section-title") : otherCss = "flex-grow:1;";
const jhs_sortMethod = localStorage.getItem("jhs_sortMethod"), sortText = "当前排序方式: " + ("rateCount" === jhs_sortMethod ? "评价人数" : "date" === jhs_sortMethod ? "时间" : "默认");
$el.append(`\n \n \n `);
}
if (isJavBus) {
const isStarPage = currentHref.includes("/star/");
let addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020";
if (isStarPage) {
const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo();
if (blacklist.find((item => item.starId === actressPageInfo.starId))) {
addBlacklistBtnText = "已加入黑名单";
addBlacklistBtnColor = "#885d5d";
}
}
$(".masonry").parent().prepend(`\n \n `);
}
const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin");
newVideoPlugin && newVideoPlugin.showNewVideoCount().then();
}
bindEvent() {
$("#waitCheckBtn").on("click", (event => {
this.openWaitCheck(event).then();
}));
$("#waitDownBtn").on("click", (event => {
this.openFavorite(event).then();
}));
$("#newVideoBtn").on("click", (event => {
this.getBean("NewVideoPlugin").openDialog();
}));
$("#blacklistBtn").on("click", (event => {
this.getBean("BlacklistPlugin").openBlacklistDialog();
}));
$("#sort-toggle-btn").on("click", (event => {
const currentMethod = localStorage.getItem("jhs_sortMethod") || "default";
// 1. 定义排序状态的循环顺序
const sortCycle = ["default", "rateCount", "totalScore", "date"];
let nextIndex = sortCycle.indexOf(currentMethod) + 1;
if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0;
const newMethod = sortCycle[nextIndex];
// 2. 映射对应的文本
const methodText = {
default: "默认",
rateCount: "评价人数",
totalScore: "总分",
date: "时间"
}[newMethod];
$(event.target).text(`当前排序方式: ${methodText}`);
localStorage.setItem("jhs_sortMethod", newMethod);
this.sortItems().then();
}));
const blacklistPlugin = this.getBean("BlacklistPlugin");
$("#addBlacklistBtn").on("click", (async event => {
await blacklistPlugin.addBlacklist(event);
}));
$("#filterAllVideo").on("click", (async event => {
let tempEvent = {
clientX: event.clientX,
clientY: event.clientY + 80
}, $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10");
if (0 === $actor.length) {
show.error("获取演员名称失败");
return;
}
let actorName = $actor.text().trim().split(",")[0];
utils.q(tempEvent, "一键屏蔽已选分类的视频列表至鉴定记录中?", (async () => {
this.loadObj = loading();
try {
await blacklistPlugin.filterAllVideo(actorName);
window.refresh();
} catch (e) {
console.error(e);
} finally {
this.loadObj.close();
}
}));
}));
}
async sortItems() {
if (currentHref.includes("handle") || currentHref.includes("advanced_search")) return;
const autoPage = await storageManager.getSetting("autoPage");
if (isSearchPage || autoPage === YES) return;
const method = localStorage.getItem("jhs_sortMethod");
if (!method) return;
$(".movie-list .item").each((function(index) {
$(this).attr("data-original-index") || $(this).attr("data-original-index", index);
}));
const $container = $(".movie-list"), $items = $(".item", $container);
if ("default" === method) {
$items.sort((function(a, b) {
return $(a).data("original-index") - $(b).data("original-index");
})).appendTo($container);
} else {
const items = $items.get();
items.sort((function(a, b) {
// 按评价人数排序
if ("rateCount" === method) {
const getScore = el => {
const match = $(el).find(".score .value").text().match(/由(\d+)人/);
return match ? parseFloat(match[1]) : 0;
};
return getScore(b) - getScore(a);
}
// 新增:按总分排序 (平均分 * 评价人数)
else if ("totalScore" === method) {
const getTotalScore = el => {
const text = $(el).find(".score .value").text();
// 分别提取分数和人数,增加容错率
const scoreMatch = text.match(/([\d.]+)分/);
const countMatch = text.match(/由(\d+)人/);
const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0;
const count = countMatch ? parseFloat(countMatch[1]) : 0;
return score * count;
};
return getTotalScore(b) - getTotalScore(a);
}
// 按时间排序
else {
const getDate = el => {
const dateStr = $(el).find(".meta").text().trim();
return new Date(dateStr);
};
return getDate(b) - getDate(a);
}
}));
$container.empty().append(items);
}
}
async openWaitCheck(event) {
const maxCount = await storageManager.getSetting("waitCheckCount", 5);
let count = 0;
$(`${this.getSelector().itemSelector}:visible`).each(((i, el) => {
if (count >= maxCount) return !1;
const $box2 = $(el);
if ($box2.find(".status-tag").length > 0) return;
const {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2);
if (carNum2.includes("FC2-")) {
const movieId = this.parseMovieId(aHref);
this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, aHref);
} else {
let url = aHref + (aHref.includes("?") ? "&autoPlay=1" : "?autoPlay=1");
window.open(url);
}
count++;
}));
0 === count && show.info("没有需鉴定的视频");
}
async openFavorite() {
let favoriteList, openCount = await storageManager.getSetting("waitCheckCount", 5), randomOpenWaitDown = await storageManager.getSetting("randomOpenWaitDown", NO), dataList = await storageManager.getCarList();
favoriteList = randomOpenWaitDown === YES ? dataList.filter((item => item.status === Status_FAVORITE)).sort((() => Math.random() - .5)) : dataList.filter((item => item.status === Status_FAVORITE)).sort(((a, b) => b.createDate - a.createDate));
for (let i = 0; i < openCount; i++) {
if (i >= favoriteList.length) return;
let data = favoriteList[i], carNum2 = data.carNum, url = data.url;
if (carNum2.includes("FC2-")) {
const movieId = this.parseMovieId(url);
await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, url);
} else window.open(url);
clog.debug("打开已收藏", carNum2, url);
}
}
}
const translateText = async (text, sourceLang = "ja", targetLang = "zh-CN") => {
if (!text) throw new Error("翻译文本不能为空");
const url = "https://translate-pa.googleapis.com/v1/translate?" + new URLSearchParams({
"params.client": "gtx",
dataTypes: "TRANSLATION",
key: "AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA",
"query.sourceLanguage": sourceLang,
"query.targetLanguage": targetLang,
"query.text": text
}), res = await fetch(url);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
return (await res.json()).translation;
}, TAG_STYLES = {
IS_FILTERED: {
text: "🚫 已屏蔽",
color: "#de3333",
reasonType: "单番号屏蔽",
isCounted: !0,
countKey: "currentPageFilterCount"
},
IS_FAVORITE: {
text: "⭐ 已收藏",
color: "#25b1dc",
reasonType: "",
isCounted: !0,
countKey: "currentPageFavoriteCount"
},
IS_HAS_DOWN: {
text: "📥️ 已下载",
color: "#7bc73b",
reasonType: "",
isCounted: !0,
countKey: "currentPageHasDownCount"
},
IS_HAS_WATCH: {
text: "🔍 已观看",
color: "#d7a80c",
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 ListPagePlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "currentPageFilterCount", 0);
__publicField(this, "currentPageFavoriteCount", 0);
__publicField(this, "currentPageHasDownCount", 0);
__publicField(this, "currentPageHasWatchCount", 0);
__publicField(this, "currentPageKeywordFilterCount", 0);
__publicField(this, "currentPageActorFilterCount", 0);
__publicField(this, "currentPageWaitCheckCount", 0);
__publicField(this, "currentPageTotalCount", 0);
__publicField(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {});
__publicField(this, "writeQueue", Promise.resolve());
}
getName() {
return "ListPagePlugin";
}
async handle() {
this.cleanRepeatId();
this.replaceHdImg();
this.addJumpPageControl();
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 $expandButton = $(".tag-expand");
if (0 === $expandButton.length) return;
const isExpanded = "true" === localStorage.getItem("jhs_tag_expand"), $actorTagsContent = $(".actor-tags .content");
isExpanded && $actorTagsContent.hasClass("collapse") && $expandButton[0].click();
$expandButton.on("click", (function() {
const newState = !$actorTagsContent.hasClass("collapse");
localStorage.setItem("jhs_tag_expand", newState.toString());
}));
}
checkDom() {
if (!window.isListPage) return;
const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector);
if (!targetNode) {
console.error("没有找到容器节点!");
return;
}
const observer = new MutationObserver((async mutations => {
observer.disconnect();
try {
this.replaceHdImg();
this.addJumpPageControl();
this.fixBusTitleBox();
await this.doFilter();
await this.getBean("ListPageButtonPlugin").sortItems();
this.getBean("CoverButtonPlugin").addSvgBtn().then();
$(this.getSelector().itemSelector + " a").attr("target", "_blank");
this.getBean("AutoPagePlugin").checkLoad();
} finally {
observer.observe(targetNode, config);
}
})), config = {
childList: !0,
subtree: !1
};
observer.observe(targetNode, config);
}
fixBusTitleBox() {
if (!isJavBus) return;
$(this.getSelector().itemSelector).toArray().forEach((ele => {
var _a2;
let $box2 = $(ele);
if ($box2.find(".avatar-box").length > 0) return;
const title = (null == (_a2 = $box2.find("img").attr("title")) ? void 0 : _a2.trim()) || "";
$box2.find(".photo-info span:first").contents().first().wrap(`${title}`);
$box2.find("br").remove();
}));
}
cleanRepeatId() {
if (!isJavBus) return;
$("#waterfall_h").removeAttr("id").attr("id", "no-page");
const $waterfalls = $('[id="waterfall"]');
0 !== $waterfalls.length && $waterfalls.each((function() {
const $current = $(this);
if (!$current.hasClass("masonry")) {
$current.children().insertAfter($current);
$current.remove();
}
}));
}
async doFilter() {
if (!window.isListPage) return;
let movieList = $(this.getSelector().itemSelector).toArray();
if (movieList.length) {
await this.filterMovieList(movieList);
await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList);
isJavBus && await this.getBean("BusImgPlugin").logImageHeightsByRow();
}
}
async filterMovieList(movieList) {
utils.time("累计耗费时间");
utils.time("读取数据耗时");
const [carList, filterKeywordList, blacklist, blacklistCarList, settingObj] = await Promise.all([ storageManager.getCarList(), storageManager.getTitleFilterKeyword(), storageManager.getBlacklist(), storageManager.getBlacklistCarList(), storageManager.getSetting() ]), t1 = utils.time("读取数据耗时"), carNumSets = carList.reduce(((dataMap, item) => {
const statusKey = item.status;
dataMap.hasOwnProperty(statusKey) && dataMap[statusKey].add(item.carNum);
return dataMap;
}), {
[Status_FILTER]: new Set,
[Status_FAVORITE]: new Set,
[Status_HAS_DOWN]: new Set,
[Status_HAS_WATCH]: new Set
});
utils.time("组装数据耗时");
const starIdToRoleMap = new Map(blacklist.map((item => [ item.starId, item.role ]))), {actorCarNumToNameMap: actorCarNumToNameMap, actressCarNumToNameMap: actressCarNumToNameMap} = blacklistCarList.reduce(((dataMap, carItem) => {
const role = starIdToRoleMap.get(carItem.starId);
if (!role) {
clog.error("黑名单数据源丢失演员信息", carItem);
return dataMap;
}
const targetMap = "actor" === role ? dataMap.actorCarNumToNameMap : dataMap.actressCarNumToNameMap;
targetMap.has(carItem.carNum) || targetMap.set(carItem.carNum, carItem.names);
return dataMap;
}), {
actorCarNumToNameMap: new Map,
actressCarNumToNameMap: new Map
}), t2 = utils.time("组装数据耗时"), settings = {
showFilterItem: (null == settingObj ? void 0 : settingObj.showFilterItem) ?? NO,
showFilterActorItem: (null == settingObj ? void 0 : settingObj.showFilterActorItem) ?? NO,
showFilterKeywordItem: (null == settingObj ? void 0 : settingObj.showFilterKeywordItem) ?? NO,
showFavoriteItem: (null == settingObj ? void 0 : settingObj.showFavoriteItem) ?? YES,
showHasDownItem: (null == settingObj ? void 0 : settingObj.showHasDownItem) ?? YES,
showHasWatchItem: (null == settingObj ? void 0 : settingObj.showHasWatchItem) ?? YES,
showAllItem: (null == settingObj ? void 0 : settingObj.showAllItem) ?? NO,
tagPosition: (null == settingObj ? void 0 : settingObj.tagPosition) || "rightTop",
movieShowType: (null == settingObj ? void 0 : settingObj.movieShowType) || "hide"
};
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(movieList.map((async ele => {
let $box2 = $(ele);
if (isJavBus && $box2.find(".avatar-box").length > 0) return;
const {carNum: carNum2, title: title} = this.getBoxCarInfo($box2), {filter: filter, favorite: favorite, hasDown: hasDown, hasWatch: hasWatch} = carNumSets, isFavorite = favorite.has(carNum2), isHasDown = hasDown.has(carNum2), isHasWatch = hasWatch.has(carNum2), isFiltered = filter.has(carNum2), isFilterActorMale = actorCarNumToNameMap.has(carNum2), isFilterActorFemale = actressCarNumToNameMap.has(carNum2), isFilterActor = isFilterActorMale || isFilterActorFemale, foundKeyword = filterKeywordList.find((kw => title.includes(kw) || carNum2.startsWith(kw))), isFilterKeyword = !!foundKeyword;
if (!isSearchPage) {
let shouldHide = settings.showFavoriteItem === NO && isFavorite || settings.showHasDownItem === NO && isHasDown || settings.showHasWatchItem === NO && isHasWatch || settings.showFilterItem === NO && isFiltered && !(isFavorite || isHasDown || isHasWatch) || settings.showFilterActorItem === NO && isFilterActor || settings.showFilterKeywordItem === NO && isFilterKeyword;
if ($box2.attr("data-movieShowType") !== settings.movieShowType) {
$box2.css("border", "");
$box2.children().css("visibility", "");
$box2.removeAttr("data-hide");
$box2.show();
}
const isCurrentlyHidden = $box2.attr("data-hide") === YES;
settings.showAllItem === YES && (shouldHide = !1);
if (shouldHide !== isCurrentlyHidden) {
shouldHide ? $box2.attr("data-hide", YES) : $box2.removeAttr("data-hide");
if ("hide" === settings.movieShowType) shouldHide ? $box2.hide() : $box2.show(); else {
if ("visibility" !== settings.movieShowType) throw new Error("movieShowType值有误:" + settings.movieShowType);
{
const $content = $box2.children(), borderStyle = shouldHide ? "1px solid rgb(192 176 176)" : "none", visibilityValue = shouldHide ? "hidden" : "visible";
$box2.css("border", borderStyle);
$content.css("visibility", visibilityValue);
}
}
$box2.attr("data-movieShowType") !== settings.movieShowType && $box2.attr("data-movieShowType", settings.movieShowType);
}
}
let tag = TAG_STYLES.IS_WAIT_CHECK, filterReason = null;
if (isFiltered) tag = TAG_STYLES.IS_FILTERED; else if (isFavorite) tag = TAG_STYLES.IS_FAVORITE; else if (isHasDown) tag = TAG_STYLES.IS_HAS_DOWN; else if (isHasWatch) tag = TAG_STYLES.IS_HAS_WATCH; else if (isFilterKeyword) {
tag = TAG_STYLES.IS_KEYWORD_FILTER;
filterReason = foundKeyword || "未知";
} else if (isFilterActorMale) {
tag = TAG_STYLES.IS_ACTOR_FILTER;
filterReason = actorCarNumToNameMap.get(carNum2) || "";
} else if (isFilterActorFemale) {
tag = TAG_STYLES.IS_ACTRESS_FILTER;
filterReason = actressCarNumToNameMap.get(carNum2) || "";
}
filterReason || (filterReason = tag.reasonType);
tag.isCounted && this[tag.countKey]++;
this.currentPageTotalCount++;
$box2.find(".status-tag").remove();
const tagPositionCss = "rightTop" === settings.tagPosition ? "right: 0; top:5px;" : "left: 0; top:5px;";
if (tag.text) {
const tagHtml = isJavDb ? `\n ${tag.text}\n ` : `\n ${tag.text}\n `;
isJavDb && $box2.find(".tags").append(tagHtml);
if (isJavBus) {
const $itemTag = $box2.find(".item-tag");
$itemTag.length ? $itemTag.append(tagHtml) : $box2.find(".photo-info > span > div").append(tagHtml);
}
}
await this.translate($box2);
})));
const t3 = utils.time("处理页面耗时"), t4 = utils.time("累计耗费时间");
$("#waitDownBtn span").text(`打开已收藏 (${carNumSets.favorite.size})`);
clog.log(`\n \n \n | ${t1} | \n ${t2} | \n
\n \n \n | ${t3} | \n ${t4} | \n
\n \n | 项目 | \n 数量 | \n 项目 | \n 数量 | \n
\n \n \n | 屏蔽单番号 | \n ${this.currentPageFilterCount} | \n 收藏 | \n ${this.currentPageFavoriteCount} | \n
\n \n \n | 屏蔽演员 | \n ${this.currentPageActorFilterCount} | \n 已下载 | \n ${this.currentPageHasDownCount} | \n
\n \n \n | 屏蔽关键词 | \n ${this.currentPageKeywordFilterCount} | \n 已观看 | \n ${this.currentPageHasWatchCount} | \n
\n \n \n | 待鉴定 | \n ${this.currentPageWaitCheckCount} | \n | \n | \n
\n \n \n | 总数 | \n ${this.currentPageTotalCount} | \n
\n
\n `);
}
async bindClick() {
let selector = this.getSelector();
$(selector.boxSelector).on("click", ".item img", (async event => {
event.preventDefault();
event.stopPropagation();
if ($(event.target).closest("div.meta-buttons").length) return;
const $box2 = $(event.target).closest(".item"), {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2);
let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", YES);
if (carNum2.includes("FC2-")) {
let movieId = this.parseMovieId(aHref);
this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref);
} else if (dialogOpenDetail === YES) {
utils.openPage(aHref, carNum2, !0, event);
this.$currentImage = null;
} else window.open(aHref);
}));
$(selector.boxSelector).on("click", ".item video", (async event => {
const video = event.currentTarget;
video.paused ? video.play().catch((e => console.error("播放失败:", e))) : video.pause();
event.preventDefault();
event.stopPropagation();
}));
$(selector.boxSelector).on("click", ".item .video-title", (async event => {
if ($(event.target).closest('[class^="jhs-match-"]').length) return;
const $box2 = $(event.currentTarget).closest(".item"), {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2);
if (carNum2.includes("FC2-")) {
event.preventDefault();
let movieId = this.parseMovieId(aHref);
this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref);
}
}));
$(selector.boxSelector).on("contextmenu", ".item img, .item video", (async event => {
event.preventDefault();
const $box2 = $(event.target).closest(".item"), {carNum: carNum2, url: url, publishTime: publishTime} = this.getBoxCarInfo($box2);
let $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"), actorName = "";
$actor.length && (actorName = $actor.text().trim().split(",")[0].replace("(無碼)", ""));
utils.q(event, `是否屏蔽番号 ${carNum2}?`, (async () => {
setTimeout((async () => {
actorName || (actorName = await this.parseActressName(url));
await storageManager.saveCar({
carNum: carNum2,
url: url,
names: actorName,
actionType: Status_FILTER,
publishTime: publishTime
});
window.refresh();
show.ok("操作成功");
}));
}));
}));
}
async parseActressName(url) {
let actorName = null;
if (await storageManager.getSetting("enableSaveActressCarInfo", NO) === YES) {
clog.debug("鉴定补录演员信息-已启用, 开始解析详情页");
clog.debug("开始解析演员详情页", url);
const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html);
isJavDb ? actorName = $dom.find(".female").prev().map(((i, el) => $(el).text())).get().join(" ") : isJavBus && (actorName = $dom.find('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" "));
clog.debug("解析到名称:", actorName);
}
return actorName;
}
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 settingObj = await storageManager.getSetting();
this.filterHotKey = settingObj.filterHotKey;
this.favoriteHotKey = settingObj.favoriteHotKey;
this.hasDownHotKey = settingObj.hasDownHotKey;
this.hasWatchHotKey = settingObj.hasWatchHotKey;
this.enableImageHotKey = settingObj.enableImageHotKey || NO;
this.clogHotKey = settingObj.clogHotKey;
this.foldCategoryHotKey = settingObj.foldCategoryHotKey;
this.showFilterItemHotKey = settingObj.showFilterItemHotKey;
this.showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey;
this.showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey;
this.showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey;
this.showHasDownItemHotKey = settingObj.showHasDownItemHotKey;
this.showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey;
this.showAllItemHotKey = settingObj.showAllItemHotKey;
const HOTKEY_REGISTRY = {
showFilterItemHotKey: "showFilterItem",
showFilterActorItemHotKey: "showFilterActorItem",
showFilterKeywordItemHotKey: "showFilterKeywordItem",
showFavoriteItemHotKey: "showFavoriteItem",
showHasDownItemHotKey: "showHasDownItem",
showHasWatchItemHotKey: "showHasWatchItem",
showAllItemHotKey: "showAllItem"
}, registerToggleHotkey = (hotkeyName, settingKey) => {
const hotkeyString = this[hotkeyName];
hotkeyString && HotkeyManager.registerHotkey(hotkeyString, (async event => {
const newVal = await storageManager.getSetting(settingKey) === YES ? NO : YES;
await storageManager.saveSettingItem(settingKey, newVal);
window.refresh();
}));
};
for (const hotkeyName in HOTKEY_REGISTRY) if (HOTKEY_REGISTRY.hasOwnProperty(hotkeyName)) {
registerToggleHotkey(hotkeyName, HOTKEY_REGISTRY[hotkeyName]);
}
if (this.enableImageHotKey === NO) return;
const handleAction = async (boxInfo, status) => {
setTimeout((async () => {
let actorName = await this.parseActressName(boxInfo.url);
await storageManager.saveCar({
carNum: boxInfo.carNum,
url: boxInfo.url,
names: actorName,
actionType: status,
publishTime: boxInfo.publishTime
});
window.refresh();
show.ok("操作成功");
}));
}, handlers = {};
this.filterHotKey && (handlers[this.filterHotKey] = boxInfo => {
handleAction(boxInfo, Status_FILTER);
});
this.favoriteHotKey && (handlers[this.favoriteHotKey] = boxInfo => {
handleAction(boxInfo, Status_FAVORITE);
});
this.hasDownHotKey && (handlers[this.hasDownHotKey] = boxInfo => {
handleAction(boxInfo, Status_HAS_DOWN);
});
this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = boxInfo => {
handleAction(boxInfo, Status_HAS_WATCH);
});
this.clogHotKey && HotkeyManager.registerHotkey(this.clogHotKey, (event => {
clog.toggleExpandCollapsed();
}));
this.foldCategoryHotKey && HotkeyManager.registerHotkey(this.foldCategoryHotKey, (event => {
const $btn = $("#foldCategoryBtn");
$btn.length && $btn[0].click();
}));
const registerImageHotkey = (key, handler) => {
HotkeyManager.registerHotkey(key, (event => {
const activeElement = document.activeElement;
if (!("INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable) && this.$currentImage) {
const $box2 = this.$currentImage.closest(".item"), boxInfo = this.getBoxCarInfo($box2);
handler(boxInfo);
}
}));
};
Object.entries(handlers).forEach((([key, handler]) => {
registerImageHotkey(key, handler);
}));
}
replaceHdImg(coverImgNodeList) {
coverImgNodeList && "string" == typeof coverImgNodeList.jquery && (coverImgNodeList = coverImgNodeList.toArray());
coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector));
isJavDb && coverImgNodeList.forEach((img => {
img.src = img.src.replace("thumbs", "covers");
img.title = "";
}));
if (isJavBus) {
const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = img => {
if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) {
img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1");
img.dataset.hdReplaced = "true";
img.dataset.title = img.title;
img.title = "";
}
}, DMM_THUMB_REGEX = /ps(\.jpg|\.jpeg|\.png)$/i, replaceWithHdForDMM = img => {
if (img.src && DMM_THUMB_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) {
img.src = img.src.replace(DMM_THUMB_REGEX, "pl$1");
img.dataset.hdReplaced = "true";
img.dataset.title = img.title;
img.title = "";
}
};
coverImgNodeList.forEach((img => {
replaceWithHd(img);
replaceWithHdForDMM(img);
}));
}
storageManager.getSetting("hoverBigImg", NO).then((hoverBigImg => {
hoverBigImg === YES && (window.imageHoverPreviewObj ? window.imageHoverPreviewObj.bindEvents() : window.imageHoverPreviewObj = new ImageHoverPreview({
selector: this.getSelector().coverImgSelector
}));
}));
}
async translate($box2) {
if (await storageManager.getSetting("translateTitle", YES) !== YES) return;
let content, carNum2, $title = $box2.find(".video-title");
if (isJavDb) {
content = $title.contents().filter(((i, node) => 3 === node.nodeType && "" !== node.textContent.trim())).text().trim();
carNum2 = $box2.find(".video-title strong").text().trim();
} else {
content = $box2.find("img").attr("data-title").trim();
carNum2 = $box2.find("a").attr("href").split("/").filter(Boolean).pop().trim();
}
if (this.cache[carNum2]) {
let _this = this;
$title.contents().each((function() {
3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " ");
}));
$title.attr("title", _this.cache[carNum2]);
} else translateText(content).then((result => {
if (isJavDb) {
$title.contents().each((function() {
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " ");
}));
$title.attr("title", result);
} else $title.text(result);
this.writeQueue = this.writeQueue.then((() => {
this.cache[carNum2] = result;
localStorage.setItem("jhs_translate", JSON.stringify(this.cache));
}));
})).catch((error => {
console.error("翻译失败:", error);
}));
}
async revertTranslation() {
$(this.getSelector().itemSelector).toArray().forEach((ele => {
let $box2 = $(ele);
const originalContent = $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title");
let carNum2;
isJavDb && (carNum2 = $box2.find(".video-title strong").text().trim());
const $title = $box2.find(".video-title");
$title.contents().each((function() {
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + originalContent + " ");
}));
$title.removeAttr("title");
}));
}
addJumpPageControl() {
if ($("#gemini-jump-page-control").length > 0) return;
if (0 === $(".pagination-link.is-current").length) return;
const currentPageNum = utils.getUrlParam(currentHref, "page") || 1, $input = $("", {
type: "number",
id: "jumpPageInput",
placeholder: "页码",
min: "1",
style: "width: 60px; margin-left: 10px; padding: 10px; border: 1px solid #ccc; font-size: 14px;",
value: currentPageNum + 1
}), $button = $("", {
text: "跳转",
style: "margin-left: 5px; padding: 9px 8px; cursor: pointer; border: 1px solid #ccc; background-color: #f0f0f0; font-size: 14px;"
}), $jumpLi = $("", {
id: "gemini-jump-page-control"
}).append($input).append($button);
$(".pagination-list").append($jumpLi);
const jumpToPage = () => {
const pageNumber = parseInt($input.val(), 10);
if (isNaN(pageNumber) || pageNumber < 1) {
$input.focus();
return;
}
const newUrl = new URL(window.location.href);
newUrl.searchParams.set("page", pageNumber.toString());
window.location.href = newUrl.toString();
};
$button.on("click", jumpToPage);
$input.on("keypress", (function(e) {
if (13 === e.which) {
jumpToPage();
e.preventDefault();
}
}));
}
}
class AutoPagePlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "preloadDistance", 500);
__publicField(this, "currentPage", this.getInitialPageNumber());
__publicField(this, "pageItems", []);
}
getName() {
return "AutoPagePlugin";
}
async initCss() {
return "\n \n ";
}
async handle() {
this.waterfall().then();
}
getInitialPageNumber() {
if (isJavBus) {
const match = currentHref.match(/\/(page|star\/[^/]+)\/(\d+)/);
return match ? parseInt(match[2], 10) : 1;
}
if (isJavDb) {
const match = currentHref.match(/[?&]page=(\d+)/);
return match ? parseInt(match[1], 10) : 1;
}
return 1;
}
async waterfall() {
if (await this.shouldDisablePaging()) return;
const selector = this.getSelector();
this.container = document.querySelector(selector.boxSelector);
if (!this.container) {
console.error("没有找到容器节点,停止瀑布流!");
return;
}
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 nextLink = document.querySelector(selector.nextPageSelector);
this.nextUrl = null == nextLink ? void 0 : nextLink.href;
this.hasMore = !!this.nextUrl;
setTimeout((() => {
this.checkLoad();
}), 1e3);
this.hasMore || this.setState("waterfall-no-more", "已经到底了");
}
async loadNextPage() {
var _a2;
if (await storageManager.getSetting("autoPage", YES) === NO) {
this.setState("waterfall-loading", "");
return;
}
if (this.isLoading || !this.nextUrl) return;
this.isLoading = !0;
this.setState("waterfall-loading", "加载中...");
const selector = this.getSelector();
try {
const pageNum = utils.getUrlParam(this.nextUrl, "page");
let maxPage = 60;
currentHref.includes("c11") && (maxPage = 30);
if (isJavDb && pageNum > maxPage || currentHref.includes("month")) {
const beyond60Plugin = this.getBean("Beyond60Plugin");
if (beyond60Plugin) {
const {html: html2, nextUrl: nextUrl, hasMore: hasMore} = await beyond60Plugin.handleBeyond60(this.nextUrl);
if (html2) {
const pageTop2 = this.container.scrollHeight;
this.pageItems.push({
page: this.currentPage + 1,
top: pageTop2,
url: this.nextUrl
});
$(".movie-list").append(html2);
}
this.hasMore = hasMore;
this.nextUrl = nextUrl;
const $ul = beyond60Plugin.createPagination(pageNum, hasMore);
$(".pagination").html($ul);
this.setState("waterfall-loading", "");
this.hasMore || this.setState("waterfall-no-more", "已经到底了");
return;
}
}
const html = await gmHttp.get(this.nextUrl);
clog.log("请求下一页内容:", this.nextUrl);
const $dom = utils.htmlTo$dom(html);
isJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove();
let itemList = $dom.find(this.getSelector().requestDomItemSelector);
const currentBoxCarInfoList = this.getBoxCarInfoList(), nextPageBoxCarInfoList = this.getBoxCarInfoList(itemList);
if (this.checkDuplicateCarNumbers(currentBoxCarInfoList, nextPageBoxCarInfoList)) {
this.nextUrl = null;
this.hasMore = !1;
this.setState("waterfall-error", "翻页内容出现重复数据, 可能首页已更新了新视频 或 页码受JavDB限制, 停止瀑布流");
return;
}
const pageTop = this.container.scrollHeight;
this.pageItems.push({
page: this.currentPage + 1,
top: pageTop,
url: this.nextUrl
});
const listPagePlugin = this.getBean("ListPagePlugin");
let coverImgNodeList = $dom.find(this.getSelector().coverImgSelector);
listPagePlugin.replaceHdImg(coverImgNodeList);
$(this.getSelector().boxSelector).append(itemList);
this.nextUrl = null == (_a2 = $dom.find(selector.nextPageSelector)) ? void 0 : _a2.attr("href");
this.hasMore = !!this.nextUrl;
let pagination = $dom.find(".pagination");
$(".pagination").replaceWith(pagination);
this.setState("waterfall-loading", "");
this.hasMore || this.setState("waterfall-no-more", "已经到底了");
} catch (e) {
clog.error("加载失败:", e);
this.setState("waterfall-error", "加载失败,点击重试");
} finally {
this.isLoading = !1;
}
}
checkScrollPosition() {
const scrollPosition = window.scrollY;
for (let i = this.pageItems.length - 1; i >= 0; i--) {
const page = this.pageItems[i];
if (scrollPosition >= page.top) {
if (this.currentPage !== page.page) {
this.currentPage = page.page;
this.updatePageUrl(page.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;
await storageManager.getSetting("autoPage", YES);
return [ "search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100" ].some((path => currentHref.includes(path)));
}
updatePageUrl_old(href) {
window.history.pushState({}, "", href);
if (isJavBus) {
const match = href.match(/\/(page|star\/.*?)\/(\d+)/), pageNumber = match ? parseInt(match[2], 10) : null;
document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁");
}
}
updatePageUrl(url) {
window.history.replaceState({}, "", url);
isJavBus && (document.title = document.title.replace(/第\d+頁/, `第${this.currentPage}頁`));
}
setState(state, text) {
this.loader.className = `jhs-scroll ${state}`;
this.loader.textContent = text;
}
}
class AliyunApi {
constructor(refresh_token) {
this.baseApiUrl = "https://api.aliyundrive.com";
this.refresh_token = refresh_token;
this.authorization = null;
this.default_drive_id = null;
this.backupFolderId = null;
}
async getDefaultDriveId() {
if (this.default_drive_id) return this.default_drive_id;
this.userInfo = await this.getUserInfo();
this.default_drive_id = this.userInfo.default_drive_id;
return this.default_drive_id;
}
async getHeaders() {
if (this.authorization) return {
authorization: this.authorization
};
this.authorization = await this.getAuthorization();
return {
authorization: this.authorization
};
}
async getAuthorization() {
let url = this.baseApiUrl + "/v2/account/token", data = {
refresh_token: this.refresh_token,
grant_type: "refresh_token"
};
try {
return "Bearer " + (await gmHttp.post(url, data)).access_token;
} catch (e) {
throw e.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : e;
}
}
async getUserInfo() {
const headers = await this.getHeaders();
let url = this.baseApiUrl + "/v2/user/get";
return await gmHttp.post(url, {}, headers);
}
async deleteFile(file_id, drive_id = null) {
if (!file_id) throw new Error("未传入file_id");
drive_id || (drive_id = await this.getDefaultDriveId());
let data = {
file_id: file_id,
drive_id: drive_id
}, url = this.baseApiUrl + "/v2/recyclebin/trash";
const headers = await this.getHeaders();
await gmHttp.post(url, data, headers);
return {};
}
async createFolder(name2, drive_id = null, parent_folder_id = "root") {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/adrive/v2/file/createWithFolders", data = {
name: name2,
type: "folder",
parent_file_id: parent_folder_id,
check_name_mode: "auto_rename",
content_hash_name: "sha1",
drive_id: drive_id
};
const headers = await this.getHeaders();
return await gmHttp.post(url, data, headers);
}
async getFileList(parent_folder_id = "root", drive_id = null) {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/adrive/v3/file/list";
const data = {
drive_id: drive_id,
parent_file_id: parent_folder_id,
limit: 200,
all: !1,
url_expire_sec: 14400,
image_thumbnail_process: "image/resize,w_256/format,avif",
image_url_process: "image/resize,w_1920/format,avif",
video_thumbnail_process: "video/snapshot,t_120000,f_jpg,m_lfit,w_256,ar_auto,m_fast",
fields: "*",
order_by: "updated_at",
order_direction: "DESC"
}, headers = await this.getHeaders();
return (await gmHttp.post(url, data, headers)).items;
}
async uploadFile(folder_id, fileName, uploadContent, drive_id = null) {
show.info("请求存储空间中...");
let createFileUrl = this.baseApiUrl + "/adrive/v2/file/createWithFolders";
drive_id || (drive_id = await this.getDefaultDriveId());
let data = {
drive_id: drive_id,
part_info_list: [ {
part_number: 1
} ],
parent_file_id: folder_id,
name: fileName,
type: "file",
check_name_mode: "auto_rename"
};
const headers = await this.getHeaders(), createFileResult = await gmHttp.post(createFileUrl, data, headers), upload_id = createFileResult.upload_id, upload_file_id = createFileResult.file_id, upload_url = createFileResult.part_info_list[0].upload_url;
show.info("开始上传文件...");
await this._doUpload(upload_url, uploadContent);
await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", data = {
drive_id: drive_id,
file_id: upload_file_id,
upload_id: upload_id
}, headers);
}
_doUpload(upload_url, uploadContent) {
return new Promise(((resolve, reject) => {
$.ajax({
type: "PUT",
url: upload_url,
data: uploadContent,
contentType: " ",
processData: !1,
success: (res, status, xhr) => {
200 === xhr.status ? resolve({}) : reject(xhr);
},
error: xhr => {
clog.error("上传失败", xhr.responseText);
reject(xhr);
}
});
}));
}
async getDownloadUrl(file_id, drive_id = null) {
drive_id || (drive_id = await this.getDefaultDriveId());
let url = this.baseApiUrl + "/v2/file/get_download_url";
const headers = await this.getHeaders();
let data = {
file_id: file_id,
drive_id: drive_id
};
return (await gmHttp.post(url, data, headers)).url;
}
async _createBackupFolder(folderName) {
const fileList = await this.getFileList();
let folderObj = null;
for (let i = 0; i < fileList.length; i++) {
let file = fileList[i];
if (file.name === folderName) {
folderObj = file;
break;
}
}
if (!folderObj) {
show.info("不存在备份目录, 进行创建");
folderObj = await this.createFolder(folderName);
}
this.backupFolderId = folderObj.file_id;
}
async backup(folderName, fileName, uploadContent) {
if (this.backupFolderId) await this.uploadFile(this.backupFolderId, fileName, uploadContent); else {
await this._createBackupFolder(folderName);
await this.uploadFile(this.backupFolderId, fileName, uploadContent);
}
}
async getBackupList(folderName) {
let dataList;
if (this.backupFolderId) dataList = await this.getFileList(this.backupFolderId); else {
await this._createBackupFolder(folderName);
dataList = await this.getFileList(this.backupFolderId);
}
const fileList = [];
dataList.forEach((data => {
fileList.push({
name: data.name,
fileId: data.file_id,
createTime: data.created_at,
size: data.size
});
}));
return fileList;
}
}
class WebDavApi {
constructor(davUrl, username, password) {
this.davUrl = davUrl.endsWith("/") ? davUrl : davUrl + "/";
this.username = username;
this.password = password;
this.folderName = null;
}
_getAuthHeaders() {
return {
Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`,
Depth: "1"
};
}
_sendRequest(method, path, headers = {}, data) {
return new Promise(((resolve, reject) => {
const url = this.davUrl + path, allHeaders = {
...this._getAuthHeaders(),
...headers
};
GM_xmlhttpRequest({
method: method,
url: url,
headers: allHeaders,
data: data,
onload: response => {
if (response.status >= 200 && response.status < 300) resolve(response); else {
console.error(response);
reject(new Error(`请求失败 ${response.status}: ${response.statusText}`));
}
},
onerror: response => {
console.error("请求WebDav发生错误:", response);
reject(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确"));
}
});
}));
}
async backup(folderName, fileName, uploadContent) {
await this._sendRequest("MKCOL", folderName);
const path = folderName + "/" + fileName;
await this._sendRequest("PUT", path, {
"Content-Type": "text/plain"
}, uploadContent);
}
async getFileList(folderName) {
var _a2, _b, _c;
const xmlResponse = (await this._sendRequest("PROPFIND", folderName, {
"Content-Type": "application/xml"
}, '\n \n \n \n \n \n \n \n \n \n ')).responseText, items = (new DOMParser).parseFromString(xmlResponse, "text/xml").getElementsByTagNameNS("DAV:", "response"), fileList = [];
for (let i = 0; i < items.length; i++) {
if (0 === i) continue;
let item = items[i];
console.log(item);
const name2 = item.getElementsByTagNameNS("DAV:", "displayname")[0].textContent, size = (null == (_a2 = item.getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : _a2.textContent) || "0", createTime = (null == (_b = item.getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : _b.textContent) || (null == (_c = item.getElementsByTagNameNS("DAV:", "getlastmodified")[0]) ? void 0 : _c.textContent) || "";
"0" !== size && fileList.push({
fileId: name2,
name: name2,
size: Number(size),
createTime: createTime
});
}
fileList.reverse();
return fileList;
}
async deleteFile(fileId) {
let path = this.folderName + "/" + encodeURI(fileId);
await this._sendRequest("DELETE", path, {
"Cache-Control": "no-cache"
});
}
async getBackupList(folderName) {
this.folderName = folderName;
await this._sendRequest("MKCOL", folderName);
return this.getFileList(folderName);
}
async getFileContent(filePath) {
let path = this.folderName + "/" + filePath;
return (await this._sendRequest("GET", path, {
Accept: "application/octet-stream"
})).responseText;
}
}
class SettingPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "folderName", "JHS-数据备份");
__publicField(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 settingObj = await storageManager.getSetting();
let containerWidth = (null == settingObj ? void 0 : settingObj.containerWidth) ?? "100", containerColumns = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == settingObj ? void 0 : settingObj.containerColumns) ?? 5;
this.applyImageMode().then();
let containerWidthCss = `\n section .container{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n }\n .movie-list, .movie-list.v{\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `;
isJavBus && (containerWidthCss = `\n .container-fluid .row{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n margin: auto auto;\n }\n \n .container {\n max-width: 1000px !important;\n min-width: 80%;\n margin: auto auto;\n }\n \n .masonry {\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `);
return `\n \n `;
}
async handle() {
await storageManager.getSetting("enableClog", YES) === YES && clog.show();
if (isJavDb) {
let handleResize2 = function() {
if ($(".navbar-search").is(":hidden")) {
$(".mini-setting-box").hide();
$(".setting-box").show();
} else {
$(".mini-setting-box").show();
$(".setting-box").hide();
}
};
$("#navbar-menu-user .navbar-end").prepend('');
utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => {
$(".miniHistoryBtnBox").before('\n \n ');
handleResize2();
}));
$(window).resize(handleResize2);
}
if (isJavBus) {
utils.loopDetector((() => $("#waitCheckBtn").length), (() => {
$("#waitCheckBtn").parent().append('\n \n ');
}), 1, 1e4, !1);
isDetailPage && $("h3").before('\n \n ');
}
$(".main-nav, .container-fluid").on("click", "#setting-btn, #mini-setting-btn", (() => {
clog.lowZIndex();
this.openSettingDialog();
}));
$(".main-nav, .container-fluid").on("mouseenter", ".setting-box", (async () => {
$(".simple-setting").html(await 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", (async () => {
$(".mini-simple-setting").html(await this.simpleSetting()).show();
this.initSimpleSettingForm().then();
clog.lowZIndex();
})).on("mouseleave", ".mini-setting-box", (() => {
$(".mini-simple-setting").html("").hide();
}));
}
async openSettingDialog(defaultActivePanel = "backup-panel", fun) {
const buttonsHTML = this.cacheItems.map((item => `\n \n
${item.text}
\n
\n \n \n
\n
\n `)).join("");
let videoQualityHtml = "";
qualityOptions.forEach((option => {
option.canSelect && (videoQualityHtml += ``);
}));
const coverButtonPlugin = this.getBean("CoverButtonPlugin");
let settingHtml = `\n \n
\n \n \n \n \n \n \n \n \n
\n \n
\n
\n \n \x3c!-- 阿里云盘面板 --\x3e\n
\n
\n \n \n \n
\n \n
\n
\n
refresh_token:\n
\n \n
\n
\n \n
\n \n
\n
WebDav备份\n
\n \n \n
\n
\n
\n
\n
\n
\n \n \n \x3c!-- 基础设置面板 --\x3e\n
\n
\n
已鉴定标签展示位置:\n
\n \n
\n
\n \n
\n
已鉴定内容处理方式:\n
\n \n
\n
\n \n
\n
\n 鉴定补录演员信息 ❓\n \n
\n \n
\n
\n \n
\n \n
\n \n 列表页功能按钮\n \n
\n \n
\n \n
\n \n
\n
打开待鉴定|已收藏 窗口数:\n
\n \n
\n
\n \n
\n \n \n
\n \n
\n \n 封面快捷按钮\n \n
\n \n
\n
\n ${coverButtonPlugin.screenSvg}长缩略图:\n \n
\n \n
\n
\n \n
\n
\n ${coverButtonPlugin.videoSvg}预览视频:\n \n
\n \n
\n
\n \n
\n
\n ${coverButtonPlugin.handleSvg}鉴定按钮:\n \n
\n \n
\n
\n \n
\n
\n ${coverButtonPlugin.siteSvg}第三方跳转:\n \n
\n \n
\n
\n \n
\n
\n ${coverButtonPlugin.copySvg}复制按钮:\n \n
\n \n
\n
\n \n
\n\n
\n
预览视频默认画质:\n
\n \n
\n
\n \n
\n
评论区条数:\n
\n \n
\n
\n \n
\n
\n 高亮已收藏演员 ❓\n \n
\n \n
\n
\n \n
\n\n
\n \n
\n
请求超时时间(毫秒):\n
\n \n
\n
\n \n
\n \n
\n \n
\n
\n 启用控制台日志:\n \n
\n \n
\n
\n\n
\n
\n \n \x3c!-- 定时任务 --\x3e\n
\n \n
\n
\n
请求间隔时间(毫秒):\n
\n \n
\n
\n \n
\n \n
\n
自动检测屏蔽黑名单演员\n
\n
\n 任务开关: ❓ \n \n
\n \n
\n
\n
\n
任务间隔时间:\n
\n \n
\n
\n
\n
检测规则:\n
\n \n
\n
\n
\n \n
\n
自动同步已收藏的演员\n
\n
\n 任务开关: ❓ \n \n
\n \n
\n
\n
\n
任务间隔时间:\n
\n \n
\n
\n
\n \n
\n
自动检测已收藏演员的最新作品\n
\n
\n 任务开关: ❓ \n \n
\n \n
\n
\n
\n
任务间隔时间:\n
\n \n
\n
\n
\n
检测规则:\n
\n \n
\n
\n
\n
\n \n \x3c!-- 域名设置面板 --\x3e\n
\n
\n
域名 - MissAv:\n
\n \n
\n
\n
\n
域名 - Jable:\n
\n \n
\n
\n
\n
域名 - Avgle:\n
\n \n
\n
\n
\n
域名 - JavTrailer:\n
\n \n
\n
\n
\n
域名 - 123Av:\n
\n \n
\n
\n
\n
域名 - JavDb:\n
\n \n
\n
\n
\n
域名 - JavBus:\n
\n \n
\n
\n
\n
域名 - SupJav:\n
\n \n
\n
\n
\n \n \x3c!-- 快捷键 --\x3e\n
\n
快捷键修改后, 刷新页面生效
\n
\n
\n
\n
\n
\n \n
\n
\n ❓ 对视频列表页启用快捷键:\n \n
\n \n
\n
\n
\n \n
\n\n\n
\n
显示已鉴定内容\n
\n
\n
\n
\n
\n
\n
\n
\n\n
\n \n \x3c!-- 屏蔽设置面板 --\x3e\n
\n
\n
\n 启用划词屏蔽 ❓ \n \n
\n \n
\n
\n \n
\n \n
\n
\n
评论区屏蔽词:\n
\n \n 添加\n
\n
\n
\n
\n \n
\n \n
\n
\n
视频标题屏蔽词:\n
\n \n 添加\n
\n
\n
\n
\n
\n
\n
以下操作, 不会对核心数据造成影响
\n
\n
\n ${buttonsHTML}\n
\n
\n
\n
\n
如果JAV-JHS给您带来了便捷和价值,请考虑给予一点支持,您的鼓励是我持续创作的最大动力!感谢您的慷慨支持!
\n
\n
\n
\n

\n
TRC20-USDT
\n
\n
点击地址可复制
\n
\n
\n
\n
\n
\n \n
\n 保存设置\n ♾️ 清理全部缓存\n
\n
\n
\n `;
layer.open({
type: 1,
title: "设置",
content: settingHtml,
area: utils.getResponsiveArea([ "55%", "90%" ]),
scrollbar: !1,
success: (layero, index) => {
$(layero).find(".layui-layer-content").css("position", "relative");
this.loadForm();
this.bindClick();
utils.setupEscClose(index);
fun && fun();
},
end: () => {
this.getBean("CoverButtonPlugin").enableSvgBtn();
}
});
}
async simpleSetting() {
let settingObj = await storageManager.getSetting();
const showFilterItemHotKey = settingObj.showFilterItemHotKey, showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey, showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey, showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey, showHasDownItemHotKey = settingObj.showHasDownItemHotKey, showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey, showAllItemHotKey = settingObj.showAllItemHotKey;
return `\n \n `;
}
async loadForm() {
let settingObj = await storageManager.getSetting();
$("#videoQuality").val(settingObj.videoQuality);
$("#reviewCount").val(settingObj.reviewCount || 20);
$("#tagPosition").val(settingObj.tagPosition || "rightTop");
$("#movieShowType").val(settingObj.movieShowType || "hide");
$("#waitCheckCount").val(settingObj.waitCheckCount || 5);
$("#showWaitCheckBtn").prop("checked", !settingObj.showWaitCheckBtn || settingObj.showWaitCheckBtn === YES);
$("#showWaitDownBtn").prop("checked", !settingObj.showWaitDownBtn || settingObj.showWaitDownBtn === YES);
$("#randomOpenWaitDown").prop("checked", !!settingObj.randomOpenWaitDown && settingObj.randomOpenWaitDown === YES);
$("#checkConcurrencyCount").val(settingObj.checkConcurrencyCount || 2);
$("#checkRequestSleep").val(settingObj.checkRequestSleep || 100);
$("#enableCheckBlacklist").val(settingObj.enableCheckBlacklist || YES);
$("#checkBlacklist_intervalTime").val(settingObj.checkBlacklist_intervalTime || 12);
$("#checkBlacklist_ruleTime").val(settingObj.checkBlacklist_ruleTime || 8760);
$("#enableCheckFavoriteActress").val(settingObj.enableCheckFavoriteActress || YES);
$("#checkFavoriteActress_IntervalTime").val(settingObj.checkFavoriteActress_IntervalTime || 24);
$("#enableCheckNewVideo").val(settingObj.enableCheckNewVideo || YES);
$("#checkNewVideo_intervalTime").val(settingObj.checkNewVideo_intervalTime || 12);
$("#checkNewVideo_ruleTime").val(settingObj.checkNewVideo_ruleTime || 8760);
const highlightedTagNumber = settingObj.highlightedTagNumber || 1, highlightedTagColor = settingObj.highlightedTagColor || "#ce2222";
$("#highlightedTagNumber").val(settingObj.highlightedTagNumber || 1);
$("#highlightedTagColor").val(settingObj.highlightedTagColor || "#ce2222");
$("#highlightedTagLabel").css("border", `${highlightedTagNumber}px solid ${highlightedTagColor}`);
$("#enableClog").val(settingObj.enableClog || YES);
$("#clogMsgCount").val(settingObj.clogMsgCount || 2e3);
$("#refresh_token").val(settingObj.refresh_token || "");
$("#httpTimeout").val(settingObj.httpTimeout || 5e3);
$("#httpRetryCount").val(settingObj.httpRetryCount || 3);
$("#webDavUrl").val(settingObj.webDavUrl || "");
$("#webDavUsername").val(settingObj.webDavUsername || "");
$("#webDavPassword").val(settingObj.webDavPassword || "");
$("#enableTitleSelectFilter").prop("checked", !settingObj.enableTitleSelectFilter || settingObj.enableTitleSelectFilter === YES);
$("#enableFavoriteActresses").prop("checked", !settingObj.enableFavoriteActresses || settingObj.enableFavoriteActresses === YES);
$("#enableSaveActressCarInfo").prop("checked", !!settingObj.enableSaveActressCarInfo && settingObj.enableSaveActressCarInfo === YES);
$("#enableScreenSvg").prop("checked", !settingObj.enableScreenSvg || settingObj.enableScreenSvg === YES);
$("#enableVideoSvg").prop("checked", !settingObj.enableVideoSvg || settingObj.enableVideoSvg === YES);
$("#enableHandleSvg").prop("checked", !settingObj.enableHandleSvg || settingObj.enableHandleSvg === YES);
$("#enableSiteSvg").prop("checked", !settingObj.enableSiteSvg || settingObj.enableSiteSvg === YES);
$("#enableCopySvg").prop("checked", !settingObj.enableCopySvg || settingObj.enableCopySvg === YES);
const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), javTrailersUrl = await otherSitePlugin.getJavTrailersUrl(), av123Url = await otherSitePlugin.getAv123Url(), javDbUrl = await otherSitePlugin.getJavDbUrl(), javBusUrl = await otherSitePlugin.getJavBusUrl(), supJavUrl = await otherSitePlugin.getSupJavUrl();
$("#missAvUrl").val(missAvUrl);
$("#jableUrl").val(jableUrl);
$("#avgleUrl").val(avgleUrl);
$("#javTrailersUrl").val(javTrailersUrl);
$("#av123Url").val(av123Url);
$("#javDbUrl").val(javDbUrl);
$("#javBusUrl").val(javBusUrl);
$("#supJavUrl").val(supJavUrl);
let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword();
reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => {
this.addLabelTag("#reviewKeywordContainer", reviewKeyword);
}));
filterKeywordList && filterKeywordList.forEach((reviewKeyword => {
this.addLabelTag("#filterKeywordContainer", reviewKeyword);
}));
[ "#reviewKeywordContainer", "#filterKeywordContainer" ].forEach((containerId => {
$(`${containerId} .add-tag-btn`).on("click", (event => this.addKeyword(event, containerId)));
$(`${containerId} .keyword-input`).on("keypress", (event => {
"Enter" === event.key && this.addKeyword(event, containerId);
}));
}));
$("#hotkey-panel [id]").map(((i, el) => el.id)).get().forEach((containerId => {
const $element = $(`#${containerId}`), defaultValue = void 0 !== settingObj[containerId] ? settingObj[containerId] : $element.attr("data-default-hotkey") || "";
$element.val(defaultValue).on("input", (event => {
let value = $(event.target).val();
if (/[\u4e00-\u9fa5]/.test(value) || /^Shift[a-zA-Z0-9]+$/.test(value)) {
$(event.target).val("");
show.error("非法输入:不能输入中文或输入法转换错误");
}
})).on("keydown", (event => this.handleHotkeyInput(event, $element)));
}));
$("#enableImageHotKey").prop("checked", !!settingObj.enableImageHotKey && settingObj.enableImageHotKey === YES);
}
handleHotkeyInput(event, $input) {
event.preventDefault();
const hotkey = this.parseHotkey(event);
"" !== hotkey ? this.isDuplicateHotkey(hotkey, $input.attr("id")) ? show.error("该快捷键已被其他功能使用!") : $input.val(hotkey) : $input.val("");
}
parseHotkey(event) {
if ("Backspace" === event.key || "Process" === event.key) return "";
const keys = [];
event.ctrlKey && keys.push("Ctrl");
event.shiftKey && keys.push("Shift");
event.altKey && keys.push("Alt");
event.metaKey && keys.push("Cmd");
const key = {
" ": "Space",
Control: "Ctrl",
Meta: "Cmd",
ArrowUp: "Up",
ArrowDown: "Down",
ArrowLeft: "Left",
ArrowRight: "Right"
}[event.key] || (event.key.length > 1 ? event.key.replace("Arrow", "") : event.key);
[ "Control", "Shift", "Alt", "Meta" ].includes(event.key) || keys.push(key);
return keys.length > 0 ? keys.join("+") : "";
}
isDuplicateHotkey(hotkey, currentInputId) {
let isDuplicate = !1;
$("#hotkey-panel [id]").each(((i, el) => {
if (el.id !== currentInputId && hotkey && hotkey === $(el).val()) {
isDuplicate = !0;
return !1;
}
}));
return isDuplicate;
}
async initSimpleSettingForm() {
let settingObj = await storageManager.getSetting();
$("#containerColumns").val(settingObj.containerColumns || 5);
$("#showContainerColumns").text(settingObj.containerColumns || 5);
$("#containerWidth").val((settingObj.containerWidth || 100) - 70);
$("#showContainerWidth").text((settingObj.containerWidth || 100) + "%");
$("#dialogOpenDetail").prop("checked", !settingObj.dialogOpenDetail || settingObj.dialogOpenDetail === YES);
$("#needClosePage").prop("checked", !settingObj.needClosePage || settingObj.needClosePage === YES);
$("#autoPage").prop("checked", !settingObj.autoPage || settingObj.autoPage === YES);
$("#translateTitle").prop("checked", !settingObj.translateTitle || settingObj.translateTitle === YES);
$("#enableLoadActressInfo").prop("checked", !settingObj.enableLoadActressInfo || settingObj.enableLoadActressInfo === YES);
$("#enableLoadOtherSite").prop("checked", !settingObj.enableLoadOtherSite || settingObj.enableLoadOtherSite === YES);
$("#containerColumns").on("input", (async event => {
let columns = $("#containerColumns").val();
$("#showContainerColumns").text(columns);
if (isJavDb) {
document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
}
if (isJavBus) {
document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
}
await storageManager.saveSettingItem("containerColumns", columns);
this.applyImageMode();
}));
$("#containerWidth").on("input", (async event => {
let containerWidth = parseInt($(event.target).val());
const value = containerWidth + 70 + "%";
$("#showContainerWidth").text(value);
if (isJavDb) {
document.querySelector("section .container").style.minWidth = value;
}
if (isJavBus) {
document.querySelector(".container-fluid .row").style.minWidth = value;
}
storageManager.saveSettingItem("containerWidth", containerWidth + 70);
}));
$("#dialogOpenDetail").on("change", (event => {
let dialogOpenDetail = $("#dialogOpenDetail").is(":checked") ? YES : NO;
storageManager.saveSettingItem("dialogOpenDetail", dialogOpenDetail);
}));
$("#showFilterItem").prop("checked", !!settingObj.showFilterItem && settingObj.showFilterItem === YES);
$("#showFilterActorItem").prop("checked", !!settingObj.showFilterActorItem && settingObj.showFilterActorItem === YES);
$("#showFilterKeywordItem").prop("checked", !!settingObj.showFilterKeywordItem && settingObj.showFilterKeywordItem === YES);
$("#showFavoriteItem").prop("checked", !settingObj.showFavoriteItem || settingObj.showFavoriteItem === YES);
$("#showHasDownItem").prop("checked", !settingObj.showHasDownItem || settingObj.showHasDownItem === YES);
$("#showHasWatchItem").prop("checked", !settingObj.showHasWatchItem || settingObj.showHasWatchItem === YES);
$("#showFilterItem").on("change", (async event => {
let showFilterItem = $("#showFilterItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showFilterItem", showFilterItem);
window.refresh();
}));
$("#showFilterActorItem").on("change", (async event => {
let showFilterActorItem = $("#showFilterActorItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showFilterActorItem", showFilterActorItem);
window.refresh();
}));
$("#showFilterKeywordItem").on("change", (async event => {
let showFilterKeywordItem = $("#showFilterKeywordItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showFilterKeywordItem", showFilterKeywordItem);
window.refresh();
}));
$("#showFavoriteItem").on("change", (async event => {
let showFavoriteItem = $("#showFavoriteItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showFavoriteItem", showFavoriteItem);
window.refresh();
}));
$("#showHasDownItem").on("change", (async event => {
let showHasDownItem = $("#showHasDownItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showHasDownItem", showHasDownItem);
window.refresh();
}));
$("#showHasWatchItem").on("change", (async event => {
let showHasWatchItem = $("#showHasWatchItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showHasWatchItem", showHasWatchItem);
window.refresh();
}));
const $otherCheckboxes = $("#showFilterItem, #showFilterActorItem, #showFilterKeywordItem, #showFavoriteItem, #showHasDownItem, #showHasWatchItem"), updateOtherCheckboxesState = () => {
const isShowAllChecked = $("#showAllItem").is(":checked");
$otherCheckboxes.prop("disabled", isShowAllChecked);
isShowAllChecked ? $otherCheckboxes.attr("data-tip", "请先关闭显示所有才可点击") : $otherCheckboxes.removeAttr("data-tip");
};
$("#showAllItem").prop("checked", !!settingObj.showAllItem && settingObj.showAllItem === YES);
$("#showAllItem").on("change", (async event => {
let showAllItem = $("#showAllItem").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("showAllItem", showAllItem);
updateOtherCheckboxesState();
window.refresh();
}));
updateOtherCheckboxesState();
$("#needClosePage").on("change", (async event => {
await storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? YES : NO);
window.refresh();
}));
$("#autoPage").on("change", (async event => {
const autoPage = $("#autoPage").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("autoPage", autoPage);
autoPage === YES ? $("#sort-toggle-btn").hide() : $("#sort-toggle-btn").show();
}));
$("#translateTitle").on("change", (async event => {
const translateTitle = $("#translateTitle").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("translateTitle", translateTitle);
if (translateTitle === YES) {
await this.getBean("ListPagePlugin").doFilter();
isDetailPage && await this.getBean("TranslatePlugin").translate();
} else {
await this.getBean("ListPagePlugin").revertTranslation();
$(".translated-title").remove();
}
}));
$("#hoverBigImg").prop("checked", !!settingObj.hoverBigImg && settingObj.hoverBigImg === YES);
$("#hoverBigImg").on("change", (async event => {
const hoverBigImg = $("#hoverBigImg").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("hoverBigImg", hoverBigImg);
hoverBigImg === YES ? window.imageHoverPreviewObj = new ImageHoverPreview({
selector: this.getSelector().coverImgSelector
}) : window.imageHoverPreviewObj && window.imageHoverPreviewObj.destroy();
}));
$("#enableLoadActressInfo").on("change", (async event => {
const enableLoadActressInfo = $("#enableLoadActressInfo").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enableLoadActressInfo", enableLoadActressInfo);
enableLoadActressInfo === YES ? this.getBean("ActressInfoPlugin").loadActressInfo() : $(".actress-info").remove();
}));
$("#enableLoadOtherSite").on("change", (async event => {
const enableLoadOtherSite = $("#enableLoadOtherSite").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enableLoadOtherSite", enableLoadOtherSite);
enableLoadOtherSite === YES ? this.getBean("OtherSitePlugin").loadOtherSite().then() : $("#otherSiteBox").remove();
}));
$("#enableLoadScreenShot").prop("checked", !settingObj.enableLoadScreenShot || settingObj.enableLoadScreenShot === YES);
$("#enableLoadScreenShot").on("change", (async event => {
const enableLoadScreenShot = $("#enableLoadScreenShot").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enableLoadScreenShot", enableLoadScreenShot);
enableLoadScreenShot === YES ? this.getBean("ScreenShotPlugin").loadScreenShot().then() : $(".screen-container").remove();
}));
$("#enableLoadPreviewVideo").prop("checked", !settingObj.enableLoadPreviewVideo || settingObj.enableLoadPreviewVideo === YES);
$("#enableLoadPreviewVideo").on("change", (async event => {
const enableLoadPreviewVideo = $("#enableLoadPreviewVideo").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enableLoadPreviewVideo", enableLoadPreviewVideo);
}));
$("#enable115Match").prop("checked", !!settingObj.enable115Match && settingObj.enable115Match === YES);
$("#enable115Match").on("change", (async event => {
const enable115Match = $("#enable115Match").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enable115Match", enable115Match);
let movieList = $(this.getSelector().itemSelector).toArray();
await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList);
}));
$("#enableVerticalModel").prop("checked", !!settingObj.enableVerticalModel && settingObj.enableVerticalModel === YES);
$("#enableVerticalModel").on("change", (async event => {
const enableVerticalModel = $("#enableVerticalModel").is(":checked") ? YES : NO;
await storageManager.saveSettingItem("enableVerticalModel", enableVerticalModel);
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\n\n\n
使用说明
\n \n
\n 1. 无法查看预览视频,提示分流?
\n \n
JavDB限制日本IP的访问,而预览视频来自DMM,需要日本IP才能访问。
\n
这样会导致二者无法同时使用,需要对其一进行代理转发。
\n
将 cc3001.dmm.co.jp 及 dmm.co 分流到日本ip。
\n
Clash Verge分流规则设置 (如果你是别的代理软件,自行搜索如何分流)
\n
\n \n \n
\n 2. 如何屏蔽某一系列的番号?
\n \n
方法一:设置中-添加视频标题关键词,如: VENX-
\n
方法二:进入详情页,选中标题文字,右键可加入
\n

\n
\n \n\n
\n 3. 屏蔽某演员,如何只屏蔽单体影片?
\n \n
屏蔽演员前,先筛选分类,再点屏蔽
\n

\n
\n \n \n
\n 4. 如何多浏览器同时登录115网盘?
\n \n
① 访问115登录页, 选择JHS-扫码面板, 并扫码登录
\n

\n
\n \n
② 进入网盘后, 右下角悬浮按钮, 复制Cookie
\n

\n
\n \n
③ 打开另一个浏览器(需装JHS脚本), 进入登录页面, 选择JHS-扫码面板, 输入Cookie并回车
\n

\n
\n \n
\n',
area: utils.getResponsiveArea([ "50%", "90%" ])
});
}));
}
async applyImageMode() {
$("#verticalImgStyle").remove();
if (await storageManager.getSetting("enableVerticalModel", NO) === YES) {
let imgPosition = "100% 50% !important";
window.location.href.includes("/advanced_search?type=100") && (imgPosition = "50% 50% !important");
const verticalStyle = `\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: ${imgPosition};\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 `;
$("\n ";
}
open(onOpenFun) {
layer.open({
type: 1,
title: "以图识图",
content: '\n \n
\n
\n
拖拽图片到此处 或 点击按钮选择图片
\n
也可以直接 Ctrl+V 粘贴图片或 图片URL
\n
\n
选择图片\n
\n
\n \n
\n \n
\n \n
\n
![]()
\n
\n 搜索图片\n 取消\n
\n \n
\n
\n \n
\n ',
area: utils.isMobile() ? utils.getResponsiveArea() : [ "40%", "80%" ],
success: async layero => {
this.initEventListeners();
onOpenFun && onOpenFun();
},
end: () => {
$(document).off("paste.searchImg");
}
});
}
initEventListeners() {
const $uploadArea = $("#upload-area"), $fileInput = $("#image-file"), $selectBtn = $("#select-image-btn"), $previewArea = $("#preview-area"), $previewImage = $("#preview-image"), $actionBtns = $("#action-btns"), $searchImage = $("#handle-btn"), $cancelBtn = $("#cancel-btn"), $urlInputContainer = $("#url-input-container"), $imageUrlInput = $("#image-url"), $searchResults = $("#search-results"), $siteBtnsContainer = $("#search-img-site-btns-container");
$uploadArea.on("dragover", (e => {
e.preventDefault();
$uploadArea.addClass("highlight");
})).on("dragleave", (() => {
$uploadArea.removeClass("highlight");
})).on("drop", (e => {
e.preventDefault();
$uploadArea.removeClass("highlight");
if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) {
this.handleImageFile(e.originalEvent.dataTransfer.files[0]);
this.resetSearchUI();
}
}));
$selectBtn.on("click", (() => {
$fileInput.trigger("click");
}));
$fileInput.on("change", (e => {
if (e.target.files && e.target.files[0]) {
this.handleImageFile(e.target.files[0]);
this.resetSearchUI();
}
}));
$(document).on("paste.searchImg", (async e => {
const items = e.originalEvent.clipboardData.items;
for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) {
const blob = items[i].getAsFile();
this.handleImageFile(blob);
this.resetSearchUI();
return;
}
const text = e.originalEvent.clipboardData.getData("text");
if (text && utils.isUrl(text)) {
$urlInputContainer.show();
$imageUrlInput.val(text);
$previewImage.attr("src", text);
$previewArea.show();
this.resetSearchUI();
}
}));
$searchImage.on("click", (async () => {
const imageSrc = $previewImage.attr("src");
if (imageSrc) {
if (!this.isUploading) {
this.isUploading = !0;
try {
const imgUrl = await this.searchByImage(imageSrc);
$actionBtns.hide();
$searchResults.show();
$siteBtnsContainer.empty();
const key = "jhs_selectedSites", selectedSites = JSON.parse(localStorage.getItem(key) || "{}");
this.siteList.forEach((site => {
const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl)), isChecked = !1 !== selectedSites[site.name];
$siteBtnsContainer.append(`\n \n \n
\n ${site.name}\n \n `);
}));
$siteBtnsContainer.on("change", ".site-checkbox", (function() {
const siteName = $(this).data("site-name");
selectedSites[siteName] = $(this).is(":checked");
localStorage.setItem(key, JSON.stringify(selectedSites));
}));
$siteBtnsContainer.show();
} finally {
this.isUploading = !1;
}
}
} else show.info("请粘贴或上传图片");
}));
$cancelBtn.on("click", (() => {
$previewArea.hide();
$urlInputContainer.hide();
$fileInput.val("");
$imageUrlInput.val("");
}));
$imageUrlInput.on("change", (() => {
if (utils.isUrl($imageUrlInput.val())) {
$previewImage.attr("src", $imageUrlInput.val());
$previewArea.show();
}
}));
$("#openAll").on("click", (() => {
$(".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(file) {
const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container");
if (!file.type.match("image.*")) {
show.info("请选择图片文件");
return;
}
const reader = new FileReader;
reader.onload = e => {
previewImage.src = e.target.result;
previewArea.style.display = "block";
urlInputContainer.style.display = "none";
$("#handle-btn")[0].click();
};
reader.readAsDataURL(file);
}
async searchByImage(imageSrc) {
let loadObj = loading();
try {
let imageUrl = imageSrc;
if (imageSrc.startsWith("data:")) {
show.info("开始上传图片...");
const imgurUrl = await async function(base64Data) {
var _a2;
const matches = base64Data.match(/^data:(.+);base64,(.+)$/);
if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据");
const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i);
const byteArray = new Uint8Array(byteNumbers), blob = new Blob([ byteArray ], {
type: mimeType
}), formData = new FormData;
formData.append("image", blob);
const response = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: {
Authorization: "Client-ID d70305e7c3ac5c6"
},
body: formData
}), data = await response.json();
if (data.success && data.data && data.data.link) return data.data.link;
throw new Error((null == (_a2 = data.data) ? void 0 : _a2.error) || "上传到Imgur失败");
}(imageSrc);
if (!imgurUrl) {
show.error("上传到失败");
return;
}
imageUrl = imgurUrl;
}
return imageUrl;
} catch (error) {
show.error(`搜索失败: ${error.message}`);
console.error("搜索失败:", error);
} finally {
loadObj.close();
}
}
}
class BusNavBarPlugin extends BasePlugin {
getName() {
return "BusNavBarPlugin";
}
handle() {
$("#navbar > div > div > span").append('\n 识图\n ');
$("#search-img-btn").on("click", (() => {
this.getBean("ImageRecognitionPlugin").open();
}));
}
}
class RelatedPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "floorIndex", 1);
__publicField(this, "isInit", !1);
}
getName() {
return "RelatedPlugin";
}
async showRelated($eleBox, movieId) {
const enableLoadRelated = await storageManager.getSetting("enableLoadRelated", NO), $magnets = $eleBox;
if (movieId) {
$magnets.append(`\n \n `);
$("#relatedFold").on("click", (event => {
event.preventDefault();
event.stopPropagation();
const $text = $("#relatedFold .toggle-text"), $icon = $("#relatedFold .toggle-icon"), isFolded = "展开" === $text.text();
$text.text(isFolded ? "折叠" : "展开");
$icon.text(isFolded ? "▲" : "▼");
if (isFolded) {
$("#relatedContainer").show();
$("#relatedFooter").show();
if (!this.isInit) {
this.fetchAndDisplayRelateds(movieId);
this.isInit = !0;
}
storageManager.saveSettingItem("enableLoadRelated", YES);
} else {
$("#relatedContainer").hide();
$("#relatedFooter").hide();
storageManager.saveSettingItem("enableLoadRelated", NO);
}
}));
$magnets.append('');
$magnets.append('');
enableLoadRelated === YES && await this.fetchAndDisplayRelateds(movieId);
} else show.error("未传入movieId");
}
async fetchAndDisplayRelateds(movieId) {
const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter");
$relatedContainer.append('获取清单中...
');
let dataList = null;
try {
dataList = await javDbApi.related(movieId, 1, 20);
} catch (e) {
console.error("获取清单失败:", e);
} finally {
$("#relatedLoading").remove();
}
if (dataList) if (0 !== dataList.length) {
this.displayRelateds(dataList, $relatedContainer);
if (20 === dataList.length) {
$relatedFooter.html('\n \n 加载更多清单\n \n 已加载全部清单
\n ');
let currentPage = 1, $loadMoreRelateds = $("#loadMoreRelateds");
$loadMoreRelateds.on("click", (async () => {
$loadMoreRelateds.text("加载中...").prop("disabled", !0);
currentPage++;
let moreData;
try {
moreData = await javDbApi.related(movieId, currentPage, 20);
} catch (e) {
console.error("加载更多清单失败:", e);
} finally {
$loadMoreRelateds.text("加载失败, 请点击重试").prop("disabled", !1);
}
if (moreData) {
this.displayRelateds(moreData, $relatedContainer);
if (moreData.length < 20) {
$loadMoreRelateds.remove();
$("#relatedEnd").show();
} else $loadMoreRelateds.text("加载更多清单").prop("disabled", !1);
}
}));
} else $relatedFooter.html('已加载全部清单
');
} else $relatedContainer.append('无清单
'); else {
$relatedContainer.append('\n \n ');
$("#retryFetchRelateds").on("click", (async () => {
$("#retryFetchRelateds").parent().remove();
await this.fetchAndDisplayRelateds(movieId);
}));
}
}
displayRelateds(dataList, $container) {
dataList.length && dataList.forEach((item => {
let commentHtml = `\n \n
#${this.floorIndex++}\n
创建时间: ${item.createTime}\n
${item.name}
\n
视频个数: ${item.movieCount}
\n
收藏次数: ${item.collectionCount} 被查看次数: ${item.viewCount}
\n
\n `;
$container.append(commentHtml);
}));
}
}
class WantAndWatchedVideosPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "type", null);
}
getName() {
return "WantAndWatchedVideosPlugin";
}
async handle() {
if (window.location.href.includes("/want_watch_videos")) {
$("h3").append('导入至 JHS');
$("#wantWatchBtn").on("click", (event => {
this.type = Status_FAVORITE;
this.importWantWatchVideos(event, "是否将 想看的影片 导入到 JHS-收藏?");
}));
}
if (window.location.href.includes("/watched_videos")) {
$("h3").append('导入至 JHS');
$("#wantWatchBtn").on("click", (event => {
this.type = Status_HAS_DOWN;
this.importWantWatchVideos(event, "是否将 看过的影片 导入到 JHS-已下载?");
}));
}
}
importWantWatchVideos(event, title) {
utils.q(null, `${title}
执行此功能前请记得备份数据`, (async () => {
let loadObj = loading();
try {
await this.parseMovieList();
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}));
}
async parseMovieList($dom) {
let movieList, nextPageLink;
if ($dom) {
movieList = $dom.find(this.getSelector().itemSelector);
nextPageLink = $dom.find(".pagination-next").attr("href");
} else {
movieList = $(this.getSelector().itemSelector);
nextPageLink = $(".pagination-next").attr("href");
}
for (const element of movieList) {
const item = $(element), href = item.find("a").attr("href"), carNum2 = item.find(".video-title strong").text().trim(), publishTime = item.find(".meta").text().trim();
if (href && carNum2) try {
if (await storageManager.getCar(carNum2)) {
show.info(`${carNum2} 已存在, 跳过`);
continue;
}
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: null,
actionType: this.type,
publishTime: publishTime
});
} catch (error) {
console.error(`保存失败 [${carNum2}]:`, error);
}
}
if (nextPageLink) {
show.info("发现下一页,正在解析:", nextPageLink);
await new Promise((resolve => setTimeout(resolve, 1e3)));
$.ajax({
url: nextPageLink,
method: "GET",
success: html => {
const parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html"));
this.parseMovieList(next$dom);
},
error: function(err) {
console.error(err);
show.error("加载下一页失败:" + err.message);
}
});
} else {
show.ok("导入结束!");
window.refresh();
}
}
}
class CoverButtonPlugin extends BasePlugin {
getName() {
return "CoverButtonPlugin";
}
async initCss() {
return `\n \n `;
}
handle() {
if (window.isListPage) {
this.addSvgBtn();
this.bindClick().then();
}
}
async addSvgBtn() {
$(this.getSelector().itemSelector).toArray().forEach((ele => {
let $box2 = $(ele);
if (!($box2.find(".tool-box").length > 0)) {
isJavDb && $box2.find(".tags").append(`\n \n `);
if (isJavBus) {
if ($box2.find(".avatar-box").length > 0) return;
$box2.find(".photo-info").append(`\n \n `);
}
}
}));
this.enableSvgBtn();
}
async enableSvgBtn() {
const settingObj = await storageManager.getSetting(), {enableScreenSvg: enableScreenSvg = YES, enableVideoSvg: enableVideoSvg = YES, enableHandleSvg: enableHandleSvg = YES, enableSiteSvg: enableSiteSvg = YES, enableCopySvg: enableCopySvg = YES} = settingObj;
[ {
selector: ".screenSvg",
enabled: enableScreenSvg
}, {
selector: ".videoSvg",
enabled: enableVideoSvg
}, {
selector: ".handleSvg",
enabled: enableHandleSvg
}, {
selector: ".siteSvg",
enabled: enableSiteSvg
}, {
selector: ".copySvg",
enabled: enableCopySvg
} ].forEach((({selector: selector, enabled: enabled}) => {
$(selector).toggle(enabled === YES);
}));
}
async bindClick() {
this.getSelector();
const listPagePlugin = this.getBean("ListPagePlugin");
$(document).on("click", ".more-tools-container", (event => {
event.preventDefault();
var $currentTools = $(event.target).closest(".more-tools-container").find(".more-tools");
$(".more-tools").not($currentTools).stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
$currentTools.is(":visible") ? $currentTools.stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide() : $currentTools.stop(!0, !0).removeClass("elastic-out").addClass("elastic-in").show();
}));
$(document).on("click", (function(event) {
$(event.target).closest(".more-tools-container").length || $(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
}));
$(document).on("click", ".videoSvg", (event => {
event.preventDefault();
$('.videoSvg[title!="播放视频"]').each(((index, element) => {
const $otherSvgElement = $(element);
let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find("img"), {carNum: carNum2} = this.getBoxCarInfo($otherBox);
this.showImg($otherSvgElement, $otherImg, carNum2);
$otherSvgElement.html(this.videoSvg).attr("title", "播放视频");
}));
const $currentBox = $(event.target).closest(".item"), $svgElement = $currentBox.find(".videoSvg");
if ("播放视频" === $svgElement.attr("title")) {
$svgElement.html(this.recoveryVideoSvg).attr("title", "切回封面");
const {carNum: carNum2} = this.getBoxCarInfo($currentBox);
let $img = $currentBox.find("img");
if (!$img.length) {
show.error("没有找到图片");
return;
}
this.showVideo($svgElement, $img, carNum2).then();
}
}));
$(document).on("click", ".screenSvg", (async event => {
event.preventDefault();
let loadObj = loading();
try {
const $box2 = $(event.currentTarget).closest(".item");
let {carNum: carNum2} = this.getBoxCarInfo($box2);
carNum2 = carNum2.replace("FC2-", "");
const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2);
loadObj.close();
showImageViewer(imgUrl);
} catch (error) {
console.error("图片预览出错:", error);
show.error("图片预览出错:" + error);
} finally {
loadObj.close();
}
}));
$(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", (event => {
event.preventDefault();
event.stopPropagation();
const $btn = $(event.target).closest(".menu-btn"), $box2 = $btn.closest(".item"), {carNum: carNum2, url: url, publishTime: publishTime} = this.getBoxCarInfo($box2), handleAction = async status => {
let actress = await listPagePlugin.parseActressName(url);
await storageManager.saveCar({
carNum: carNum2,
url: url,
names: actress,
actionType: status,
publishTime: publishTime
});
window.refresh();
show.ok("操作成功");
};
$btn.hasClass("filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER))) : $btn.hasClass("favoriteBtn") ? handleAction(Status_FAVORITE).then() : $btn.hasClass("hasDownBtn") ? handleAction(Status_HAS_DOWN).then() : $btn.hasClass("hasWatchBtn") && handleAction(Status_HAS_WATCH).then();
$(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide();
}));
const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), av123Url = await otherSitePlugin.getAv123Url();
$(document).on("click", ".site-jable, .site-avgle, .site-miss-av, .site-123-av", (event => {
event.preventDefault();
event.stopPropagation();
const $currentTarget = $(event.currentTarget), $box2 = $currentTarget.closest(".item"), {carNum: carNum2} = this.getBoxCarInfo($box2);
let url = null;
$currentTarget.hasClass("site-jable") ? url = `${jableUrl}/search/${carNum2}/` : $currentTarget.hasClass("site-avgle") ? url = `${avgleUrl}/vod/search.html?wd=${carNum2}` : $currentTarget.hasClass("site-miss-av") ? url = `${missAvUrl}/search/${carNum2}` : $currentTarget.hasClass("site-123-av") && (url = `${av123Url}/ja/search?keyword=${carNum2}`);
event && (event.ctrlKey || event.metaKey) ? GM_openInTab(url, {
insert: 0
}) : window.open(url);
}));
$(document).on("click", ".titleSvg, .carNumSvg, .downSvg", (event => {
event.preventDefault();
event.stopPropagation();
const $box2 = $(event.currentTarget).closest(".item"), {carNum: carNum2, title: title} = this.getBoxCarInfo($box2), $img = $box2.find(isJavBus ? ".photo-frame img" : ".cover img");
$(event.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", title) : $(event.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", carNum2) : $(event.currentTarget).hasClass("downSvg") && fetch($img.attr("src")).then((response => response.blob())).then((blob => {
utils.download(blob, carNum2 + " " + title + ".jpg");
}));
}));
}
showImg($svgElement, $img, carNum2) {
$svgElement.html(this.videoSvg).attr("title", "播放视频");
let $video = $(`#${`${carNum2}_preview_video`}`);
if ($video.length > 0) {
$video[0].pause();
$video.parent().hide();
}
$img.show();
$img.removeClass("loading");
$img.next(".loading-spinner").remove();
}
async showVideo($svgElement, $img, carNum2) {
const id = `${carNum2}_preview_video`;
let $video = $(`#${id}`);
if ($video.length > 0) {
$video.parent().show();
$video[0].play();
$img.hide();
return;
}
$img.addClass("loading");
$img.after('');
const poster = $img.attr("src"), dmmVideoMap = await getDmmVideo(carNum2);
if (!dmmVideoMap) {
show.error("未解析到视频");
this.showImg($svgElement, $img, carNum2);
return;
}
let defaultVideoQuality = await storageManager.getSetting("videoQuality");
defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality);
let videoUrl = dmmVideoMap[defaultVideoQuality], videoHtml = `\n \n \n
\n `;
isJavBus && (videoHtml = `\n \n \n
\n `);
$img.parent().append(videoHtml);
$img.hide();
$img.removeClass("loading");
$img.next(".loading-spinner").remove();
$video = $(`#${id}`);
let videoElement = $video[0];
videoElement.load();
videoElement.muted = !1;
videoElement.play();
$video.trigger("focus");
}
}
class Fc2By123AvPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "$contentBox", $(".section .container"));
__publicField(this, "urlParams", new URLSearchParams(window.location.search));
__publicField(this, "sortVal", this.urlParams.get("sort") || "release_date");
__publicField(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1);
__publicField(this, "maxPage", null);
__publicField(this, "keyword", this.urlParams.get("keyword") || null);
}
getName() {
return "Fc2By123AvPlugin";
}
async getBaseUrl() {
const otherSitePlugin = this.getBean("OtherSitePlugin");
return await otherSitePlugin.getAv123Url() + "/ja";
}
handle() {
$("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('123Av-Fc2');
$('.tabs li:contains("FC2")').after('123Av-Fc2');
if (currentHref.includes("/advanced_search?type=100")) {
this.hookPage();
this.handleQuery().then();
}
}
hookPage() {
let $h2 = $("h2.section-title");
$h2.contents().first().replaceWith("123Av");
$h2.css("marginBottom", "0");
$h2.append('\n \n ');
$("#search-123av-keyword").val(this.keyword);
$("#search-123av-btn").on("click", (async () => {
let keyword = $("#search-123av-keyword").val().trim();
if (keyword) {
this.keyword = keyword;
utils.setHrefParam("keyword", keyword);
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('');
this.$contentBox.append('');
this.$contentBox.append('');
$(".tool-box").append('\n \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 $target = $(e.target);
this.sortVal = $target.data("sort");
utils.setHrefParam("sort", this.sortVal);
$target.siblings().removeClass("is-info");
$target.addClass("is-info");
this.handleQuery();
}));
$(".page-box").append('\n \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();
if (this.currentPage > 1) {
this.currentPage--;
utils.setHrefParam("page", this.currentPage);
this.renderPagination();
this.handleQuery();
}
}));
$(".pagination-next").on("click", (e => {
e.preventDefault();
if (this.currentPage < this.maxPage) {
this.currentPage++;
utils.setHrefParam("page", this.currentPage);
this.renderPagination();
this.handleQuery();
}
}));
}
renderPagination() {
const $paginationList = $(".pagination-list");
$paginationList.empty();
let startPage = Math.max(1, this.currentPage - 2), endPage = Math.min(this.maxPage, this.currentPage + 2);
this.currentPage <= 3 ? endPage = Math.min(6, this.maxPage) : this.currentPage >= this.maxPage - 2 && (startPage = Math.max(this.maxPage - 5, 1));
if (startPage > 1) {
$paginationList.append('');
startPage > 2 && $paginationList.append('');
}
for (let i = startPage; i <= endPage; i++) {
const activeClass = i === this.currentPage ? " is-current" : "";
$paginationList.append(``);
}
if (endPage < this.maxPage) {
endPage < this.maxPage - 1 && $paginationList.append('');
$paginationList.append(``);
}
}
async handleQuery() {
let loadObj = loading();
try {
let pagesToFetch = [];
pagesToFetch = 1 === this.currentPage ? [ 1, 2 ] : [ 2 * this.currentPage - 1, 2 * this.currentPage ];
if (this.keyword) {
pagesToFetch = [ 1 ];
$(".page-box").hide();
$(".tool-box").hide();
}
const baseUrl = await this.getBaseUrl(), fetchPromises = pagesToFetch.map((page => {
let url = `${baseUrl}/tags/fc2?sort=${this.sortVal}&page=${page}`;
this.keyword && (url = `${baseUrl}/search?keyword=${this.keyword}`);
return gmHttp.get(url);
})), htmlResults = await Promise.all(fetchPromises);
let dataList = [];
for (const html of htmlResults) {
let $dom = $(html);
$dom.find(".box-item").each(((index, element) => {
const $item = $(element), imgSrc = $item.find("img").attr("data-src");
let carNum2 = $item.find("img").attr("title");
const detailLink = $item.find(".detail a"), link = detailLink.attr("href"), href = baseUrl + (link.startsWith("/") ? link : "/" + link), title = detailLink.text().trim().replace(carNum2 + " - ", "");
carNum2 = carNum2.replace("FC2-PPV", "FC2");
dataList.push({
imgSrc: imgSrc,
carNum: carNum2,
href: href,
title: title
});
}));
if (!this.maxPage) {
let rawMaxPage, lastPageItem = $dom.find(".page-item:not(.disabled)").last();
if (lastPageItem.find("a.page-link").length) {
let href = lastPageItem.find("a.page-link").attr("href");
rawMaxPage = parseInt(href.split("page=")[1]);
} else rawMaxPage = parseInt(lastPageItem.find("span.page-link").text());
this.maxPage = Math.ceil(rawMaxPage / 2);
this.renderPagination();
}
}
if (0 === dataList.length) {
console.log(dataList);
show.error("无结果");
let errorUrl = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}`;
this.keyword && (errorUrl = `${baseUrl}/search?keyword=${this.keyword}`);
console.error("获取数据失败!", errorUrl);
}
let movieHtml = this.markDataListHtml(dataList);
$(".movie-list").html(movieHtml);
await utils.smoothScrollToTop();
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}
async open123AvFc2Dialog(carNum2, href) {
let otherHtml = "";
await storageManager.getSetting("enableLoadOtherSite", YES) === YES && (otherHtml = '第三方站点:
');
let pageHtml = `\n \n \x3c!--
\n \n
\n
--\x3e\n
\n \n ${otherHtml}\n \n
\n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n \x3c!--
--\x3e\n
\n `;
layer.open({
type: 1,
title: carNum2,
content: pageHtml,
area: utils.getDefaultArea(),
skin: "movie-detail-layer",
scrollbar: !1,
success: (layero, index) => {
utils.setupEscClose(index);
this.loadData(carNum2, href);
let keyword = carNum2.replace("FC2-", "");
$("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword));
$("#favoriteBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_FAVORITE,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#filterBtn").on("click", (event => {
utils.q(event, `是否屏蔽${carNum2}?`, (async () => {
const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_FILTER,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
window.location.href.includes("collection_codes?movieId") && utils.closePage();
}));
}));
$("#hasDownBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_HAS_DOWN,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#hasWatchBtn").on("click", (async event => {
const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text();
await storageManager.saveCar({
carNum: carNum2,
url: href,
names: actress,
actionType: Status_HAS_WATCH,
publishTime: publishTime
});
window.refresh();
layer.closeAll();
}));
$("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event)));
$("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2)));
let tempCarNum = carNum2.replace("FC2-", "");
this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then();
}
});
}
async loadData(carNum2, href) {
let loadObj = loading();
try {
const {id: id, publishDate: publishDate, title: title, moviePoster: moviePoster} = await this.get123AvVideoInfo(href);
$(".movie-info-container").html(`\n ${title || "无标题"}
\n \n \n \n 販売者: \n
\n \n \n ${publishDate || ""}
\n\n `);
this.getImgList(carNum2).then();
this.getActressInfo(carNum2).then();
this.getBean("TranslatePlugin").translate(carNum2, !1).then();
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}
handleLongImg(carNum2) {
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
$(".movie-gallery .image-list").prepend(' 正在加载缩略图
');
const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2);
if (imgUrl) {
$(".screen-container").html(`
`);
$(".screen-container").on("click", (event => {
event.stopPropagation();
event.preventDefault();
showImageViewer(event.currentTarget);
}));
}
}));
}
async get123AvVideoInfo(href) {
const html = await gmHttp.get(href), match = html.match(/v-scope="Movie\({id:\s*(\d+),/), id = match ? match[1] : null, $dom = utils.htmlTo$dom(html);
return {
id: id,
publishDate: $dom.find('span:contains("リリース日:")').next("span").text(),
title: $dom.find("h1").text().trim(),
moviePoster: $dom.find("#player").attr("data-poster")
};
}
async getActressInfo(fc2Num) {
let url = `https://fc2ppvdb.com/articles/${fc2Num.replace("FC2-", "")}`;
const html = await gmHttp.get(url), $dom = $(html), actressNodeList = $dom.find("div").filter((function() {
return 0 === $(this).text().trim().indexOf("女優:");
}));
if (0 === actressNodeList.length || actressNodeList.length > 1) {
show.error("解析女优信息失败");
return;
}
const $actress = $(actressNodeList[0]).find("a");
let actorsHtml = "主演: ";
if ($actress.length > 0) {
let actress = "";
$actress.each(((index, ele) => {
let $actor = $(ele), name2 = $actor.text(), actressHref = $actor.attr("href");
actorsHtml += `${name2}`;
actress += name2 + " ";
}));
$("#data-actress").text(actress);
} else actorsHtml += "暂无演员信息";
$(".actor-list").html(actorsHtml);
const sellerNodeList = $dom.find("div").filter((function() {
return 0 === $(this).text().trim().indexOf("販売者:");
}));
if (sellerNodeList.length > 0) {
const $sellerA = $(sellerNodeList[0]).find("a");
if ($sellerA.length > 0) {
const $seller = $($sellerA[0]);
let name2 = $seller.text(), sellerHref = $seller.attr("href");
$(".movie-seller").html(`販売者: ${name2}`);
}
}
}
async getImgList(fc2Num) {
let tempCarNum = fc2Num.replace("FC2-", ""), url = `https://adult.contents.fc2.com/article/${fc2Num.replace("FC2-", "")}/`;
const html = await gmHttp.get(url, null, {
referer: url
});
let imgList = $(html).find(".items_article_SampleImagesArea img").map((function() {
return $(this).attr("src");
})).get(), imagesHtml = "";
Array.isArray(imgList) && imgList.length > 0 ? imagesHtml = imgList.map(((img, index) => `\n \n
\n \n `)).join("") : $(".movie-gallery").html("剧照: 暂无剧照
");
$(".image-list").html(imagesHtml);
this.handleLongImg(tempCarNum);
}
async getMovie(id, moviePoster) {
let url = `${await this.getBaseUrl()}/ajax/v/${id}/videos`, loadObj = loading();
try {
let movieList = (await gmHttp.get(url)).result.watch;
if (movieList.length > 0) {
movieList.forEach((movieItem => {
movieItem.url = movieItem.url + "?poster=" + moviePoster;
}));
return movieList;
}
return null;
} catch (e) {
console.error(e);
} finally {
loadObj.close();
}
}
markDataListHtml(movies) {
let moviesHtml = "";
movies.forEach((movie => {
moviesHtml += `\n \n `;
}));
return moviesHtml;
}
}
class MagnetHubPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "currentEngine", null);
__publicField(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 \n ";
}
createMagnetHub(keyword) {
keyword = keyword.replace("FC2-", "");
const $container = $(''), $tabs = $(''), key = "jhs_magnetHub_selectedEngine", savedEngineId = localStorage.getItem(key);
let defaultEngineIndex = 0;
const $tabBox = $('');
this.searchEngines.forEach(((engine, index) => {
const $tab = $(`${engine.name}
`);
if (savedEngineId && engine.id === savedEngineId) {
$tab.addClass("active");
this.currentEngine = engine;
defaultEngineIndex = index;
} else if (0 === index && !savedEngineId) {
$tab.addClass("active");
this.currentEngine = engine;
}
$tabBox.append($tab);
}));
$tabs.append($tabBox);
$tabs.append(`原网页`);
$container.append($tabs);
const $resultsContainer = $('');
$container.append($resultsContainer);
$container.on("click", ".magnet-tab", (e => {
const engineId = $(e.target).data("engine");
this.currentEngine = this.searchEngines.find((engine => engine.id === engineId));
$("#targetBox").attr("href", this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword)));
localStorage.setItem(key, engineId);
$container.find(".magnet-tab").removeClass("active");
$(e.target).addClass("active");
this.searchEngine($resultsContainer, this.currentEngine, keyword);
}));
this.searchEngine($resultsContainer, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword);
return $container;
}
searchEngine($container, engine, keyword) {
$container.html(`正在从 ${engine.name} 搜索 "${keyword}"...
`);
const cacheKey = `${engine.name}_${keyword}`;
sessionStorage.getItem(cacheKey);
const url = engine.url.replace("{keyword}", encodeURIComponent(keyword));
engine.parseHtml && GM_xmlhttpRequest({
method: "GET",
url: url,
onload: response => {
try {
const results = engine.parseHtml.call(this, response.responseText, keyword);
results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results));
this.displayResults($container, results, engine.name);
} catch (e) {
$container.html(`解析 ${engine.name} 结果失败: ${e.message}
`);
}
},
onerror: error => {
$container.html(`从 ${engine.name} 获取数据失败: ${error.statusText}
`);
}
});
engine.parseJson && engine.parseJson.call(this, $container, engine, keyword, cacheKey);
}
displayResults($container, results, engineName) {
$container.empty();
if (0 !== results.length) {
results.forEach((result => {
const $result = $(`\n \n
\n
\n 大小: ${result.size || "未知"}\n 日期: ${result.date || "未知"}\n
\n
\n 复制链接\n 115离线下载\n
\n
\n `);
$container.append($result);
}));
$container.on("click", ".copy-btn", (function() {
const $btn = $(this), magnet = $btn.data("magnet");
navigator.clipboard ? navigator.clipboard.writeText(magnet).then((() => {
showCopiedFeedback($btn);
})).catch((err => {
fallbackCopy(magnet, $btn);
})) : fallbackCopy(magnet, $btn);
}));
$container.on("click", ".down-115", (async event => {
const magnet = $(event.currentTarget).data("magnet");
let loadObj = loading();
try {
await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet);
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
}));
} else $container.append('没有找到相关结果
');
function showCopiedFeedback($btn) {
const originalText = $btn.text();
$btn.addClass("copied").text("已复制");
setTimeout((() => {
$btn.removeClass("copied").text(originalText);
}), 2e3);
}
function fallbackCopy(text, $btn) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand("copy");
showCopiedFeedback($btn);
} catch (err) {
console.error("复制失败:", err);
alert("复制失败,请手动复制链接");
}
document.body.removeChild(textarea);
}
}
parseBTSOW($container, engine, keyword, cacheKey) {
const _this = this;
GM_xmlhttpRequest({
method: "POST",
url: engine.url,
headers: {
"Content-Type": "application/json"
},
data: `[{"search":"${keyword}"},50,1]`,
onload: response => {
try {
const dataList = JSON.parse(response.responseText).data, results = [];
for (let i = 0; i < dataList.length; i++) {
let item = dataList[i];
results.push({
title: item.name,
magnet: "magnet:?xt=urn:btih:" + item.hash,
size: (item.size / 1073741824).toFixed(2) + " GB",
date: utils.formatDate(new Date(1e3 * item.lastUpdateTime))
});
}
results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results));
_this.displayResults($container, results, engine.name);
} catch (e) {
$container.html(`解析 ${engine.name} 结果失败: ${e.message}
`);
}
},
onerror: error => {
$container.html(`从 ${engine.name} 获取数据失败: ${error.statusText}
`);
}
});
}
parseU3C3(html, keyword) {
const $dom = utils.htmlTo$dom(html), results = [];
$dom.find(".torrent-list tbody tr").each(((i, el) => {
const $el = $(el);
if ($el.text().includes("置顶")) return;
const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim();
if (!title.toLowerCase().includes(keyword.toLowerCase())) return;
const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim();
magnet && results.push({
title: title,
magnet: magnet,
size: size,
date: date
});
}));
return results;
}
parseSukebei(html, keyword) {
const $dom = utils.htmlTo$dom(html), results = [];
$dom.find(".torrent-list tbody tr").each(((i, el) => {
const $el = $(el);
if ($el.text().includes("置顶")) return;
const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim();
if (!title.toLowerCase().includes(keyword.toLowerCase())) return;
const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim();
magnet && results.push({
title: title,
magnet: magnet,
size: size,
date: date
});
}));
return results;
}
}
class ScreenShotPlugin extends BasePlugin {
getName() {
return "ScreenShotPlugin";
}
async handle() {
this.loadScreenShot().then();
}
async loadScreenShot() {
if (!isDetailPage) return;
if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return;
let carNum2 = this.getPageInfo().carNum;
isJavDb && $(".preview-images .tile-item").first().before(' 正在加载缩略图
');
isJavBus && $("#sample-waterfall .sample-box:first").after(' 正在加载缩略图
');
try {
const imgUrl = await this.getScreenshot(carNum2);
this.addImg("缩略图", imgUrl);
clog.log("加载缩略图:", imgUrl);
} catch (e) {
this.showErrorFallback(carNum2, e);
}
}
async getScreenshot(carNum2) {
const cacheData = localStorage.getItem("jhs_screenShot") ? JSON.parse(localStorage.getItem("jhs_screenShot")) : {};
if (cacheData[carNum2]) {
clog.debug("缓存中存在缩略图:", carNum2, cacheData[carNum2]);
return cacheData[carNum2];
}
let imgUrl;
try {
imgUrl = await Promise.any([ this.getJavStoreScreenShot(carNum2) ]);
} catch (e) {
clog.error("获取缩略图资源失败:", imgUrl, e);
throw e;
}
if (!imgUrl) {
this.showErrorFallback(carNum2, null);
return null;
}
const httpsIndex = imgUrl.indexOf("https://");
-1 !== httpsIndex && (imgUrl = imgUrl.substring(httpsIndex));
cacheData[carNum2] = imgUrl;
clog.log("缩略图获取成功:", imgUrl);
localStorage.setItem("jhs_screenShot", JSON.stringify(cacheData));
return imgUrl;
}
async getJavStoreScreenShot(carNum2) {
let url = `https://javstore.net/search/${carNum2}.html`;
clog.log("正在解析缩略图:", url);
let html = await gmHttp.get(url);
const $dom = utils.htmlTo$dom(html);
let detailPageUrl = null;
$dom.find("#content_news h3 span a").each((function() {
if ($(this).attr("title").toLowerCase().includes(carNum2.toLowerCase())) {
detailPageUrl = $(this).attr("href");
return !1;
}
}));
if (!detailPageUrl) {
clog.error("JavStore, 查询番号失败:", url);
return null;
}
let detailPageHtml = await gmHttp.get(detailPageUrl);
const $detailPageDom = utils.htmlTo$dom(detailPageHtml);
let imgUrl = $detailPageDom.find("a:contains('CLICK HERE')").attr("href") || $detailPageDom.find("img[src*='_s.jpg']").attr("src");
if (!imgUrl) {
clog.error("JavStore, 解析预览图失败:", url);
return null;
}
return imgUrl.replace(".th", "");
}
async getJavBestScreenShot(carNum2) {
let url = `https://javbest.net/?s=${carNum2}`;
clog.log("正在解析缩略图:", url);
let html = await gmHttp.get(url);
const $dom = utils.htmlTo$dom(html), href = $dom.find(".app_loop_thumb a").first().attr("href");
if (!href) {
clog.error("解析JavBest搜索页失败:", url);
throw new Error("解析JavBest搜索页失败");
}
const title = $dom.find(".app_loop_thumb a").first().attr("title");
if (!title.toLowerCase().includes(carNum2.toLowerCase())) {
clog.error("解析JavBest搜索页失败:", title);
throw new Error("解析JavBest搜索页失败");
}
const detailPageHtml = await gmHttp.get(href);
let imgUrl = $(detailPageHtml).find('#content a img[src*="_t.jpg"]').attr("src");
if (!imgUrl) {
clog.error("解析JavBest缩略图失败:", url);
throw new Error("解析JavBest缩略图失败");
}
imgUrl = imgUrl.replace("_t", "").replace("http:", "https:");
return imgUrl;
}
async getJavFreeScreenShot(carNum2) {
let url = `https://javfree.me/search/${carNum2}/`, html = await gmHttp.get(url);
const $itemList = utils.htmlTo$dom(html).find("article h2.entry-title a");
if (!$itemList || 0 === $itemList.length) {
clog.error("解析JavFree搜索页失败:", url);
throw new Error("解析JavFree搜索页失败");
}
let detailPageUrl = $($itemList[0]).attr("href"), detailPageHtml = await gmHttp.get(detailPageUrl);
const $imgList = utils.htmlTo$dom(detailPageHtml).find("#main > article > .entry-content > p img");
if (!$imgList || 0 === $imgList.length) {
clog.error("解析JavFree详情页失败:", detailPageUrl);
throw new Error("解析JavFree详情页失败");
}
const srcList = $imgList.filter((function() {
const src = $(this).attr("src");
return src && src.toLowerCase().endsWith(".jpeg");
})).map((function() {
return $(this).attr("src");
})).get();
console.log(srcList);
return srcList.at(-1);
}
addImg(title, imgUrl) {
if (imgUrl) {
isJavDb && $(".screen-container").html(`
`);
isJavBus && $(".screen-container").html(``);
$(".screen-container").on("click", (event => {
event.stopPropagation();
event.preventDefault();
showImageViewer(event.currentTarget);
}));
}
}
showErrorFallback(carNum2, error) {
var _a2;
console.error("获取缩略图失败:", null == (_a2 = null == error ? void 0 : error.message) ? void 0 : _a2.substring(0, 100));
let differentCss = isJavBus ? "margin-top: 30px" : "margin-top: 50px";
$(".screen-container").html(`获取缩略图失败
点击重试 或 前往确认`).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", (async e => {
e.stopPropagation();
e.preventDefault();
$(".screen-container").html(`正在重新加载...
`);
try {
const imgUrl = await this.getScreenshot(carNum2);
this.addImg("缩略图", imgUrl);
} catch (err) {
this.showErrorFallback(carNum2, err);
}
})).on("click", ".check-link", (async e => {
e.stopPropagation();
e.preventDefault();
window.open(`https://javstore.net/search/${carNum2}.html`, "_blank");
}));
}
}
const getDownPathList = async () => {
const res = await gmHttp.get("https://webapi.115.com/offine/downpath");
return "object" == typeof res ? res.data : null;
}, searchFiles = async (search_value, offset = 0, limit = 30) => {
const url = `https://webapi.115.com/files/search?search_value=${encodeURIComponent(search_value)}&offset=${offset}&limit=${limit}`;
return await gmHttp.get(url);
};
class WangPan115TaskPlugin extends BasePlugin {
getName() {
return "WangPan115TaskPlugin";
}
async handle() {
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
$(el).parent().append($("").text("115离线下载").addClass("button is-info is-small").click((async event => {
event.stopPropagation();
event.preventDefault();
let loadObj = loading();
try {
await this.handleAddTask($(el).attr("data-clipboard-text"));
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
})));
}));
isJavBus && isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
this.bus115Down();
}));
}
async bus115Down() {
$("#magnet-table tr").each(((i, row) => {
const magnetLink = $(row).find("td:nth-child(1) a").attr("href");
if (magnetLink && magnetLink.includes("magnet:")) {
const actionCell = $("").addClass("action-cell");
$("").text("115离线下载").addClass("button is-info is-small").click((async event => {
event.stopPropagation();
event.preventDefault();
let loadObj = loading();
try {
await this.handleAddTask(magnetLink);
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
})).appendTo(actionCell);
$(row).append(actionCell);
}
}));
$("#magnet-table tbody").length > 0 && $("#magnet-table tbody tr").append($("").text("操作"));
}
async getSavePathId(nyName) {
let savePath115 = await storageManager.getSetting("savePath115", "云下载");
nyName && (savePath115 = savePath115.replaceAll("{ny}", nyName));
savePath115 = savePath115.replaceAll("{date}", utils.formatDate(new Date));
}
async handleAddTask(magnetLink, savePath) {
const singInfo = await (async () => {
const res = await gmHttp.get("https://115.com/?ct=offline&ac=space&_=" + (new Date).getTime());
return "object" == typeof res ? res : null;
})();
if (!singInfo) {
show.error("未登录115网盘", {
close: !0,
duration: -1,
callback: async () => {
window.open("https://115.com");
}
});
return;
}
const sign = singInfo.sign, time = singInfo.time, userId = this.getUserId(), result = await (async (magnet, wp_path_id = "", uid, sign, time) => {
const data = {
url: encodeURIComponent(magnet),
wp_path_id: "",
uid: uid,
sign: sign,
time: time
};
return await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=add_task_url", data);
})(magnetLink, userId, sign, time);
console.log("离线下载返回值:", result);
let infoHash = result.info_hash, fileId = await this.getFileId(userId, sign, time, infoHash), openUrl = "https://115.com/?tab=offline&mode=wangpan";
fileId && (openUrl = `https://115.com/?cid=${fileId}&offset=0&mode=wangpan`);
let title = "添加成功, 是否前往查看?";
!1 === result.state && (title = result.error_msg + " 是否前往查看?");
utils.q(null, title, (async () => {
let fileId2 = await this.getFileId(userId, sign, time, infoHash);
fileId2 && (openUrl = `https://115.com/?cid=${fileId2}&offset=0&mode=wangpan`);
window.open(openUrl);
}));
}
async getUserId() {
let downPathList = await getDownPathList();
if (downPathList && downPathList.length > 0) return downPathList[0].id;
{
show.info("没有默认离线目录, 正在创建中...");
const dirId = (await (async (dirName, pid = 0) => {
const data = {
pid: pid,
cname: dirName
};
return await gmHttp.postFileFormData("https://webapi.115.com/files/add", data);
})("云下载")).file_id;
await (async dirId => {
const data = {
file_id: dirId
};
return await gmHttp.postFileFormData("https://webapi.115.com/offine/downpath", data);
})(dirId);
show.info("创建完成, 开始执行离线下载");
downPathList = await getDownPathList();
if (downPathList && downPathList.length > 0) return downPathList[0].id;
throw new Error("获取115用户Id失败");
}
}
async getFileId(userId, sign, time, infoHash) {
const taskList = await (async (uid, sign, time) => {
const data = {
page: 1,
uid: uid,
sign: sign,
time: time
};
return (await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=task_lists", data)).tasks;
})(userId, sign, time);
console.log("云离线列表:", taskList);
let fileId = null;
for (let i = 0; i < taskList.length; i++) {
let task = taskList[i];
if (task.info_hash === infoHash) {
fileId = task.file_id;
break;
}
}
return fileId;
}
}
class WangPan115Plugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "JHS_115_COOKIE", "jhs_115_cookie");
__publicField(this, "JHS_115_MAX_AGE", "jhs_115_max_age");
}
getName() {
return "WangPan115Plugin";
}
async initCss() {
return "\n \n ";
}
async handle() {
if (!currentHref.includes("&ac=userfile") && currentHref.includes("115")) {
utils.loopDetector((() => $("#js-login-box").length > 0), (() => {
if (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 jhs_115_cookie = localStorage.getItem(this.JHS_115_COOKIE), jhs_115_max_age = localStorage.getItem(this.JHS_115_MAX_AGE);
document.cookie.includes("SEID") || null === jhs_115_cookie || utils.q(null, "检测到上次登录已有缓存cookie, 是否使用并登录?", (() => {
utils.addCookie(jhs_115_cookie, {
maxAge: parseInt(jhs_115_max_age),
domain: ".115.com"
});
window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}));
}), 20, 1500, !0);
}
hookPage() {
const $cookieTab = $('🔰 JHS-扫码');
$(".ltab-office").after($cookieTab);
const $cookieScene = $(`\n \n \n 使用115App扫码登录 \n \n \n \n \n \n \n \n \n \n \n 二维码占位区域\n \n \n \n \n 已有Cookie? 在此输入并回车\n \n \n \n \n \n \n `);
$("#js-login_box").find(".login-footer").before($cookieScene);
}
bindClick() {
$("#jhs-cookie").on("click", (() => {
const finishedEl = document.querySelector('[lg_rel="finished"]');
if (finishedEl) finishedEl.style.display = "none"; else {
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((tab => {
tab.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 finishedEl = document.querySelector('[lg_rel="finished"]');
if (finishedEl) finishedEl.style.display = "flex"; else {
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 loginTimeout = null;
$("#login-115-type").on("change", (async event => {
let login115Type = $("#login-115-type").val();
if (!login115Type) return;
const loginInfo = (await (async loginType => {
let url = `https://qrcodeapi.115.com/api/1.0/${loginType}/1.0/token/`;
return await gmHttp.get(url);
})(login115Type)).data, qrcode = loginInfo.qrcode, sign = loginInfo.sign, time = loginInfo.time, uid = loginInfo.uid;
console.log("生成二维码:", loginInfo);
const $qrcodeBox = $("#qrcode-box");
$qrcodeBox.css("display", "flex");
$qrcodeBox.html("");
new QRCode($qrcodeBox[0], {
text: qrcode,
width: 150,
height: 150,
correctLevel: QRCode.CorrectLevel.H
});
loginTimeout && clearTimeout(loginTimeout);
const checkLoginRecursive = async () => {
try {
const loginResult = await (async (uid, time, sign) => {
let url = `https://qrcodeapi.115.com/get/status/?uid=${uid}&time=${time}&sign=${sign}`;
return await gmHttp.get(url);
})(uid, time, sign);
console.log("已扫码, 正在获取结果:", loginResult);
let data = loginResult.data, msg = data.msg, status = data.status;
if (msg) {
console.log(msg);
show.info(msg);
}
if (2 === status) {
show.ok("扫码登录成功");
const checkResult = await (async (loginType, uid) => {
const data = {
app: loginType,
account: uid
}, url = `https://passportapi.115.com/app/1.0/${loginType}/1.0/login/qrcode/`;
return await gmHttp.postFileFormData(url, data);
})(login115Type, uid);
console.log("扫码登录成功:", checkResult);
if (checkResult.data && checkResult.data.cookie) {
const cookie = checkResult.data.cookie, cookieStr = `UID=${cookie.UID}; CID=${cookie.CID}; SEID=${cookie.SEID}; KID=${cookie.KID}`;
console.log("解析出cookie:", cookieStr);
localStorage.setItem(this.JHS_115_COOKIE, cookieStr);
localStorage.setItem(this.JHS_115_MAX_AGE, $("#cookie-expiry-select").val());
window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}
return;
}
loginTimeout = setTimeout(checkLoginRecursive, 500);
} catch (error) {
console.error("登录检查失败:", error);
}
};
await checkLoginRecursive();
}));
const handleCookie = () => {
const cookieStr = cookieInput.value, expirySelect = document.getElementById("cookie-expiry-select");
let maxAge = parseInt(expirySelect.value);
utils.addCookie(cookieStr, {
maxAge: maxAge,
domain: ".115.com"
});
window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan";
}, cookieInput = document.getElementById("cookie-str-input");
cookieInput.addEventListener("keydown", (function(event) {
if ("Enter" === event.key) {
event.preventDefault();
handleCookie();
}
}));
$("#submit-cookie-btn").on("click", (() => {
handleCookie();
}));
}
showMessage(message) {
const toast = document.createElement("div");
toast.textContent = message;
toast.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(toast);
setTimeout((() => {
toast.style.opacity = "1";
}), 10);
setTimeout((() => {
toast.style.opacity = "0";
setTimeout((() => toast.remove()), 500);
}), 3e3);
}
createCookiePanel() {
const cookieValue = localStorage.getItem(this.JHS_115_COOKIE);
if (!cookieValue) return;
const panel = document.createElement("div");
panel.id = "jhs-cookie-panel";
panel.innerHTML = `\n \n \n ${cookieValue} \n 复制 Cookie\n \n `;
document.body.appendChild(panel);
const header = document.getElementById("jhs-cookie-header");
document.getElementById("jhs-cookie-content");
const toggleIcon = document.getElementById("jhs-toggle-icon"), copyButton = document.getElementById("jhs-copy-btn");
header.addEventListener("click", (() => {
const isExpanded = panel.classList.toggle("expanded");
toggleIcon.textContent = isExpanded ? "▲" : "▼";
}));
copyButton.addEventListener("click", (async e => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(cookieValue);
this.showMessage("Cookie 已成功复制到剪贴板!");
} catch (err) {
console.error("Failed to copy text using clipboard API: ", err);
const tempTextArea = document.createElement("textarea");
tempTextArea.value = cookieValue;
document.body.appendChild(tempTextArea);
tempTextArea.select();
document.execCommand("copy");
document.body.removeChild(tempTextArea);
this.showMessage("Cookie 已复制! (回退方案)");
}
}));
panel.classList.remove("expanded");
}
}
const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "loginStatus", _WangPan115MatchPlugin.LoginStatus.UNCHECKED);
}
getName() {
return "WangPan115MatchPlugin";
}
async initCss() {
return "\n \n ";
}
async handle() {
$(document).on("click", ".jhs-match-no-login-btn", (async event => {
event.preventDefault();
event.stopPropagation();
await this.handleLoginRedirect();
}));
$(document).on("click", ".jhs-match-btn", (event => {
event.preventDefault();
event.stopImmediatePropagation();
this.showMatchDetail(event.currentTarget);
}));
$(document).on("click", ".jhs-match-error-btn", (async event => {
event.preventDefault();
event.stopPropagation();
await this.retryMatch(event.currentTarget);
}));
await this.matchDetailPage();
$(document).on("click", ".jhs-match-detail-error-btn", (async event => {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).replaceWith("匹配中...");
try {
const carNum2 = this.getPageInfo().carNum, matchList = await this.searchFiles(carNum2);
$(".jhs-115-match-detail").remove();
await this.matchDetailPage(matchList);
} catch (error) {
console.error(`重新匹配失败 [${carNum}]:`, error);
this.showMatchError($box, carNum, error);
}
}));
}
async matchDetailPage(matchList) {
if (!isDetailPage) return;
if (await storageManager.getSetting("enable115Match", NO) === NO) return;
const $detail = $('\n \n \n \n \n | 115匹配 | \n \n \n | 名称 | \n 大小 | \n 时间 | \n 播放 | \n \n \n \n \n \n \n '), $tbody = $detail.find("tbody");
try {
const carNum2 = this.getPageInfo().carNum;
matchList || (matchList = await this.searchFiles(carNum2));
await this.checkLoginStatus();
if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) $tbody.append(` | | \n 未登录\n | `); else if (matchList.length > 0) {
const rowsHtml = matchList.map((match => `\n \n | ${match.name} | \n ${this.formatSize(match.size)} | \n ${match.createTime} | \n \n 播放\n | \n \n `)).join("");
$tbody.append(rowsHtml);
} else $tbody.append(`| \n 未匹配\n | `);
} catch (error) {
$tbody.append(`| \n 加载失败,请重试\n | `);
console.error("加载文件列表时发生错误:", error);
}
if (isJavDb) if ($("#all-match-box").length) $("#all-match-box").append($detail); else {
$("#tabs-container").before("");
$("#all-match-box").append($detail);
} else $("#mag-submit-show").before($detail);
}
async matchMovieList(movieListElement) {
if (await storageManager.getSetting("enable115Match", NO) !== NO) {
await this.checkLoginStatus();
await this.processMovieElements(movieListElement);
} else $(".video-title [class^='jhs-match-']").remove();
}
showMatchDetail(buttonElement) {
const $el = $(buttonElement), matchData = $el.attr("data-match");
$(".jhs-match-detail").remove();
const matches = this.parseMatchData(matchData);
if (0 === matches.length) return;
if (1 === matches.length) {
const pickcode = matches[0].videoId;
window.open(`https://115vod.com/?pickcode=${pickcode}&share_id=0`, "_blank");
return;
}
const $detail = this.createMatchDetailElement(matches);
this.positionDetailElement($detail, $el);
this.addOutsideClickHandler($detail);
$detail.on("click", (e => {
e.stopPropagation();
}));
}
parseMatchData(matchData) {
try {
return JSON.parse(matchData) || [];
} catch (e) {
console.error("解析匹配数据失败:", e);
return [];
}
}
createMatchDetailElement(matches) {
const $detail = $(`\n \n \n \n \n | 名称 | \n 大小 | \n 时间 | \n 播放 | \n \n \n \n ${matches.map((match => `\n \n | ${match.name} | \n ${this.formatSize(match.size)} | \n ${match.createTime} | \n \n 播放\n | \n \n `)).join("")}\n \n \n \n `);
$("body").append($detail);
return $detail;
}
positionDetailElement($detail, $trigger) {
const offset = $trigger.offset();
$detail.css({
top: offset.top - $detail.outerHeight() + 20,
left: offset.left
});
}
addOutsideClickHandler($detail) {
setTimeout((() => {
$(document).on("click.jhs-match-detail", (e => {
if (!$detail.is(e.target) && 0 === $detail.has(e.target).length) {
$detail.remove();
$(document).off("click.jhs-match-detail");
}
}));
}), 100);
}
async retryMatch(buttonElement) {
const $el = $(buttonElement), $box2 = $el.closest(".movie-box, .item"), carNum2 = $el.attr("data-keyword");
$el.replaceWith("匹配中...");
try {
const matchList = await this.searchFiles(carNum2);
this.updateMatchStatus($box2, carNum2, matchList);
} catch (error) {
console.error(`重新匹配失败 [${carNum2}]:`, error);
this.showMatchError($box2, carNum2, error);
}
}
updateMatchStatus($box2, carNum2, matchList) {
matchList.length > 0 ? $box2.find(".jhs-match-btn").replaceWith(`匹配${matchList.length}个`) : $box2.find(".jhs-match-btn").replaceWith(`未匹配`);
}
async handleLoginRedirect() {
window.open("https://115.com");
}
async searchFiles(carNum2) {
var _a2;
let searchKeyword = carNum2.toLowerCase().replace("fc2-", "");
return (null == (_a2 = (await searchFiles(searchKeyword)).data) ? void 0 : _a2.map((data => ({
folderId: data.fid,
videoId: data.pc,
name: data.n,
createTime: utils.formatDate(new Date(1e3 * data.te)),
size: data.s,
isVideo: [ ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv" ].some((ext => {
var _a3;
return null == (_a3 = data.n) ? void 0 : _a3.toLowerCase().endsWith(ext);
}))
}))).filter((x => x.folderId && x.isVideo && x.name.toLowerCase().includes(searchKeyword)))) || [];
}
showMatchError($box2, carNum2, error) {
$box2.find(".jhs-match-btn").replaceWith(`匹配失败`);
show.error(`${carNum2} 匹配失败: ${error.message || "网络错误"}`);
}
async checkLoginStatus() {
var _a2;
if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.UNCHECKED) try {
const testResult = await searchFiles("test");
this.loginStatus = (null == (_a2 = testResult.error) ? void 0 : _a2.includes("登录")) ? _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT : _WangPan115MatchPlugin.LoginStatus.LOGGED_IN;
} catch {
this.loginStatus = _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT;
}
}
async processMovieElements(movieListElement) {
const promises = Array.from(movieListElement).filter((ele => !utils.isHidden(ele))).filter((ele => !(isJavBus && $(ele).find(".avatar-box").length > 0))).map((ele => this.processSingleMovieElement(ele)));
await Promise.all(promises);
}
async processSingleMovieElement(element) {
const $box2 = $(element), {carNum: carNum2} = this.getBoxCarInfo($box2);
if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus !== _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) try {
const matchList = await this.searchFiles(carNum2);
this.addTag($box2, carNum2, matchList);
} catch (error) {
console.error(`搜索失败 [${carNum2}]:`, error);
this.addTag($box2, carNum2, []);
} else this.addTag($box2, carNum2, []);
}
addTag($box2, carNum2, matchList) {
if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) $box2.find(".video-title").prepend(`未登录`); else if (matchList.length > 0) {
const title = 1 === matchList.length ? "点击直接播放" : `点击查看${matchList.length}个匹配结果`;
$box2.find(".video-title").prepend(`匹配${matchList.length}个`);
} else $box2.find(".video-title").prepend(`未匹配`);
}
formatSize(bytes) {
if (!bytes) return "-";
const units = [ "B", "KB", "MB", "GB", "TB" ];
let size = parseFloat(bytes), unit = 0;
for (;size >= 1024 && unit < units.length - 1; ) {
size /= 1024;
unit++;
}
return `${size.toFixed(2)} ${units[unit]}`;
}
};
__publicField(_WangPan115MatchPlugin, "LoginStatus", {
UNCHECKED: -1,
LOGGED_OUT: 0,
LOGGED_IN: 1
});
let WangPan115MatchPlugin = _WangPan115MatchPlugin;
class FavoriteActressesPlugin extends BasePlugin {
getName() {
return "FavoriteActressesPlugin";
}
async handle() {
this.bindEvent();
await this.highlightActress();
this.replaceActressAvatar();
}
async highlightActress() {
if (!isDetailPage) return;
if (await storageManager.getSetting("enableFavoriteActresses", YES) !== YES) return;
const favoriteActressesInfoList = await storageManager.getFavoriteActressList();
if (!favoriteActressesInfoList || 0 === favoriteActressesInfoList.length) return;
const favoriteStarIdSet = new Set;
favoriteActressesInfoList.forEach((actress => {
actress.starId && favoriteStarIdSet.add(String(actress.starId).trim());
}));
0 !== favoriteStarIdSet.size && $(".female").prev().each(((index, element) => {
const $element = $(element), href = $element.attr("href");
let elementStarId = null;
if (href) {
const parts = (href.endsWith("/") ? href.slice(0, -1) : href).split("/"), lastPart = parts[parts.length - 1];
lastPart && (elementStarId = lastPart.trim());
}
let isFavorite = !1;
elementStarId && (isFavorite = favoriteStarIdSet.has(elementStarId));
if (isFavorite) {
$element.addClass("highlighted");
$element.attr("title", "高亮已收藏演员, 可在设置-基础配置中关闭");
}
}));
}
async removeActorFromStorage(actorId) {
await storageManager.removeFavoriteActress(actorId) && clog.log("移除演员成功");
}
bindEvent() {
const listIdRegex = /\/actors\/(\w+)\/(collect|uncollect)/;
$(document).on("confirm:complete", 'a[href*="/actors/"][href*="/uncollect"]', (async event => {
const [userConfirmed] = event.detail;
if (!userConfirmed) return;
const match = $(event.currentTarget).attr("href").match(listIdRegex), actorId = match ? match[1] : null;
actorId && await this.removeActorFromStorage(actorId);
}));
$("#button-collect-actor").click((async event => {
const match = $("#button-collect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null;
let nameList = [], $actor = $(".actor-section-name");
$actor.length && $actor.text().trim().split(",").forEach((name2 => {
nameList.push(name2.trim());
}));
let $sectionMeta = $(".section-meta:not(:contains('影片'))");
$sectionMeta.length && $sectionMeta.text().trim().split(",").forEach((name2 => {
nameList.push(name2.trim());
}));
if (!nameList) {
clog.error("获取演员名称失败");
return;
}
const actorName = nameList[0];
if (!actorId) {
clog.error("无法获取演员ID进行收藏操作。");
return;
}
const avatar = ($(".avatar").first().css("background-image") || "").replace(/^url\(["']?|["']?\)$/g, ""), actressInfo = {
starId: actorId,
name: actorName,
allName: nameList,
avatar: avatar
};
1 === await storageManager.addFavoriteActressList([ actressInfo ]) ? clog.log(`收藏演员成功: ${actorName} (ID: ${actorId})`) : clog.log(`收藏演员失败: ${actorName} (ID: ${actorId})`);
}));
$("#button-uncollect-actor").click((async event => {
const match = $("#button-uncollect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null;
actorId ? await this.removeActorFromStorage(actorId) : clog.error("无法获取演员ID进行取消收藏操作。");
}));
}
async replaceActressAvatar() {
const actressId = this.getActressId();
if (!actressId) return;
const actress = (await storageManager.getFavoriteActressList()).find((item => item.starId === actressId));
if (actress && actress.avatar) {
const newAvatarUrl = `url('${actress.avatar}')`;
let $avatarElement = $(".avatar").first();
if (0 === $avatarElement.length) {
const newAvatarHtml = '';
$(".section-columns").prepend(newAvatarHtml);
$avatarElement = $(".avatar").first();
}
if (0 === $avatarElement.length) return;
if ($avatarElement.css("background-image").trim().toLowerCase() !== newAvatarUrl.trim().toLowerCase()) {
$avatarElement.css("background-image", newAvatarUrl);
$avatarElement.css("background-size", "cover");
$avatarElement.css("background-position", "top center");
$avatarElement.css("background-repeat", "no-repeat");
}
}
}
}
class BusImgPlugin extends BasePlugin {
getName() {
return "BusImgPlugin";
}
handle() {}
async getVisibleImageItems(itemSelector, imgSelector) {
let itemList = [];
const allItems = document.querySelectorAll(itemSelector);
for (const item of allItems) {
if (!utils.isHidden(item)) {
const imgElement = item.querySelector(imgSelector);
if (!(imgElement instanceof HTMLImageElement)) continue;
imgElement.style.removeProperty("height");
let imageHeight = imgElement.offsetHeight;
imageHeight > 0 && itemList.push({
element: item,
imgElement: imgElement,
height: imageHeight
});
}
}
return itemList;
}
async logImageHeightsByRow() {
if (await storageManager.getSetting("enableVerticalModel", NO) === YES) return;
const itemSelector = this.getSelector().itemSelector, containerColumns = await storageManager.getSetting("containerColumns", 5), itemList = await this.getVisibleImageItems(itemSelector, "img");
if (0 === itemList.length) return;
const groupedItems = [];
for (let i = 0; i < itemList.length; i++) {
const rowIndex = Math.floor(i / containerColumns);
groupedItems[rowIndex] || (groupedItems[rowIndex] = []);
groupedItems[rowIndex].push(itemList[i]);
}
groupedItems.forEach(((row, rowIndex) => {
const originalHeights = row.map((item => item.height));
if (originalHeights.length < 2) return;
const minHeight = Math.min(...originalHeights), maxHeight = Math.max(...originalHeights);
let targetHeight = 0;
if (maxHeight - minHeight > 50) {
targetHeight = minHeight;
row.forEach((item => {
if (item.height !== targetHeight) {
const heightValue = `${targetHeight}px`;
item.imgElement.style.setProperty("height", heightValue, "important");
}
}));
}
}));
}
}
class TranslatePlugin extends BasePlugin {
getName() {
return "TranslatePlugin";
}
async initCss() {
return "\n \n ";
}
handle() {
isDetailPage && this.translate();
}
async translate(carNum2, showCarNum = !0) {
if (await storageManager.getSetting("translateTitle", YES) !== YES) return;
isJavBus && (showCarNum = !1);
let $titleElement = $(".origin-title");
$titleElement.length || ($titleElement = $(".current-title"));
$titleElement.length || ($titleElement = $("h3"));
if (!$titleElement.length) return;
const originalText = $titleElement.text().trim();
if (!originalText) {
show.error("获取标题失败, 无法进行翻译");
return;
}
$titleElement.after('翻译中... ');
const $loadingElement = $titleElement.next(".translated-title");
carNum2 || (carNum2 = this.getPageInfo().carNum);
const cache = localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {};
cache[carNum2] ? $loadingElement.html(showCarNum ? carNum2 + " " + cache[carNum2] : cache[carNum2]) : translateText(originalText, "ja", "zh-CN").then((translatedText => {
$loadingElement.html(showCarNum ? carNum2 + " " + translatedText : translatedText);
})).catch((error => {
console.error("翻译失败:", error);
$loadingElement.replaceWith(`翻译失败: ${error.message} `);
}));
}
}
class TaskPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "singleTaskKey", "checkNewActressActorFilterCar");
__publicField(this, "taskConfig", null);
__publicField(this, "storageQueue", new AsyncQueue);
__publicField(this, "lastCheckFavoriteActressTimeKey", "jhs_time_checkFavoriteActress");
__publicField(this, "lastCheckBlacklistTimeKey", "jhs_time_checkBlacklist");
__publicField(this, "lastCheckNewVideoTimeKey", "jhs_time_checkNewVideo");
}
getName() {
return "TaskPlugin";
}
async limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, taskFn) {
this.showIsRun();
const activePromises = [], totalItems = taskParamList.length;
let processedCount = 0;
for (const taskParam of taskParamList) {
const p = taskFn(taskParam).finally((() => {
activePromises.splice(activePromises.indexOf(p), 1);
}));
activePromises.push(p);
processedCount++;
if (activePromises.length >= checkConcurrencyCount) {
const remainingToStart = totalItems - processedCount;
clog.debug(`剩余任务数: ${remainingToStart}`);
await Promise.race(activePromises);
await utils.sleep(checkRequestSleep);
}
}
await Promise.all(activePromises);
}
isUnnecessaryCheck(lastCheckTimeStr, checkIntervalTime) {
if (!checkIntervalTime) throw new Error("未传入checkIntervalTime");
checkIntervalTime = parseInt(checkIntervalTime);
return utils.getHourDifference(new Date(lastCheckTimeStr), new Date) < checkIntervalTime;
}
handle() {
this.doTask().then();
}
showIsRun() {
show.info("正在执行检测任务中, 请勿关闭当前窗口", {
duration: 3e3
});
}
async doTask() {
if (isListPage) {
await this.loadConfig();
this.javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl();
return navigator.locks.request(this.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
if (lock) {
if (isListPage) {
this.taskConfig.enableCheckBlacklist === YES ? await this.checkBlacklist() : clog.warn("自动检测屏蔽黑名单-禁用");
if (!isJavBus) {
if (this.taskConfig.enableCheckFavoriteActress === YES) {
const lastCheckFavoriteActressTimeStr = localStorage.getItem(this.lastCheckFavoriteActressTimeKey), checkFavoriteActress_IntervalTime = this.taskConfig.checkFavoriteActress_IntervalTime, isUnCheck = lastCheckFavoriteActressTimeStr && this.isUnnecessaryCheck(lastCheckFavoriteActressTimeStr, checkFavoriteActress_IntervalTime), isLogin = $('a[href*="/users/profile"]').length > 0;
isUnCheck && clog.debug(`检测同步演员, 上次检测时间: ${lastCheckFavoriteActressTimeStr} 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时 未到时间`);
!isUnCheck && isLogin && await this.checkFavoriteActress();
} else clog.warn("自动同步已收藏的演员-禁用");
this.taskConfig.enableCheckNewVideo === YES ? await this.checkNewVideo() : clog.warn("自动检测已收藏演员的最新作品-禁用");
}
}
} else clog.debug("争夺任务锁失败, 跳过执行");
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
})).finally((() => {
setTimeout((() => {
this.doTask();
}), 3e5);
}));
}
}
async loadConfig() {
const settingObj = await storageManager.getSetting();
this.taskConfig = {
checkConcurrencyCount: settingObj.checkConcurrencyCount ? Number(settingObj.checkConcurrencyCount) : 2,
checkRequestSleep: settingObj.checkRequestSleep ? Number(settingObj.checkRequestSleep) : 100,
enableCheckBlacklist: settingObj.enableCheckBlacklist || YES,
checkBlacklist_intervalTime: settingObj.checkBlacklist_intervalTime ? Number(settingObj.checkBlacklist_intervalTime) : 12,
checkBlacklist_ruleTime: settingObj.checkBlacklist_ruleTime ? Number(settingObj.checkBlacklist_ruleTime) : 8760,
enableCheckFavoriteActress: settingObj.enableCheckFavoriteActress || YES,
checkFavoriteActress_IntervalTime: settingObj.checkFavoriteActress_IntervalTime ? Number(settingObj.checkFavoriteActress_IntervalTime) : 24,
enableCheckNewVideo: settingObj.enableCheckNewVideo || YES,
checkNewVideo_intervalTime: settingObj.checkNewVideo_intervalTime ? Number(settingObj.checkNewVideo_intervalTime) : 12,
checkNewVideo_ruleTime: settingObj.checkNewVideo_ruleTime ? Number(settingObj.checkNewVideo_ruleTime) : 8760
};
}
async checkBlacklist(isManual) {
let blacklist = await storageManager.getBlacklist();
if (0 === blacklist.length) return;
blacklist = blacklist.sort(((a, b) => a.createTime < b.createTime ? 1 : a.createTime > b.createTime ? -1 : 0));
const checkConcurrencyCount = this.taskConfig.checkConcurrencyCount, checkRequestSleep = this.taskConfig.checkRequestSleep, checkBlacklist_intervalTime = this.taskConfig.checkBlacklist_intervalTime, checkBlacklist_ruleTime = this.taskConfig.checkBlacklist_ruleTime, lastCheckBlacklistTimeStr = localStorage.getItem(this.lastCheckBlacklistTimeKey);
if (!isManual && lastCheckBlacklistTimeStr && this.isUnnecessaryCheck(lastCheckBlacklistTimeStr, checkBlacklist_intervalTime)) {
clog.debug(`检测黑名单, 上次检测时间: ${lastCheckBlacklistTimeStr} 检测间隔时间: ${checkBlacklist_intervalTime}小时 未到时间`);
return;
}
const taskParamList = [], msgList = [];
for (const blacklistItem of blacklist) {
let name2 = blacklistItem.name, checkTime = blacklistItem.checkTime, lastPublishTime = blacklistItem.lastPublishTime, url = blacklistItem.url;
if (new URL(window.location.href).hostname === new URL(url).hostname) {
if (isManual || !checkTime || !this.isUnnecessaryCheck(checkTime, checkBlacklist_intervalTime)) if (!lastPublishTime || 0 === checkBlacklist_ruleTime || this.isUnnecessaryCheck(lastPublishTime, checkBlacklist_ruleTime)) taskParamList.push(blacklistItem); else {
let msg = `检测黑名单: ${name2} ${lastPublishTime} 停更超过${checkBlacklist_ruleTime / 24 / 365}年,跳过检测`;
msgList.push(msg);
$("#checkBlacklistMsg").text(msg);
}
} else clog.log("黑名单地址非同域名,跳过", url);
}
if (0 === taskParamList.length) return;
msgList.forEach((msg => {
clog.log(msg);
}));
clog.log(`检测屏蔽黑名单, 总任务数: ${taskParamList.length}, 并发限制:${checkConcurrencyCount}, 请求间隔时间:${checkRequestSleep}ms`);
const blacklistPlugin = this.getBean("BlacklistPlugin");
await this.limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, (async task => {
let {starId: starId, name: name2, url: url} = task;
try {
clog.log("正在检屏黑名单演员:", name2, url);
$("#checkBlacklistMsg").text(`正在检屏黑名单演员: ${name2} ${url}`);
const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html);
this.storageQueue.addTask((async () => {
let {lastPublishTime: lastPublishTime} = await blacklistPlugin.parseAndSaveFilterInfo($dom, name2, starId);
await storageManager.updateBlacklistItem({
starId: starId,
name: name2,
checkTime: utils.getNowStr(),
lastPublishTime: lastPublishTime
});
}));
} catch (e) {
$("#checkBlacklistMsg").text(`检测屏蔽演员信息, 发生错误: ${url}`);
clog.error("检测屏蔽演员信息, 发生错误:", url, e);
show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right");
}
}));
await this.storageQueue.waitAllFinished();
const updateLastCheckBlacklistTimeStr = utils.getNowStr();
localStorage.setItem(this.lastCheckBlacklistTimeKey, updateLastCheckBlacklistTimeStr);
clog.log('-------- END 检测屏蔽黑名单 END --------');
$("#checkBlacklistMsg").text("检测屏蔽黑名单, 结束");
this.getBean("BlacklistPlugin").resetBtnTip().then();
}
async checkFavoriteActress() {
const checkUrl = `${this.javDbUrl}/users/collection_actors`, actorInfoList = [];
await this.scrapeActorInfo(checkUrl, actorInfoList);
clog.log("所有演员信息已收集, 总计数量:", actorInfoList.length);
$("#checkNewVideoMsg").text("同步完成");
if (actorInfoList.length > 0) {
await storageManager.addFavoriteActressList(actorInfoList);
localStorage.setItem(this.lastCheckFavoriteActressTimeKey, utils.getNowStr());
this.getBean("NewVideoPlugin").resetBtnTip().then();
}
}
async scrapeActorInfo(currentUrl, actorInfoList) {
clog.log(`正在抓取页面: ${currentUrl}`);
$("#checkNewVideoMsg").text(`正在解析已收藏的演员: ${currentUrl}`);
try {
const html = await gmHttp.get(currentUrl), $dom = utils.htmlTo$dom(html);
$dom.find("#actors .actor-box a").each(((i, element) => {
const $a = $(element), title = $a.attr("title"), href = $a.attr("href");
if (title && href) {
const allName = title.split(",").map((name3 => name3.trim())).filter((name3 => name3.length > 0)), name2 = allName[0] || "", segments = new URL(href, this.javDbUrl).pathname.split("/").filter((s => s.length > 0));
let starId = "";
segments.length > 0 && (starId = segments[segments.length - 1]);
let actressType = "censored";
const avatar = $a.find("img").attr("src"), $infoSpan = $a.find(".info");
$infoSpan.length && $infoSpan.text().trim().includes("無碼") && (actressType = "uncensored");
actorInfoList.push({
starId: starId,
name: name2,
allName: allName,
avatar: avatar,
actressType: actressType,
lastCheckTime: null,
lastUpdateTime: null
});
}
}));
const nextRelativeUrl = $dom.find(".pagination-next").attr("href");
if (nextRelativeUrl) {
const nextAbsoluteUrl = new URL(nextRelativeUrl, this.javDbUrl).href;
await this.scrapeActorInfo(nextAbsoluteUrl, actorInfoList);
}
} catch (error) {
clog.error(`抓取 ${currentUrl} 时发生错误:`, error);
}
}
async checkNewVideo(isManual) {
const dataActressInfoList = await storageManager.getFavoriteActressList(), actressInfoList = utils.genericSort(dataActressInfoList, [ {
key: item => {
var _a2;
return (null == (_a2 = item.newVideoList) ? void 0 : _a2.length) ?? 0;
},
order: "desc"
}, {
key: "lastPublishTime",
order: "desc"
} ]), checkConcurrencyCount = this.taskConfig.checkConcurrencyCount, checkRequestSleep = this.taskConfig.checkRequestSleep, checkNewVideo_intervalTime = this.taskConfig.checkNewVideo_intervalTime, checkNewVideo_ruleTime = this.taskConfig.checkNewVideo_ruleTime, lastCheckNewVideoTimeStr = localStorage.getItem(this.lastCheckNewVideoTimeKey);
if (!isManual && lastCheckNewVideoTimeStr && this.isUnnecessaryCheck(lastCheckNewVideoTimeStr, checkNewVideo_intervalTime)) {
clog.debug(`检测新作品, 上次检测时间: ${lastCheckNewVideoTimeStr} 检测间隔时间: ${checkNewVideo_intervalTime}小时 未到时间`);
return;
}
const taskParamList = [], msgList = [];
for (const actress of actressInfoList) {
const {lastCheckTime: lastCheckTime, lastPublishTime: lastPublishTime, name: name2} = actress;
!isManual && lastCheckTime && this.isUnnecessaryCheck(lastCheckTime, checkNewVideo_intervalTime) || (!lastPublishTime || 0 === checkNewVideo_ruleTime || this.isUnnecessaryCheck(lastPublishTime, checkNewVideo_ruleTime) ? taskParamList.push(actress) : msgList.push(`检测新作品: ${name2} ${lastPublishTime} 停更超过${checkNewVideo_ruleTime / 24 / 365}年,跳过检测`));
}
if (0 === taskParamList.length) return;
msgList.forEach((msg => {
clog.log(msg);
}));
clog.log(`检测最新作品, 总任务数: ${taskParamList.length}, 并发限制:${checkConcurrencyCount}, 请求间隔时间:${checkRequestSleep}ms`);
const filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorActressCarList = await storageManager.getBlacklistCarList(), filterActorActressCarNumList = new Set(filterActorActressCarList.map((car => car.carNum)));
await this.limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, (async task => {
const {lastCheckTime: lastCheckTime, name: name2, starId: starId} = task;
let url = `${this.javDbUrl}/actors/${starId}?t=d`;
try {
clog.log("正在检测最新作品, 演员:", name2, url);
$("#checkNewVideoMsg").text(`正在检测最新作品, 演员: ${name2}`);
const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html);
this.storageQueue.addTask((async () => {
await this.parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList);
}));
} catch (e) {
clog.error("检测屏蔽演员信息, 发生错误:", url, e);
console.error("检测屏蔽演员信息, 发生错误:", url, e);
show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right");
}
}));
await this.storageQueue.waitAllFinished();
localStorage.setItem(this.lastCheckNewVideoTimeKey, utils.getNowStr());
clog.log('检测最新作品---结束');
$("#checkNewVideoMsg").text("检测完毕");
const newVideoPlugin = this.getBean("NewVideoPlugin");
newVideoPlugin.loadData();
newVideoPlugin.resetBtnTip().then();
}
async parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList) {
let movieList, nextPageLink, tempIsJavBus = !1, selectorType = "javdb";
if ($dom.text().includes("javbus")) {
tempIsJavBus = !0;
selectorType = "javbus";
}
tempIsJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove();
movieList = $dom.find(this.getSelector(selectorType).requestDomItemSelector);
nextPageLink = $dom.find(this.getSelector(selectorType).nextPageSelector).attr("href");
if (nextPageLink && 0 === movieList.length) {
clog.error("新作品检测-解析列表失败");
show.error("新作品检测-解析列表失败");
throw new Error("新作品检测-解析列表失败");
}
let carNumList = [], lastPublishTime = null;
for (const element of movieList) {
const $item = $(element), {carNum: carNum2, url: url, title: title, publishTime: publishTime} = this.getBoxCarInfo($item);
if (!carNum2) continue;
if (!filterKeywordList.find((kw => title.includes(kw) || carNum2.startsWith(kw))) && !filterActorActressCarNumList.has(carNum2)) {
lastPublishTime || (lastPublishTime = publishTime);
carNumList.push(carNum2);
}
}
const carList = await storageManager.getCarList(), storageCarNumList = new Set(carList.map((car => car.carNum))), nonExistingCarNumList = carNumList.filter((carNum2 => !storageCarNumList.has(carNum2)));
nonExistingCarNumList.length > 0 && clog.log(`检测出新作品, ${name2}, 共${nonExistingCarNumList.length}部`);
await storageManager.updateFavoriteActress({
starId: starId,
lastCheckTime: utils.getNowStr(),
newVideoList: nonExistingCarNumList,
lastPublishTime: lastPublishTime
});
}
async checkOneNewVideo(actress) {
const filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorActressCarList = await storageManager.getBlacklistCarList(), filterActorActressCarNumList = new Set(filterActorActressCarList.map((car => car.carNum))), {lastCheckTime: lastCheckTime, name: name2, starId: starId} = actress;
let url = `${this.javDbUrl}/actors/${starId}?t=d`;
const $checkNewVideoMsg = $("#checkNewVideoMsg");
try {
clog.log("正在检测最新作品, 演员:", name2, url);
$checkNewVideoMsg.text(`正在检测最新作品, 演员: ${name2}`);
const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html);
await this.parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList);
clog.log('检测最新作品---结束');
$checkNewVideoMsg.text("检测完毕");
this.getBean("NewVideoPlugin").loadData();
} catch (e) {
clog.error("检测屏蔽演员信息, 发生错误:", url, e);
show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right");
$checkNewVideoMsg.text(`检测屏蔽演员信息, 发生错误: ${url}`);
}
}
}
const CDN_SOURCES = [ {
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/"
} ];
let currentCdnIndex = parseInt(localStorage.getItem("jhs_img_cdn_index") || "0", 10);
(currentCdnIndex >= CDN_SOURCES.length || currentCdnIndex < 0) && (currentCdnIndex = 0);
let G_FRIENDS_JSON_URL = CDN_SOURCES[currentCdnIndex].json, CDN_BASE_URL = CDN_SOURCES[currentCdnIndex].base;
const STORE_NAME = "filetreeStore", dbHelper = {
db: null,
async open() {
return this.db ? this.db : new Promise(((resolve, reject) => {
const request = indexedDB.open("GfriendsAvatarDB", 1);
request.onupgradeneeded = event => {
this.db = event.target.result;
this.db.objectStoreNames.contains(STORE_NAME) || this.db.createObjectStore(STORE_NAME);
};
request.onsuccess = event => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = event => {
console.error("IndexedDB open error:", event.target.errorCode);
reject(new Error("Failed to open IndexedDB"));
};
}));
},
async get(key) {
await this.open();
return new Promise((resolve => {
const request = this.db.transaction([ STORE_NAME ], "readonly").objectStore(STORE_NAME).get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => resolve(null);
}));
},
async set(key, value) {
await this.open();
return new Promise(((resolve, reject) => {
const request = this.db.transaction([ STORE_NAME ], "readwrite").objectStore(STORE_NAME).put(value, key);
request.onsuccess = () => resolve();
request.onerror = event => {
console.error("IndexedDB set error:", event.target.errorCode);
reject(new Error("Failed to write to IndexedDB"));
};
}));
}
};
let G_FRIENDS_DATA_CACHE = null, G_FRIENDS_AVATAR_MAP = null;
function buildAvatarMap(rawData) {
if (!rawData || !rawData.Content) return null;
const map = {}, contentData = rawData.Content;
for (const companyName in contentData) {
const encodedCompany = encodeURIComponent(companyName);
for (const fileName in contentData[companyName]) {
let cleanNamePart = fileName.replace(/\.jpg$/i, "").split("-")[0];
cleanNamePart.startsWith("AI-Fix-") && (cleanNamePart = cleanNamePart.substring(7));
const actorNameKey = cleanNamePart.toLowerCase().trim();
if (actorNameKey.length > 0) {
const actualResourcePath = contentData[companyName][fileName], queryIndex = actualResourcePath.indexOf("?");
let encodedFileNamePart, queryString = "";
if (queryIndex > -1) {
encodedFileNamePart = encodeURIComponent(actualResourcePath.substring(0, queryIndex));
queryString = actualResourcePath.substring(queryIndex);
} else encodedFileNamePart = encodeURIComponent(actualResourcePath);
const fullUrl = `${CDN_BASE_URL}${encodedCompany}/${encodedFileNamePart}${queryString}`;
map[actorNameKey] || (map[actorNameKey] = []);
map[actorNameKey].includes(fullUrl) || map[actorNameKey].push(fullUrl);
}
}
}
return map;
}
async function searchActorAvatars(searchNames) {
let loadObj = loading();
try {
await async function() {
if (G_FRIENDS_DATA_CACHE && G_FRIENDS_AVATAR_MAP) return G_FRIENDS_DATA_CACHE;
let cachedData = null;
try {
cachedData = await dbHelper.get("filetree_data");
} catch (e) {
console.error("读取 IndexedDB 失败:", e);
}
if (cachedData && cachedData.Content) {
G_FRIENDS_DATA_CACHE = cachedData;
G_FRIENDS_AVATAR_MAP = buildAvatarMap(cachedData);
if (G_FRIENDS_AVATAR_MAP) return G_FRIENDS_DATA_CACHE;
}
show.info("正在载入头像数据源...");
const response = await fetch(G_FRIENDS_JSON_URL);
if (!response.ok) throw new Error(`请求头像源失败: ${response.status}`);
const data = await response.json();
if (data && data.Content) {
G_FRIENDS_DATA_CACHE = data;
G_FRIENDS_AVATAR_MAP = buildAvatarMap(data);
try {
await dbHelper.set("filetree_data", data);
clog.debug("载入头像数据源并写入缓存成功!");
} catch (e) {
clog.error(e);
show.error("头像数据源写入缓存失败,可能磁盘已满或其他权限问题。");
}
return G_FRIENDS_DATA_CACHE;
}
console.log(data);
throw new Error("解析头像数据源失败");
}();
} catch (e) {
show.error(e);
return [];
} finally {
loadObj.close();
}
if (!G_FRIENDS_AVATAR_MAP) return [];
const foundLinks = new Set, searchKeys = searchNames.map((name2 => name2.toLowerCase().trim())).filter((n => n.length > 0));
if (0 === searchKeys.length) return [];
for (const searchKey of searchKeys) {
const links = G_FRIENDS_AVATAR_MAP[searchKey];
links && links.forEach((link => foundLinks.add(link)));
}
return Array.from(foundLinks);
}
class NewVideoPlugin extends BasePlugin {
constructor() {
super(...arguments);
__publicField(this, "currentPage", 1);
__publicField(this, "pageSize", 30);
}
getName() {
return "NewVideoPlugin";
}
async initCss() {
return "\n \n ";
}
async showNewVideoCount() {
const totalNewVideoCount = (await storageManager.getFavoriteActressList()).reduce(((accumulator, actress) => {
var _a2;
return accumulator + ((null == (_a2 = actress.newVideoList) ? void 0 : _a2.length) ?? 0);
}), 0);
$("#newVideoCount").text(`${totalNewVideoCount}`);
}
async resetBtnTip() {
const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime;
$("#checkFavoriteActress").attr("data-tip", `上次自动同步时间: ${lastCheckTimeStr}; 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时`);
$("#checkNewVideo").attr("data-tip", `上次检测时间: ${lastCheckNewVideoTimeStr}; 检测间隔时间: ${checkNewVideo_intervalTime}小时`);
}
async openDialog() {
const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime;
let html = `\n \n `;
layer.open({
type: 1,
title: '新作品检测 ❓',
content: html,
scrollbar: !1,
area: utils.getResponsiveArea([ "80%", "90%" ]),
anim: -1,
success: async (layero, index) => {
this.loadData();
this.bindClick();
utils.setupEscClose(index);
}
});
}
bindClick() {
const taskPlugin = this.getBean("TaskPlugin");
$("#reLoad").on("click", (event => {
this.loadData();
$("#checkNewVideoMsg").text("");
}));
$("#toSetting").on("click", (event => {
this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => {
$("#setting-checkFavoriteActress").css({
border: "1px solid #f40"
});
$("#setting-checkNewVideo").css({
border: "1px solid #f40"
});
}));
}));
$("#checkFavoriteActress").on("click", (event => {
utils.q({
clientX: event.clientX,
clientY: event.clientY + 20
}, "是否手动同步演员?", (() => {
navigator.locks.request(taskPlugin.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
if (!lock) {
show.error("当前有定时任务在后台执行中, 无法发起手动任务");
return;
}
if ($('a[href*="/users/profile"]').length > 0) {
await taskPlugin.checkFavoriteActress();
this.loadData();
} else show.error("未登录JavDb, 同步失败");
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
}));
}));
}));
$("#checkNewVideo").on("click", (event => {
utils.q({
clientX: event.clientX,
clientY: event.clientY + 20
}, "是否手动检测最新作品?", (() => {
navigator.locks.request(taskPlugin.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
lock ? await taskPlugin.checkNewVideo(!0) : show.error("当前有定时任务在后台执行中, 无法发起手动任务");
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
}));
}));
}));
$("#paramActressType").on("change", (event => {
this.loadData();
}));
}
loadData() {
this.currentPage = 1;
this.renderActressCards().then();
}
async renderActressCards() {
const $actressCardContainer = $("#actress-card-container");
if (!$actressCardContainer.length) return;
let favoriteActressesList = await storageManager.getFavoriteActressList();
const paramActressType = $("#paramActressType").val();
"all" !== paramActressType && (favoriteActressesList = favoriteActressesList.filter((actress => actress.actressType === paramActressType)));
const fullActressList = utils.genericSort(favoriteActressesList, [ {
key: item => {
var _a2;
return (null == (_a2 = item.newVideoList) ? void 0 : _a2.length) ?? 0;
},
order: "desc"
}, {
key: "lastPublishTime",
order: "desc"
} ]), totalCount = fullActressList.length, totalPages = Math.ceil(totalCount / this.pageSize), startIndex = (this.currentPage - 1) * this.pageSize, endIndex = startIndex + this.pageSize, pagedData = fullActressList.slice(startIndex, endIndex), javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl(), taskPlugin = this.getBean("TaskPlugin"), checkNewVideo_ruleTime = await storageManager.getSetting("checkNewVideo_ruleTime") || 8760, cardsHtml = pagedData.map((data => {
var _a2, _b, _c;
const allNamesStr = Array.isArray(data.allName) ? data.allName.join(",") : "";
Array.isArray(data.newVideoList) && data.newVideoList.join(",");
const detailLink = `${javDbUrl}/actors/${data.starId}?t=d`;
let isUnCheck = !1;
data.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(data.lastPublishTime, checkNewVideo_ruleTime));
let actressTypeText = "未知", actressTypeBgc = "#9E9E9E";
if ("uncensored" === data.actressType) {
actressTypeText = "无码";
actressTypeBgc = "#4CAF50";
} else if ("censored" === data.actressType) {
actressTypeText = "有码";
actressTypeBgc = "#FF9800";
}
let cardUnnecessaryBtnCss = "";
isUnCheck && (cardUnnecessaryBtnCss = "background: linear-gradient(145deg, #e0e0e0 0%, #cabdbd 100%);box-shadow: none");
return `\n \n \n \n \n\n \n \n \n 上次检测: ${data.lastCheckTime || ""}\n \n \n 最后发行作品: ${data.lastPublishTime || ""}\n \n\n \n ${isUnCheck ? "停更" + checkNewVideo_ruleTime / 24 / 365 + "年以上, 下轮任务不再进行检测" : ""}\n \n \n \n ${data.remark || ""}\n \n \n \n \n ${actressTypeText} \n \n 🔔 ${(null == (_c = data.newVideoList) ? void 0 : _c.length) || 0} \n \n \n `;
})).join("");
$actressCardContainer.html(cardsHtml);
$(".btn-delete-actress").off("click").on("click", (e => {
e.preventDefault();
const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId));
utils.q(e, `是否取消收藏 ${actress.name}?`, (async () => {
let deleteActressUrl = `${await this.getBean("OtherSitePlugin").getJavDbUrl()}/actors/${starId}/uncollect`;
const csrfToken = document.querySelector("meta[name=csrf-token]").content, res = await gmHttp.post(deleteActressUrl, null, {
"x-csrf-token": csrfToken
});
if (res.includes("removeClass")) {
await storageManager.removeFavoriteActress(starId);
this.loadData();
} else {
show.error("移除失败");
clog.error("移除失败,返回值:", res);
}
}));
}));
$(".btn-edit-actress").off("click").on("click", (e => {
e.preventDefault();
const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId));
actress ? this.editActress(actress) : show.error(`未找到 starId 为 ${starId} 的女优记录。`);
}));
$(".btn-check-actress").off("click").on("click", (e => {
e.preventDefault();
navigator.locks.request(taskPlugin.singleTaskKey, {
ifAvailable: !0
}, (async lock => {
if (!lock) {
show.error("当前有定时任务在后台执行中, 无法发起手动任务");
return;
}
const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId));
await taskPlugin.checkOneNewVideo(actress);
})).catch((error => {
console.error("锁任务出现错误:", error);
clog.error("锁任务出现错误:", error);
}));
}));
this.renderPagination(totalCount, totalPages);
show.ok("加载完成");
}
async editActress(actress) {
const initialName = actress.name, initialAvatar = actress.avatar, initialRemark = actress.remark || "", initialAllName = Array.isArray(actress.allName) ? actress.allName.join(",") : "", initialNewVideoList = Array.isArray(actress.newVideoList) ? actress.newVideoList.join(",") : "", starId = actress.starId, textareaStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", initActressType = actress.actressType || "", editFormHtml = `\n \n \n  \n \n \n \n \n \n 搜索头像\n \n \n 选择 CDN 源\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;
layer.open({
type: 1,
title: `编辑女优: ${initialName} (${starId})`,
area: [ "500px", "750px" ],
content: editFormHtml,
btn: [ "保存", "取消" ],
success: (layero, index) => {
const autoResizeTextarea = $textarea => {
$textarea.css("height", "auto");
$textarea.css("height", $textarea[0].scrollHeight + 15 + "px");
};
$("#edit-actress-avatar").on("input", (function() {
const newUrl = $(this).val();
$("#edit-avatar-preview").attr("src", newUrl);
}));
const $allNameTextarea = $("#edit-actress-allname");
$allNameTextarea.on("input", (function() {
autoResizeTextarea($(this));
}));
autoResizeTextarea($allNameTextarea);
const $videoListTextarea = $("#edit-actress-newvideolist");
$videoListTextarea.on("input", (function() {
autoResizeTextarea($(this));
}));
autoResizeTextarea($videoListTextarea);
$("#search-avatar-btn").on("click", (async () => {
await this.searchAvatar();
}));
$("#select-cdn-btn").on("click", (async () => {
await async function() {
const initialIndex = currentCdnIndex, radioOptions = CDN_SOURCES.map(((source, index) => `\n \n \n \n \n `)).join(""), cdnSelectHtml = `\n \n 请选择头像数据源 (当前: ${CDN_SOURCES[initialIndex].name}): \n ${radioOptions}\n 切换源会清除本地缓存的数据,并在下次搜索时重新加载。 \n \n `;
layer.open({
type: 1,
title: "选择 CDN 源",
area: [ "400px", "auto" ],
content: cdnSelectHtml,
btn: [ "确定", "取消" ],
success: (layero, index) => {
utils.setupEscClose(index);
},
yes: async index => {
const newIndexStr = $('input[name="cdn-source"]:checked').val(), newIndex = parseInt(newIndexStr, 10);
if (newIndex !== currentCdnIndex) {
currentCdnIndex = newIndex;
localStorage.setItem("jhs_img_cdn_index", newIndex.toString());
G_FRIENDS_JSON_URL = CDN_SOURCES[newIndex].json;
CDN_BASE_URL = CDN_SOURCES[newIndex].base;
G_FRIENDS_DATA_CACHE = null;
G_FRIENDS_AVATAR_MAP = null;
try {
await dbHelper.set("filetree_data", null);
} catch (e) {
clog.error("清除 IndexedDB 缓存失败:", e);
}
show.ok(`CDN 源已切换为: ${CDN_SOURCES[newIndex].name}`);
layer.close(index);
} else layer.close(index);
}
});
}();
}));
utils.setupEscClose(index);
},
yes: async index => {
const newAvatar = $("#edit-actress-avatar").val().trim(), newName = $("#edit-actress-name").val().trim(), newAllNameStr = $("#edit-actress-allname").val().trim(), newVideoListStr = $("#edit-actress-newvideolist").val().trim(), newRemark = $("#edit-remark").val().trim(), newActressType = $("#actressType").val();
if (!newName) {
show.error("主名称不能为空");
return !1;
}
const newAllName = newAllNameStr.split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0)), newVideoList = newVideoListStr.split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0));
actress.avatar = newAvatar;
actress.name = newName;
actress.allName = newAllName;
actress.newVideoList = newVideoList;
actress.actressType = newActressType;
actress.remark = newRemark;
if (await storageManager.updateFavoriteActress(actress)) show.error("修改失败"); else {
show.ok(`女优 ${newName} 信息已更新`);
await this.renderActressCards();
layer.close(index);
}
}
});
}
renderPagination(totalCount, totalPages) {
const currentPage = this.currentPage;
let paginationHtml = "";
const $actressPagination = $("#actress-pagination");
if (0 === totalPages) {
paginationHtml = '共 0 条记录';
$actressPagination.html(paginationHtml);
return;
}
currentPage > 1 && totalPages > 5 && (paginationHtml += '');
currentPage > 1 && (paginationHtml += ``);
let startPage = Math.max(1, currentPage - Math.floor(2.5)), endPage = Math.min(totalPages, startPage + 5 - 1);
endPage - startPage < 4 && (startPage = Math.max(1, endPage - 5 + 1));
for (let i = startPage; i <= endPage; i++) {
paginationHtml += ``;
}
currentPage < totalPages && (paginationHtml += ``);
currentPage < totalPages && totalPages > 5 && (paginationHtml += ``);
paginationHtml += `共 ${totalCount} 条记录 (第 ${currentPage}/${totalPages} 页)`;
$actressPagination.html(paginationHtml);
$(".pagination-btn").off("click").on("click", (e => {
if ($(e.currentTarget).is("[disabled]")) return;
const newPage = parseInt($(e.currentTarget).data("page"));
if (newPage >= 1 && newPage <= totalPages && newPage !== this.currentPage) {
this.currentPage = newPage;
this.renderActressCards();
}
}));
}
async searchAvatar() {
const $mainNameInput = $("#edit-actress-name"), $aliasInput = $("#edit-actress-allname"), currentName = $mainNameInput.val().trim(), searchNames = $aliasInput.val().trim().split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0));
currentName && searchNames.unshift(currentName);
if (0 === searchNames.length) {
show.error("请先填写女优主名称或别名进行搜索。");
return;
}
const loadObj = loading("正在搜索头像...");
let imageLinks = [];
try {
imageLinks = await searchActorAvatars(searchNames);
} catch (e) {
show.error(`头像数据加载或搜索失败: ${e.message || e}`);
return;
} finally {
loadObj.close();
}
if (0 === imageLinks.length) {
show.error(`未找到与 '${searchNames.join("、")}' 相关的头像。请检查名称。`);
return;
}
const imageItems = imageLinks.map(((url, index) => `\n \n  \n ... \n \n `)).join(""), imageListHtml = `\n \n \n \n \n 点击图片即可选择(初始共 ${imageLinks.length} 张)\n \n \n \n `;
let errorCount = 0;
layer.open({
type: 1,
title: `选择女优头像 (${imageLinks.length} 张)`,
area: utils.getResponsiveArea([ "900px", "85%" ]),
content: imageListHtml,
btn: [ "关闭" ],
success: (selectionLayero, selectionIndex) => {
const $container = $(selectionLayero), $images = $container.find(".gfriends-selectable-img"), $prompt = $container.find("#gfriends-prompt");
$images.each((function() {
const $img = $(this), wrapperId = $img.data("wrapper-id"), $wrapper = $container.find(`#${wrapperId}`), $sizeTag = $container.find(`.gfriends-size-tag[data-size-for="${wrapperId}"]`);
$img.on("load", (function() {
const width = this.naturalWidth, height = this.naturalHeight;
$sizeTag.text(`${width} x ${height}`);
}));
$img.on("error", (function() {
$wrapper.remove();
errorCount++;
const validCount = imageLinks.length - errorCount;
$prompt.text(`点击图片即可选择(已移除 ${errorCount} 张错误图片,剩余 ${validCount} 张)`);
if (0 === validCount) {
show.error("所有搜索到的头像链接均已失效,无法选择。");
layer.close(selectionIndex);
}
}));
this.complete && (this.naturalWidth > 0 ? $img.trigger("load") : $img.trigger("error"));
}));
$images.on("click", (function() {
const $clickedImg = $(this), selectedUrl = $clickedImg.data("url");
$("#edit-actress-avatar").val(selectedUrl);
$("#edit-avatar-preview").attr("src", selectedUrl);
$images.removeClass("is-selected");
$clickedImg.addClass("is-selected");
setTimeout((() => {
layer.close(selectionIndex);
}), 150);
}));
utils.setupEscClose(selectionIndex);
}
});
}
}
const originalLayerClose = layer.close;
layer.close = function(index) {
const result = originalLayerClose.call(this, index);
!function(waitTime = 10) {
setTimeout((() => {
const openLayerCount = document.querySelectorAll(".layui-layer-shade").length;
document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : "";
}), waitTime);
}();
return result;
};
const originalLayerOpen = layer.open;
layer.open = function(options) {
const originalSuccess = (options = options || {}).success;
options.success = function(layero, index) {
"function" == typeof originalSuccess && originalSuccess.call(this, layero, index);
utils.setupEscClose(index);
};
return originalLayerOpen.call(this, options);
};
utils.importResource("https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/layer.min.css");
utils.importResource("https://cdn.jsdelivr.net/npm/toastify-js@1.12.0/src/toastify.min.css");
utils.importResource("https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.css");
utils.importResource("https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/css/tabulator_semanticui.min.css");
const pluginManager = function() {
const pluginManager2 = new PluginManager;
unsafeWindow.pluginManager = window.pluginManager = pluginManager2;
let hostname = window.location.hostname;
if (isJavDb) {
pluginManager2.register(ListPagePlugin);
pluginManager2.register(AutoPagePlugin);
pluginManager2.register(Fc2Plugin);
pluginManager2.register(FoldCategoryPlugin);
pluginManager2.register(ListPageButtonPlugin);
pluginManager2.register(HistoryPlugin);
pluginManager2.register(SettingPlugin);
pluginManager2.register(NavBarPlugin);
pluginManager2.register(HitShowPlugin);
pluginManager2.register(TOP250Plugin);
pluginManager2.register(CoverButtonPlugin);
pluginManager2.register(ImageRecognitionPlugin);
pluginManager2.register(Fc2By123AvPlugin);
pluginManager2.register(WangPan115MatchPlugin);
pluginManager2.register(DetailPagePlugin);
pluginManager2.register(ReviewPlugin);
pluginManager2.register(RelatedPlugin);
pluginManager2.register(DetailPageButtonPlugin);
pluginManager2.register(HighlightMagnetPlugin);
pluginManager2.register(PreviewVideoPlugin);
pluginManager2.register(FilterTitleKeywordPlugin);
pluginManager2.register(ActressInfoPlugin);
pluginManager2.register(OtherSitePlugin);
pluginManager2.register(WangPan115TaskPlugin);
pluginManager2.register(TranslatePlugin);
pluginManager2.register(WantAndWatchedVideosPlugin);
pluginManager2.register(MagnetHubPlugin);
pluginManager2.register(ScreenShotPlugin);
pluginManager2.register(BlacklistPlugin);
pluginManager2.register(FavoriteActressesPlugin);
pluginManager2.register(NewVideoPlugin);
pluginManager2.register(TaskPlugin);
}
if (isJavBus) {
pluginManager2.register(ListPagePlugin);
pluginManager2.register(ListPageButtonPlugin);
pluginManager2.register(SettingPlugin);
pluginManager2.register(HistoryPlugin);
pluginManager2.register(AutoPagePlugin);
pluginManager2.register(ImageRecognitionPlugin);
pluginManager2.register(BusNavBarPlugin);
pluginManager2.register(CoverButtonPlugin);
pluginManager2.register(WangPan115MatchPlugin);
pluginManager2.register(BusImgPlugin);
pluginManager2.register(BusDetailPagePlugin);
pluginManager2.register(DetailPageButtonPlugin);
pluginManager2.register(ReviewPlugin);
pluginManager2.register(FilterTitleKeywordPlugin);
pluginManager2.register(HighlightMagnetPlugin);
pluginManager2.register(BusPreviewVideoPlugin);
pluginManager2.register(MagnetHubPlugin);
pluginManager2.register(ScreenShotPlugin);
pluginManager2.register(OtherSitePlugin);
pluginManager2.register(WangPan115TaskPlugin);
pluginManager2.register(TranslatePlugin);
pluginManager2.register(BlacklistPlugin);
pluginManager2.register(TaskPlugin);
}
hostname.includes("javtrailers") && pluginManager2.register(JavTrailersPlugin);
hostname.includes("subtitlecat") && pluginManager2.register(SubTitleCatPlugin);
(hostname.includes("aliyundrive") || hostname.includes("alipan")) && pluginManager2.register(AliyunPanPlugin);
hostname.includes("115.com") && pluginManager2.register(WangPan115Plugin);
return pluginManager2;
}();
pluginManager.processCss().then();
!async function() {
window.isDetailPage = function() {
let href = window.location.href;
return isJavDb ? href.split("?")[0].includes("/v/") : !!isJavBus && $("#magnet-table").length > 0;
}();
window.isListPage = function() {
let href = window.location.href;
return isJavDb ? $(".movie-list").length > 0 || href.includes("advanced_search") : !!isJavBus && $(".masonry > div .item").length > 0;
}();
window.isFc2Page = function() {
let href = window.location.href;
return href.includes("advanced_search?type=3") || href.includes("advanced_search?type=100");
}();
isJavDb && /(^|;)\s*locale\s*=\s*en\s*($|;)/i.test(document.cookie) && show.error("请切换到中文语言下才可正常使用本脚本", {
duration: -1
});
pluginManager.processPlugins().then();
}(); |